Skip to content

Commit 3225811

Browse files
committed
Bring over maybe_step_through_wrapper! and add debug_command
These come from or are inspired by Debugger.jl
1 parent a738cd0 commit 3225811

File tree

3 files changed

+136
-1
lines changed

3 files changed

+136
-1
lines changed

src/JuliaInterpreter.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ using InteractiveUtils
1313
using CodeTracking
1414

1515
export @interpret, Compiled, Frame, root, leaf,
16-
BreakpointRef, breakpoint, @breakpoint, breakpoints, enable, disable, remove
16+
BreakpointRef, breakpoint, @breakpoint, breakpoints, enable, disable, remove,
17+
debug_command
1718

1819
module CompiledCalls
1920
# This module is for handling intrinsics that must be compiled (llvmcall)

src/commands.jl

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,3 +207,123 @@ function next_line!(@nospecialize(recurse), frame::Frame, istoplevel::Bool=false
207207
maybe_next_call!(recurse, frame, pc)
208208
end
209209
next_line!(frame::Frame, istoplevel::Bool=false) = next_line!(finish_and_return!, frame, istoplevel)
210+
211+
"""
212+
cframe = maybe_step_through_wrapper!(recurse, frame)
213+
cframe = maybe_step_through_wrapper!(frame)
214+
215+
Return the new frame of execution, potentially stepping through "wrapper" methods like those
216+
that supply default positional arguments or handle keywords. `cframe` is the leaf frame from
217+
which execution should start.
218+
"""
219+
function maybe_step_through_wrapper!(@nospecialize(recurse), frame::Frame)
220+
code = frame.framecode
221+
stmts, scope = code.src.code, code.scope::Method
222+
length(stmts) < 2 && return frame
223+
last = stmts[end-1]
224+
isexpr(last, :(=)) && (last = last.args[2])
225+
is_kw = isa(scope, Method) && startswith(String(Base.unwrap_unionall(scope.sig).parameters[1].name.name), "#kw")
226+
if is_kw || isexpr(last, :call) && any(x->x==Core.SlotNumber(1), last.args)
227+
# If the last expr calls #self# or passes it to an implementation method,
228+
# this is a wrapper function that we might want to step through
229+
while frame.pc != length(stmts)-1
230+
pc = next_call!(recurse, frame, false) # since we're in a Method we're not at toplevel
231+
end
232+
ret = evaluate_call!(dummy_breakpoint, frame, last)
233+
@assert isa(ret, BreakpointRef)
234+
return maybe_step_through_wrapper!(recurse, callee(frame))
235+
end
236+
return frame
237+
end
238+
maybe_step_through_wrapper!(frame::Frame) = maybe_step_through_wrapper!(finish_and_return!, frame)
239+
240+
"""
241+
ret = maybe_reset_frame!(recurse, frame, pc, rootistoplevel)
242+
243+
Perform a return to the caller, or descend to the level of a breakpoint.
244+
`pc` is the return state from the previous command (e.g., `next_call!` or similar).
245+
`rootistoplevel` should be true if the root frame is top-level.
246+
247+
`ret` will be `nothing` if we have just completed a top-level frame. Otherwise,
248+
249+
cframe, cpc = ret
250+
251+
where `cframe` is the frame from which execution should continue and `cpc` is the state
252+
of `cframe` (the program counter, a `BreakpointRef`, or `nothing`).
253+
"""
254+
function maybe_reset_frame!(@nospecialize(recurse), frame::Frame, @nospecialize(pc), rootistoplevel::Bool)
255+
isa(pc, BreakpointRef) && return leaf(frame), pc
256+
if pc === nothing
257+
val = get_return(frame)
258+
recycle(frame)
259+
frame = caller(frame)
260+
frame === nothing && return nothing
261+
frame.callee = nothing
262+
maybe_assign!(frame, val)
263+
frame.pc += 1
264+
pc = maybe_next_call!(recurse, frame, rootistoplevel && frame.caller===nothing)
265+
return maybe_reset_frame!(recurse, frame, pc, rootistoplevel)
266+
end
267+
return frame, pc
268+
end
269+
270+
"""
271+
ret = debug_command(recurse, frame, cmd, rootistoplevel=false)
272+
ret = debug_command(frame, cmd, rootistoplevel=false)
273+
274+
Perform one "debugger" command. `cmd` should be one of:
275+
276+
- "n": advance to the next line
277+
- "s": step into the next call
278+
- "c": continue execution until termination or reaching a breakpoint
279+
- "finish": finish the current frame and return to the parent
280+
281+
or one of the 'advanced' commands
282+
283+
- "nc": step forward to the next call
284+
- "se": execute a single statement
285+
- "si": execute a single statement, stepping in if it's a call
286+
- "sg": step into the generator of a generated function
287+
288+
`rootistoplevel` and `ret` are as described for [`JuliaInterpreter.maybe_reset_frame!`](@ref).
289+
Unlike other commands, the default setting for `recurse` is `Compiled()`.
290+
"""
291+
function debug_command(@nospecialize(recurse), frame::Frame, cmd::AbstractString, rootistoplevel::Bool=false)
292+
# ::Union{Val{:nc},Val{:n},Val{:se}},
293+
# Need try/catch
294+
istoplevel = rootistoplevel && frame.caller === nothing
295+
cmd == "nc" && return maybe_reset_frame!(recurse, frame, next_call!(recurse, frame, istoplevel), rootistoplevel)
296+
cmd == "n" && return maybe_reset_frame!(recurse, frame, next_line!(recurse, frame, istoplevel), rootistoplevel)
297+
if cmd == "si"
298+
stmt = pc_expr(frame)
299+
cmd = is_call(stmt) ? "s" : "se"
300+
end
301+
cmd == "se" && return maybe_reset_frame!(recurse, frame, step_expr!(recurse, frame, istoplevel), rootistoplevel)
302+
enter_generated = false
303+
if cmd == "sg"
304+
enter_generated = true
305+
cmd = "s"
306+
end
307+
if cmd == "s"
308+
pc = maybe_next_call!(recurse, frame, istoplevel)
309+
(isa(pc, BreakpointRef) || pc === nothing) && return maybe_reset_frame!(recurse, frame, pc, rootistoplevel)
310+
stmt0 = stmt = pc_expr(frame, pc)
311+
if isexpr(stmt, :(=))
312+
stmt = stmt.args[2]
313+
end
314+
ret = evaluate_call!(dummy_breakpoint, frame, stmt; enter_generated=enter_generated)
315+
isa(ret, BreakpointRef) && return maybe_reset_frame!(recurse, frame, ret, rootistoplevel)
316+
maybe_assign!(frame, stmt0, ret)
317+
frame.pc = ret + 1
318+
return frame, frame.pc
319+
end
320+
if cmd == "c"
321+
r = root(frame)
322+
ret = finish_stack!(recurse, r, rootistoplevel)
323+
return isa(ret, BreakpointRef) ? (leaf(r), ret) : nothing
324+
end
325+
cmd == "finish" && return maybe_reset_frame!(recurse, frame, finish!(recurse, frame, istoplevel), rootistoplevel)
326+
throw(ArgumentError("command $cmd not recognized"))
327+
end
328+
debug_command(frame::Frame, cmd::AbstractString, rootistoplevel::Bool=false) =
329+
debug_command(Compiled(), frame, cmd, rootistoplevel)

src/interpret.jl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,20 @@ function do_assignment!(frame, @nospecialize(lhs), @nospecialize(rhs))
310310
end
311311
end
312312

313+
function maybe_assign!(frame, @nospecialize(stmt), @nospecialize(val))
314+
pc = frame.pc
315+
if isexpr(stmt, :(=))
316+
lhs = stmt.args[1]
317+
do_assignment!(frame, lhs, val)
318+
elseif isassign(frame, pc)
319+
lhs = getlhs(pc)
320+
do_assignment!(frame, lhs, val)
321+
end
322+
return nothing
323+
end
324+
maybe_assign!(frame, @nospecialize(val)) = maybe_assign!(frame, pc_expr(frame), val)
325+
326+
313327
function eval_rhs(@nospecialize(recurse), frame, node::Expr)
314328
head = node.head
315329
if head == :new

0 commit comments

Comments
 (0)