Skip to content

Commit

Permalink
Switch callsite detection to use stmt info (#128)
Browse files Browse the repository at this point in the history
Rips out all the heuristics that are duplicating the analysis
that inference already does to figure out what is getting called.
This should be much more precise and general. We may want to
revisit the whole CallInfo setup in the future to maybe use the
stmt info more directly, but this should be good for the moment.
  • Loading branch information
Keno authored Mar 10, 2021
1 parent 78ccf0e commit 9938126
Show file tree
Hide file tree
Showing 6 changed files with 284 additions and 222 deletions.
100 changes: 91 additions & 9 deletions src/Cthulhu.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ macro descend_code_typed(ex0...)
InteractiveUtils.gen_call_with_extracted_types_and_kwargs(__module__, :descend_code_typed, ex0)
end

"""
@interp
For debugging. Returns a CthulhuInterpreter from the appropriate entrypoint.
"""
macro interp(ex0...)
InteractiveUtils.gen_call_with_extracted_types_and_kwargs(__module__, :mkinterp, ex0)
end

"""
@descend_code_warntype
Expand Down Expand Up @@ -141,12 +150,47 @@ const descend = descend_code_typed

descend(ci::CthulhuInterpreter, mi::MethodInstance; kwargs...) = _descend(ci, mi; iswarn=false, interruptexc=false, kwargs...)

function codeinst_rt(code::CodeInstance)
rettype = code.rettype
if isdefined(code, :rettype_const)
rettype_const = code.rettype_const
if isa(rettype_const, Vector{Any}) && !(Vector{Any} <: rettype)
return Core.PartialStruct(rettype, rettype_const)
elseif rettype <: Core.OpaqueClosure && isa(rettype_const, Core.PartialOpaque)
return rettype_const
elseif isa(rettype_const, Core.InterConditional)
return rettype_const
else
return Const(rettype_const)
end
else
return rettype
end
end

function lookup(interp::CthulhuInterpreter, mi::MethodInstance, optimize::Bool)
if !optimize
codeinf = copy(interp.unopt[mi].src)
rt = interp.unopt[mi].rt
infos = interp.unopt[mi].stmt_infos
slottypes = codeinf.slottypes
else
codeinst = interp.opt[mi]
ir = Core.Compiler.copy(codeinst.inferred.ir)
codeinf = ir
rt = codeinst_rt(codeinst)
infos = ir.stmts.info
slottypes = ir.argtypes
end
(codeinf, rt, infos, slottypes)
end

##
# _descend is the main driver function.
# src/reflection.jl has the tools to discover methods
# src/ui.jl provides the user facing interface to which _descend responds
##
function _descend(interp::CthulhuInterpreter, mi::MethodInstance; iswarn::Bool, params=current_params(), optimize::Bool=true, interruptexc::Bool=true, verbose=true, kwargs...)
function _descend(interp::CthulhuInterpreter, mi::MethodInstance; override::Union{Nothing, InferenceResult} = nothing, iswarn::Bool, params=current_params(), optimize::Bool=true, interruptexc::Bool=true, verbose=true, kwargs...)
debuginfo = true
if :debuginfo in keys(kwargs)
selected = kwargs[:debuginfo]
Expand All @@ -157,13 +201,44 @@ function _descend(interp::CthulhuInterpreter, mi::MethodInstance; iswarn::Bool,
display_CI = true
while true
debuginfo_key = debuginfo ? :source : :none
codeinf = copy(optimize ? interp.opt[mi].inferred : interp.unopt[mi].src)
rt = optimize ? interp.opt[mi].rettype : interp.unopt[mi].rt
preprocess_ci!(codeinf, mi, optimize, CONFIG)
callsites = find_callsites(codeinf, mi, codeinf.slottypes; params=params, kwargs...)

display_CI && cthulu_typed(stdout, debuginfo_key, codeinf, rt, mi, iswarn, verbose)
display_CI = true
if override === nothing && optimize && interp.opt[mi].inferred === nothing
# This was inferred to a pure constant - we have no code to show,
# but make something up that looks plausible.
if display_CI
println(stdout)
println(stdout, "│ ─ $(string(Callsite(-1, MICallInfo(mi, interp.opt[mi].rettype), :invoke)))")
println(stdout, "│ return ", Const(interp.opt[mi].rettype_const))
println(stdout)
end
callsites = Callsite[]
else
if override !== nothing
if !haskey(interp.unopt, override)
Core.eval(Core.Main, :(the_issue = $override))
end
codeinf = optimize ? override.src : interp.unopt[override].src
rt = optimize ? override.result : interp.unopt[override].rt
if optimize
let os = codeinf
codeinf = Core.Compiler.copy(codeinf.ir)
infos = codeinf.stmts.info
slottypes = codeinf.argtypes
end
else
codeinf = copy(codeinf)
infos = interp.unopt[override].stmt_infos
slottypes = codeinf.slottypes
end
else
(codeinf, rt, infos, slottypes) = lookup(interp, mi, optimize)
end
preprocess_ci!(codeinf, mi, optimize, CONFIG)
callsites = find_callsites(codeinf, infos, mi, slottypes; params=params, kwargs...)

display_CI && cthulu_typed(stdout, debuginfo_key, codeinf, rt, mi, iswarn, verbose)
display_CI = true
end

TerminalMenus.config(cursor = '', scroll = :wrap)
menu = CthulhuMenu(callsites, optimize)
Expand Down Expand Up @@ -213,7 +288,9 @@ function _descend(interp::CthulhuInterpreter, mi::MethodInstance; iswarn::Bool,
continue
end

_descend(interp, next_mi; params=params, optimize=optimize,
_descend(interp, next_mi;
override = isa(callsite.info, ConstPropCallInfo) ? callsite.info.result : nothing,
params=params, optimize=optimize,
iswarn=iswarn, debuginfo=debuginfo_key, interruptexc=interruptexc, verbose=verbose, kwargs...)

elseif toggle === :warn
Expand Down Expand Up @@ -272,12 +349,17 @@ function do_typeinf!(interp, mi)
return nothing
end

function _descend(@nospecialize(F), @nospecialize(TT); params=current_params(), kwargs...)
function mkinterp(@nospecialize(F), @nospecialize(TT))
ci = CthulhuInterpreter()
sigt = Base.signature_type(F, TT)
match = Base._which(sigt)
mi = Core.Compiler.specialize_method(match)
do_typeinf!(ci, mi)
(ci, mi)
end

function _descend(@nospecialize(F), @nospecialize(TT); params=current_params(), kwargs...)
(ci, mi) = mkinterp(F, TT)
_descend(ci, mi; params=params, kwargs...)
end

Expand Down
31 changes: 30 additions & 1 deletion src/callsite.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ using Unicode

abstract type CallInfo; end

using Core.Compiler: MethodMatch

# Call could be resolved to a singular MI
struct MICallInfo <: CallInfo
mi::MethodInstance
rt
end
MICallInfo(match::MethodMatch, rt) = MICallInfo(Core.Compiler.specialize_method(match), rt)
get_mi(ci::MICallInfo) = ci.mi

# Failed
Expand Down Expand Up @@ -49,12 +52,25 @@ struct TaskCallInfo <: CallInfo
end
get_mi(tci::TaskCallInfo) = get_mi(tci.ci)

# OpaqueClosure CallInfo
struct OCCallInfo <: CallInfo
ci::MICallInfo
end
OCCallInfo(mi::MethodInstance, rt) = OCCallInfo(MICallInfo(mi, rt))
get_mi(tci::OCCallInfo) = get_mi(tci.ci)

# Special handling for ReturnTypeCall
struct ReturnTypeCallInfo <: CallInfo
called_mi::CallInfo
end
get_mi(rtci::ReturnTypeCallInfo) = get_mi(rtci.called_mi)

struct ConstPropCallInfo <: CallInfo
mi::CallInfo
result::InferenceResult
end
get_mi(cpci::ConstPropCallInfo) = cpci.result.linfo

# CUDA callsite
struct CuCallInfo <: CallInfo
cumi::MICallInfo
Expand Down Expand Up @@ -100,7 +116,7 @@ function Base.print(io::TextWidthLimiter, s::String)
end

function headstring(@nospecialize(T))
T = Base.unwrapva(T)
T = widenconst(Base.unwrapva(T))
if T isa Union || T === Union{}
return string(T)
elseif T isa UnionAll
Expand Down Expand Up @@ -164,6 +180,13 @@ function show_callinfo(limiter, ci::Union{MultiCallInfo, FailedCallInfo, Generat
__show_limited(limiter, name, tt, rt)
end

function show_callinfo(limiter, ci::ConstPropCallInfo)
# XXX: The first argument could be const-overriden too
name = ci.result.linfo.def.name
tt = ci.result.argtypes[2:end]
__show_limited(limiter, name, tt, ci.mi.rt)
end

function Base.show(io::IO, c::Callsite)
limit = get(io, :limit, false)::Bool
cols = limit ? (displaysize(io)::Tuple{Int,Int})[2] : typemax(Int)
Expand Down Expand Up @@ -200,6 +223,12 @@ function Base.show(io::IO, c::Callsite)
print(limiter, " = cucall < ")
show_callinfo(limiter, c.info.cumi)
print(limiter, " >")
elseif isa(c.info, ConstPropCallInfo)
print(limiter, " = < constprop > ")
show_callinfo(limiter, c.info)
elseif isa(c.info, OCCallInfo)
print(limiter, " = < opaque closure call > ")
show_callinfo(limiter, c.info.ci)
end
end

Expand Down
13 changes: 10 additions & 3 deletions src/codeview.jl
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,21 @@ function cthulhu_warntype(io::IO, src, rettype, debuginfo, stable_code)
debuginfo = Base.IRShow.debuginfo(debuginfo)
lineprinter = Base.IRShow.__debuginfo[debuginfo]
lambda_io::IOContext = io
if src.slotnames !== nothing
if hasfield(typeof(src), :slotnames) && src.slotnames !== nothing
slotnames = Base.sourceinfo_slotnames(src)
lambda_io = IOContext(lambda_io, :SOURCE_SLOTNAMES => slotnames)
show_variables(io, src, slotnames)
end
print(io, "Body")
InteractiveUtils.warntype_type_printer(io, rettype, true)
println(io)
ir_printer = stable_code ? Base.IRShow.show_ir : show_ir
ir_printer(lambda_io, src, lineprinter(src), InteractiveUtils.warntype_type_printer)
if isa(src, IRCode)
show(io, src)
# XXX this doesn't properly show warntype
else
ir_printer = stable_code ? Base.IRShow.show_ir : show_ir
ir_printer(lambda_io, src, lineprinter(src), InteractiveUtils.warntype_type_printer)
end
return nothing
end

Expand All @@ -81,6 +86,8 @@ function cthulu_typed(io::IO, debuginfo_key, CI, rettype, mi, iswarn, stable_cod

if iswarn
cthulhu_warntype(io, CI, rettype, debuginfo_key, stable_code)
elseif isa(CI, IRCode)
show(io, CI)
else
show(io, CI, debuginfo = debuginfo_key)
end
Expand Down
44 changes: 39 additions & 5 deletions src/interpreter.jl
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
using Core.Compiler: AbstractInterpreter, NativeInterpreter, InferenceState,
OptimizationState, CodeInfo, CodeInstance, InferenceResult
OptimizationState, CodeInfo, CodeInstance, InferenceResult, WorldRange,
IRCode, SSAValue, inlining_policy

struct InferredSource
src::CodeInfo
stmt_infos::Vector{Any}
rt::Any
end

struct OptimizedSource
ir::IRCode
isinlineable::Bool
end

mutable struct CthulhuInterpreter <: AbstractInterpreter
native::NativeInterpreter

unopt::Dict{MethodInstance, InferredSource}
unopt::Dict{Union{MethodInstance, InferenceResult}, InferredSource}
opt::Dict{MethodInstance, CodeInstance}

msgs::Dict{MethodInstance, Vector{Pair{Int, String}}}
end

CthulhuInterpreter() = CthulhuInterpreter(
NativeInterpreter(),
Dict{MethodInstance, CodeInfo}(),
Dict{MethodInstance, CodeInfo}(),
Dict{MethodInstance, InferredSource}(),
Dict{MethodInstance, CodeInstance}(),
Dict{MethodInstance, Vector{Tuple{Int, String}}}()
)

Expand All @@ -46,6 +52,7 @@ Core.Compiler.setindex!(a::Dict, b, c) = setindex!(a, b, c)
Core.Compiler.may_optimize(ei::CthulhuInterpreter) = true
Core.Compiler.may_compress(ei::CthulhuInterpreter) = false
Core.Compiler.may_discard_trees(ei::CthulhuInterpreter) = false
Core.Compiler.verbose_stmt_info(ei::CthulhuInterpreter) = true

function Core.Compiler.add_remark!(ei::CthulhuInterpreter, sv::InferenceState, msg)
push!(get!(ei.msgs, sv.linfo, Tuple{Int, String}[]),
Expand All @@ -54,10 +61,37 @@ end

function Core.Compiler.finish(state::InferenceState, ei::CthulhuInterpreter)
r = invoke(Core.Compiler.finish, Tuple{InferenceState, AbstractInterpreter}, state, ei)
ei.unopt[state.linfo] = InferredSource(
ei.unopt[Core.Compiler.any(state.result.overridden_by_const) ? state.result : state.linfo] = InferredSource(
copy(isa(state.src, OptimizationState) ?
state.src.src : state.src),
copy(state.stmt_info),
state.result.result)
return r
end

function Core.Compiler.transform_result_for_cache(interp::CthulhuInterpreter, linfo::MethodInstance,
valid_worlds::Core.Compiler.WorldRange, @nospecialize(inferred_result))
if isa(inferred_result, OptimizationState)
opt = inferred_result
if isdefined(opt, :ir)
return OptimizedSource(opt.ir, opt.src.inlineable)
end
end
return inferred_result
end

function Core.Compiler.inlining_policy(interp::CthulhuInterpreter)
function (src)
@assert isa(src, OptimizedSource)
return src.isinlineable ? src.ir : nothing
end
end

function Core.Compiler.finish!(interp::CthulhuInterpreter, caller::InferenceResult)
if isa(caller.src, OptimizationState)
opt = caller.src
if isdefined(opt, :ir)
caller.src = OptimizedSource(opt.ir, opt.src.inlineable)
end
end
end
Loading

0 comments on commit 9938126

Please sign in to comment.