Skip to content

Commit

Permalink
Look through SSAValue to find include and similar
Browse files Browse the repository at this point in the history
There's no guarantee that all include calls will be literal GlobalRefs
and in fact JuliaLang/julia#56746 will likely
make them never GlobalRef.
  • Loading branch information
Keno committed Dec 5, 2024
1 parent 71eb16a commit a2943da
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 17 deletions.
26 changes: 19 additions & 7 deletions src/lowered.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ add_dependencies!(methodinfo::MethodInfo, be::CodeEdges, src, isrequired) = meth
add_includes!(methodinfo::MethodInfo, mod::Module, filename) = methodinfo

function is_some_include(@nospecialize(f))
@assert !isa(f, Core.SSAValue) && !isa(f, JuliaInterpreter.SSAValue)
if isa(f, GlobalRef)
return f.name === :include
elseif isa(f, Symbol)
Expand All @@ -44,17 +45,21 @@ function is_some_include(@nospecialize(f))
end

# This is not generally used, see `is_method_or_eval` instead
function hastrackedexpr(stmt; heads=LoweredCodeUtils.trackedheads)
function hastrackedexpr(stmt, code; heads=LoweredCodeUtils.trackedheads)
haseval = false
if isa(stmt, Expr)
haseval = matches_eval(stmt)
if stmt.head === :call
f = stmt.args[1]
while isa(f, Core.SSAValue) || isa(f, JuliaInterpreter.SSAValue)
f = code[f.id]
end

Check warning on line 56 in src/lowered.jl

View check run for this annotation

Codecov / codecov/patch

src/lowered.jl#L54-L56

Added lines #L54 - L56 were not covered by tests
callee_matches(f, Core, :_typebody!) && return true, haseval
callee_matches(f, Core, :_setsuper!) && return true, haseval
is_some_include(f) && return true, haseval
elseif stmt.head === :thunk
any(s->any(hastrackedexpr(s; heads=heads)), (stmt.args[1]::Core.CodeInfo).code) && return true, haseval
newcode = (stmt.args[1]::Core.CodeInfo).code
any(s->any(hastrackedexpr(s, newcode; heads=heads)), newcode) && return true, haseval
elseif stmt.head heads
return true, haseval
end
Expand All @@ -70,14 +75,21 @@ function matches_eval(stmt::Expr)
(isa(f, GlobalRef) && f.name === :eval) || is_quotenode_egal(f, Core.eval)
end

function categorize_stmt(@nospecialize(stmt))
function categorize_stmt(@nospecialize(stmt), code::Vector{Any})
ismeth, haseval, isinclude, isnamespace, istoplevel = false, false, false, false, false
if isa(stmt, Expr)
haseval = matches_eval(stmt)
ismeth = stmt.head === :method || (stmt.head === :thunk && defines_function(only(stmt.args)))
istoplevel = stmt.head === :toplevel
isnamespace = stmt.head === :export || stmt.head === :import || stmt.head === :using
isinclude = stmt.head === :call && is_some_include(stmt.args[1])
isinclude = false
if stmt.head === :call && length(stmt.args) >= 1
callee = stmt.args[1]
while isa(callee, Core.SSAValue) || isa(callee, JuliaInterpreter.SSAValue)
callee = code[callee.id]
end
isinclude = is_some_include(callee)
end
end
return ismeth, haseval, isinclude, isnamespace, istoplevel
end
Expand Down Expand Up @@ -112,7 +124,7 @@ function minimal_evaluation!(@nospecialize(predicate), methodinfo, mod::Module,
evalassign = false
for (i, stmt) in enumerate(src.code)
if !isrequired[i]
isrequired[i], haseval = predicate(stmt)::Tuple{Bool,Bool}
isrequired[i], haseval = predicate(stmt, src.code)::Tuple{Bool,Bool}
if haseval # line `i` may be the equivalent of `f = Core.eval`, so...
isrequired[edges.succs[i]] .= true # ...require each stmt that calls `eval` via `f(expr)`
isrequired[i] = true
Expand Down Expand Up @@ -169,8 +181,8 @@ end
minimal_evaluation!(predicate, methodinfo, moduleof(frame), frame.framecode.src, mode)

function minimal_evaluation!(methodinfo, frame::JuliaInterpreter.Frame, mode::Symbol)
minimal_evaluation!(methodinfo, frame, mode) do @nospecialize(stmt)
ismeth, haseval, isinclude, isnamespace, istoplevel = categorize_stmt(stmt)
minimal_evaluation!(methodinfo, frame, mode) do @nospecialize(stmt), code
ismeth, haseval, isinclude, isnamespace, istoplevel = categorize_stmt(stmt, code)
isreq = ismeth | isinclude | istoplevel
return mode === :sigs ? (isreq, haseval) : (isreq | isnamespace, haseval)
end
Expand Down
26 changes: 17 additions & 9 deletions src/packagedef.jl
Original file line number Diff line number Diff line change
Expand Up @@ -447,15 +447,23 @@ push_expr!(methodinfo::CodeTrackingMethodInfo, mod::Module, ex::Expr) = (push!(m
pop_expr!(methodinfo::CodeTrackingMethodInfo) = (pop!(methodinfo.exprstack); methodinfo)
function add_dependencies!(methodinfo::CodeTrackingMethodInfo, edges::CodeEdges, src, musteval)
isempty(src.code) && return methodinfo
stmt1 = first(src.code)
if isa(stmt1, Core.GotoIfNot) && (dep = stmt1.cond; isa(dep, Union{GlobalRef,Symbol}))
# This is basically a hack to look for symbols that control definition of methods via a conditional.
# It is aimed at solving #249, but this will have to be generalized for anything real.
for (stmt, me) in zip(src.code, musteval)
me || continue
if hastrackedexpr(stmt)[1]
push!(methodinfo.deps, dep)
break
for i = 1:length(src.code)
stmt = src.code[i]
if isa(stmt, Core.GotoIfNot)
dep = stmt.cond
while (isa(dep, Core.SSAValue) || isa(dep, JuliaInterpreter.SSAValue))
dep = src.code[dep.id]
end
if isa(dep, Union{GlobalRef,Symbol})
# This is basically a hack to look for symbols that control definition of methods via a conditional.
# It is aimed at solving #249, but this will have to be generalized for anything real.
for (stmt, me) in zip(src.code, musteval)
me || continue
if hastrackedexpr(stmt, src.code)[1]
push!(methodinfo.deps, dep)
break
end
end
end
end
end
Expand Down
5 changes: 4 additions & 1 deletion test/backedges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ do_test("Backedges") && @testset "Backedges" begin
src2 = src.code[idtype].args[1]
methodinfo = Revise.MethodInfo()
isrequired = Revise.minimal_evaluation!(methodinfo, frame, :sigs)[1]
@test sum(isrequired) == length(src.code)-count(e->isexpr(e, :latestworld), src.code)-1 # skips the `return` at the end
laststmt = src.code[end]
@assert isa(laststmt, Core.ReturnNode)
to_skip = isa(laststmt.val, Revise.JuliaInterpreter.SSAValue) ? 2 : 1
@test sum(isrequired) == length(src.code)-count(e->isexpr(e, :latestworld), src.code)-to_skip # skips the `return` at the end (and its argument)

src = """
# issue #249
Expand Down

0 comments on commit a2943da

Please sign in to comment.