Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pass enter_generated settings through to recursive frame creation #163

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 26 additions & 12 deletions src/construct.jl
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,13 @@ end

get_source(meth::Method) = Base.uncompressed_ast(meth)

function get_source(g::GeneratedFunctionStub, env)
b = g(env..., g.argnames...)
b isa CodeInfo && return b
return eval(b)
end
# The following creates the CodeInfo holding the generated expression. May be
# useful if we decide to revert to that behavior.
# function get_source(g::GeneratedFunctionStub, env)
# b = @which g(env..., g.argnames...)
# b isa CodeInfo && return b
# return eval(b)
# end

function copy_codeinfo(code::CodeInfo)
@static if VERSION < v"1.1.0-DEV.762"
Expand Down Expand Up @@ -129,6 +131,7 @@ function prepare_framecode(method::Method, @nospecialize(argtypes); enter_genera
framecode = get(framedict, method, nothing)
end
if framecode === nothing
method0 = method
if is_generated(method) && !enter_generated
# If we're stepping into a staged function, we need to use
# the specialization, rather than stepping through the
Expand All @@ -138,16 +141,20 @@ function prepare_framecode(method::Method, @nospecialize(argtypes); enter_genera
generator = false
else
if is_generated(method)
code = get_source(method.generator, lenv)
g = method.generator
methsg = collect(methods(g.gen))
@assert length(methsg) == 1
method = first(methsg)
code = get_source(method)
generator = true
else
code = get_source(method)
generator = false
end
end
framecode = FrameCode(method, code; generator=generator)
if is_generated(method) && !enter_generated
genframedict[(method, argtypes)] = framecode
if is_generated(method0) && !generator
genframedict[(method0, argtypes)] = framecode
else
framedict[method] = framecode
end
Expand Down Expand Up @@ -225,8 +232,10 @@ function prepare_call(@nospecialize(f), allargs; enter_generated = false)
isa(ret, Compiled) && return ret
# Typical return
framecode, lenv = ret
if is_generated(method) && enter_generated
if framecode.generator
args = Any[_Typeof(a) for a in args]
selfarg = Base.unwrap_unionall(scopeof(framecode).sig).parameters[1] # #self#
args = Any[selfarg, lenv..., args...]
end
return framecode, args, lenv, argtypes
end
Expand Down Expand Up @@ -292,7 +301,12 @@ end
Construct a new `Frame` for `framecode`, given lowered-code arguments `frameargs` and
static parameters `lenv`. See [`JuliaInterpreter.prepare_call`](@ref) for information about how to prepare the inputs.
"""
function prepare_frame(framecode::FrameCode, args::Vector{Any}, lenv::SimpleVector)
function prepare_frame(framecode::FrameCode, args::Vector{Any}, lenv::SimpleVector; enter_generated=false)
s = scopeof(framecode)
if framecode.generator
args = Any[_Typeof(a) for a in args]
args = Any[Base.unwrap_unionall(s.sig).parameters[1], lenv..., args...] # first is #self#
end
framedata = prepare_framedata(framecode, args)
resize!(framedata.sparams, length(lenv))
# Add static parameters to environment
Expand All @@ -304,8 +318,8 @@ function prepare_frame(framecode::FrameCode, args::Vector{Any}, lenv::SimpleVect
return Frame(framecode, framedata)
end

function prepare_frame_caller(caller::Frame, framecode::FrameCode, args::Vector{Any}, lenv::SimpleVector)
caller.callee = frame = prepare_frame(framecode, args, lenv)
function prepare_frame_caller(caller::Frame, framecode::FrameCode, args::Vector{Any}, lenv::SimpleVector; enter_generated=false)
caller.callee = frame = prepare_frame(framecode, args, lenv; enter_generated=enter_generated)
frame.caller = caller
return frame
end
Expand Down
2 changes: 1 addition & 1 deletion src/interpret.jl
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ function evaluate_call_recurse!(@nospecialize(recurse), frame::Frame, call_expr:
end
return framecode # this was a Builtin
end
newframe = prepare_frame_caller(frame, framecode, fargs, lenv)
newframe = prepare_frame_caller(frame, framecode, fargs, lenv; enter_generated=enter_generated)
npc = newframe.pc
shouldbreak(newframe, npc) && return BreakpointRef(newframe.framecode, npc)
# if the following errors, handle_err will pop the stack and recycle newframe
Expand Down
35 changes: 25 additions & 10 deletions test/debug.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ end
function step_through_frame(frame_creator)
rets = []
for cmd in ALL_COMMANDS
frame = frame_creator()
frame = frame_creator()
ret = step_through_command(frame, cmd)
push!(rets, ret)
end
Expand All @@ -28,11 +28,15 @@ end
step_through(f, args...; kwargs...) = step_through_frame(() -> enter_call(f, args...; kwargs...))
step_through(expr::Expr) = step_through_frame(() -> enter_call_expr(expr))

@generated function generatedfoo(T)
:(return $T)
@generated function generatedfoo(x)
# A deliberately-complicated way to perform this operation
ex = Expr(:block)
push!(ex.args, :(return x))
return ex
end
callgenerated() = generatedfoo(1)
@generated function generatedparams(a::Array{T,N}) where {T,N}
zz = 1
:(return ($T,$N))
end
callgeneratedparams() = generatedparams([1 2; 3 4])
Expand Down Expand Up @@ -91,34 +95,45 @@ struct B{T} end
@test isa(pc, BreakpointRef)
@test JuliaInterpreter.scopeof(f).name == :generatedfoo
stmt = JuliaInterpreter.pc_expr(f)
@test stmt.head == :return && stmt.args[1] === Int
@test stmt.head == :return
@test debug_command(frame, :c) === nothing
@test frame.callee === nothing
@test get_return(frame) === Int
@test get_return(frame) === 1
# This time, step into the generated function itself
frame = enter_call_expr(:($(callgenerated)()))
f, pc = debug_command(frame, :sg)
@test isa(pc, BreakpointRef)
@test JuliaInterpreter.scopeof(f).name == :generatedfoo
stmt = JuliaInterpreter.pc_expr(f)
@test stmt.head == :return && @lookup(f, stmt.args[1]) === 1
if stmt === nothing
f, pc = debug_command(f, :se)
stmt = JuliaInterpreter.pc_expr(f)
end
@test stmt.head == :(=)
f2, pc = debug_command(f, :finish)
@test JuliaInterpreter.scopeof(f2).name == :callgenerated
# Now finish the regular function
@test debug_command(frame, :finish) === nothing
@test frame.callee === nothing
@test get_return(frame) === 1
excmp = quote return x end
deleteat!(excmp.args, 1) # delete LineNumberNode
@test get_return(frame) == excmp

# Parametric generated function (see #157)
frame = fr = JuliaInterpreter.enter_call(callgeneratedparams)
while fr.pc < JuliaInterpreter.nstatements(fr.framecode) - 1
fr, pc = debug_command(fr, :se)
end
fr, pc = debug_command(fr, :sg)
@test JuliaInterpreter.scopeof(fr).name == :generatedparams
JuliaInterpreter.finish!(fr)
vzz = filter(v -> v.name == :zz, JuliaInterpreter.locals(fr))[1]
@test vzz.value == 1
fr, pc = debug_command(fr, :finish)
@test debug_command(fr, :finish) === nothing
@test JuliaInterpreter.get_return(fr) == (Int, 2)
ex = get_return(fr)
isa(ex.args[1], LineNumberNode) && deleteat!(ex.args, 1)
excmp = quote return ($Int, 2) end
deleteat!(excmp.args, 1)
@test ex == excmp
end

@testset "Optional arguments" begin
Expand Down