From 378e780b7e90c248a6770dda5035561aee60eec1 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Fri, 22 Nov 2024 03:16:58 +0900 Subject: [PATCH 1/3] adjust to JuliaLang/julia#54899 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Although these code paths don’t seem to be used very often. --- src/reflection.jl | 7 ++++++- test/irutils.jl | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/reflection.jl b/src/reflection.jl index d1613ea7..9e39cd93 100644 --- a/src/reflection.jl +++ b/src/reflection.jl @@ -76,7 +76,12 @@ function find_callsites(interp::AbstractInterpreter, CI::Union{Core.CodeInfo, IR (; head, args) = stmt if head === :invoke rt = argextype(SSAValue(id), CI, sptypes, slottypes) - mi = args[1]::MethodInstance + arg1 = args[1] + if arg1 isa CodeInstance + mi = arg1.def + else + mi = arg1::MethodInstance + end effects = get_effects(interp, mi, false) callsite = Callsite(id, MICallInfo(mi, rt, effects), head) elseif head === :foreigncall diff --git a/test/irutils.jl b/test/irutils.jl index 453b4ccb..d82e9d10 100644 --- a/test/irutils.jl +++ b/test/irutils.jl @@ -35,7 +35,11 @@ end # check if `x` is a statically-resolved call of a function whose name is `sym` isinvoke(y) = @nospecialize(x) -> isinvoke(y, x) isinvoke(sym::Symbol, @nospecialize(x)) = isinvoke(mi->mi.def.name===sym, x) +if VERSION ≥ v"1.12.0-DEV.1667" +isinvoke(pred::Function, @nospecialize(x)) = isexpr(x, :invoke) && pred(x.args[1]::CodeInstance) +else isinvoke(pred::Function, @nospecialize(x)) = isexpr(x, :invoke) && pred(x.args[1]::MethodInstance) +end function fully_eliminated(@nospecialize args...; retval=(@__FILE__), kwargs...) code = code_typed1(args...; kwargs...).code From 2a935dacfe755d6f44ad83ba3b9849736ee2e99a Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Fri, 22 Nov 2024 03:22:44 +0900 Subject: [PATCH 2/3] Update test/irutils.jl --- test/irutils.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/irutils.jl b/test/irutils.jl index d82e9d10..9e2a6c23 100644 --- a/test/irutils.jl +++ b/test/irutils.jl @@ -35,7 +35,7 @@ end # check if `x` is a statically-resolved call of a function whose name is `sym` isinvoke(y) = @nospecialize(x) -> isinvoke(y, x) isinvoke(sym::Symbol, @nospecialize(x)) = isinvoke(mi->mi.def.name===sym, x) -if VERSION ≥ v"1.12.0-DEV.1667" +@static if VERSION ≥ v"1.12.0-DEV.1667" isinvoke(pred::Function, @nospecialize(x)) = isexpr(x, :invoke) && pred(x.args[1]::CodeInstance) else isinvoke(pred::Function, @nospecialize(x)) = isexpr(x, :invoke) && pred(x.args[1]::MethodInstance) From c1c6407be27a474054f625fbc4ef1151da1e988f Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Wed, 27 Nov 2024 20:03:41 +0900 Subject: [PATCH 3/3] wip: make Cthulhu cache `CodeInstance`-based --- src/Cthulhu.jl | 172 +++++++++++++++++++--------------------- src/callsite.jl | 102 +++++++++++------------- src/codeview.jl | 22 ++--- src/interface.jl | 46 +++++------ src/interpreter.jl | 82 +++++++++++-------- src/reflection.jl | 95 ++++++++++------------ test/generate_irshow.jl | 4 +- test/setup.jl | 12 +-- test/test_Cthulhu.jl | 24 +++--- test/test_codeview.jl | 14 ++-- test/test_irshow.jl | 4 +- 11 files changed, 275 insertions(+), 302 deletions(-) diff --git a/src/Cthulhu.jl b/src/Cthulhu.jl index 6b0f3246..dc449aca 100644 --- a/src/Cthulhu.jl +++ b/src/Cthulhu.jl @@ -347,26 +347,39 @@ end get_effects(codeinst::CodeInstance) = CC.decode_effects(codeinst.ipo_purity_bits) get_effects(codeinst::CodeInfo) = CC.decode_effects(codeinst.purity) -get_effects(src::InferredSource) = src.effects get_effects(result::InferenceResult) = result.ipo_effects get_effects(result::CC.ConstPropResult) = get_effects(result.result) get_effects(result::CC.ConcreteResult) = result.effects get_effects(result::CC.SemiConcreteResult) = result.effects +struct LookupResult + src::Union{CodeInfo,IRCode} + rt + exct + infos::Vector{CCCallInfo} + slottypes::Vector{Any} + effects::Effects + codeinf::Union{Nothing,CodeInfo} + function LookupResult(src::Union{CodeInfo,IRCode}, @nospecialize(rt), @nospecialize(exct), + infos::Vector{CCCallInfo}, slottypes::Vector{Any}, + effects::Effects, codeinf::Union{Nothing,CodeInfo}) + return new(src, rt, exct, infos, slottypes, effects, codeinf) + end +end + # `@constprop :aggressive` here in order to make sure the constant propagation of `allow_no_src` -@constprop :aggressive function lookup(interp::CthulhuInterpreter, mi::MethodInstance, optimize::Bool; allow_no_src::Bool=false) +@constprop :aggressive function lookup(interp::CthulhuInterpreter, ci::CodeInstance, optimize::Bool; allow_no_src::Bool=false) if optimize - return lookup_optimized(interp, mi, allow_no_src) + return lookup_optimized(interp, ci, allow_no_src) else - return lookup_unoptimized(interp, mi) + return lookup_unoptimized(interp, ci) end end -function lookup_optimized(interp::CthulhuInterpreter, mi::MethodInstance, allow_no_src::Bool=false) - codeinst = interp.opt[mi] - rt = cached_return_type(codeinst) - exct = cached_exception_type(codeinst) - opt = codeinst.inferred +function lookup_optimized(interp::CthulhuInterpreter, ci::CodeInstance, allow_no_src::Bool=false) + rt = cached_return_type(ci) + exct = cached_exception_type(ci) + opt = ci.inferred if opt !== nothing opt = opt::OptimizedSource src = CC.copy(opt.ir) @@ -378,28 +391,29 @@ function lookup_optimized(interp::CthulhuInterpreter, mi::MethodInstance, allow_ # But with coverage on, the empty function body isn't empty due to :code_coverage_effect expressions. codeinf = src = nothing infos = [] - slottypes = Any[Base.unwrap_unionall(mi.specTypes).parameters...] + slottypes = Any[Base.unwrap_unionall(ci.def.specTypes).parameters...] else Core.eval(Main, quote interp = $interp - mi = $mi + ci = $ci end) error("couldn't find the source; inspect `Main.interp` and `Main.mi`") end - effects = get_effects(codeinst) - return (; src, rt, exct, infos, slottypes, effects, codeinf) + effects = get_effects(ci) + return LookupResult(src, rt, exct, infos, slottypes, effects, codeinf) end -function lookup_unoptimized(interp::CthulhuInterpreter, mi::MethodInstance) - codeinf = src = copy(interp.unopt[mi].src) - (; rt, exct) = interp.unopt[mi] - infos = interp.unopt[mi].stmt_info - effects = get_effects(interp.unopt[mi]) +function lookup_unoptimized(interp::CthulhuInterpreter, ci::CodeInstance) + unopt = interp.unopt[ci] + codeinf = src = copy(unopt.src) + (; rt, exct) = unopt + infos = unopt.stmt_info + effects = unopt.effects slottypes = src.slottypes if isnothing(slottypes) slottypes = Any[ Any for i = 1:length(src.slotflags) ] end - return (; src, rt, exct, infos, slottypes, effects, codeinf) + return LookupResult(src, rt, exct, infos, slottypes, effects, codeinf) end function lookup_constproped(interp::CthulhuInterpreter, override::InferenceResult, optimize::Bool) @@ -412,29 +426,21 @@ end function lookup_constproped_optimized(interp::CthulhuInterpreter, override::InferenceResult) opt = override.src - if isa(opt, OptimizedSource) - # `(override::InferenceResult).src` might has been transformed to OptimizedSource already, - # e.g. when we switch from constant-prop' unoptimized source - src = CC.copy(opt.ir) - rt = override.result - exct = @static hasfield(InferenceResult, :exc_result) ? override.exc_result : nothing - infos = src.stmts.info - slottypes = src.argtypes - codeinf = opt.src - effects = opt.effects - return (; src, rt, exct, infos, slottypes, effects, codeinf) - else - # the source might be unavailable at this point, - # when a result is fully constant-folded etc. - return lookup(interp, override.linfo, true) - end + isa(opt, OptimizedSource) || error("couldn't find the source") + # `(override::InferenceResult).src` might has been transformed to OptimizedSource already, + # e.g. when we switch from constant-prop' unoptimized source + src = CC.copy(opt.ir) + rt = override.result + exct = @static hasfield(InferenceResult, :exc_result) ? override.exc_result : nothing + infos = src.stmts.info + slottypes = src.argtypes + codeinf = opt.src + effects = opt.effects + return LookupResult(src, rt, exct, infos, slottypes, effects, codeinf) end function lookup_constproped_unoptimized(interp::CthulhuInterpreter, override::InferenceResult) - unopt = get(interp.unopt, override, nothing) - if unopt === nothing - unopt = interp.unopt[override.linfo] - end + unopt = interp.unopt[override] codeinf = src = copy(unopt.src) (; rt, exct) = unopt infos = unopt.stmt_info @@ -443,7 +449,7 @@ function lookup_constproped_unoptimized(interp::CthulhuInterpreter, override::In if isnothing(slottypes) slottypes = Any[ Any for i = 1:length(src.slotflags) ] end - return (; src, rt, exct, infos, slottypes, effects, codeinf) + return LookupResult(src, rt, exct, infos, slottypes, effects, codeinf) end function lookup_semiconcrete(interp::CthulhuInterpreter, override::SemiConcreteCallInfo, optimize::Bool) @@ -453,8 +459,8 @@ function lookup_semiconcrete(interp::CthulhuInterpreter, override::SemiConcreteC infos = src.stmts.info slottypes = src.argtypes effects = get_effects(override) - (; codeinf) = lookup(interp, get_mi(override), optimize) - return (; src, rt, exct, infos, slottypes, effects, codeinf) + codeinf = nothing # TODO try to find `CodeInfo` for the regular inference? + return LookupResult(src, rt, exct, infos, slottypes, effects, codeinf) end function get_override(@nospecialize(info)) @@ -496,8 +502,6 @@ function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::Abs debuginfo = getfield(DInfo, debuginfo)::DebugInfo end - is_cached(key::MethodInstance) = can_descend(interp, key, optimize & !annotate_source) - menu_options = (; cursor = '•', scroll_wrap = true) display_CI = true view_cmd = cthulhu_typed @@ -525,15 +529,16 @@ function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::Abs (; src, rt, exct, infos, slottypes, codeinf, effects) = lookup_semiconcrete(interp, curs, override, optimize & !annotate_source) else if optimize && !annotate_source - codeinst = get_optimized_codeinst(interp, curs) + codeinst = curs.ci if codeinst.inferred === nothing if isdefined(codeinst, :rettype_const) + # TODO use `codeinfo_for_const` # This was inferred to a pure constant - we have no code to show, # but make something up that looks plausible. callsites = Callsite[] if display_CI exct = @static VERSION ≥ v"1.11.0-DEV.945" ? codeinst.exct : nothing - callsite = Callsite(-1, MICallInfo(codeinst.def, codeinst.rettype, get_effects(codeinst), exct), :invoke) + callsite = Callsite(-1, EdgeCallInfo(codeinst, codeinst.rettype, get_effects(codeinst), exct), :invoke) println(iostream) println(iostream, "│ ─ $callsite") println(iostream, "│ return ", Const(codeinst.rettype_const)) @@ -552,16 +557,17 @@ function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::Abs end (; src, rt, exct, infos, slottypes, effects, codeinf) = lookup(interp, curs, optimize & !annotate_source) end - mi = get_mi(curs) + ci = get_ci(curs) + mi = ci.def src = preprocess_ci!(src, mi, optimize & !annotate_source, CONFIG) if (optimize & !annotate_source) || isa(src, IRCode) # optimization might have deleted some statements infos = src.stmts.info else @assert length(src.code) == length(infos) end - infkey = override isa InferenceResult ? override : mi + infkey = override isa InferenceResult ? override : ci @static if VERSION ≥ v"1.11.0-DEV.1127" - pc2excts = exception_type ? get_excts(interp, infkey) : nothing + pc2excts = exception_type ? get_pc_exct(interp, infkey) : nothing else if exception_type @warn "Statement-wise and call-wise exception type information is available only on v\"1.11.0-DEV.1127\" and later" @@ -579,13 +585,14 @@ function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::Abs end if display_CI - pc2remarks = remarks ? get_remarks(interp, infkey) : nothing - pc2effects = with_effects ? get_effects(interp, infkey) : nothing + pc2remarks = remarks ? get_pc_remarks(interp, infkey) : nothing + pc2effects = with_effects ? get_pc_effects(interp, infkey) : nothing printstyled(IOContext(iostream, :limit=>true), mi.def, '\n'; bold=true) if debuginfo == DInfo.compact - str = let debuginfo=debuginfo, src=src, codeinf=codeinf, rt=rt, mi=mi, + str = let debuginfo=debuginfo, src=src, codeinf=codeinf, rt=rt, iswarn=iswarn, hide_type_stable=hide_type_stable, - pc2remarks=pc2remarks, pc2effects=pc2effects, inline_cost=inline_cost, type_annotations=type_annotations + pc2remarks=pc2remarks, pc2effects=pc2effects, pc2excts=pc2excts, + inline_cost=inline_cost, type_annotations=type_annotations ioctx = IOContext(iostream, :color => true, :displaysize => displaysize(iostream), # displaysize doesn't propagate otherwise @@ -593,7 +600,7 @@ function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::Abs :with_effects => with_effects, :exception_type => exception_type) stringify(ioctx) do lambda_io - cthulhu_typed(lambda_io, debuginfo, annotate_source ? codeinf : src, rt, exct, effects, mi; + cthulhu_typed(lambda_io, debuginfo, annotate_source ? codeinf : src, rt, exct, effects, ci; iswarn, optimize, hide_type_stable, pc2remarks, pc2effects, pc2excts, inline_cost, type_annotations, annotate_source, inlay_types_vscode, diagnostics_vscode, @@ -608,10 +615,10 @@ function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::Abs print(iostream, str) else lambda_io = IOContext(iostream, - :SOURCE_SLOTNAMES => Base.sourceinfo_slotnames(codeinf), + :SOURCE_SLOTNAMES => codeinf === nothing ? false : Base.sourceinfo_slotnames(codeinf), :with_effects => with_effects, :exception_type => exception_type) - cthulhu_typed(lambda_io, debuginfo, src, rt, exct, effects, mi; + cthulhu_typed(lambda_io, debuginfo, src, rt, exct, effects, ci; iswarn, optimize, hide_type_stable, pc2remarks, pc2effects, pc2excts, inline_cost, type_annotations, annotate_source, inlay_types_vscode, diagnostics_vscode, @@ -692,14 +699,7 @@ function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::Abs end # forcibly enter and inspect the frame, although the native interpreter gave up - if info isa UncachedCallInfo - @info """ - Inference didn't cache this call information because of imprecise analysis due to recursion: - Cthulhu nevertheless is trying to descend into it for further inspection. - """ - additional_descend(get_mi(info)::MethodInstance) - continue - elseif info isa TaskCallInfo + if info isa TaskCallInfo @info """ Inference didn't analyze this call because it is a dynamic call: Cthulhu nevertheless is trying to descend into it for further inspection. @@ -744,10 +744,6 @@ function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::Abs TypedSyntax.clear_diagnostics_vscode() elseif toggle === :optimize optimize ⊻= true - if !is_cached(get_mi(curs)) - @warn "Can't switch to post-optimization state, since this inference frame isn't cached." - optimize ⊻= true - end if remarks && optimize @warn "Disable optimization to see the inference remarks." end @@ -790,8 +786,8 @@ function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::Abs revise = getfield(mod, :revise)::Function revise() mi = get_specialization(mi.specTypes)::MethodInstance - do_typeinf!(interp, mi) - curs = update_cursor(curs, mi) + ci = do_typeinf!(interp, mi) + curs = update_cursor(curs, ci) else @warn "Failed to load Revise." end @@ -845,50 +841,48 @@ end function do_typeinf!(interp::AbstractInterpreter, mi::MethodInstance) result = InferenceResult(mi) - @static if isdefined(CC, :engine_reserve) - ci = CC.engine_reserve(interp, mi) - result.ci = ci - end + ci = CC.engine_reserve(interp, mi) + result.ci = ci # we may want to handle the case when `InferenceState(...)` returns `nothing`, # which indicates code generation of a `@generated` has been failed, # and show it in the UI in some way? frame = InferenceState(result, #=cache_mode=#:global, interp)::InferenceState CC.typeinf(interp, frame) - return nothing + return ci end -get_specialization(@nospecialize tt::Type{<:Tuple}) = specialize_method(Base._which(tt)) - get_specialization(@nospecialize(f), @nospecialize(tt=default_tt(f))) = get_specialization(Base.signature_type(f, tt)) +get_specialization(@nospecialize tt::Type{<:Tuple}) = + specialize_method(Base._which(tt)) function mkinterp(interp::AbstractInterpreter, @nospecialize(args...)) interp′ = CthulhuInterpreter(interp) mi = get_specialization(args...) - do_typeinf!(interp′, mi) - (interp′, mi) + ci = do_typeinf!(interp′, mi) + return interp′, ci end mkinterp(@nospecialize(args...); interp::AbstractInterpreter=NativeInterpreter()) = mkinterp(interp, args...) -_descend(interp::AbstractInterpreter, mi::MethodInstance; terminal=default_terminal(), kwargs...) = - _descend(terminal, interp, mi; kwargs...) -_descend(term::AbstractTerminal, interp::AbstractInterpreter, mi::MethodInstance; kwargs...) = - _descend(term, interp, AbstractCursor(interp, mi); kwargs...) function _descend(@nospecialize(args...); interp::AbstractInterpreter=NativeInterpreter(), kwargs...) - (interp′, mi) = mkinterp(interp, args...) - _descend(interp′, mi; kwargs...) + (interp′, ci) = mkinterp(interp, args...) + _descend(interp′, ci; kwargs...) end +_descend(interp::AbstractInterpreter, ci::CodeInstance; terminal=default_terminal(), kwargs...) = + _descend(terminal, interp, ci; kwargs...) +_descend(term::AbstractTerminal, interp::AbstractInterpreter, ci::CodeInstance; kwargs...) = + _descend(term, interp, AbstractCursor(interp, ci); kwargs...) function _descend(term::AbstractTerminal, mi::MethodInstance; interp::AbstractInterpreter=NativeInterpreter(), kwargs...) interp′ = Cthulhu.CthulhuInterpreter(interp) - Cthulhu.do_typeinf!(interp′, mi) - _descend(term, interp′, mi; kwargs...) + ci = Cthulhu.do_typeinf!(interp′, mi) + _descend(term, interp′, ci; kwargs...) end function _descend(term::AbstractTerminal, @nospecialize(args...); interp::AbstractInterpreter=NativeInterpreter(), kwargs...) - (interp′, mi) = mkinterp(interp, args...) - _descend(term, interp′, mi; kwargs...) + (interp′, ci) = mkinterp(interp, args...) + _descend(term, interp′, ci; kwargs...) end descend_code_typed(b::Bookmark; kw...) = diff --git a/src/callsite.jl b/src/callsite.jl index a8959a83..ed034721 100644 --- a/src/callsite.jl +++ b/src/callsite.jl @@ -3,30 +3,30 @@ using Unicode abstract type CallInfo end # Call could be resolved to a singular MI -struct MICallInfo <: CallInfo - mi::MethodInstance +struct EdgeCallInfo <: CallInfo + ci::CodeInstance rt effects::Effects exct - function MICallInfo(mi::MethodInstance, @nospecialize(rt), effects, @nospecialize(exct=nothing)) + function EdgeCallInfo(ci::CodeInstance, @nospecialize(rt), effects::Effects, @nospecialize(exct=nothing)) if isa(rt, LimitedAccuracy) - return LimitedCallInfo(new(mi, ignorelimited(rt), effects, exct)) + return LimitedCallInfo(new(ci, ignorelimited(rt), effects, exct)) else - return new(mi, rt, effects, exct) + return new(ci, rt, effects, exct) end end end -get_mi(ci::MICallInfo) = ci.mi -get_rt(ci::MICallInfo) = ci.rt -get_effects(ci::MICallInfo) = ci.effects -get_exct(ci::MICallInfo) = ci.exct +get_ci(ci::EdgeCallInfo) = ci.ci +get_rt(ci::EdgeCallInfo) = ci.rt +get_effects(ci::EdgeCallInfo) = ci.effects +get_exct(ci::EdgeCallInfo) = ci.exct abstract type WrappedCallInfo <: CallInfo end get_wrapped(ci::WrappedCallInfo) = ci.wrapped ignorewrappers(ci::CallInfo) = ci ignorewrappers(ci::WrappedCallInfo) = ignorewrappers(get_wrapped(ci)) -get_mi(ci::WrappedCallInfo) = get_mi(ignorewrappers(ci)) +get_ci(ci::WrappedCallInfo) = get_ci(ignorewrappers(ci)) get_rt(ci::WrappedCallInfo) = get_rt(ignorewrappers(ci)) get_effects(ci::WrappedCallInfo) = get_effects(ignorewrappers(ci)) get_exct(ci::WrappedCallInfo) = get_exct(ignorewrappers(ci)) @@ -44,22 +44,17 @@ struct RTCallInfo <: CallInfo exct end get_rt(ci::RTCallInfo) = ci.rt -get_mi(ci::RTCallInfo) = nothing +get_ci(ci::RTCallInfo) = nothing get_effects(ci::RTCallInfo) = Effects() get_exct(ci::RTCallInfo) = ci.exct -# uncached callsite, we can't recurse into this call -struct UncachedCallInfo <: WrappedCallInfo - wrapped::CallInfo -end - struct PureCallInfo <: CallInfo argtypes::Vector{Any} rt PureCallInfo(argtypes::Vector{Any}, @nospecialize(rt)) = new(argtypes, rt) end -get_mi(::PureCallInfo) = nothing +get_ci(::PureCallInfo) = nothing get_rt(pci::PureCallInfo) = pci.rt get_effects(::PureCallInfo) = EFFECTS_TOTAL get_exct(::PureCallInfo) = Union{} @@ -69,7 +64,7 @@ struct FailedCallInfo <: CallInfo sig rt end -get_mi(ci::FailedCallInfo) = fail(ci) +get_ci(ci::FailedCallInfo) = fail(ci) get_rt(ci::FailedCallInfo) = fail(ci) get_effects(ci::FailedCallInfo) = fail(ci) get_exct(ci::FailedCallInfo) = fail(ci) @@ -83,7 +78,7 @@ struct GeneratedCallInfo <: CallInfo sig rt end -get_mi(genci::GeneratedCallInfo) = fail(genci) +get_ci(genci::GeneratedCallInfo) = fail(genci) get_rt(genci::GeneratedCallInfo) = fail(genci) get_effects(genci::GeneratedCallInfo) = fail(genci) get_exct(genci::GeneratedCallInfo) = fail(genci) @@ -101,7 +96,7 @@ struct MultiCallInfo <: CallInfo @nospecialize(exct=nothing)) = new(sig, rt, exct, callinfos) end -get_mi(ci::MultiCallInfo) = error("Can't extract MethodInstance from multiple call informations") +get_ci(ci::MultiCallInfo) = error("Can't extract MethodInstance from multiple call informations") get_rt(ci::MultiCallInfo) = ci.rt get_effects(mci::MultiCallInfo) = mapreduce(get_effects, CC.merge_effects, mci.callinfos) get_exct(ci::MultiCallInfo) = ci.exct @@ -109,7 +104,7 @@ get_exct(ci::MultiCallInfo) = ci.exct struct TaskCallInfo <: CallInfo ci::CallInfo end -get_mi(tci::TaskCallInfo) = get_mi(tci.ci) +get_ci(tci::TaskCallInfo) = get_ci(tci.ci) get_rt(tci::TaskCallInfo) = get_rt(tci.ci) get_effects(tci::TaskCallInfo) = get_effects(tci.ci) get_exct(tci::TaskCallInfo) = get_exct(tci.ci) @@ -118,7 +113,7 @@ struct InvokeCallInfo <: CallInfo ci::CallInfo InvokeCallInfo(@nospecialize ci::CallInfo) = new(ci) end -get_mi(ici::InvokeCallInfo) = get_mi(ici.ci) +get_ci(ici::InvokeCallInfo) = get_ci(ici.ci) get_rt(ici::InvokeCallInfo) = get_rt(ici.ci) get_effects(ici::InvokeCallInfo) = get_effects(ici.ci) get_exct(ici::InvokeCallInfo) = get_exct(ici.ci) @@ -128,7 +123,7 @@ struct OCCallInfo <: CallInfo ci::CallInfo OCCallInfo(@nospecialize ci::CallInfo) = new(ci) end -get_mi(occi::OCCallInfo) = get_mi(occi.ci) +get_ci(occi::OCCallInfo) = get_ci(occi.ci) get_rt(occi::OCCallInfo) = get_rt(occi.ci) get_effects(occi::OCCallInfo) = get_effects(occi.ci) get_exct(occi::OCCallInfo) = get_exct(occi.ci) @@ -137,52 +132,52 @@ get_exct(occi::OCCallInfo) = get_exct(occi.ci) struct ReturnTypeCallInfo <: CallInfo vmi::CallInfo # virtualized method call end -get_mi((; vmi)::ReturnTypeCallInfo) = isa(vmi, FailedCallInfo) ? nothing : get_mi(vmi) +get_ci((; vmi)::ReturnTypeCallInfo) = isa(vmi, FailedCallInfo) ? nothing : get_ci(vmi) get_rt((; vmi)::ReturnTypeCallInfo) = Type{isa(vmi, FailedCallInfo) ? Union{} : widenconst(get_rt(vmi))} get_effects(::ReturnTypeCallInfo) = EFFECTS_TOTAL get_exct(::ReturnTypeCallInfo) = Union{} # FIXME struct ConstPropCallInfo <: CallInfo - mi::CallInfo + ci::CallInfo result::InferenceResult end -get_mi(cpci::ConstPropCallInfo) = cpci.result.linfo -get_rt(cpci::ConstPropCallInfo) = get_rt(cpci.mi) +get_ci(cpci::ConstPropCallInfo) = get_ci(cpci.ci) +get_rt(cpci::ConstPropCallInfo) = get_rt(cpci.ci) get_effects(cpci::ConstPropCallInfo) = get_effects(cpci.result) -get_exct(cpci::ConstPropCallInfo) = get_exct(cpci.mi) +get_exct(cpci::ConstPropCallInfo) = get_exct(cpci.ci) struct ConcreteCallInfo <: CallInfo - mi::CallInfo + ci::CallInfo argtypes::ArgTypes end -get_mi(ceci::ConcreteCallInfo) = get_mi(ceci.mi) -get_rt(ceci::ConcreteCallInfo) = get_rt(ceci.mi) -get_effects(ceci::ConcreteCallInfo) = get_effects(ceci.mi) -get_exct(cici::ConcreteCallInfo) = get_exct(ceci.mi) +get_ci(ceci::ConcreteCallInfo) = get_ci(ceci.ci) +get_rt(ceci::ConcreteCallInfo) = get_rt(ceci.ci) +get_effects(ceci::ConcreteCallInfo) = get_effects(ceci.ci) +get_exct(cici::ConcreteCallInfo) = get_exct(ceci.ci) struct SemiConcreteCallInfo <: CallInfo - mi::CallInfo + ci::CallInfo ir::IRCode end -get_mi(scci::SemiConcreteCallInfo) = get_mi(scci.mi) -get_rt(scci::SemiConcreteCallInfo) = get_rt(scci.mi) -get_effects(scci::SemiConcreteCallInfo) = get_effects(scci.mi) -get_exct(scci::SemiConcreteCallInfo) = get_exct(scci.mi) +get_ci(scci::SemiConcreteCallInfo) = get_ci(scci.ci) +get_rt(scci::SemiConcreteCallInfo) = get_rt(scci.ci) +get_effects(scci::SemiConcreteCallInfo) = get_effects(scci.ci) +get_exct(scci::SemiConcreteCallInfo) = get_exct(scci.ci) # CUDA callsite struct CuCallInfo <: CallInfo - cumi::MICallInfo + ci::EdgeCallInfo end -get_mi(gci::CuCallInfo) = get_mi(gci.cumi) -get_rt(gci::CuCallInfo) = get_rt(gci.cumi) -get_effects(gci::CuCallInfo) = get_effects(gci.cumi) +get_ci(gci::CuCallInfo) = get_ci(gci.ci) +get_rt(gci::CuCallInfo) = get_rt(gci.ci) +get_effects(gci::CuCallInfo) = get_effects(gci.ci) struct Callsite id::Int # ssa-id info::CallInfo head::Symbol end -get_mi(c::Callsite) = get_mi(c.info) +get_ci(c::Callsite) = get_ci(c.info) get_effects(c::Callsite) = get_effects(c.info) # Callsite printing @@ -277,17 +272,17 @@ function Base.show(io::IO, (;exct)::ExctWrapper) printstyled(io, "(↑::", exct, ")"; color) end -function show_callinfo(limiter, mici::MICallInfo) - mi = mici.mi +function show_callinfo(limiter, ci::EdgeCallInfo) + mi = ci.ci.def tt = (Base.unwrap_unionall(mi.specTypes)::DataType).parameters[2:end] if !isa(mi.def, Method) name = ":toplevel" else name = mi.def.name end - rt = get_rt(mici) - exct = get_exct(mici) - __show_limited(limiter, name, tt, rt, get_effects(mici), exct) + rt = get_rt(ci) + exct = get_exct(ci) + __show_limited(limiter, name, tt, rt, get_effects(ci), exct) end function show_callinfo(limiter, ci::Union{MultiCallInfo, FailedCallInfo, GeneratedCallInfo}) @@ -317,20 +312,20 @@ 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] - ci = ignorewrappers(ci.mi)::MICallInfo + ci = ignorewrappers(ci.ci)::EdgeCallInfo __show_limited(limiter, name, tt, get_rt(ci), get_effects(ci)) end function show_callinfo(limiter, ci::SemiConcreteCallInfo) # XXX: The first argument could be const-overriden too - name = get_mi(ci).def.name + name = get_ci(ci).def.def.name tt = ci.ir.argtypes[2:end] __show_limited(limiter, name, tt, get_rt(ci), get_effects(ci)) end function show_callinfo(limiter, ci::ConcreteCallInfo) # XXX: The first argument could be const-overriden too - name = get_mi(ci).def.name + name = get_ci(ci).def.def.name tt = ci.argtypes[2:end] __show_limited(limiter, name, tt, get_rt(ci), get_effects(ci)) end @@ -435,7 +430,7 @@ function Base.show(io::IO, c::Callsite) limiter = TextWidthLimiter(io, cols) limiter.width += 1 # for the '%' character print(limiter, string(c.id)) - if isa(info, MICallInfo) + if isa(info, EdgeCallInfo) print(limiter, optimize ? string(" = ", c.head, ' ') : " = ") show_callinfo(limiter, info) else @@ -457,7 +452,6 @@ function wrapped_callinfo(limiter, ci::WrappedCallInfo) print(limiter, " > ") end _wrapped_callinfo(limiter, ::LimitedCallInfo) = print(limiter, "limited") -_wrapped_callinfo(limiter, ::UncachedCallInfo) = print(limiter, "uncached") # is_callsite returns true if `call` dispatches to `callee` # See also `maybe_callsite` below @@ -527,7 +521,7 @@ function maybe_callsite(info::RTCallInfo, @nospecialize(tt::Type)) end return true end -function maybe_callsite(info::MICallInfo, @nospecialize(tt::Type)) +function maybe_callsite(info::EdgeCallInfo, @nospecialize(tt::Type)) return tt <: info.mi.specTypes end diff --git a/src/codeview.jl b/src/codeview.jl index 3ab19d95..d45c8fff 100644 --- a/src/codeview.jl +++ b/src/codeview.jl @@ -117,29 +117,21 @@ is_type_unstable(@nospecialize(type)) = type isa Type && (!Base.isdispatchelem(t cthulhu_warntype(args...; kwargs...) = cthulhu_warntype(stdout::IO, args...; kwargs...) function cthulhu_warntype(io::IO, debuginfo::AnyDebugInfo, - src::Union{CodeInfo,IRCode}, @nospecialize(rt), effects::Effects, mi::Union{Nothing,MethodInstance}=nothing; + src::Union{CodeInfo,IRCode}, @nospecialize(rt), effects::Effects, codeinst::Union{Nothing,CodeInstance}=nothing; hide_type_stable::Bool=false, inline_cost::Bool=false, optimize::Bool=false, interp::CthulhuInterpreter=CthulhuInterpreter()) if inline_cost isa(mi, MethodInstance) || error("Need a MethodInstance to show inlining costs. Call `cthulhu_typed` directly instead.") end - cthulhu_typed(io, debuginfo, src, rt, nothing, effects, mi; iswarn=true, optimize, hide_type_stable, inline_cost, interp) + cthulhu_typed(io, debuginfo, src, rt, nothing, effects, codeinst; iswarn=true, optimize, hide_type_stable, inline_cost, interp) return nothing end -# # for API consistency with the others -# function cthulhu_typed(io::IO, mi::MethodInstance, optimize, debuginfo, params, config::CthulhuConfig) -# interp = mkinterp(mi) -# (; src, rt, infos, slottypes) = lookup(interp, mi, optimize) -# ci = Cthulhu.preprocess_ci!(src, mi, optimize, config) -# cthulhu_typed(io, debuginfo, src, rt, mi) -# end - cthulhu_typed(io::IO, debuginfo::DebugInfo, args...; kwargs...) = cthulhu_typed(io, Symbol(debuginfo), args...; kwargs...) function cthulhu_typed(io::IO, debuginfo::Symbol, src::Union{CodeInfo,IRCode}, @nospecialize(rt), @nospecialize(exct), - effects::Effects, mi::Union{Nothing,MethodInstance}; + effects::Effects, codeinst::Union{Nothing,CodeInstance}; iswarn::Bool=false, hide_type_stable::Bool=false, optimize::Bool=true, pc2remarks::Union{Nothing,PC2Remarks}=nothing, pc2effects::Union{Nothing,PC2Effects}=nothing, @@ -148,6 +140,8 @@ function cthulhu_typed(io::IO, debuginfo::Symbol, inlay_types_vscode::Bool=false, diagnostics_vscode::Bool=false, jump_always::Bool=false, interp::AbstractInterpreter=CthulhuInterpreter()) + mi = codeinst === nothing ? nothing : codeinst.def + debuginfo = IRShow.debuginfo(debuginfo) lineprinter = __debuginfo[debuginfo] rettype = ignorelimited(rt) @@ -316,11 +310,11 @@ function cthulhu_typed(io::IO, debuginfo::Symbol, end println(lambda_io) else - isa(mi, MethodInstance) || throw("`mi::MethodInstance` is required") + isa(codeinst, CodeInstance) || throw("`codeinst::CodeInstance` is required") cfg = src isa IRCode ? src.cfg : CC.compute_basic_blocks(src.code) max_bb_idx_size = length(string(length(cfg.blocks))) str = irshow_config.line_info_preprinter(lambda_io, " "^(max_bb_idx_size + 2), -1) - callsite = Callsite(0, MICallInfo(mi, rettype, effects, exct), :invoke) + callsite = Callsite(0, EdgeCallInfo(codeinst, rettype, effects, exct), :invoke) println(lambda_io, "∘ ", "─"^(max_bb_idx_size), str, " ", callsite) end @@ -459,7 +453,7 @@ function Base.show( (; interp, mi) = b (; effects) = lookup(interp, mi, optimize) if get(io, :typeinfo, Any) === Bookmark # a hack to check if in Vector etc. - print(io, Callsite(-1, MICallInfo(b.mi, rt, Effects()), :invoke)) + print(io, Callsite(-1, EdgeCallInfo(b.mi, rt, Effects()), :invoke)) print(io, " (world: ", world, ")") return end diff --git a/src/interface.jl b/src/interface.jl index 9c9428b5..5c5b3f76 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -4,14 +4,13 @@ Required overloads: - `Cthulhu.lookup(interp::AbstractInterpreter, curs::AbstractCursor, optimize::Bool)` - `Cthulhu.lookup_constproped(interp::AbstractInterpreter, curs::AbstractCursor, override::InferenceResult, optimize::Bool)` -- `Cthulhu.get_mi(curs::AbstractCursor) -> MethodInstance` -- `Cthulhu.get_optimized_codeinst(interp::AbstractInterpreter, curs::AbstractCursor) -> CodeInstance` +- `Cthulhu.get_ci(curs::AbstractCursor) -> CodeInstance` - `Cthulhu.update_cursor(curs::AbstractCursor, mi::MethodInstance)` - `Cthulhu.navigate(curs::AbstractCursor, callsite::Callsite) -> AbstractCursor` """ abstract type AbstractCursor; end struct CthulhuCursor <: AbstractCursor - mi::MethodInstance + ci::CodeInstance end lookup(interp::AbstractInterpreter, curs::AbstractCursor, optimize::Bool) = error(lazy""" @@ -19,7 +18,7 @@ missing `$AbstractCursor` API: `$(typeof(curs))` is required to implement the `$lookup(interp::$(typeof(interp)), curs::$(typeof(curs)), optimize::Bool)` interface. """) lookup(interp::CthulhuInterpreter, curs::CthulhuCursor, optimize::Bool) = - lookup(interp, get_mi(curs), optimize) + lookup(interp, get_ci(curs), optimize) lookup_constproped(interp::AbstractInterpreter, curs::AbstractCursor, override::InferenceResult, optimize::Bool) = error(lazy""" missing `$AbstractCursor` API: @@ -35,23 +34,17 @@ missing `$AbstractCursor` API: lookup_semiconcrete(interp::CthulhuInterpreter, ::CthulhuCursor, override::SemiConcreteCallInfo, optimize::Bool) = lookup_semiconcrete(interp, override, optimize) -get_mi(curs::AbstractCursor) = error(lazy""" +get_ci(curs::AbstractCursor) = error(lazy""" missing `$AbstractCursor` API: -`$(typeof(curs))` is required to implement the `$get_mi(curs::$(typeof(curs))) -> MethodInstance` interface. +`$(typeof(curs))` is required to implement the `$get_ci(curs::$(typeof(curs))) -> CodeInstance` interface. """) -get_mi(curs::CthulhuCursor) = curs.mi +get_ci(curs::CthulhuCursor) = curs.ci -get_optimized_codeinst(interp::AbstractInterpreter, curs::AbstractCursor) = error(lazy""" -missing `$AbstractCursor` API: -`$(typeof(curs))` is required to implement the `$get_optimized_codeinst(interp::$(typeof(interp)), curs::$(typeof(curs))) -> CodeInstance` interface. -""") -get_optimized_codeinst(interp::CthulhuInterpreter, curs::CthulhuCursor) = interp.opt[curs.mi] - -update_cursor(curs::AbstractCursor, ::MethodInstance) = error(lazy""" +update_cursor(curs::AbstractCursor, ::CodeInstance) = error(lazy""" missing `$AbstractCursor` API: `$(typeof(curs))` is required to implement the `$update_cursor(curs::$(typeof(curs)), mi::MethodInstance) -> $(typeof(curs))` interface. """) -update_cursor(curs::CthulhuCursor, mi::MethodInstance) = CthulhuCursor(mi) +update_cursor(curs::CthulhuCursor, ci::CodeInstance) = CthulhuCursor(ci) # TODO: This interface is incomplete, should probably also take a current cursor, # or maybe be `CallSite based` @@ -60,31 +53,28 @@ missing `$AbstractInterpreter` API: `$(typeof(interp))` is required to implement the `$can_descend(interp::$(typeof(interp)), @nospecialize(key), optimize::Bool) -> Bool` interface. """) can_descend(interp::CthulhuInterpreter, @nospecialize(key), optimize::Bool) = - haskey(optimize ? interp.opt : interp.unopt, key) + haskey(optimize ? key isa CodeInstance : interp.unopt, key) navigate(curs::AbstractCursor, callsite::Callsite) = error(lazy""" missing `$AbstractCursor` API: `$(typeof(curs))` is required to implement the `$navigate(curs::$(typeof(curs)), callsite::Callsite) -> AbstractCursor` interface. """) -navigate(curs::CthulhuCursor, callsite::Callsite) = CthulhuCursor(get_mi(callsite)) +navigate(curs::CthulhuCursor, callsite::Callsite) = CthulhuCursor(get_ci(callsite)) -get_remarks(::AbstractInterpreter, ::InferenceKey) = nothing -get_remarks(interp::CthulhuInterpreter, key::InferenceKey) = get(interp.remarks, key, nothing) +get_pc_remarks(::AbstractInterpreter, ::InferenceKey) = nothing +get_pc_remarks(interp::CthulhuInterpreter, key::InferenceKey) = get(interp.remarks, key, nothing) -get_effects(::AbstractInterpreter, ::InferenceKey) = nothing -get_effects(interp::CthulhuInterpreter, key::InferenceKey) = get(interp.effects, key, nothing) +get_pc_effects(::AbstractInterpreter, ::InferenceKey) = nothing +get_pc_effects(interp::CthulhuInterpreter, key::InferenceKey) = get(interp.effects, key, nothing) -get_excts(::AbstractInterpreter, ::InferenceKey) = nothing -get_excts(interp::CthulhuInterpreter, key::InferenceKey) = get(interp.exception_types, key, nothing) +get_pc_exct(::AbstractInterpreter, ::InferenceKey) = nothing +get_pc_exct(interp::CthulhuInterpreter, key::InferenceKey) = get(interp.exception_types, key, nothing) # This method is optional, but should be implemented if there is # a sensible default cursor for a MethodInstance -AbstractCursor(interp::AbstractInterpreter, mi::MethodInstance) = CthulhuCursor(mi) +AbstractCursor(interp::AbstractInterpreter, ci::CodeInstance) = CthulhuCursor(ci) -function get_effects(interp::CthulhuInterpreter, mi::MethodInstance, opt::Bool) - effects = opt ? interp.opt : interp.unopt - return haskey(effects, mi) ? get_effects(effects[mi]) : Effects() -end +get_mi(curs::AbstractCursor) = get_ci(curs).def mutable struct CustomToggle onoff::Bool diff --git a/src/interpreter.jl b/src/interpreter.jl index 74ed04d4..728366b7 100644 --- a/src/interpreter.jl +++ b/src/interpreter.jl @@ -16,19 +16,18 @@ struct OptimizedSource effects::Effects end -const InferenceKey = Union{MethodInstance,InferenceResult} -const InferenceDict{T} = IdDict{InferenceKey, T} -const OptimizationDict = IdDict{MethodInstance, CodeInstance} +const InferenceKey = Union{CodeInstance,InferenceResult} # TODO make this `CodeInstance` fully +const InferenceDict{InferenceValue} = IdDict{InferenceKey, InferenceValue} const PC2Remarks = Vector{Pair{Int, String}} const PC2Effects = Dict{Int, Effects} const PC2Excts = Dict{Int, Any} +mutable struct CthulhuCacheToken end + struct CthulhuInterpreter <: AbstractInterpreter + cache_token::CthulhuCacheToken native::AbstractInterpreter - unopt::InferenceDict{InferredSource} - opt::OptimizationDict - remarks::InferenceDict{PC2Remarks} effects::InferenceDict{PC2Effects} exception_types::InferenceDict{PC2Excts} @@ -36,9 +35,9 @@ end function CthulhuInterpreter(interp::AbstractInterpreter=NativeInterpreter()) return CthulhuInterpreter( + CthulhuCacheToken(), interp, InferenceDict{InferredSource}(), - OptimizationDict(), InferenceDict{PC2Remarks}(), InferenceDict{PC2Effects}(), InferenceDict{PC2Excts}()) @@ -68,32 +67,56 @@ CC.method_table(interp::CthulhuInterpreter) = CC.method_table(interp.native) # internal code cache, technically, there's no requirement to supply `cache_owner` as an # identifier for the internal code cache. However, the definition of `cache_owner` is # necessary for utilizing the default `CodeInstance` constructor, define the overload here. -struct CthulhuCacheToken - token -end -CC.cache_owner(interp::CthulhuInterpreter) = CthulhuCacheToken(CC.cache_owner(interp.native)) +CC.cache_owner(interp::CthulhuInterpreter) = interp.cache_token end -struct CthulhuCache - cache::OptimizationDict +function get_inference_key(state::InferenceState) + @static if VERSION ≥ v"1.12.0-DEV.1531" + result = state.result + if CC.is_constproped(state) + return result # TODO result.ci_as_edge? + elseif isdefined(result, :ci) + return result.ci + else + # Core.println("Missing edges for ", result.linfo) + return nothing + end + elseif VERSION ≥ v"1.12.0-DEV.317" + return CC.is_constproped(state) ? state.result : state.linfo + else + return CC.any(state.result.overridden_by_const) ? state.result : state.linfo + end end -CC.code_cache(interp::CthulhuInterpreter) = WorldView(CthulhuCache(interp.opt), WorldRange(get_inference_world(interp))) -CC.get(wvc::WorldView{CthulhuCache}, mi::MethodInstance, default) = get(wvc.cache.cache, mi, default) -CC.haskey(wvc::WorldView{CthulhuCache}, mi::MethodInstance) = haskey(wvc.cache.cache, mi) -CC.setindex!(wvc::WorldView{CthulhuCache}, ci::CodeInstance, mi::MethodInstance) = setindex!(wvc.cache.cache, ci, mi) function CC.add_remark!(interp::CthulhuInterpreter, sv::InferenceState, msg) - key = (@static VERSION ≥ v"1.12.0-DEV.317" ? CC.is_constproped(sv) : CC.any(sv.result.overridden_by_const)) ? sv.result : sv.linfo + key = get_inference_key(sv) + key === nothing && return nothing push!(get!(PC2Remarks, interp.remarks, key), sv.currpc=>msg) end function CC.merge_effects!(interp::CthulhuInterpreter, sv::InferenceState, effects::Effects) - key = (@static VERSION ≥ v"1.12.0-DEV.317" ? CC.is_constproped(sv) : CC.any(sv.result.overridden_by_const)) ? sv.result : sv.linfo + key = get_inference_key(sv) + key === nothing && return nothing pc2effects = get!(interp.effects, key, PC2Effects()) - pc2effects[sv.currpc] = CC.merge_effects(get!(pc2effects, sv.currpc, EFFECTS_TOTAL), effects) + old_effects = get(pc2effects, sv.currpc, EFFECTS_TOTAL) + pc2effects[sv.currpc] = CC.merge_effects(effects, old_effects) @invoke CC.merge_effects!(interp::AbstractInterpreter, sv::InferenceState, effects::Effects) end +@static if VERSION ≥ v"1.11.0-DEV.1127" +function CC.update_exc_bestguess!(interp::CthulhuInterpreter, @nospecialize(exct), + frame::InferenceState) + key = get_inference_key(frame) + if key !== nothing + pc2excts = get!(PC2Excts, interp.exception_types, key) + old_exct = get(pc2excts, frame.currpc, Union{}) + pc2excts[frame.currpc] = CC.tmerge(CC.typeinf_lattice(interp), exct, old_exct) + end + return @invoke CC.update_exc_bestguess!(interp::AbstractInterpreter, exct::Any, + frame::InferenceState) +end +end + function InferredSource(state::InferenceState) unoptsrc = copy(state.src) exct = @static VERSION ≥ v"1.11.0-DEV.207" ? state.result.exc_result : nothing @@ -107,8 +130,10 @@ end function cthulhu_finish(@specialize(finishfunc), state::InferenceState, interp::CthulhuInterpreter) res = @invoke finishfunc(state::InferenceState, interp::AbstractInterpreter) - key = (@static VERSION ≥ v"1.12.0-DEV.317" ? CC.is_constproped(state) : CC.any(state.result.overridden_by_const)) ? state.result : state.linfo - interp.unopt[key] = InferredSource(state) + key = get_inference_key(state) + if key !== nothing + interp.unopt[key] = InferredSource(state) + end return res end @@ -219,7 +244,7 @@ end function CC.IRInterpretationState(interp::CthulhuInterpreter, code::CodeInstance, mi::MethodInstance, argtypes::Vector{Any}, world::UInt) - inferred = @atomic :monotonic code.inferred + inferred = code.inferred inferred === nothing && return nothing inferred = inferred::OptimizedSource ir = CC.copy(inferred.ir) @@ -237,14 +262,3 @@ function CC.IRInterpretationState(interp::CthulhuInterpreter, return CC.IRInterpretationState(interp, spec_info, ir, mi, argtypes, world, code.min_world, code.max_world) end - -@static if VERSION ≥ v"1.11.0-DEV.1127" -function CC.update_exc_bestguess!(interp::CthulhuInterpreter, @nospecialize(exct), - frame::InferenceState) - key = (@static VERSION ≥ v"1.12.0-DEV.317" ? CC.is_constproped(frame) : CC.any(frame.result.overridden_by_const)) ? frame.result : frame.linfo - pc2excts = get!(PC2Excts, interp.exception_types, key) - pc2excts[frame.currpc] = CC.tmerge(CC.typeinf_lattice(interp), exct, get(pc2excts, frame.currpc, Union{})) - return @invoke CC.update_exc_bestguess!(interp::AbstractInterpreter, exct::Any, - frame::InferenceState) -end -end diff --git a/src/reflection.jl b/src/reflection.jl index 9e39cd93..ebd99bc4 100644 --- a/src/reflection.jl +++ b/src/reflection.jl @@ -14,8 +14,8 @@ function transform(::Val{:CuFunction}, callsite, callexpr, CI, mi, slottypes; wo return Callsite(callsite.id, CuCallInfo(callinfo(Tuple{widenconst(ft), tt.val.parameters...}, Nothing; world)), callsite.head) end -function find_callsites(interp::AbstractInterpreter, CI::Union{Core.CodeInfo, IRCode}, - stmt_infos::Union{Vector{CCCallInfo}, Nothing}, mi::Core.MethodInstance, +function find_callsites(interp::AbstractInterpreter, CI::Union{CodeInfo,IRCode}, + stmt_infos::Union{Vector{CCCallInfo}, Nothing}, mi::MethodInstance, slottypes::Vector{Any}, optimize::Bool=true, annotate_source::Bool=false, pc2excts::Union{Nothing,PC2Excts}=nothing) sptypes = sptypes_from_meth_instance(mi) @@ -76,14 +76,9 @@ function find_callsites(interp::AbstractInterpreter, CI::Union{Core.CodeInfo, IR (; head, args) = stmt if head === :invoke rt = argextype(SSAValue(id), CI, sptypes, slottypes) - arg1 = args[1] - if arg1 isa CodeInstance - mi = arg1.def - else - mi = arg1::MethodInstance - end - effects = get_effects(interp, mi, false) - callsite = Callsite(id, MICallInfo(mi, rt, effects), head) + arg1 = args[1]::CodeInstance + effects = get_effects(arg1) # TODO + callsite = Callsite(id, EdgeCallInfo(arg1, rt, effects), head) elseif head === :foreigncall # special handling of jl_new_task length(args) > 0 || continue @@ -102,11 +97,11 @@ function find_callsites(interp::AbstractInterpreter, CI::Union{Core.CodeInfo, IR if callsite !== nothing info = callsite.info - if info isa MICallInfo - mi = get_mi(info) - meth = mi.def + if info isa EdgeCallInfo + ci = get_ci(info) + meth = ci.def.def if isa(meth, Method) && nameof(meth.module) === :CUDAnative && meth.name === :cufunction - callsite = transform(Val(:CuFunction), callsite, c, CI, mi, slottypes; world=get_inference_world(interp)) + callsite = transform(Val(:CuFunction), callsite, c, CI, ci.def, slottypes; world=get_inference_world(interp)) end end @@ -127,8 +122,6 @@ end function process_const_info(interp::AbstractInterpreter, @nospecialize(thisinfo), argtypes::ArgTypes, @nospecialize(rt), @nospecialize(result), optimize::Bool, @nospecialize(exct)) - is_cached(@nospecialize(key)) = can_descend(interp, key, optimize) - if isnothing(result) return thisinfo elseif (@static VERSION ≥ v"1.11.0-DEV.851" && true) && result isa CC.VolatileInferenceResult @@ -136,42 +129,30 @@ function process_const_info(interp::AbstractInterpreter, @nospecialize(thisinfo) # will always transform `frame.result.src` to `OptimizedSource` when frame is inferred return thisinfo elseif isa(result, CC.ConcreteResult) - @static if VERSION ≥ v"1.12.0-DEV.1531" - linfo = result.edge.def - else - linfo = result.mi - end + edge = result.edge effects = get_effects(result) - mici = MICallInfo(linfo, rt, effects, exct) + mici = EdgeCallInfo(edge, rt, effects, exct) return ConcreteCallInfo(mici, argtypes) elseif isa(result, CC.ConstPropResult) - result = result.result - linfo = result.linfo effects = get_effects(result) - mici = MICallInfo(linfo, rt, effects, exct) - return ConstPropCallInfo(is_cached(optimize ? linfo : result) ? mici : UncachedCallInfo(mici), result) + result = result.result + mici = EdgeCallInfo(result.ci_as_edge, rt, effects, exct) + return ConstPropCallInfo(mici, result) elseif isa(result, CC.SemiConcreteResult) - @static if VERSION ≥ v"1.12.0-DEV.1531" - linfo = result.edge.def - else - linfo = result.mi - end effects = get_effects(result) - mici = MICallInfo(linfo, rt, effects, exct) + mici = EdgeCallInfo(result.edge, rt, effects, exct) return SemiConcreteCallInfo(mici, result.ir) else @assert isa(result, CC.InferenceResult) - linfo = result.linfo effects = get_effects(result) - mici = MICallInfo(linfo, rt, effects, exct) - return ConstPropCallInfo(is_cached(optimize ? linfo : result) ? mici : UncachedCallInfo(mici), result) + mici = EdgeCallInfo(result.ci_as_edge, rt, effects, exct) + return ConstPropCallInfo(mici, result) end end function process_info(interp::AbstractInterpreter, @nospecialize(info::CCCallInfo), argtypes::ArgTypes, @nospecialize(rt), optimize::Bool, @nospecialize(exct)) - is_cached(@nospecialize(key)) = can_descend(interp, key, optimize) process_recursive(@nospecialize(newinfo)) = process_info(interp, newinfo, argtypes, rt, optimize, exct) if isa(info, MethodResultPure) @@ -183,16 +164,14 @@ function process_info(interp::AbstractInterpreter, @nospecialize(info::CCCallInf end end if isa(info, MethodMatchInfo) - if info.results === missing - return CallInfo[] - end - matches = info.results.matches return CallInfo[let - mi = specialize_method(match) - effects = get_effects(interp, mi, false) - mici = MICallInfo(mi, rt, effects, exct) - is_cached(mi) ? mici : UncachedCallInfo(mici) - end for match::Core.MethodMatch in matches] + if edge === nothing + RTCallInfo(unwrapconst(argtypes[1]), argtypes[2:end], rt, exct) + else + effects = get_effects(edge) + EdgeCallInfo(edge, rt, effects, exct) + end + end for edge in info.edges if edge !== nothing] elseif isa(info, UnionSplitInfo) @static if hasfield(UnionSplitInfo, :split) return mapreduce(process_recursive, vcat, info.split; init=CallInfo[])::Vector{CallInfo} @@ -212,17 +191,25 @@ function process_info(interp::AbstractInterpreter, @nospecialize(info::CCCallInf process_const_info(interp, infos[i], argtypes, rt, result, optimize, exct) end for (i, result) in enumerate(info.results)] elseif isa(info, CC.InvokeCallInfo) - mi = specialize_method(info.match; preexisting=true) - effects = get_effects(interp, mi, false) - thisinfo = MICallInfo(mi, rt, effects) - innerinfo = process_const_info(interp, thisinfo, argtypes, rt, info.result, optimize, exct) + edge = info.edge + if edge !== nothing + effects = get_effects(edge) + thisinfo = EdgeCallInfo(edge, rt, effects) + innerinfo = process_const_info(interp, thisinfo, argtypes, rt, info.result, optimize, exct) + else + innerinfo = RTCallInfo(unwrapconst(argtypes[1]), argtypes[2:end], rt, exct) + end info = InvokeCallInfo(innerinfo) return CallInfo[info] elseif isa(info, CC.OpaqueClosureCallInfo) - mi = specialize_method(info.match; preexisting=true) - effects = get_effects(interp, mi, false) - thisinfo = MICallInfo(mi, rt, effects) - innerinfo = process_const_info(interp, thisinfo, argtypes, rt, info.result, optimize, exct) + edge = info.edge + if edge !== nothing + effects = get_effects(edge) + thisinfo = EdgeCallInfo(edge, rt, effects) + innerinfo = process_const_info(interp, thisinfo, argtypes, rt, info.result, optimize, exct) + else + innerinfo = RTCallInfo(unwrapconst(argtypes[1]), argtypes[2:end], rt, exct) + end info = OCCallInfo(innerinfo) return CallInfo[info] elseif isa(info, CC.OpaqueClosureCreateInfo) @@ -306,7 +293,7 @@ function callinfo(sig, rt, max_methods=-1; world=get_world_counter()) else mi = specialize_method(meth, atypes, sparams) if mi !== nothing - push!(callinfos, MICallInfo(mi, rt, Effects())) + push!(callinfos, EdgeCallInfo(mi, rt, Effects())) else push!(callinfos, FailedCallInfo(sig, rt)) end diff --git a/test/generate_irshow.jl b/test/generate_irshow.jl index 2a95722e..941ff7b0 100644 --- a/test/generate_irshow.jl +++ b/test/generate_irshow.jl @@ -11,7 +11,7 @@ function generate_test_cases(f, tt, fname=string(nameof(f))) outputs = Dict() tf = (true, false) for optimize in tf - (; src, infos, mi, rt, exct, effects, slottypes) = cthulhu_info(f, tt; optimize); + (; src, infos, codeinst, rt, exct, effects, slottypes) = cthulhu_info(f, tt; optimize); for (debuginfo, iswarn, hide_type_stable, inline_cost, type_annotations) in Iterators.product( instances(Cthulhu.DInfo.DebugInfo), tf, tf, tf, tf, ) @@ -20,7 +20,7 @@ function generate_test_cases(f, tt, fname=string(nameof(f))) s = sprint(; context=:color=>true) do io Cthulhu.cthulhu_typed(io, debuginfo, - src, rt, exct, effects, mi; + src, rt, exct, effects, codeinst; iswarn, hide_type_stable, inline_cost, type_annotations) end s = strip_base_linenums(s) diff --git a/test/setup.jl b/test/setup.jl index 498ebcd2..3976692b 100644 --- a/test/setup.jl +++ b/test/setup.jl @@ -9,20 +9,20 @@ end function cthulhu_info(@nospecialize(f), @nospecialize(tt=()); optimize=true, interp=Cthulhu.CC.NativeInterpreter()) - (interp, mi) = Cthulhu.mkinterp(f, tt; interp) + (interp, codeinst) = Cthulhu.mkinterp(f, tt; interp) (; src, rt, exct, infos, slottypes, effects) = - Cthulhu.lookup(interp, mi, optimize; allow_no_src=true) + Cthulhu.lookup(interp, codeinst, optimize; allow_no_src=true) if src !== nothing config = Cthulhu.CthulhuConfig(; dead_code_elimination=true) - src = Cthulhu.preprocess_ci!(src, mi, optimize, config) + src = Cthulhu.preprocess_ci!(src, codeinst.def, optimize, config) end - return (; interp, src, infos, mi, rt, exct, slottypes, effects) + return (; interp, src, infos, codeinst, rt, exct, slottypes, effects) end function find_callsites_by_ftt(@nospecialize(f), @nospecialize(TT=Tuple{}); optimize=true) - (; interp, src, infos, mi, slottypes) = cthulhu_info(f, TT; optimize) + (; interp, src, infos, codeinst, slottypes) = cthulhu_info(f, TT; optimize) src === nothing && return Cthulhu.Callsite[] - callsites, _ = Cthulhu.find_callsites(interp, src, infos, mi, slottypes, optimize) + callsites, _ = Cthulhu.find_callsites(interp, src, infos, codeinst.def, slottypes, optimize) @test all(c -> Cthulhu.get_effects(c) isa Cthulhu.Effects, callsites) return callsites end diff --git a/test/test_Cthulhu.jl b/test/test_Cthulhu.jl index e013adbc..bcc3e98d 100644 --- a/test/test_Cthulhu.jl +++ b/test/test_Cthulhu.jl @@ -274,7 +274,7 @@ end @test isa(callinfo, Cthulhu.MultiCallInfo) callinfos = callinfo.callinfos @test length(callinfos) == 2 - @test count(ci->isa(ci, Cthulhu.MICallInfo), callinfos) == 1 # getindex(::Vector{Any}, ::Const(1)) + @test count(ci->isa(ci, Cthulhu.EdgeCallInfo), callinfos) == 1 # getindex(::Vector{Any}, ::Const(1)) @test count(ci->isa(ci, Cthulhu.ConstPropCallInfo) || isa(ci, Cthulhu.SemiConcreteCallInfo), callinfos) == 1 # getindex(::Const(tuple(1,nothing)), ::Const(1)) end @@ -354,8 +354,8 @@ function bar346(x::ComplexF64) return sin(x.im) end @testset "issue #346" begin - let (; interp, src, infos, mi, slottypes) = cthulhu_info(bar346, Tuple{ComplexF64}; optimize=false) - callsites, _ = Cthulhu.find_callsites(interp, src, infos, mi, slottypes, false) + let (; interp, src, infos, codeinst, slottypes) = cthulhu_info(bar346, Tuple{ComplexF64}; optimize=false) + callsites, _ = Cthulhu.find_callsites(interp, src, infos, codeinst.def, slottypes, false) @test isa(callsites[1].info, Cthulhu.SemiConcreteCallInfo) @test occursin("= < semi-concrete eval > getproperty(::ComplexF64,::Core.Const(:re))::Float64", string(callsites[1])) @test Cthulhu.get_rt(callsites[end].info) == Core.Const(sin(1.0)) @@ -389,7 +389,7 @@ end @test length(callsites) == 2 callinfo1 = callsites[1].info @test callinfo1 isa Cthulhu.ReturnTypeCallInfo - @test callinfo1.vmi isa Cthulhu.MICallInfo + @test callinfo1.vmi isa Cthulhu.EdgeCallInfo io = IOBuffer() Cthulhu.show_callinfo(io, callinfo1) @test String(take!(io)) == "only_ints(::$Int)::$Int" @@ -526,7 +526,7 @@ let callsites = find_callsites_by_ftt(callf, Tuple{Union{typeof(sin), typeof(cos @test any(mi->mi.def.name === :cos, mis) @test any(mi->mi.def.name === :sin, mis) else - @test all(cs->cs.info isa Union{Cthulhu.MICallInfo,Cthulhu.MultiCallInfo}, callsites) + @test all(cs->cs.info isa Union{Cthulhu.EdgeCallInfo,Cthulhu.MultiCallInfo}, callsites) end end @@ -548,7 +548,7 @@ let callsites = find_callsites_by_ftt(toggler, Tuple{Bool}) @test any(mi->mi.def.name === :cos, mis) @test any(mi->mi.def.name === :sin, mis) else - @test all(cs->cs.info isa Union{Cthulhu.MICallInfo,Cthulhu.MultiCallInfo}, callsites) + @test all(cs->cs.info isa Union{Cthulhu.EdgeCallInfo,Cthulhu.MultiCallInfo}, callsites) end end @@ -674,9 +674,9 @@ end end end function doprint(f) - (; src, mi, rt, exct, effects) = cthulhu_info(f; optimize=false) + (; src, codeinst, rt, exct, effects) = cthulhu_info(f; optimize=false) io = IOBuffer() - Cthulhu.cthulhu_typed(io, :none, src, rt, exct, effects, mi; iswarn=false) + Cthulhu.cthulhu_typed(io, :none, src, rt, exct, effects, codeinst; iswarn=false) return String(take!(io)) end @test occursin("invoke f1()::…\n", doprint(getfield(m, :f1))) @@ -1001,8 +1001,8 @@ end @test root.data.callstr == "sqrt(::Float64)" @test isempty(root.children) - # Create an MICallInfo for this `mi`, ensure it works with `show_callinfo()` - callinfo = Cthulhu.MICallInfo(mi, rt, CC.Effects()) + # Create an EdgeCallInfo for this `mi`, ensure it works with `show_callinfo()` + callinfo = Cthulhu.EdgeCallInfo(mi, rt, CC.Effects()) io = IOBuffer() Cthulhu.show_callinfo(io, callinfo) @@ -1023,8 +1023,8 @@ end f515() = cglobal((:foo, bar)) @testset "issue #515" begin - let (; interp, src, infos, mi, slottypes) = cthulhu_info(f515) - callsites, _ = Cthulhu.find_callsites(interp, src, infos, mi, slottypes) + let (; interp, src, infos, codeinst, slottypes) = cthulhu_info(f515) + callsites, _ = Cthulhu.find_callsites(interp, src, infos, codeinst.def, slottypes) @test isempty(callsites) end end diff --git a/test/test_codeview.jl b/test/test_codeview.jl index 2cfb9dc2..90c8d2e0 100644 --- a/test/test_codeview.jl +++ b/test/test_codeview.jl @@ -10,9 +10,9 @@ using .TestCodeViewSandbox Revise.track(TestCodeViewSandbox, normpath(@__DIR__, "TestCodeViewSandbox.jl")) @testset "printer test" begin - (; interp, src, infos, mi, rt, exct, effects, slottypes) = cthulhu_info(testf_revise); + (; interp, src, infos, codeinst, rt, exct, effects, slottypes) = cthulhu_info(testf_revise); tf = (true, false) - + mi = codeinst.def @testset "codeview: $codeview" for codeview in Cthulhu.CODEVIEWS if !@isdefined(Revise) codeview == Cthulhu.cthulhu_ast && continue @@ -40,7 +40,7 @@ Revise.track(TestCodeViewSandbox, normpath(@__DIR__, "TestCodeViewSandbox.jl")) @testset "type_annotations: $type_annotations" for type_annotations in tf io = IOBuffer() Cthulhu.cthulhu_typed(io, debuginfo, - src, rt, exct, effects, mi; + src, rt, exct, effects, codeinst; iswarn, hide_type_stable, inline_cost, type_annotations) @test !isempty(String(take!(io))) # just check it works end @@ -52,7 +52,7 @@ end @testset "hide type-stable statements" begin let # optimize code - (; src, infos, mi, rt, exct, effects, slottypes) = @eval Module() begin + (; src, infos, codeinst, rt, exct, effects, slottypes) = @eval Module() begin const globalvar = Ref(42) $cthulhu_info() do a = sin(globalvar[]) @@ -62,7 +62,7 @@ end end function prints(; kwargs...) io = IOBuffer() - Cthulhu.cthulhu_typed(io, :none, src, rt, exct, effects, mi; kwargs...) + Cthulhu.cthulhu_typed(io, :none, src, rt, exct, effects, codeinst; kwargs...) return String(take!(io)) end @@ -79,7 +79,7 @@ end end let # unoptimize code - (; src, infos, mi, rt, exct, effects, slottypes) = @eval Module() begin + (; src, infos, codeinst, rt, exct, effects, slottypes) = @eval Module() begin const globalvar = Ref(42) $cthulhu_info(; optimize=false) do a = sin(globalvar[]) @@ -89,7 +89,7 @@ end end function prints(; kwargs...) io = IOBuffer() - Cthulhu.cthulhu_typed(io, :none, src, rt, exct, effects, mi; kwargs...) + Cthulhu.cthulhu_typed(io, :none, src, rt, exct, effects, codeinst; kwargs...) return String(take!(io)) end diff --git a/test/test_irshow.jl b/test/test_irshow.jl index b9418a10..c7009dfe 100644 --- a/test/test_irshow.jl +++ b/test/test_irshow.jl @@ -10,7 +10,7 @@ include("IRShowSandbox.jl") tf = (true, false) @testset "optimize: $optimize" for optimize in tf - (; src, infos, mi, rt, exct, effects, slottypes) = cthulhu_info(IRShowSandbox.foo, (Int, Int); optimize); + (; src, infos, codeinst, rt, exct, effects, slottypes) = cthulhu_info(IRShowSandbox.foo, (Int, Int); optimize); @testset "debuginfo: $debuginfo" for debuginfo in instances(Cthulhu.DInfo.DebugInfo) @testset "iswarn: $iswarn" for iswarn in tf @@ -22,7 +22,7 @@ include("IRShowSandbox.jl") s = sprint(; context=:color=>true) do io Cthulhu.cthulhu_typed(io, debuginfo, - src, rt, exct, effects, mi; + src, rt, exct, effects, codeinst; iswarn, hide_type_stable, inline_cost, type_annotations) end s = strip_base_linenums(s)