Skip to content

Commit

Permalink
simplify code location queries (#240)
Browse files Browse the repository at this point in the history
Especially, we really don't need to pass on `analyzer::AbstractAnalyzer`
to correctly check if a frame is top-level module or not, because JET
analyzes top-level thunk only from `virtual_process`.
And it vastly simplifies the accompanying code locations queries.
  • Loading branch information
aviatesk authored Aug 26, 2021
1 parent f77e53e commit 6591201
Show file tree
Hide file tree
Showing 8 changed files with 55 additions and 54 deletions.
5 changes: 4 additions & 1 deletion src/JET.jl
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,10 @@ end

ignorenotfound(@nospecialize(t)) = t === NOT_FOUND ? Bottom : t

# location

include("locinfo.jl")

# includes
# ========

Expand All @@ -505,7 +509,6 @@ include("abstractinterpretation.jl")
include("typeinfer.jl")
include("analyzer.jl")
include("jetcache.jl")
include("locinfo.jl")
# top-level analysis
include("graph.jl")
include("virtualprocess.jl")
Expand Down
15 changes: 6 additions & 9 deletions src/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,9 @@ end
bail_out_toplevel_call(analyzer::AbstractAnalyzer, ...)
An overload for `abstract_call_gf_by_type(analyzer::AbstractAnalyzer, ...)`, which keeps
inference on non-concrete call sites in a toplevel frame created by
[`virtual_process`](@ref).
inference on non-concrete call sites in a toplevel frame created by [`virtual_process`](@ref).
"""
function CC.bail_out_toplevel_call(analyzer::AbstractAnalyzer, @nospecialize(sig), sv)
return isa(sv.linfo.def, Module) && !isdispatchtuple(sig) && !istoplevel(analyzer, sv)
end
CC.bail_out_toplevel_call(analyzer::AbstractAnalyzer, @nospecialize(sig), sv) = false

@doc """
bail_out_call(analyzer::AbstractAnalyzer, ...)
Expand Down Expand Up @@ -310,7 +307,7 @@ end
function update_reports!(analyzer::AbstractAnalyzer, sv::InferenceState)
rs = get_to_be_updated(analyzer)
if !isempty(rs)
vf = get_virtual_frame(analyzer, sv)
vf = get_virtual_frame(sv)
for r in rs
pushfirst!(r.vst, vf)
end
Expand Down Expand Up @@ -378,7 +375,7 @@ end
end # @static if isdefined(CC, :abstract_invoke)

function CC.abstract_eval_special_value(analyzer::AbstractAnalyzer, @nospecialize(e), vtypes::VarTable, sv::InferenceState)
toplevel = istoplevel(analyzer, sv)
toplevel = istoplevel(sv)
if toplevel
if isa(e, Slot) && is_global_slot(analyzer, e)
if get_slottype((sv, get_currpc(sv)), e) === Bottom
Expand Down Expand Up @@ -544,7 +541,7 @@ function (::BasicPass)(::Type{NonBooleanCondErrorReport}, analyzer::AbstractAnal
end

function CC.abstract_eval_statement(analyzer::AbstractAnalyzer, @nospecialize(e), vtypes::VarTable, sv::InferenceState)
if istoplevel(analyzer, sv)
if istoplevel(sv)
if get_concretized(analyzer)[get_currpc(sv)]
return Any # bail out if it has been interpreted by `ConcreteInterpreter`
end
Expand All @@ -556,7 +553,7 @@ end
function CC.finish(me::InferenceState, analyzer::AbstractAnalyzer)
@invoke finish(me::InferenceState, analyzer::AbstractInterpreter)

if istoplevel(analyzer, me)
if istoplevel(me)
# find assignments of abstract global variables, and assign types to them,
# so that later analysis can refer to them

Expand Down
6 changes: 3 additions & 3 deletions src/analyzer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -482,9 +482,9 @@ function maybe_initialize_caches!(analyzer::AbstractAnalyzer)
end

# check if we're in a toplevel module
@inline istoplevel(analyzer::AbstractAnalyzer, sv::InferenceState) = istoplevel(analyzer, sv.linfo)
@inline istoplevel(::AbstractAnalyzer, ::OptimizationState) = false # optimization never happen for top-level code
@inline istoplevel(analyzer::AbstractAnalyzer, linfo::MethodInstance) = get_toplevelmod(analyzer) === linfo.def
@inline istoplevel(sv::InferenceState) = istoplevel(sv.linfo)
@inline istoplevel(::OptimizationState) = false # optimization never happen for top-level code
@inline istoplevel(linfo::MethodInstance) = isa(linfo.def, Module)

is_global_slot(analyzer::AbstractAnalyzer, slot::Int) = slot in keys(get_global_slots(analyzer))
is_global_slot(analyzer::AbstractAnalyzer, slot::Slot) = is_global_slot(analyzer, slot_id(slot))
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces.jl
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ get_spec_args(T::Type{<:InferenceErrorReport}) = error("`get_spec

# default constructor to create a report from abstract interpretation routine
function (T::Type{<:InferenceErrorReport})(analyzer::AbstractAnalyzer, state, @nospecialize(spec_args...))
vf = get_virtual_frame(analyzer, state)
vf = get_virtual_frame(state)
msg = get_msg(T, analyzer, state, spec_args...)
return T([vf], msg, vf.sig, spec_args...)
end
Expand Down
4 changes: 2 additions & 2 deletions src/legacy/abstractinterpretation
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ function abstract_call_gf_by_type(interp::AbstractAnalyzer, @nospecialize(f), ar
edges = MethodInstance[]
nonbot = 0 # the index of the only non-Bottom inference result if > 0
seen = 0 # number of signatures actually inferred
istoplevel = sv.linfo.def isa Module
# istoplevel = sv.linfo.def isa Module
multiple_matches = napplicable > 1

if f !== nothing && napplicable == 1 && is_method_pure(applicable[1]::MethodMatch)
Expand All @@ -142,7 +142,7 @@ function abstract_call_gf_by_type(interp::AbstractAnalyzer, @nospecialize(f), ar
method = match.method
sig = match.spec_types
#=== abstract_call_gf_by_type patch point 2 start ===#
if istoplevel && !isdispatchtuple(sig) && !JET.istoplevel(interp, sv) # keep going for "our" toplevel frame
if #= istoplevel && !isdispatchtuple(sig) =# false # keep going for "our" toplevel frame
#=== abstract_call_gf_by_type patch point 2 end ===#
# only infer concrete call sites in top-level expressions
add_remark!(interp, sv, "Refusing to infer non-concrete call site in top-level expression")
Expand Down
65 changes: 33 additions & 32 deletions src/locinfo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
# =============

# get location information at the given program counter (or a current counter if not specified)
function get_virtual_frame(analyzer::AbstractAnalyzer, state::StateAtPC)
sig = get_sig(analyzer, state)
function get_virtual_frame(state::StateAtPC)
sig = get_sig(state)
file, line = get_file_line(state)
linfo = isa(state, MethodInstance) ? state : first(state).linfo
return VirtualFrame(file, line, sig, linfo)
end
get_virtual_frame(analyzer::AbstractAnalyzer, sv::InferenceState) = get_virtual_frame(analyzer, (sv, get_currpc(sv)))
function get_virtual_frame(analyzer::AbstractAnalyzer, linfo::MethodInstance)
sig = get_sig(analyzer, linfo)
get_virtual_frame(sv::InferenceState) = get_virtual_frame((sv, get_currpc(sv)))
function get_virtual_frame(linfo::MethodInstance)
sig = get_sig(linfo)
file, line = get_file_line(linfo)
return VirtualFrame(file, line, sig, linfo)
end
Expand All @@ -31,7 +31,7 @@ end
# =========

# adapted from https://github.com/JuliaLang/julia/blob/0f11a7bb07d2d0d8413da05dadd47441705bf0dd/base/show.jl#L989-L1011
function get_sig(analyzer::AbstractAnalyzer, l::MethodInstance)
function get_sig(l::MethodInstance)
def = l.def
ret = if isa(def, Method)
if isdefined(def, :generator) && l === def.generator
Expand Down Expand Up @@ -66,18 +66,18 @@ end
end
end

@inline get_sig(analyzer::AbstractAnalyzer, s::StateAtPC) = return _get_sig(analyzer, s, get_stmt(s))
@inline get_sig(s::StateAtPC) = return _get_sig(s, get_stmt(s))

@inline _get_sig(analyzer::AbstractAnalyzer, s::StateAtPC, @nospecialize(x)) = return first(_get_sig_type(analyzer, s, x))::Vector{Any}
@inline _get_sig(s::StateAtPC, @nospecialize(x)) = return first(_get_sig_type(s, x))::Vector{Any}

function _get_callsig(analyzer::AbstractAnalyzer, s::StateAtPC, @nospecialize(f), args::Vector{Any};
function _get_callsig(s::StateAtPC, @nospecialize(f), args::Vector{Any};
splat::Bool = false)
sig = _get_sig(analyzer, s, f)
sig = _get_sig(s, f)
push!(sig, '(')

nargs = length(args)
for (i, arg) in enumerate(args)
arg_sig = _get_sig(analyzer, s, arg)
arg_sig = _get_sig(s, arg)
append!(sig, arg_sig)
if i nargs
push!(sig, ", ")
Expand All @@ -90,7 +90,7 @@ function _get_callsig(analyzer::AbstractAnalyzer, s::StateAtPC, @nospecialize(f)
return sig
end

function _get_sig_type(analyzer::AbstractAnalyzer, s::StateAtPC, expr::Expr)
function _get_sig_type(s::StateAtPC, expr::Expr)
head = expr.head
if head === :call
f = first(expr.args)
Expand All @@ -103,24 +103,24 @@ function _get_sig_type(analyzer::AbstractAnalyzer, s::StateAtPC, expr::Expr)
end
f = args[2]
args = args[3:end]
return _get_callsig(analyzer, s, f, args; splat = true), nothing
return _get_callsig(s, f, args; splat = true), nothing
else
return _get_callsig(analyzer, s, f, args), nothing
return _get_callsig(s, f, args), nothing
end
elseif head === :invoke
f = expr.args[2]
args = expr.args[3:end]
return _get_callsig(analyzer, s, f, args), nothing
return _get_callsig(s, f, args), nothing
elseif head === :(=)
return _get_sig_type(analyzer, s, last(expr.args))
return _get_sig_type(s, last(expr.args))
elseif head === :static_parameter
typ = widenconst(first(s).sptypes[first(expr.args)])
return Any['_', typ], typ
else
return Any[string(expr)], nothing
end
end
function _get_sig_type(analyzer::AbstractAnalyzer, (sv, _)::StateAtPC, ssa::SSAValue)
function _get_sig_type((sv, _)::StateAtPC, ssa::SSAValue)
news = (sv, ssa.id)
if isa(sv, OptimizationState)
# when working on `OptimizationState`, the SSA traverse could be really long because
Expand All @@ -129,22 +129,22 @@ function _get_sig_type(analyzer::AbstractAnalyzer, (sv, _)::StateAtPC, ssa::SSAV
sig = Any["%$(ssa.id)", typ]
else
# XXX the same problem _may_ happen for `InferenceState` too ?
sig, sig_typ = _get_sig_type(analyzer, news, get_stmt(news))
sig, sig_typ = _get_sig_type(news, get_stmt(news))
typ = widenconst(ignorelimited(ignorenotfound(get_ssavaluetype(news))))
sig_typ == typ || push!(sig, typ) # XXX I forgot why I added this line ...
end
return sig, typ
end
function _get_sig_type(analyzer::AbstractAnalyzer, s::StateAtPC, slot::SlotNumber)
function _get_sig_type(s::StateAtPC, slot::SlotNumber)
sv = first(s)
name = get_slotname(sv, slot)
sig = string(name)
if isempty(sig)
sig = string(slot) # fallback if no explicit slotname
end
if istoplevel(analyzer, sv)
if istoplevel(sv)
# this is a abstract global variable, form the global reference
return _get_sig_type(analyzer, s, GlobalRef(get_toplevelmod(analyzer), name))
return _get_sig_type(s, GlobalRef(sv.linfo.def::Module, name))
else
# we can use per-program counter type after inference
t = (isa(sv, InferenceState) && sv.inferred) ? get_slottype(sv, slot) : get_slottype(s, slot)
Expand All @@ -153,31 +153,32 @@ function _get_sig_type(analyzer::AbstractAnalyzer, s::StateAtPC, slot::SlotNumbe
end
end
# NOTE `Argument` is introduced by optimization, and so we don't need to handle abstract global variable here, etc.
function _get_sig_type(analyzer::AbstractAnalyzer, (sv, _)::StateAtPC, arg::Argument)
function _get_sig_type((sv, _)::StateAtPC, arg::Argument)
name = get_slotname(sv, arg.n)
sig = string(name)
typ = widenconst(ignorelimited(get_slottype(sv, arg))) # after optimization we shouldn't use `get_slottype(::StateAtPC, ::Any)`
return Any[sig, typ], typ
end
_get_sig_type(analyzer::AbstractAnalyzer, _::StateAtPC, gr::GlobalRef) = Any[string(gr.mod, '.', gr.name)], nothing
function _get_sig_type(analyzer::AbstractAnalyzer, s::StateAtPC, name::Symbol)
if istoplevel(analyzer, first(s))
_get_sig_type(_::StateAtPC, gr::GlobalRef) = Any[string(gr.mod, '.', gr.name)], nothing
function _get_sig_type(s::StateAtPC, name::Symbol)
sv = first(s)
if istoplevel(sv)
# this is concrete global variable, form the global reference
return _get_sig_type(analyzer, s, GlobalRef(get_toplevelmod(analyzer), name))
return _get_sig_type(s, GlobalRef(sv.linfo.def, name))
else
return Any[repr(name; context = :compact => true)], nothing
end
end
function _get_sig_type(analyzer::AbstractAnalyzer, s::StateAtPC, gotoifnot::GotoIfNot)
sig = Any[string("goto %", gotoifnot.dest, " if not "), _get_sig(analyzer, s, gotoifnot.cond)...]
function _get_sig_type(s::StateAtPC, gotoifnot::GotoIfNot)
sig = Any[string("goto %", gotoifnot.dest, " if not "), _get_sig(s, gotoifnot.cond)...]
return sig, nothing
end
function _get_sig_type(analyzer::AbstractAnalyzer, s::StateAtPC, rn::ReturnNode)
sig = is_unreachable(rn) ? Any["unreachable"] : Any["return ", _get_sig(analyzer, s, rn.val)...]
function _get_sig_type(s::StateAtPC, rn::ReturnNode)
sig = is_unreachable(rn) ? Any["unreachable"] : Any["return ", _get_sig(s, rn.val)...]
return sig, nothing
end
function _get_sig_type(analyzer::AbstractAnalyzer, ::StateAtPC, qn::QuoteNode)
function _get_sig_type(::StateAtPC, qn::QuoteNode)
typ = typeof(qn.value)
return Any[string(qn), typ], typ
end
_get_sig_type(analyzer::AbstractAnalyzer, ::StateAtPC, @nospecialize(x)) = Any[repr(x; context = :compact => true)], nothing
_get_sig_type(::StateAtPC, @nospecialize(x)) = Any[repr(x; context = :compact => true)], nothing
4 changes: 2 additions & 2 deletions src/tfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ end
get_spec_args(report::SeriousExceptionReport) = (report.err,)

function SeriousExceptionReport(analyzer::AbstractAnalyzer, state::InferenceState, err)
vf = get_virtual_frame(analyzer, state)
vf = get_virtual_frame(state)
msg = string(first(split(sprint(showerror, err), '\n')))
ret = SeriousExceptionReport([vf], msg, vf.sig, err)
push!(get_throw_locs(analyzer), get_lin((state, get_currpc(state))))
Expand Down Expand Up @@ -203,7 +203,7 @@ function istoplevel_globalref(analyzer::AbstractAnalyzer, sv::InferenceState)
def.name === :getproperty || return false
def.sig === Tuple{typeof(getproperty), Module, Symbol} || return false
parent = sv.parent
return !isnothing(parent) && istoplevel(analyzer, parent)
return !isnothing(parent) && istoplevel(parent)
end

# `return_type_tfunc` internally uses `abstract_call` to model `Core.Compiler.return_type`
Expand Down
8 changes: 4 additions & 4 deletions src/typeinfer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ function report_undefined_local_slots!(analyzer::AbstractAnalyzer, frame::Infere
sym = stmt.args[1]::Symbol

# slots in toplevel frame may be a abstract global slot
istoplevel(analyzer, frame) && is_global_slot(analyzer, sym) && continue
istoplevel(frame) && is_global_slot(analyzer, sym) && continue

if unsound
next_idx = idx + 1
Expand Down Expand Up @@ -257,12 +257,12 @@ This is reported only when it's not caught by control flow.
throw_calls::Vector{Tuple{Int,Expr}} # (pc, call)
end
function UncaughtExceptionReport(analyzer::AbstractAnalyzer, sv::InferenceState, throw_calls::Vector{Tuple{Int,Expr}})
vf = get_virtual_frame(analyzer, sv.linfo)
vf = get_virtual_frame(sv.linfo)
msg = length(throw_calls) == 1 ? "may throw" : "may throw either of"
sig = Any[]
ncalls = length(throw_calls)
for (i, (pc, call)) in enumerate(throw_calls)
call_sig = _get_sig(analyzer, (sv, pc), call)
call_sig = _get_sig((sv, pc), call)
append!(sig, call_sig)
i ncalls && push!(sig, ", ")
end
Expand Down Expand Up @@ -309,7 +309,7 @@ function is_throw_call_expr(analyzer::AbstractAnalyzer, frame::InferenceState, @
if isa(e, Expr)
if e.head === :call
f = e.args[1]
if istoplevel(analyzer, frame) && isa(f, Symbol)
if istoplevel(frame) && isa(f, Symbol)
f = GlobalRef(get_toplevelmod(analyzer), f)
end
if isa(f, GlobalRef)
Expand Down

0 comments on commit 6591201

Please sign in to comment.