Skip to content
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
33 changes: 17 additions & 16 deletions src/codeedges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -243,24 +243,25 @@ function direct_links!(cl::CodeLinks, src::CodeInfo)
icl = CodeLinks(cl.thismod, arg1)
add_inner!(cl, icl, i)
continue
elseif isexpr(stmt, :method)
if length(stmt.args) === 1
elseif isexpr(stmt, :method) || is_define_method_call_2arg(stmt) || is_define_method_call_4arg(stmt)
if ismethod1(stmt)
# A function with no methods was defined. Associate its new binding to it.
name = stmt.args[1]
if isa(name, Symbol)
name = GlobalRef(cl.thismod, name)
name = method_name(stmt)
if isa(name, Symbol) || isa(name, QuoteNode)
name = isa(name, QuoteNode) ? name.value : name
mmod = method_module(stmt)
name = GlobalRef(mmod !== nothing ? mmod : cl.thismod, name::Symbol)
end
if !isa(name, GlobalRef)
error("name ", typeof(name), " not recognized")
if isa(name, GlobalRef)
assign = get!(Vector{Int}, cl.nameassigns, name)
push!(assign, i)
targetstore = get!(Links, cl.namepreds, name)
target = P(name, targetstore)
add_links!(target, stmt, cl)
end
assign = get!(Vector{Int}, cl.nameassigns, name)
push!(assign, i)
targetstore = get!(Links, cl.namepreds, name)
target = P(name, targetstore)
add_links!(target, stmt, cl)
elseif length(stmt.args) === 3 && (arg3 = stmt.args[3]; arg3 isa CodeInfo) # method definition
elseif ismethod3(stmt) && (mbody = method_body(stmt); mbody isa CodeInfo) # method definition
# A method was defined for an existing function.
icl = CodeLinks(cl.thismod, arg3)
icl = CodeLinks(cl.thismod, mbody)
add_inner!(cl, icl, i)
end
rhs = stmt
Expand Down Expand Up @@ -957,7 +958,7 @@ function add_typedefs!(isrequired, src::CodeInfo, edges::CodeEdges, (typedef_blo
for s in var.succs
s ∈ norequire && continue
stmt2 = stmts[s]
if isexpr(stmt2, :method) && (fname = (stmt2::Expr).args[1]; fname === false || fname === nothing)
if ismethod(stmt2) && (fname = method_name(stmt2::Expr); fname === false || fname === nothing)
isrequired[s] = true
end
end
Expand All @@ -973,7 +974,7 @@ function add_typedefs!(isrequired, src::CodeInfo, edges::CodeEdges, (typedef_blo
while i <= length(stmts) && !ismethod3(stmts[i])
i += 1
end
if i <= length(stmts) && (stmts[i]::Expr).args[1] == false
if i <= length(stmts) && method_name(stmts[i]::Expr) == false
tpreds = terminal_preds(i, edges)
if minimum(tpreds) == idx && i ∉ norequire
changed |= !isrequired[i]
Expand Down
56 changes: 38 additions & 18 deletions src/signatures.jl
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ If no 3-argument `:method` expression is found, `nothing` will be returned in pl
function signature(interp::Interpreter, frame::Frame, @nospecialize(stmt), pc::Int)
mod = moduleof(frame)
lastpc = frame.pc = pc
while !isexpr(stmt, :method, 3) # wait for the 3-arg version
while !ismethod3(stmt) # wait for the 3-arg :method or 4-arg define_method
if isanonymous_typedef(stmt)
lastpc = pc = step_through_methoddef(interp, frame, stmt) # define an anonymous function
elseif is_Typeof_for_anonymous_methoddef(stmt, frame.framecode.src.code, mod)
Expand All @@ -52,8 +52,20 @@ function signature(interp::Interpreter, frame::Frame, @nospecialize(stmt), pc::I
stmt = pc_expr(frame, pc)
end
isa(stmt, Expr) || return nothing, pc
mt = extract_method_table(frame, stmt)
sigsv = lookup(interp, frame, stmt.args[2])::SimpleVector
if is_define_method_call_4arg(stmt)
# For define_method(mod, name_or_mt, sigdata, body), args[3] may be a MethodTable
arg = stmt.args[3]
mt = isa(arg, Core.MethodTable) ? arg : nothing
else
mt = extract_method_table(frame, stmt)
end
if is_define_method_call_4arg(stmt)
# define_method(mod, name, sigdata, codeinfo): sigdata is a svec(types, sparams, lnn)
sigdata = lookup(interp, frame, stmt.args[4])::SimpleVector
sigsv = Core.svec(sigdata[1], sigdata[2])
else
sigsv = lookup(interp, frame, stmt.args[2])::SimpleVector
end
sigt = signature(sigsv)
return MethodInfoKey(mt, sigt), lastpc
end
Expand Down Expand Up @@ -88,11 +100,14 @@ end

function signature_top(frame, stmt::Expr, pc)
@assert ismethod3(stmt)
if is_define_method_call_4arg(stmt)
return minid(stmt.args[4], frame.framecode.src.code, pc)
end
return minid(stmt.args[2], frame.framecode.src.code, pc)
end

function step_through_methoddef(interp::Interpreter, frame::Frame, @nospecialize(stmt))
while !isexpr(stmt, :method)
while !ismethod(stmt)
pc = step_expr!(interp, frame, stmt, true)
stmt = pc_expr(frame, pc)
end
Expand Down Expand Up @@ -166,8 +181,9 @@ function identify_framemethod_calls(frame::Frame)
end
end
elseif ismethod1(stmt)
key = stmt.args[1]
key = normalize_defsig(key, frame)
key = method_name(stmt)
mmod = method_module(stmt)
key = normalize_defsig(key, mmod !== nothing ? mmod : moduleof(frame))
key = key::GlobalRef
mi = get(methodinfos, key, nothing)
if mi === nothing
Expand All @@ -176,8 +192,9 @@ function identify_framemethod_calls(frame::Frame)
mi.stop == -1 && (mi.start = i) # advance the statement # unless we've seen the method3
end
elseif ismethod3(stmt)
key = stmt.args[1]
key = normalize_defsig(key, frame)
key = method_name(stmt)
mmod = method_module(stmt)
key = normalize_defsig(key, mmod !== nothing ? mmod : moduleof(frame))
if key isa GlobalRef
# XXX A temporary hack to fix https://github.com/JuliaDebug/LoweredCodeUtils.jl/issues/80
# We should revisit it.
Expand All @@ -186,7 +203,7 @@ function identify_framemethod_calls(frame::Frame)
elseif key isa Expr # this is a module-scoped call. We don't have to worry about these because they are named
continue
end
msrc = stmt.args[3]
msrc = method_body(stmt)
if msrc isa CodeInfo
# XXX: Properly support interpolated `Core.MethodTable`. This will require using
# `stmt.args[2]` instead of `stmt.args[1]` to identify the parent function.
Expand Down Expand Up @@ -242,7 +259,8 @@ end
# try to normalize `def` to `GlobalRef` representation
function normalize_defsig(@nospecialize(def), mod::Module)
if def isa QuoteNode
def = nameof(def.value)
val = def.value
def = val isa Symbol ? val : nameof(val)
end
if def isa Symbol
def = GlobalRef(mod, def)
Expand Down Expand Up @@ -337,7 +355,7 @@ function _rename_framemethods!(interp::Interpreter, frame::Frame,
linetop, linebody, callee, caller = sc.linetop, sc.linebody, sc.callee, sc.caller
cname = get(replacements, callee, nothing)
if cname !== nothing && cname !== callee
replacename!((src.code[linetop].args[3])::CodeInfo, callee=>cname)
replacename!(method_body(src.code[linetop])::CodeInfo, callee=>cname)
end
end
return methodinfos
Expand Down Expand Up @@ -372,8 +390,8 @@ function find_name_caller_sig(interp::Interpreter, frame::Frame, pc::Int, name::
pc === nothing && return nothing
stmt = pc_expr(frame, pc)
end
body = stmt.args[3]
if normalize_defsig(stmt.args[1], frame) !== name && isa(body, CodeInfo)
body = method_body(stmt)
if normalize_defsig(method_name(stmt), frame) !== name && isa(body, CodeInfo)
# This might be the GeneratedFunctionStub for a @generated method
for (i, bodystmt) in enumerate(body.code)
if isexpr(bodystmt, :meta) && (bodystmt::Expr).args[1] === :generated
Expand Down Expand Up @@ -433,7 +451,7 @@ end
function get_running_name(interp::Interpreter, frame::Frame, pc::Int, name::GlobalRef)
nameinfo = find_name_caller_sig(interp, frame, pc, name)
if nameinfo === nothing
pc = skip_until(@nospecialize(stmt)->isexpr(stmt, :method, 3), frame, pc)
pc = skip_until(@nospecialize(stmt)->ismethod3(stmt), frame, pc)
pc = next_or_nothing(interp, frame, pc)
return name, pc, nothing
end
Expand Down Expand Up @@ -542,7 +560,7 @@ function methoddef!(interp::Interpreter, signatures::Vector{MethodInfoKey}, fram
framecode, pcin = frame.framecode, pc
if ismethod3(stmt)
pc3 = pc
arg1 = stmt.args[1]
arg1 = method_name(stmt)
(mt, sigt), pc = signature(interp, frame, stmt, pc)
meth = whichtt(sigt, mt)
if isa(meth, Method) && (meth.sig <: sigt && sigt <: meth.sig)
Expand Down Expand Up @@ -578,7 +596,8 @@ function methoddef!(interp::Interpreter, signatures::Vector{MethodInfoKey}, fram
return pc, pc3
end
ismethod1(stmt) || Base.invokelatest(error, "expected method opening, got ", stmt)
name = normalize_defsig(stmt.args[1], frame)
mmod = method_module(stmt)
name = normalize_defsig(method_name(stmt), mmod !== nothing ? mmod : moduleof(frame))
if isa(name, Bool)
error("not valid for anonymous methods")
elseif name === missing
Expand All @@ -601,14 +620,15 @@ function methoddef!(interp::Interpreter, signatures::Vector{MethodInfoKey}, fram
while true # methods containing inner methods may need multiple trips through this loop
methinfo, pc = signature(interp, frame, stmt, pc)
stmt = pc_expr(frame, pc)
while !isexpr(stmt, :method, 3)
while !ismethod3(stmt)
pc = next_or_nothing(interp, frame, pc) # this should not check define, we've probably already done this once
pc === nothing && return nothing # this was just `function foo end`, signal "no def"
stmt = pc_expr(frame, pc)
end
pc3 = pc
stmt = stmt::Expr
name3 = normalize_defsig(stmt.args[1], frame)
mmod3 = method_module(stmt)
name3 = normalize_defsig(method_name(stmt), mmod3 !== nothing ? mmod3 : moduleof(frame))
methinfo === nothing && (error("expected a signature"); return next_or_nothing(interp, frame, pc)), pc3
mt, sigt = methinfo
# Methods like f(x::Ref{<:Real}) that use gensymmed typevars will not have the *exact*
Expand Down
62 changes: 57 additions & 5 deletions src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,68 @@ end
ismethod(frame::Frame) = ismethod(pc_expr(frame))
ismethod3(frame::Frame) = ismethod3(pc_expr(frame))

ismethod(stmt) = isexpr(stmt, :method)
ismethod1(stmt) = isexpr(stmt, :method, 1)
ismethod3(stmt) = isexpr(stmt, :method, 3)
# Check if a call argument refers to Core.define_method
function is_define_method_ref(@nospecialize(f))
is_global_ref(f, Core, :define_method) && return true
@static if isdefined(Core, :define_method)
is_quotenode_egal(f, Core.define_method) && return true
end
return false
end

# define_method(mod, name) — 2-arg form creates generic function binding
function is_define_method_call_2arg(@nospecialize(stmt))
isexpr(stmt, :call) || return false
length(stmt.args) == 3 || return false
return is_define_method_ref(stmt.args[1])
end

# define_method(mod, name_or_mt, sigdata, codeinfo) — 4-arg form defines a method
function is_define_method_call_4arg(@nospecialize(stmt))
isexpr(stmt, :call) || return false
length(stmt.args) >= 5 || return false
return is_define_method_ref(stmt.args[1])
end

ismethod(stmt) = isexpr(stmt, :method) || is_define_method_call_2arg(stmt) || is_define_method_call_4arg(stmt)
ismethod1(stmt) = isexpr(stmt, :method, 1) || is_define_method_call_2arg(stmt)
ismethod3(stmt) = isexpr(stmt, :method, 3) || is_define_method_call_4arg(stmt)

# Extract the "name" argument from a method-definition statement.
# For Expr(:method, name, ...) it's args[1]; for define_method(mod, name, ...) it's args[3].
function method_name(@nospecialize(stmt))
if is_define_method_call_2arg(stmt) || is_define_method_call_4arg(stmt)
return stmt.args[3]
else
return stmt.args[1]
end
end

# Extract the module from a define_method call, or nothing for :method expressions.
function method_module(@nospecialize(stmt))
if is_define_method_call_2arg(stmt) || is_define_method_call_4arg(stmt)
return stmt.args[2] # define_method(mod, name, ...)
end
return nothing
end

# Extract the CodeInfo body from a method3 statement.
# For Expr(:method, name, sig, body) it's args[3]; for define_method(mod, name, sigdata, body) it's args[5].
function method_body(@nospecialize(stmt))
if is_define_method_call_4arg(stmt)
return stmt.args[5]
else
return stmt.args[3]
end
end

function ismethod_with_name(src, stmt, target::AbstractString; reentrant::Bool=false)
if reentrant
name = stmt
else
ismethod3(stmt) || return false
name = stmt.args[1]
if name === nothing
name = method_name(stmt)
if name === nothing && isexpr(stmt, :method)
name = stmt.args[2]
end
end
Expand Down
1 change: 1 addition & 0 deletions test/codeedges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ function hastrackedexpr(@nospecialize(stmt))
haseval = f === :eval || (callee_matches(f, Base, :getproperty) && is_quotenode(stmt.args[2], :eval))
callee_matches(f, Core, :_typebody!) && return true, haseval
callee_matches(f, Core, :_setsuper!) && return true, haseval
LoweredCodeUtils.is_define_method_ref(f) && return true, haseval
f === :include && return true, haseval
elseif stmt.head === :thunk
any(s->any(hastrackedexpr(s)), stmt.args[1].code) && return true, haseval
Expand Down
Loading