Skip to content
Merged
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
3 changes: 2 additions & 1 deletion demos/abstract_analyze_versions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ sigs, c1, c2 = split_comparable(sigstable, sigmaster)
nz1, nz2 = tally0(c1, c2)
println("$stablever has $nz1 with no backedges, master has $nz2")
mx1, mx2 = maximum(c1), maximum(c2)
isexported(sig) = (ft = Base.unwrap_unionall(sig).parameters[1]; isdefined(Main, ft.name.mt.name))
get_fname(@nospecialize(fT::DataType)) = @static VERSION ≥ v"1.13.0-DEV.647" ? fT.name.singletonname : fT.name.mt.name
isexported(sig) = (ft = Base.unwrap_unionall(sig).parameters[1]; isdefined(Main, get_fname(ft)))
colors = [isexported(sig) ? "magenta" : "green" for sig in sigs]

function on_click(event)
Expand Down
6 changes: 3 additions & 3 deletions src/MethodAnalysis.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module MethodAnalysis
using AbstractTrees

using Base: Callable, IdSet
using Core: MethodInstance, CodeInfo, SimpleVector, MethodTable
using Core: MethodInstance, CodeInstance, CodeInfo, SimpleVector, MethodTable
using Base.Meta: isexpr

export visit, visit_withmodule
Expand Down Expand Up @@ -66,11 +66,11 @@ end
# A few fields are deliberately unchecked
function equal(ci1::Core.CodeInfo, ci2::Core.CodeInfo)
ret = ci1.code == ci2.code &&
ci1.codelocs == ci2.codelocs &&
(@static hasfield(Core.CodeInfo, :debuginfo) ? ci1.debuginfo == ci2.debuginfo :
ci1.codelocs == ci2.codelocs && ci1.linetable == ci2.linetable) &&
ci1.ssavaluetypes == ci2.ssavaluetypes &&
ci1.ssaflags == ci2.ssaflags &&
ci1.method_for_inference_limit_heuristics == ci2.method_for_inference_limit_heuristics &&
ci1.linetable == ci2.linetable &&
ci1.slotnames == ci2.slotnames &&
ci1.slotflags == ci2.slotflags
if VERSION >= v"1.2"
Expand Down
19 changes: 18 additions & 1 deletion src/backedges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,19 @@ function direct_backedges(f::Union{Method,Callable}; skip::Bool=true)
end
end
return false
elseif isa(item, Core.TypeName)
tn = item::Core.TypeName
if isdefined(tn, :backedges)
# `tn.backedges` is typed as `Core.MethodCache`, but is actually
# a `Vector{Any}`; the pointer shenanigans mimic a reinterpret.
sigmis = unsafe_pointer_to_objref(pointer_from_objref(tn.backedges))::Vector{Any}
for i = 1:2:length(sigmis)
sig, edge = sigmis[i], sigmis[i+1]::CodeInstance
mi = Core.Compiler.get_ci_mi(edge)
push!(bes, Pair{Any,MethodInstance}(sig, mi))
push!(_skip, mi)
end
end
elseif isa(item, MethodInstance)
callee = item::MethodInstance
if isdefined(callee, :backedges)
Expand All @@ -91,6 +104,7 @@ if isdefined(Core.Compiler, :BackedgeIterator)
else
function push_unskipped_backedges!(bes, callee, skip, _skip)
for caller in callee.backedges
isa(caller, CodeInstance) && (caller = Core.Compiler.get_ci_mi(caller))
skip && caller ∈ _skip && continue
push!(bes, callee=>caller)
end
Expand All @@ -107,7 +121,10 @@ meaning it returns an empty list even when `mi.backedges` is not defined.
function direct_backedges(mi::MethodInstance)
out = MethodInstance[]
if isdefined(mi, :backedges)
append!(out, mi.backedges)
for edge in mi.backedges
isa(edge, CodeInstance) && (edge = Core.Compiler.get_ci_mi(edge))
push!(out, edge)
end
end
return out
end
38 changes: 28 additions & 10 deletions src/findcallers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@ export findcallers
function get_typed_instances!(srcs, @nospecialize(tt), method::Method, world, interp)
# This is essentially taken from code_typed_by_type
matches = Base._methods_by_ftype(tt, -1, world)
if matches === false
error("signature $(item.specTypes) does not correspond to a generic function")
if matches === false || matches === nothing
error("signature $tt does not correspond to a generic function")
end
for match in matches
match.method == method || continue
meth = Base.func_for_method_checked(match.method, tt, match.sparams)
(src, ty) = isdefined(Core.Compiler, :NativeInterpreter) ?
meth = match.method
if isdefined(Base, :func_for_method_checked)
meth = Base.func_for_method_checked(meth, tt, match.sparams)
end
ret = isdefined(Core.Compiler, :NativeInterpreter) ?
Core.Compiler.typeinf_code(interp, meth, match.spec_types, match.sparams, false) :
Core.Compiler.typeinf_code(meth, match.spec_types, match.sparams, false, interp)
src = isa(ret, Tuple) ? ret[1] : ret::CodeInfo
push!(srcs, (src, match.sparams))
end
return srcs
Expand All @@ -22,10 +26,10 @@ defaultinterp(world) = isdefined(Core.Compiler, :NativeInterpreter) ?
Core.Compiler.NativeInterpreter(world) :
Core.Compiler.Params(world)

function get_typed_instances(mi::MethodInstance; world=typemax(UInt), interp=defaultinterp(world))
function get_typed_instances(mi::MethodInstance; world=default_world(), interp=defaultinterp(world))
return get_typed_instances!(Tuple{CodeInfo,Core.SimpleVector}[], mi, world, interp)
end
function get_typed_instances(@nospecialize(tt), method::Method; world=typemax(UInt), interp=defaultinterp(world))
function get_typed_instances(@nospecialize(tt), method::Method; world=default_world(), interp=defaultinterp(world))
return get_typed_instances!(Tuple{CodeInfo,Core.SimpleVector}[], tt, method, world, interp)
end

Expand Down Expand Up @@ -95,8 +99,8 @@ callers3 = findcallers(f, argtyps->length(argtyps) == 1 && argtyps[1] === Vector
`findcallers` is not guaranteed to find all calls. Calls can be "obfuscated" by many mechanisms,
including calls from top level, calls where the function is a runtime variable, etc.
"""
function findcallers(f, argmatch::Union{Function,Nothing}, mis::AbstractVector{Core.MethodInstance};
callhead::Symbol=:call, world=typemax(UInt), interp=defaultinterp(world))
function findcallers(f, argmatch::Union{Function,Nothing}, mis::AbstractVector{MethodInstance};
callhead::Symbol=:call, world=default_world(), interp=defaultinterp(world))
callhead === :call || callhead === :invoke || callhead === :iterate || error(":call and :invoke are supported, got ", callhead)
# Check that f is not a type with specified parameters
if f isa DataType && !isempty(f.parameters)
Expand All @@ -105,7 +109,7 @@ function findcallers(f, argmatch::Union{Function,Nothing}, mis::AbstractVector{C
# Construct a GlobalRef version of `f`
ref = GlobalRef(parentmodule(f), nameof(f))
callers = CallMatch[]
srcs = Tuple{CodeInfo,Core.SimpleVector}[]
srcs = Tuple{CodeInfo,SimpleVector}[]
for item in mis
empty!(srcs)
try
Expand Down Expand Up @@ -140,6 +144,9 @@ function findcallers(f, argmatch::Union{Function,Nothing}, mis::AbstractVector{C
throw(err)
end
end
if isa(callee, Core.Const)
callee = callee.val
end
matches = false
if callee === f
matches = true
Expand Down Expand Up @@ -188,6 +195,12 @@ function findcallers(f, argmatch::Union{Function,Nothing}, mis::AbstractVector{C
return callers
end

@static if isdefined(Base, :get_world_counter)
default_world() = Base.get_world_counter()
else
default_world() = typemax(UInt)
end

isglobalref(@nospecialize(g), mod::Module, name::Symbol) = isa(g, GlobalRef) && g.mod === mod && g.name === name

extract(a, sparams) = isa(a, Core.Const) ? Core.Typeof(a.val) :
Expand All @@ -203,7 +216,12 @@ function eval_ssa(src, sparams, id)
if stmt.head === :call
callee = stmt.args[1]
if isglobalref(callee, Core, :apply_type)
return stmt.args[2]
ret = stmt.args[2]
if isa(ret, Core.SSAValue)
# try one level deeper
ret = eval_ssa(src, sparams, ret.id)
end
return ret
elseif isglobalref(callee, Base, :getproperty)
modg, objq = stmt.args[2], stmt.args[3]
if isa(modg, Core.SSAValue)
Expand Down
22 changes: 19 additions & 3 deletions src/visit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,11 @@ function _visit(@nospecialize(operation), @nospecialize(f::Callable), visited::I
print && println("Callable ", f)
if operation(f)
ml = methods(f)
_visit(operation, ml.mt, visited, print)
@static if hasfield(Base.MethodList, :mt)
_visit(operation, ml.mt, visited, print)
else
_visit(operation, ml.tn, visited, print)
end
for m in ml.ms
_visit(operation, m, visited, print)
end
Expand All @@ -146,14 +150,17 @@ function _visit(@nospecialize(operation), ml::Base.MethodList, visited::IdSet{An
ml ∈ visited && return nothing
push!(visited, ml)
print && println("MethodList ", ml)
_visit(operation, ml.mt, visited, print)
@static if hasfield(Base.MethodList, :mt)
_visit(operation, ml.mt, visited, print)
else
_visit(operation, ml.tn, visited, print)
end
for m in ml.ms
_visit(operation, m, visited, print)
end
return nothing
end


function _visit(@nospecialize(operation), mt::MethodTable, visited::IdSet{Any}, print::Bool)
mt ∈ visited && return nothing
push!(visited, mt)
Expand All @@ -162,6 +169,14 @@ function _visit(@nospecialize(operation), mt::MethodTable, visited::IdSet{Any},
return nothing
end

function _visit(@nospecialize(operation), tn::Core.TypeName, visited::IdSet{Any}, print::Bool)
tn ∈ visited && return nothing
push!(visited, tn)
print && println("TypeName ", tn)
operation(tn)
return nothing
end

function _visit(@nospecialize(operation), m::Method, visited::IdSet{Any}, print::Bool)
m ∈ visited && return nothing
push!(visited, m)
Expand Down Expand Up @@ -285,6 +300,7 @@ else
push!(visited, mi)
if operation(mi) && isdefined(mi, :backedges)
for edge in mi.backedges
isa(edge, CodeInstance) && (edge = Core.Compiler.get_ci_mi(edge))
_visit_backedges(operation, edge, visited)
end
end
Expand Down