diff --git a/CHANGELOG.md b/CHANGELOG.md index d7d9cc8b..cc08733b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Improvements -- The way that packages integrate with Cthulhu to customize the behavior of code introspection has been redesigned (see https://github.com/JuliaDebug/Cthulhu.jl/pull/662 for more details). +- The way that packages integrate with Cthulhu to customize the behavior of code introspection has been redesigned (https://github.com/JuliaDebug/Cthulhu.jl/pull/662 and https://github.com/JuliaDebug/Cthulhu.jl/pull/677). See the docstring of `Cthulhu.AbstractProvider` for more details. - A new UI command mapped to `DEL` (backspace, `'\x7f'`) now allows to go back (ascend) with a single key press. ### Breaking changes @@ -17,4 +17,3 @@ - The `with_effects` configuration option was renamed to `effects` for consistency with `remarks` and `exception_types`. - The `inline_cost` configuration option was renamed to `inlining_costs`, also for consistency reasons. - The `interruptexc` configuration option was removed. It used to control whether `q` exited (by throwing an `InterruptException`) or ascended, but now that backspace was added as a shortcut to ascend, we can now unconditionally exit with `q` (which actually matches its action description). - diff --git a/ext/CthulhuCompilerExt.jl b/ext/CthulhuCompilerExt.jl index 6b2659ee..d2ce778d 100644 --- a/ext/CthulhuCompilerExt.jl +++ b/ext/CthulhuCompilerExt.jl @@ -1,14 +1,32 @@ module CthulhuCompilerExt using Compiler: Compiler as CC -using Compiler.IRShow: IRShow using Cthulhu: Cthulhu -function __init__() - Cthulhu.CTHULHU_MODULE[] = @__MODULE__ - read_config!() -end +@static if CC.AbstractInterpreter !== Cthulhu.CC.AbstractInterpreter + using Compiler.IRShow: IRShow + + using Accessors + using CodeTracking: CodeTracking, definition, whereis, maybe_fix_path + using InteractiveUtils + using UUIDs + using REPL: REPL, AbstractTerminal + using JuliaSyntax + using JuliaSyntax: SyntaxNode, AbstractSyntaxNode, child, children + using TypedSyntax + using WidthLimitedIO + + using Core.IR -include("../src/CthulhuBase.jl") + include("../src/CthulhuCompiler.jl") + + function __init__() + Cthulhu.CompilerExt = @__MODULE__ + end +else + function __init__() + Cthulhu.CompilerExt = nothing + end +end end diff --git a/src/Cthulhu.jl b/src/Cthulhu.jl index 8391e51e..a0c333a7 100644 --- a/src/Cthulhu.jl +++ b/src/Cthulhu.jl @@ -2,37 +2,61 @@ module Cthulhu export @descend, @descend_code_typed, @descend_code_warntype, descend, descend_code_typed, descend_code_warntype, ascend, - AbstractProvider + AbstractProvider, + is_compiler_loaded, is_compiler_extension_loaded, get_module_for_compiler_integration const CC = Base.Compiler const IRShow = Base.IRShow -const CTHULHU_MODULE = Ref{Module}(@__MODULE__) +using Accessors +using CodeTracking: CodeTracking, definition, whereis, maybe_fix_path +using InteractiveUtils +using InteractiveUtils: is_expected_union +using UUIDs +using REPL: REPL, AbstractTerminal +using JuliaSyntax: JuliaSyntax, children +using TypedSyntax +import WidthLimitedIO: TextWidthLimiter +using Preferences +using Core.IR +using Base: default_tt, unwrap_unionall, mapany + +global CompilerExt::Union{Nothing, Module} + +is_compiler_extension_loaded() = isdefined(@__MODULE__, :CompilerExt) function is_compiler_loaded() pkgid = Base.PkgId(Base.UUID("807dbc54-b67e-4c79-8afb-eafe4df6f2e1"), "Compiler") return haskey(Base.loaded_modules, pkgid) end -is_compiler_extension_loaded() = CTHULHU_MODULE[] !== @__MODULE__ - -function resolve_module(Compiler::Module) - Compiler === Base.Compiler && return @__MODULE__ - Compiler === CTHULHU_MODULE[].Compiler && return CTHULHU_MODULE[] - return resolve_module() -end -resolve_module() = CTHULHU_MODULE[] -function resolve_module(@nospecialize(::T)) where {T} - mod = parentmodule(T) - nameof(mod) === :Compiler && return resolve_module(mod) - return resolve_module() +function get_module_for_compiler_integration(; use_compiler_stdlib::Bool = true) + !use_compiler_stdlib && return @__MODULE__ + is_compiler_extension_loaded() || error("The Cthulhu -> Compiler extension must be loaded first if `use_compiler_stdlib` is set to `true`") + return something(CompilerExt, @__MODULE__) end +cached_exception_type(code::CodeInstance) = code.exctype +get_mi(ci::CodeInstance) = CC.get_ci_mi(ci) +get_mi(mi::MethodInstance) = mi + +include("config.jl") +include("preferences.jl") __init__() = read_config!() -include("CthulhuBase.jl") +include("interface.jl") +include("callsite.jl") +include("state.jl") +include("ui.jl") +include("bookmark.jl") +include("descend.jl") +include("ascend.jl") include("backedges.jl") include("testing.jl") +function ir_to_src end + +include("CthulhuCompiler.jl") + """ @descend @@ -146,19 +170,8 @@ julia> descend() do [...] ``` """ -function descend(@nospecialize(args...); interp=nothing, - provider=nothing, - @nospecialize(kwargs...)) - if provider !== nothing - mod = resolve_module(provider) - mod.descend_with_error_handling(args...; provider, kwargs...) - elseif interp !== nothing - mod = resolve_module(interp) - mod.descend_with_error_handling(args...; interp, kwargs...) - else - mod = resolve_module() - mod.descend_with_error_handling(args...; kwargs...) - end +function descend(@nospecialize(args...); @nospecialize(kwargs...)) + descend_with_error_handling(args...; kwargs...) return nothing end @@ -236,9 +249,7 @@ with the option to `descend` into intermediate calls. Keyword arguments `pagesize, dynamic, maxsize` are passed to `Cthulhu.FoldingTrees.TreeMenu`. Any remaining `kwargs` are passed to [`descend`](@ref). """ -function ascend(@nospecialize(args...); kwargs...) - CTHULHU_MODULE[].ascend_impl(args...; kwargs...) -end +ascend(@nospecialize(args...); kwargs...) = ascend_impl(args...; kwargs...) using PrecompileTools @setup_workload begin diff --git a/src/CthulhuBase.jl b/src/CthulhuCompiler.jl similarity index 50% rename from src/CthulhuBase.jl rename to src/CthulhuCompiler.jl index 6f72c1c7..2a19aaec 100644 --- a/src/CthulhuBase.jl +++ b/src/CthulhuCompiler.jl @@ -1,56 +1,29 @@ Base.Experimental.@compiler_options compile=min optimize=1 -using Accessors -using CodeTracking: CodeTracking, definition, whereis, maybe_fix_path -using InteractiveUtils -using UUIDs -using REPL: REPL, AbstractTerminal -using JuliaSyntax -using JuliaSyntax: SyntaxNode, AbstractSyntaxNode, children, is_leaf -using TypedSyntax -using WidthLimitedIO +import .Cthulhu: AbstractProvider, get_abstract_interpreter, get_inference_world, find_method_instance, generate_code_instance, get_override, lookup, find_caller_of, get_inlining_costs, show_parameters, get_ci, get_rt, get_pc_remarks, get_pc_effects, get_pc_excts, show_callsite, show_callinfo, print_callsite_info, cthulhu_source, cthulhu_typed, cthulhu_ast, cthulhu_llvm, cthulhu_native, find_callsites, ir_to_src +using .Cthulhu: CthulhuState, CthulhuConfig, CallInfo, Callsite, cached_exception_type, get_mi + +using Base: isvarargtype, unwrapva, unwrap_unionall, mapany, get_world_counter +using JuliaSyntax: JuliaSyntax, children, is_leaf -using Core: MethodInstance, MethodMatch -using Core.IR using .CC: AbstractInterpreter, CallMeta, ApplyCallInfo, CallInfo as CCCallInfo, ConstCallInfo, EFFECTS_TOTAL, Effects, IncrementalCompact, InferenceParams, InferenceResult, InferenceState, IRCode, LimitedAccuracy, MethodMatchInfo, MethodResultPure, NativeInterpreter, NoCallInfo, OptimizationParams, OptimizationState, - UnionSplitApplyCallInfo, UnionSplitInfo, WorldRange, WorldView, get_inference_world, + UnionSplitApplyCallInfo, UnionSplitInfo, WorldRange, WorldView, argextype, argtypes_to_type, compileable_specialization, ignorelimited, singleton_type, - specialize_method, sptypes_from_meth_instance, widenconst, method_table, findsup -using Base: @constprop, default_tt, isvarargtype, unwrapva, unwrap_unionall, rewrap_unionall -const mapany = Base.mapany + specialize_method, sptypes_from_meth_instance, widenconst, method_table, findsup, + cached_return_type const ArgTypes = Vector{Any} -using Base: get_world_counter - -get_mi(ci::CodeInstance) = CC.get_ci_mi(ci) -get_mi(mi::MethodInstance) = mi - -using Preferences -include("config.jl") -include("preferences.jl") - -include("interface.jl") -include("callsite.jl") -include("compiler.jl") -include("state.jl") -include("interpreter.jl") -include("provider.jl") -include("reflection.jl") -include("ui.jl") -include("codeview.jl") -include("bookmark.jl") -include("descend.jl") -include("ascend.jl") - -resolve_module(::AbstractProvider) = @__MODULE__ - -using .CC: cached_return_type - -cached_exception_type(code::CodeInstance) = code.exctype +include("compiler/callsite.jl") +include("compiler/interface.jl") +include("compiler/lookup.jl") +include("compiler/interpreter.jl") +include("compiler/provider.jl") +include("compiler/reflection.jl") +include("compiler/codeview.jl") get_effects(codeinst::CodeInstance) = CC.decode_effects(codeinst.ipo_purity_bits) get_effects(codeinst::CodeInfo) = CC.decode_effects(codeinst.purity) diff --git a/src/ascend.jl b/src/ascend.jl index 35c8bf56..b5268664 100644 --- a/src/ascend.jl +++ b/src/ascend.jl @@ -1,6 +1,6 @@ function ascend_impl( term, mi; - interp::AbstractInterpreter=NativeInterpreter(), + interp=Base.Compiler.NativeInterpreter(), provider::AbstractProvider=AbstractProvider(interp), pagesize::Int=10, dynamic::Bool=false, maxsize::Int=pagesize, menu_options=(; pagesize), kwargs...) diff --git a/src/backedges.jl b/src/backedges.jl index ffd573e9..b5d946b6 100644 --- a/src/backedges.jl +++ b/src/backedges.jl @@ -18,7 +18,7 @@ function show_tuple_as_call(@nospecialize(highlighter), io::IO, name::Symbol, @n sig = (sig::DataType).parameters ft = sig[1] - uw = Base.unwrap_unionall(ft) + uw = unwrap_unionall(ft) if ft <: Function && isa(uw,DataType) && isempty(uw.parameters) && isdefined(uw.name.module, get_fname(uw)) && ft == typeof(getfield(uw.name.module, get_fname(uw))) @@ -44,7 +44,7 @@ end function stripType(@nospecialize(typ)) if isa(typ, UnionAll) - typ = Base.unwrap_unionall(typ) + typ = unwrap_unionall(typ) elseif isa(typ, TypeVar) || isa(typ, Union) return typ end diff --git a/src/bookmark.jl b/src/bookmark.jl index 0a3ed108..4caacbab 100644 --- a/src/bookmark.jl +++ b/src/bookmark.jl @@ -36,7 +36,7 @@ const BOOKMARKS = Bookmark[] function Base.show(io::IO, ::MIME"text/plain", bookmark::Bookmark; kwargs...) (; provider, ci) = bookmark state = CthulhuState(bookmark; kwargs...) - result = LookupResult(provider, ci, state.config.optimize) + result = lookup(provider, ci, state.config.optimize) world = get_inference_world(provider) if get(io, :typeinfo, Any) === Bookmark # a hack to check if in Vector etc. info = EdgeCallInfo(ci, result.rt, Effects()) @@ -52,7 +52,7 @@ end function Base.code_typed(bookmark::Bookmark; kwargs...) (; provider, ci) = bookmark state = CthulhuState(bookmark; kwargs...) - result = LookupResult(provider, ci, state.config.optimize) + result = lookup(provider, ci, state.config.optimize) src = something(result.src, result.ir)::Union{CodeInfo, IRCode} return src => result.rt end @@ -67,20 +67,20 @@ InteractiveUtils.code_native(bookmark::Bookmark; kwargs...) = function InteractiveUtils.code_warntype(io::IO, bookmark::Bookmark; kwargs...) (; provider, ci) = bookmark state = CthulhuState(bookmark; kwargs...) - result = LookupResult(provider, ci, state.config.optimize) + result = lookup(provider, ci, state.config.optimize) cthulhu_warntype(io, provider, state, result) end function InteractiveUtils.code_llvm(io::IO, bookmark::Bookmark; dump_module = false, raw = false, kwargs...) (; provider, ci) = bookmark state = CthulhuState(bookmark; kwargs...) - result = LookupResult(provider, ci, state.config.optimize) + result = lookup(provider, ci, state.config.optimize) cthulhu_llvm(io, provider, state, result; dump_module, raw) end function InteractiveUtils.code_native(io::IO, bookmark::Bookmark; dump_module = false, raw = false, kwargs...) (; provider, ci) = bookmark state = CthulhuState(bookmark; kwargs...) - result = LookupResult(provider, ci, state.config.optimize) + result = lookup(provider, ci, state.config.optimize) cthulhu_native(io, provider, state, result; dump_module, raw) end diff --git a/src/callsite.jl b/src/callsite.jl index 81d34005..3bb0da41 100644 --- a/src/callsite.jl +++ b/src/callsite.jl @@ -1,436 +1,35 @@ -using Unicode - abstract type CallInfo end -# Call could be resolved to a singular MI -struct EdgeCallInfo <: CallInfo - edge::CodeInstance - rt - effects::Effects - exct - function EdgeCallInfo(edge::CodeInstance, @nospecialize(rt), effects::Effects, @nospecialize(exct=nothing)) - if isa(rt, LimitedAccuracy) - return LimitedCallInfo(new(edge, ignorelimited(rt), effects, exct)) - else - return new(edge, rt, effects, exct) - end - end -end -get_ci(ci::EdgeCallInfo) = ci.edge -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_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)) - -# only appears when inspecting pre-optimization states -struct LimitedCallInfo <: WrappedCallInfo - wrapped::CallInfo -end - -# Runtime CallInfo -struct RTCallInfo <: CallInfo - f - argtyps - rt - exct -end -get_rt(ci::RTCallInfo) = ci.rt -get_ci(ci::RTCallInfo) = nothing -get_effects(ci::RTCallInfo) = Effects() -get_exct(ci::RTCallInfo) = ci.exct - -struct PureCallInfo <: CallInfo - argtypes::Vector{Any} - rt - PureCallInfo(argtypes::Vector{Any}, @nospecialize(rt)) = - new(argtypes, rt) -end -get_ci(::PureCallInfo) = nothing -get_rt(pci::PureCallInfo) = pci.rt -get_effects(::PureCallInfo) = EFFECTS_TOTAL -get_exct(::PureCallInfo) = Union{} - -# Failed -struct FailedCallInfo <: CallInfo - sig - rt -end -get_ci(ci::FailedCallInfo) = fail(ci) -get_rt(ci::FailedCallInfo) = fail(ci) -get_effects(ci::FailedCallInfo) = fail(ci) -get_exct(ci::FailedCallInfo) = fail(ci) -function fail(ci::FailedCallInfo) - @warn "MethodInstance extraction failed." ci.sig ci.rt - return nothing -end - -# Generated -struct GeneratedCallInfo <: CallInfo - sig - rt -end -get_ci(genci::GeneratedCallInfo) = fail(genci) -get_rt(genci::GeneratedCallInfo) = fail(genci) -get_effects(genci::GeneratedCallInfo) = fail(genci) -get_exct(genci::GeneratedCallInfo) = fail(genci) -function fail(genci::GeneratedCallInfo) - @warn "Can't extract MethodInstance from call to generated functions." genci.sig genci.rt - return nothing -end - -struct MultiCallInfo <: CallInfo - sig - rt - exct - callinfos::Vector{CallInfo} - MultiCallInfo(@nospecialize(sig), @nospecialize(rt), callinfos::Vector{CallInfo}, - @nospecialize(exct=nothing)) = - new(sig, rt, exct, callinfos) -end -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 - -struct TaskCallInfo <: CallInfo - ci::CallInfo -end -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) - -struct InvokeCallInfo <: CallInfo - ci::CallInfo - InvokeCallInfo(@nospecialize ci::CallInfo) = new(ci) -end -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) - -# OpaqueClosure CallInfo -struct OCCallInfo <: CallInfo - ci::CallInfo - OCCallInfo(@nospecialize ci::CallInfo) = new(ci) -end -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) - -# Special handling for ReturnTypeCall -struct ReturnTypeCallInfo <: CallInfo - vmi::CallInfo # virtualized method call -end -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 - ci::CallInfo - result::InferenceResult -end -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.ci) - -struct ConcreteCallInfo <: CallInfo - ci::CallInfo - argtypes::ArgTypes -end -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 - ci::CallInfo - ir::IRCode -end -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 - ci::EdgeCallInfo -end -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 CthulhuCallInfo <: CCCallInfo - meta::CallMeta -end -CC.add_edges_impl(edges::Vector{Any}, info::CthulhuCallInfo) = CC.add_edges!(edges, info.meta.info) -CC.nsplit_impl(info::CthulhuCallInfo) = CC.nsplit(info.meta.info) -CC.getsplit_impl(info::CthulhuCallInfo, idx::Int) = CC.getsplit(info.meta.info, idx) -CC.getresult_impl(info::CthulhuCallInfo, idx::Int) = CC.getresult(info.meta.info, idx) - struct Callsite id::Int # ssa-id info::CallInfo head::Symbol end -get_ci(c::Callsite) = get_ci(c.info) -get_effects(c::Callsite) = get_effects(c.info) - -# Callsite printing - -# compatibility (TextWidthLimiter used to live here) -has_space(limiter::TextWidthLimiter, width::Int) = limiter.width + width < limiter.limit - 1 -has_space(limiter::TextWidthLimiter, s) = has_space(limiter, textwidth(string(s))) -has_space(::IO, s) = true - -function headstring(@nospecialize(T)) - if isvarargtype(T) - T = unwrapva(T) - elseif isa(T, TypeVar) - return string(T.name) - end - T = widenconst(T) - if T isa Union || T === Union{} - return string(T)::String - elseif T isa UnionAll - return headstring(Base.unwrap_unionall(T)) - else - return string(T.name.name)::String - end -end - -function __show_limited(limiter, name, tt, @nospecialize(rt), effects, @nospecialize(exct=nothing)) - vastring(@nospecialize(T)) = (isvarargtype(T) ? headstring(T)*"..." : string(T)::String) - - # If effects are explicitly turned on, make sure to print them, even - # if there otherwise isn't space for them, since the effects are the - # most important piece of information if turned on. - show_effects = get(limiter, :effects, false)::Bool - exception_types = get(limiter, :exception_types, false)::Bool && exct !== nothing - - if isa(limiter, TextWidthLimiter) - show_effects && (limiter.width += textwidth(repr(effects)) + 1) - exception_types && (limiter.width += textwidth(string(exct)) + 1) - limiter.limit = max(limiter.width, limiter.limit) - end - - if !has_space(limiter, name) - print(limiter, '…') - @goto print_effects - end - - print(limiter, string(name)) - pstrings = String[vastring(T) for T in tt] - headstrings = String[ - T isa DataType && isempty(T.parameters) ? headstring(T) : string(headstring(T), "{…}") - for T in tt - ] - print(limiter, "(") - if length(pstrings) != 0 - # See if we have space to print all the parameters fully - if has_space(limiter, sum(textwidth, pstrings) + 3*length(pstrings)) - join(limiter, (string("::", T) for T in pstrings), ",") - # Alright, see if we at least have enough space for each head - elseif has_space(limiter, sum(textwidth, headstrings) + 3*length(pstrings)) - join(limiter, (string("::", T) for T in headstrings), ",") - # Fine, what about just indicating the number of arguments - elseif has_space(limiter, 2*(length(tt))) - join(limiter, ("…" for _ in pstrings), ",") - else - print(limiter, "…") - end - end - print(limiter, ")") - - # If we have space for the return type, print it - rt_str = string(rt)::String - if has_space(limiter, textwidth(rt_str)+2) - print(limiter, "::", rt_str) - elseif has_space(limiter, 3) - print(limiter, "::…") - end - - @label print_effects - if show_effects - # Print effects unlimited - print(limiter.io, " ", effects) - end - if exception_types - print(limiter.io, ' ', ExctWrapper(exct)) - end - - return nothing -end - -struct ExctWrapper - exct - ExctWrapper(@nospecialize exct) = new(exct) -end - -function Base.show(io::IO, (;exct)::ExctWrapper) - color = exct === Union{} ? :green : :yellow - printstyled(io, "(↑::", exct, ")"; color) -end - -function show_callinfo(limiter, ci::EdgeCallInfo) - mi = ci.edge.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(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}) - types = (ci.sig::DataType).parameters - ft, tt = types[1], types[2:end] - f = CC.singleton_type(ft) - if f !== nothing - name = "→ $f" - elseif ft isa Union - name = "→ (::Union{$(join(String[String(nameof(T)) for T in Base.uniontypes(ft)], ", "))})" - else - name = "→ (::$(nameof(ft)))" - end - __show_limited(limiter, name::String, tt, get_rt(ci), get_effects(ci)) -end - -show_callinfo(limiter, ci::RTCallInfo) = __show_limited(limiter, "$(ci.f)", ci.argtyps, get_rt(ci), get_effects(ci)) - -function show_callinfo(limiter, pci::PureCallInfo) - ft, tt... = pci.argtypes - f = CC.singleton_type(ft) - name = isnothing(f) ? "unknown" : string(f) - __show_limited(limiter, name, tt, get_rt(pci), get_effects(pci)) -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] - 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_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_ci(ci).def.def.name - tt = ci.argtypes[2:end] - __show_limited(limiter, name, tt, get_rt(ci), get_effects(ci)) -end - -function show_callinfo(limiter, rci::ReturnTypeCallInfo) - vmi = rci.vmi - if isa(vmi, FailedCallInfo) - ft = Base.tuple_type_head(vmi.sig) - f = CC.singleton_type(ft) - name = isnothing(f) ? "unknown" : string(f) - tt = Base.tuple_type_tail(vmi.sig).parameters - __show_limited(limiter, name, tt, vmi.rt, get_effects(vmi)) - else - show_callinfo(limiter, vmi) - end -end - -function print_callsite_info(limiter::IO, info::WrappedCallInfo) - wrapped_callinfo(limiter, info) - show_callinfo(limiter, ignorewrappers(info)) -end - -function print_callsite_info(limiter::IO, info::PureCallInfo) - print(limiter, "< pure > ") - show_callinfo(limiter, info) -end - -function print_callsite_info(limiter::IO, info::Union{MultiCallInfo, FailedCallInfo, GeneratedCallInfo}) - print(limiter, "call ") - show_callinfo(limiter, info) -end - -function print_callsite_info(limiter::IO, info::RTCallInfo) - print(limiter, "runtime < ") - show_callinfo(limiter, info) - print(limiter, " >") -end - -function print_callsite_info(limiter::IO, info::TaskCallInfo) - print(limiter, "task < ") - show_callinfo(limiter, info.ci) - print(limiter, " >") -end - -function print_callsite_info(limiter::IO, info::InvokeCallInfo) - print(limiter, "invoke < ") - show_callinfo(limiter, info.ci) - print(limiter, " >") -end - -function print_callsite_info(limiter::IO, info::ReturnTypeCallInfo) - print(limiter, "return_type < ") - show_callinfo(limiter, info) - print(limiter, " >") -end - -function print_callsite_info(limiter::IO, info::CuCallInfo) - print(limiter, "cucall < ") - show_callinfo(limiter, info.cumi) - print(limiter, " >") -end -function print_callsite_info(limiter::IO, info::ConstPropCallInfo) - print(limiter, "< constprop > ") - show_callinfo(limiter, info) -end - -function print_callsite_info(limiter::IO, info::SemiConcreteCallInfo) - print(limiter, " = < semi-concrete eval > ") - show_callinfo(limiter, info) -end - -function print_callsite_info(limiter::IO, info::ConcreteCallInfo) - print(limiter, "< concrete eval > ") - show_callinfo(limiter, info) -end +function find_callsites end +function get_rt end +get_ci(c::Callsite) = get_ci(c.info) +get_mi(c::Callsite) = get_mi(get_ci(c)) -function print_callsite_info(limiter::IO, info::OCCallInfo) - print(limiter, "< opaque closure call > ") - show_callinfo(limiter, info.ci) -end +show_callsite(io::IO, c::Callsite, info::CallInfo) = print_callsite_info(io, info) -const is_expected_union = InteractiveUtils.is_expected_union +function show_callinfo end +function print_callsite_info end -function Base.show(io::IO, c::Callsite) +function TextWidthLimiter(@nospecialize(io::IO), c::Callsite) + isa(io, TextWidthLimiter) && return io limit = get(io, :limit, false)::Bool cols = limit ? (displaysize(io)::Tuple{Int,Int})[2] : typemax(Int) - optimize = get(io, :optimize, true)::Bool - iswarn = get(io, :iswarn, false)::Bool - info = c.info - rt = get_rt(info) limiter = TextWidthLimiter(io, cols) + return limiter +end + +function Base.show(@nospecialize(io::IO), c::Callsite) + limiter = TextWidthLimiter(io, c) + info = c.info if c.id != -1 + iswarn = get(io, :iswarn, false)::Bool + rt = get_rt(info) if iswarn && is_type_unstable(rt) color = if rt isa Union && is_expected_union(rt) Base.warn_color() @@ -444,100 +43,6 @@ function Base.show(io::IO, c::Callsite) limiter.width += 1 # for the '%' character print(limiter, c.id, " = ") end - if isa(info, EdgeCallInfo) - optimize && print(limiter, c.head, ' ') - show_callinfo(limiter, info) - else - print_callsite_info(limiter, info) - end + show_callsite(limiter, c, info) return nothing end - -function wrapped_callinfo(limiter, ci::WrappedCallInfo) - print(limiter, " = < ") - _wrapped_callinfo(limiter, ci) - ci = get_wrapped(ci) - while isa(ci, WrappedCallInfo) - print(limiter, ", ") - _wrapped_callinfo(limiter, ci) - ci = get_wrapped(ci) - end - print(limiter, " > ") -end -_wrapped_callinfo(limiter, ::LimitedCallInfo) = print(limiter, "limited") - -# is_callsite returns true if `call` dispatches to `callee` -# See also `maybe_callsite` below -is_callsite(call::CodeInstance, callee::MethodInstance) = call.def === callee -is_callsite(::Nothing, callee::MethodInstance) = false # for when `get_ci` returns `nothing` - -# is_callsite for higher-level inputs -is_callsite(cs::Callsite, callee::MethodInstance) = is_callsite(cs.info, callee) -is_callsite(info::CallInfo, callee::MethodInstance) = is_callsite(get_ci(info), callee) -# special CallInfo cases: -function is_callsite(info::MultiCallInfo, callee::MethodInstance) - for csi in info.callinfos - is_callsite(csi, callee) && return true - end - return false -end - -# maybe_callsite returns true if `call` *might* dispatch to `callee` -# See also `is_callsite` above -function maybe_callsite(call::CodeInstance, callee::MethodInstance) - # handle comparison among Varargs - function generalized_va_subtype(@nospecialize(Tshort), @nospecialize(Tlong)) - nshort, nlong = length(Tshort.parameters), length(Tlong.parameters) - T = unwrapva(Tshort.parameters[end]) - T <: unwrapva(Tlong.parameters[end]) || return false - for i = 1:nshort-1 - Tshort.parameters[i] <: Tlong.parameters[i] || return false - end - for i = nshort:nlong-1 - T <: Tlong.parameters[i] || return false - end - return T <: unwrapva(Tlong.parameters[end]) - end - - Tcall, Tcallee = call.def.specTypes, callee.specTypes - Tcall <: Tcallee && return true - # Make sure we handle Tcall = Tuple{Vararg{String}}, Tcallee = Tuple{String,Vararg{String}} - if Base.isvatuple(Tcall) && Base.isvatuple(Tcallee) - Tcall, Tcallee = Base.unwrap_unionall(Tcall), Base.unwrap_unionall(Tcallee) - nargcall, nargcallee = length(Tcall.parameters), length(Tcallee.parameters) - nargcall == nargcallee && return false - return nargcall < nargcallee ? generalized_va_subtype(Tcall, Tcallee) : generalized_va_subtype(Tcallee, Tcall) - end - return false -end - -# maybe_callsite for higher-level inputs -maybe_callsite(cs::Callsite, callee::MethodInstance) = maybe_callsite(cs.info, callee) -maybe_callsite(cs::Callsite, @nospecialize(tt::Type)) = maybe_callsite(cs.info, tt) -maybe_callsite(info::CallInfo, callee::MethodInstance) = maybe_callsite(get_ci(info), callee) -# Special CallInfo cases: -function maybe_callsite(info::MultiCallInfo, callee::MethodInstance) - for csi in info.callinfos - maybe_callsite(csi, callee) && return true - end - return false -end -maybe_callsite(info::PureCallInfo, mi::MethodInstance) = mi.specTypes <: Tuple{mapany(Core.Typeof ∘ unwrapconst, info.argtypes)...} -maybe_callsite(info::RTCallInfo, mi::MethodInstance) = false - -function maybe_callsite(info::RTCallInfo, @nospecialize(tt::Type)) - isa(tt, Union) && return maybe_callsite(info, tt.a) || maybe_callsite(info, tt.b) - isa(tt, DataType) || return false - typeof(info.f) === tt.parameters[1] || return false - for (a, b) in zip(info.argtyps, tt.parameters[2:end]) - a === b || return false - end - return true -end -function maybe_callsite(info::EdgeCallInfo, @nospecialize(tt::Type)) - return tt <: info.mi.specTypes -end - -maybe_callsite(info::CallInfo, @nospecialize(tt::Type)) = false - -unwrapconst(@nospecialize(arg)) = arg isa Core.Const ? arg.val : arg diff --git a/src/compiler/callsite.jl b/src/compiler/callsite.jl new file mode 100644 index 00000000..781ec73e --- /dev/null +++ b/src/compiler/callsite.jl @@ -0,0 +1,508 @@ +using Unicode + +# Call could be resolved to a singular MI +struct EdgeCallInfo <: CallInfo + edge::CodeInstance + rt + effects::Effects + exct + function EdgeCallInfo(edge::CodeInstance, @nospecialize(rt), effects::Effects, @nospecialize(exct=nothing)) + if isa(rt, LimitedAccuracy) + return LimitedCallInfo(new(edge, ignorelimited(rt), effects, exct)) + else + return new(edge, rt, effects, exct) + end + end +end +get_ci(ci::EdgeCallInfo) = ci.edge +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_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)) + +# only appears when inspecting pre-optimization states +struct LimitedCallInfo <: WrappedCallInfo + wrapped::CallInfo +end + +# Runtime CallInfo +struct RTCallInfo <: CallInfo + f + argtyps + rt + exct +end +get_rt(ci::RTCallInfo) = ci.rt +get_ci(ci::RTCallInfo) = nothing +get_effects(ci::RTCallInfo) = Effects() +get_exct(ci::RTCallInfo) = ci.exct + +struct PureCallInfo <: CallInfo + argtypes::Vector{Any} + rt + PureCallInfo(argtypes::Vector{Any}, @nospecialize(rt)) = + new(argtypes, rt) +end +get_ci(::PureCallInfo) = nothing +get_rt(pci::PureCallInfo) = pci.rt +get_effects(::PureCallInfo) = EFFECTS_TOTAL +get_exct(::PureCallInfo) = Union{} + +# Failed +struct FailedCallInfo <: CallInfo + sig + rt +end +get_ci(ci::FailedCallInfo) = fail(ci) +get_rt(ci::FailedCallInfo) = fail(ci) +get_effects(ci::FailedCallInfo) = fail(ci) +get_exct(ci::FailedCallInfo) = fail(ci) +function fail(ci::FailedCallInfo) + @warn "MethodInstance extraction failed." ci.sig ci.rt + return nothing +end + +# Generated +struct GeneratedCallInfo <: CallInfo + sig + rt +end +get_ci(genci::GeneratedCallInfo) = fail(genci) +get_rt(genci::GeneratedCallInfo) = fail(genci) +get_effects(genci::GeneratedCallInfo) = fail(genci) +get_exct(genci::GeneratedCallInfo) = fail(genci) +function fail(genci::GeneratedCallInfo) + @warn "Can't extract MethodInstance from call to generated functions." genci.sig genci.rt + return nothing +end + +struct MultiCallInfo <: CallInfo + sig + rt + exct + callinfos::Vector{CallInfo} + MultiCallInfo(@nospecialize(sig), @nospecialize(rt), callinfos::Vector{CallInfo}, + @nospecialize(exct=nothing)) = + new(sig, rt, exct, callinfos) +end +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 + +struct TaskCallInfo <: CallInfo + ci::CallInfo +end +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) + +struct InvokeCallInfo <: CallInfo + ci::CallInfo + InvokeCallInfo(@nospecialize ci::CallInfo) = new(ci) +end +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) + +# OpaqueClosure CallInfo +struct OCCallInfo <: CallInfo + ci::CallInfo + OCCallInfo(@nospecialize ci::CallInfo) = new(ci) +end +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) + +# Special handling for ReturnTypeCall +struct ReturnTypeCallInfo <: CallInfo + vmi::CallInfo # virtualized method call +end +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 + ci::CallInfo + result::InferenceResult +end +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.ci) + +struct ConcreteCallInfo <: CallInfo + ci::CallInfo + argtypes::ArgTypes +end +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 + ci::CallInfo + ir::IRCode +end +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 + ci::EdgeCallInfo +end +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 CthulhuCallInfo <: CCCallInfo + meta::CallMeta +end +CC.add_edges_impl(edges::Vector{Any}, info::CthulhuCallInfo) = CC.add_edges!(edges, info.meta.info) +CC.nsplit_impl(info::CthulhuCallInfo) = CC.nsplit(info.meta.info) +CC.getsplit_impl(info::CthulhuCallInfo, idx::Int) = CC.getsplit(info.meta.info, idx) +CC.getresult_impl(info::CthulhuCallInfo, idx::Int) = CC.getresult(info.meta.info, idx) + +get_effects(c::Callsite) = get_effects(c.info) + +# Callsite printing + +# compatibility (TextWidthLimiter used to live here) +has_space(limiter::TextWidthLimiter, width::Int) = limiter.width + width < limiter.limit - 1 +has_space(limiter::TextWidthLimiter, s) = has_space(limiter, textwidth(string(s))) +has_space(::IO, s) = true + +function headstring(@nospecialize(T)) + if isvarargtype(T) + T = unwrapva(T) + elseif isa(T, TypeVar) + return string(T.name) + end + T = widenconst(T) + if T isa Union || T === Union{} + return string(T)::String + elseif T isa UnionAll + return headstring(unwrap_unionall(T)) + else + return string(T.name.name)::String + end +end + +function __show_limited(limiter, name, tt, @nospecialize(rt), effects, @nospecialize(exct=nothing)) + vastring(@nospecialize(T)) = (isvarargtype(T) ? headstring(T)*"..." : string(T)::String) + + # If effects are explicitly turned on, make sure to print them, even + # if there otherwise isn't space for them, since the effects are the + # most important piece of information if turned on. + show_effects = get(limiter, :effects, false)::Bool + exception_types = get(limiter, :exception_types, false)::Bool && exct !== nothing + + if isa(limiter, TextWidthLimiter) + show_effects && (limiter.width += textwidth(repr(effects)) + 1) + exception_types && (limiter.width += textwidth(string(exct)) + 1) + limiter.limit = max(limiter.width, limiter.limit) + end + + if !has_space(limiter, name) + print(limiter, '…') + @goto print_effects + end + + print(limiter, string(name)) + pstrings = String[vastring(T) for T in tt] + headstrings = String[ + T isa DataType && isempty(T.parameters) ? headstring(T) : string(headstring(T), "{…}") + for T in tt + ] + print(limiter, "(") + if length(pstrings) != 0 + # See if we have space to print all the parameters fully + if has_space(limiter, sum(textwidth, pstrings) + 3*length(pstrings)) + join(limiter, (string("::", T) for T in pstrings), ",") + # Alright, see if we at least have enough space for each head + elseif has_space(limiter, sum(textwidth, headstrings) + 3*length(pstrings)) + join(limiter, (string("::", T) for T in headstrings), ",") + # Fine, what about just indicating the number of arguments + elseif has_space(limiter, 2*(length(tt))) + join(limiter, ("…" for _ in pstrings), ",") + else + print(limiter, "…") + end + end + print(limiter, ")") + + # If we have space for the return type, print it + rt_str = string(rt)::String + if has_space(limiter, textwidth(rt_str)+2) + print(limiter, "::", rt_str) + elseif has_space(limiter, 3) + print(limiter, "::…") + end + + @label print_effects + if show_effects + # Print effects unlimited + print(limiter.io, " ", effects) + end + if exception_types + print(limiter.io, ' ', ExctWrapper(exct)) + end + + return nothing +end + +struct ExctWrapper + exct + ExctWrapper(@nospecialize exct) = new(exct) +end + +function Base.show(io::IO, (;exct)::ExctWrapper) + color = exct === Union{} ? :green : :yellow + printstyled(io, "(↑::", exct, ")"; color) +end + +function show_callinfo(limiter, ci::EdgeCallInfo) + mi = ci.edge.def + tt = (unwrap_unionall(mi.specTypes)::DataType).parameters[2:end] + if !isa(mi.def, Method) + name = ":toplevel" + else + name = mi.def.name + end + 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}) + types = (ci.sig::DataType).parameters + ft, tt = types[1], types[2:end] + f = CC.singleton_type(ft) + if f !== nothing + name = "→ $f" + elseif ft isa Union + name = "→ (::Union{$(join(String[String(nameof(T)) for T in Base.uniontypes(ft)], ", "))})" + else + name = "→ (::$(nameof(ft)))" + end + __show_limited(limiter, name::String, tt, get_rt(ci), get_effects(ci)) +end + +show_callinfo(limiter, ci::RTCallInfo) = __show_limited(limiter, "$(ci.f)", ci.argtyps, get_rt(ci), get_effects(ci)) + +function show_callinfo(limiter, pci::PureCallInfo) + ft, tt... = pci.argtypes + f = CC.singleton_type(ft) + name = isnothing(f) ? "unknown" : string(f) + __show_limited(limiter, name, tt, get_rt(pci), get_effects(pci)) +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] + 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_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_ci(ci).def.def.name + tt = ci.argtypes[2:end] + __show_limited(limiter, name, tt, get_rt(ci), get_effects(ci)) +end + +function show_callinfo(limiter, rci::ReturnTypeCallInfo) + vmi = rci.vmi + if isa(vmi, FailedCallInfo) + ft = Base.tuple_type_head(vmi.sig) + f = CC.singleton_type(ft) + name = isnothing(f) ? "unknown" : string(f) + tt = Base.tuple_type_tail(vmi.sig).parameters + __show_limited(limiter, name, tt, vmi.rt, get_effects(vmi)) + else + show_callinfo(limiter, vmi) + end +end + +function print_callsite_info(limiter::IO, info::WrappedCallInfo) + wrapped_callinfo(limiter, info) + show_callinfo(limiter, ignorewrappers(info)) +end + +function print_callsite_info(limiter::IO, info::PureCallInfo) + print(limiter, "< pure > ") + show_callinfo(limiter, info) +end + +function print_callsite_info(limiter::IO, info::Union{MultiCallInfo, FailedCallInfo, GeneratedCallInfo}) + print(limiter, "call ") + show_callinfo(limiter, info) +end + +function print_callsite_info(limiter::IO, info::RTCallInfo) + print(limiter, "runtime < ") + show_callinfo(limiter, info) + print(limiter, " >") +end + +function print_callsite_info(limiter::IO, info::TaskCallInfo) + print(limiter, "task < ") + show_callinfo(limiter, info.ci) + print(limiter, " >") +end + +function print_callsite_info(limiter::IO, info::InvokeCallInfo) + print(limiter, "invoke < ") + show_callinfo(limiter, info.ci) + print(limiter, " >") +end + +function print_callsite_info(limiter::IO, info::ReturnTypeCallInfo) + print(limiter, "return_type < ") + show_callinfo(limiter, info) + print(limiter, " >") +end + +function print_callsite_info(limiter::IO, info::CuCallInfo) + print(limiter, "cucall < ") + show_callinfo(limiter, info.cumi) + print(limiter, " >") +end + +function print_callsite_info(limiter::IO, info::ConstPropCallInfo) + print(limiter, "< constprop > ") + show_callinfo(limiter, info) +end + +function print_callsite_info(limiter::IO, info::SemiConcreteCallInfo) + print(limiter, " = < semi-concrete eval > ") + show_callinfo(limiter, info) +end + +function print_callsite_info(limiter::IO, info::ConcreteCallInfo) + print(limiter, "< concrete eval > ") + show_callinfo(limiter, info) +end + +function print_callsite_info(limiter::IO, info::OCCallInfo) + print(limiter, "< opaque closure call > ") + show_callinfo(limiter, info.ci) +end + +function wrapped_callinfo(limiter, ci::WrappedCallInfo) + print(limiter, " = < ") + _wrapped_callinfo(limiter, ci) + ci = get_wrapped(ci) + while isa(ci, WrappedCallInfo) + print(limiter, ", ") + _wrapped_callinfo(limiter, ci) + ci = get_wrapped(ci) + end + print(limiter, " > ") +end +_wrapped_callinfo(limiter, ::LimitedCallInfo) = print(limiter, "limited") + +# is_callsite returns true if `call` dispatches to `callee` +# See also `maybe_callsite` below +is_callsite(call::CodeInstance, callee::MethodInstance) = call.def === callee +is_callsite(::Nothing, callee::MethodInstance) = false # for when `get_ci` returns `nothing` + +# is_callsite for higher-level inputs +is_callsite(cs::Callsite, callee::MethodInstance) = is_callsite(cs.info, callee) +is_callsite(info::CallInfo, callee::MethodInstance) = is_callsite(get_ci(info), callee) +# special CallInfo cases: +function is_callsite(info::MultiCallInfo, callee::MethodInstance) + for csi in info.callinfos + is_callsite(csi, callee) && return true + end + return false +end + +# maybe_callsite returns true if `call` *might* dispatch to `callee` +# See also `is_callsite` above +function maybe_callsite(call::CodeInstance, callee::MethodInstance) + # handle comparison among Varargs + function generalized_va_subtype(@nospecialize(Tshort), @nospecialize(Tlong)) + nshort, nlong = length(Tshort.parameters), length(Tlong.parameters) + T = unwrapva(Tshort.parameters[end]) + T <: unwrapva(Tlong.parameters[end]) || return false + for i = 1:nshort-1 + Tshort.parameters[i] <: Tlong.parameters[i] || return false + end + for i = nshort:nlong-1 + T <: Tlong.parameters[i] || return false + end + return T <: unwrapva(Tlong.parameters[end]) + end + + Tcall, Tcallee = call.def.specTypes, callee.specTypes + Tcall <: Tcallee && return true + # Make sure we handle Tcall = Tuple{Vararg{String}}, Tcallee = Tuple{String,Vararg{String}} + if Base.isvatuple(Tcall) && Base.isvatuple(Tcallee) + Tcall, Tcallee = unwrap_unionall(Tcall), unwrap_unionall(Tcallee) + nargcall, nargcallee = length(Tcall.parameters), length(Tcallee.parameters) + nargcall == nargcallee && return false + return nargcall < nargcallee ? generalized_va_subtype(Tcall, Tcallee) : generalized_va_subtype(Tcallee, Tcall) + end + return false +end + +# maybe_callsite for higher-level inputs +maybe_callsite(cs::Callsite, callee::MethodInstance) = maybe_callsite(cs.info, callee) +maybe_callsite(cs::Callsite, @nospecialize(tt::Type)) = maybe_callsite(cs.info, tt) +maybe_callsite(info::CallInfo, callee::MethodInstance) = maybe_callsite(get_ci(info), callee) +# Special CallInfo cases: +function maybe_callsite(info::MultiCallInfo, callee::MethodInstance) + for csi in info.callinfos + maybe_callsite(csi, callee) && return true + end + return false +end +maybe_callsite(info::PureCallInfo, mi::MethodInstance) = mi.specTypes <: Tuple{mapany(Core.Typeof ∘ unwrapconst, info.argtypes)...} +maybe_callsite(info::RTCallInfo, mi::MethodInstance) = false + +function maybe_callsite(info::RTCallInfo, @nospecialize(tt::Type)) + isa(tt, Union) && return maybe_callsite(info, tt.a) || maybe_callsite(info, tt.b) + isa(tt, DataType) || return false + typeof(info.f) === tt.parameters[1] || return false + for (a, b) in zip(info.argtyps, tt.parameters[2:end]) + a === b || return false + end + return true +end +function maybe_callsite(info::EdgeCallInfo, @nospecialize(tt::Type)) + return tt <: info.mi.specTypes +end + +maybe_callsite(info::CallInfo, @nospecialize(tt::Type)) = false + +unwrapconst(@nospecialize(arg)) = arg isa Core.Const ? arg.val : arg + +function show_callsite(io::IO, c::Callsite, info::EdgeCallInfo) + optimize = get(io, :optimize, true)::Bool + optimize && print(io, c.head, ' ') + show_callinfo(io, info) +end diff --git a/src/codeview.jl b/src/compiler/codeview.jl similarity index 98% rename from src/codeview.jl rename to src/compiler/codeview.jl index 1c13fcc8..8628ba54 100644 --- a/src/codeview.jl +++ b/src/compiler/codeview.jl @@ -143,7 +143,7 @@ function cthulhu_typed(io::IO, provider::AbstractProvider, state::CthulhuState, TypedSyntax.display_inlay_hints_vscode(vscode_io) istruncated && @info "This method only fills in default arguments; descend into the body method to see the full source." - config.view === :source && return # nothing more to show + config.view === :source && return end end @@ -267,7 +267,7 @@ function add_callsites!(d::AbstractDict, visited_cis::AbstractSet, diagnostics:: in(callsite_ci, visited_cis) && continue push!(visited_cis, callsite_ci) - result = LookupResult(provider, callsite_ci, optimized)::LookupResult + result = lookup(provider, callsite_ci, optimized) add_callsites!(d, visited_cis, diagnostics, provider, callsite_ci, result, source_ci) end diff --git a/src/compiler/interface.jl b/src/compiler/interface.jl new file mode 100644 index 00000000..5b4d0d9f --- /dev/null +++ b/src/compiler/interface.jl @@ -0,0 +1,80 @@ +AbstractProvider(interp::NativeInterpreter) = DefaultProvider(interp) + +function AbstractProvider(interp::AbstractInterpreter) + error(lazy"""missing `$AbstractInterpreter` API: + `$(typeof(interp))` is required to implement `$AbstractProvider(interp::$(typeof(interp)))) -> AbstractProvider`. + """) +end + +get_inference_world(interp::AbstractInterpreter) = CC.get_inference_world(interp) + +function find_method_instance(provider::AbstractProvider, interp::AbstractInterpreter, @nospecialize(tt::Type{<:Tuple}), world::UInt) + mt = method_table(interp) + match, valid_worlds = findsup(tt, mt) + match === nothing && return nothing + mi = specialize_method(match) + return mi +end + +function generate_code_instance(provider::AbstractProvider, interp::AbstractInterpreter, mi::MethodInstance) + ci = run_type_inference(provider, interp, mi) + return ci +end + +get_override(provider::AbstractProvider, info::ConstPropCallInfo) = info.result +get_override(provider::AbstractProvider, info::SemiConcreteCallInfo) = info +get_override(provider::AbstractProvider, info::OCCallInfo) = get_override(provider, info.ci) + +function get_inlining_costs(provider::AbstractProvider, interp::AbstractInterpreter, mi::MethodInstance, src::Union{CodeInfo, IRCode}) + code = isa(src, IRCode) ? src.stmts.stmt : src.code + costs = zeros(Int, length(code)) + params = CC.OptimizationParams(interp) + sparams = CC.VarState[CC.VarState(sparam, false) for sparam in mi.sparam_vals] + CC.statement_costs!(costs, code, src, sparams, params) + return costs +end + +show_parameters(io::IO, provider::AbstractProvider, interp::AbstractInterpreter) = show_inference_cache(io, interp) + +function show_inference_cache(io::IO, interp::AbstractInterpreter) + @info "Dumping inference cache." + cache = CC.get_inference_cache(interp) + for (i, (; linfo, result)) in enumerate(cache) + println(io, i, ": ", linfo, "::", result) + end +end + +function find_caller_of(provider::AbstractProvider, interp::AbstractInterpreter, callee::Union{MethodInstance,Type}, mi::MethodInstance, allow_unspecialized::Bool) + ci = generate_code_instance(provider, interp, mi) + @assert get_mi(ci) === mi + locs = Tuple{Core.LineInfoNode,Int}[] + for optimize in (true, false) + result = lookup(provider, interp, ci, optimize) + callsites, _ = find_callsites(provider, result, ci) + callsites = allow_unspecialized ? filter(cs -> maybe_callsite(cs, callee), callsites) : + filter(cs -> is_callsite(cs, callee), callsites) + foreach(cs -> add_sourceline!(locs, result.src, cs.id, mi), callsites) + end + # Consolidate by method, but preserve the order + prlookup = Dict{Tuple{Symbol,Symbol},Int}() + ulocs = Pair{Tuple{Symbol,Symbol,Int},Vector{Int}}[] + if !isempty(locs) + for (loc, depth) in locs + locname = loc.method + if isa(locname, MethodInstance) + locname = locname.def.name + end + idx = get(prlookup, (locname, loc.file), nothing) + if idx === nothing + push!(ulocs, (locname, loc.file, depth) => Int[]) + prlookup[(locname, loc.file)] = idx = length(ulocs) + end + lines = ulocs[idx][2] + line = loc.line + if line ∉ lines + push!(lines, line) + end + end + end + return ulocs +end diff --git a/src/interpreter.jl b/src/compiler/interpreter.jl similarity index 99% rename from src/interpreter.jl rename to src/compiler/interpreter.jl index e1c69c3c..42cf2f30 100644 --- a/src/interpreter.jl +++ b/src/compiler/interpreter.jl @@ -26,7 +26,7 @@ Base.show(io::IO, interp::CthulhuInterpreter) = print(io, typeof(interp), '(', i CC.InferenceParams(interp::CthulhuInterpreter) = InferenceParams(interp.native) CC.OptimizationParams(interp::CthulhuInterpreter) = OptimizationParams(OptimizationParams(interp.native); preserve_local_sources=true) -CC.get_inference_world(interp::CthulhuInterpreter) = get_inference_world(interp.native) +CC.get_inference_world(interp::CthulhuInterpreter) = CC.get_inference_world(interp.native) CC.get_inference_cache(interp::CthulhuInterpreter) = CC.get_inference_cache(interp.native) CC.may_optimize(::CthulhuInterpreter) = true diff --git a/src/compiler.jl b/src/compiler/lookup.jl similarity index 55% rename from src/compiler.jl rename to src/compiler/lookup.jl index 40e975dc..40933609 100644 --- a/src/compiler.jl +++ b/src/compiler/lookup.jl @@ -1,89 +1,54 @@ -AbstractProvider(interp::NativeInterpreter) = DefaultProvider(interp) - -function AbstractProvider(interp::AbstractInterpreter) - error(lazy"""missing `$AbstractInterpreter` API: - `$(typeof(interp))` is required to implement `$AbstractProvider(interp::$(typeof(interp)))) -> AbstractProvider`. - """) -end - -function find_method_instance(provider::AbstractProvider, interp::AbstractInterpreter, @nospecialize(tt::Type{<:Tuple}), world::UInt) - mt = method_table(interp) - match, valid_worlds = findsup(tt, mt) - match === nothing && return nothing - mi = specialize_method(match) - return mi -end - -function generate_code_instance(provider::AbstractProvider, interp::AbstractInterpreter, mi::MethodInstance) - ci = run_type_inference(provider, interp, mi) - return ci -end - -function find_caller_of(provider::AbstractProvider, interp::AbstractInterpreter, callee::Union{MethodInstance,Type}, mi::MethodInstance, allow_unspecialized::Bool) - ci = generate_code_instance(provider, interp, mi) - @assert get_mi(ci) === mi - locs = Tuple{Core.LineInfoNode,Int}[] - for optimize in (true, false) - result = LookupResult(provider, interp, ci, optimize)::LookupResult - callsites, _ = find_callsites(provider, result, ci) - callsites = allow_unspecialized ? filter(cs -> maybe_callsite(cs, callee), callsites) : - filter(cs -> is_callsite(cs, callee), callsites) - foreach(cs -> add_sourceline!(locs, result.src, cs.id, mi), callsites) - end - # Consolidate by method, but preserve the order - prlookup = Dict{Tuple{Symbol,Symbol},Int}() - ulocs = Pair{Tuple{Symbol,Symbol,Int},Vector{Int}}[] - if !isempty(locs) - for (loc, depth) in locs - locname = loc.method - if isa(locname, MethodInstance) - locname = locname.def.name - end - idx = get(prlookup, (locname, loc.file), nothing) - if idx === nothing - push!(ulocs, (locname, loc.file, depth) => Int[]) - prlookup[(locname, loc.file)] = idx = length(ulocs) +struct LookupResult + ir::Union{IRCode, Nothing} # used over `src` for callsite detection and printing + src::Union{CodeInfo, Nothing} # may be required (e.g. for LLVM and native module dumps) + rt + exct + infos::Vector{Any} + slottypes::Vector{Any} + effects::Effects + optimized::Bool + function LookupResult(ir, src, @nospecialize(rt), @nospecialize(exct), + infos, slottypes, effects, optimized) + ninfos = length(infos) + if src === nothing && ir === nothing + throw(ArgumentError("At least one of `src` or `ir` must have a value")) + end + if isa(ir, IRCode) && ninfos ≠ length(ir.stmts) + throw(ArgumentError("`ir` and `infos` are inconsistent with $(length(ir.stmts)) IR statements and $ninfos call infos")) + end + if isa(src, CodeInfo) + if !isa(src.ssavaluetypes, Vector{Any}) + throw(ArgumentError("`src.ssavaluetypes::$(typeof(src.ssavaluetypes))` must be a Vector{Any}")) end - lines = ulocs[idx][2] - line = loc.line - if line ∉ lines - push!(lines, line) + if !isa(ir, IRCode) && ninfos ≠ length(src.code) + throw(ArgumentError("`src` and `infos` are inconsistent with $(length(src.code)) code statements and $ninfos call infos")) end end + return new(ir, src, rt, exct, infos, slottypes, effects, optimized) end - return ulocs -end - -function get_inlining_costs(provider::AbstractProvider, interp::AbstractInterpreter, mi::MethodInstance, src::Union{CodeInfo, IRCode}) - code = isa(src, IRCode) ? src.stmts.stmt : src.code - costs = zeros(Int, length(code)) - params = CC.OptimizationParams(interp) - sparams = CC.VarState[CC.VarState(sparam, false) for sparam in mi.sparam_vals] - CC.statement_costs!(costs, code, src, sparams, params) - return costs end -show_parameters(io::IO, provider::AbstractProvider, interp::AbstractInterpreter) = show_inference_cache(io, interp) - -function show_inference_cache(io::IO, interp::AbstractInterpreter) - @info "Dumping inference cache." - cache = CC.get_inference_cache(interp) - for (i, (; linfo, result)) in enumerate(cache) - println(io, i, ": ", linfo, "::", result) +function lookup(provider::AbstractProvider, interp::AbstractInterpreter, ci::CodeInstance, optimize::Bool) + if optimize + result = lookup_optimized(provider, interp, ci) + if result === nothing + @info """ + Inference discarded the source for this call because of recursion: + Cthulhu nevertheless is trying to retrieve the source for further inspection. + """ + result = lookup_unoptimized(provider, interp, ci) + end + return result end -end - -function LookupResult(provider::AbstractProvider, interp::AbstractInterpreter, ci::CodeInstance, optimize::Bool) - optimize && return lookup_optimized(provider, interp, ci) return lookup_unoptimized(provider, interp, ci) end -function LookupResult(provider::AbstractProvider, interp::AbstractInterpreter, result::InferenceResult, optimize::Bool) +function lookup(provider::AbstractProvider, interp::AbstractInterpreter, result::InferenceResult, optimize::Bool) optimize && return lookup_constproped_optimized(provider, interp, result) return lookup_constproped_unoptimized(provider, interp, result) end -function LookupResult(provider::AbstractProvider, interp::AbstractInterpreter, call::SemiConcreteCallInfo, optimize::Bool) +function lookup(provider::AbstractProvider, interp::AbstractInterpreter, call::SemiConcreteCallInfo, optimize::Bool) return lookup_semiconcrete(provider, interp, call) end @@ -121,10 +86,10 @@ function lookup_optimized(provider::AbstractProvider, interp::AbstractInterprete if CC.use_const_api(ci) @assert isdefined(ci, :rettype_const) @static if VERSION > v"1.13-" - range = CC.WorldRange(1, typemax(UInt)) - src = CC.codeinfo_for_const(interp, get_mi(ci), range, Core.svec(), ci.rettype_const) + world_range = CC.WorldRange(ci.min_world, ci.max_world) + src = CC.codeinfo_for_const(interp, mi, world_range, Core.svec(), ci.rettype_const) else - src = CC.codeinfo_for_const(interp, get_mi(ci), ci.rettype_const) + src = CC.codeinfo_for_const(interp, mi, ci.rettype_const) end src.ssavaluetypes = Any[Any] infos = Any[CC.NoCallInfo()] diff --git a/src/compiler/provider.jl b/src/compiler/provider.jl new file mode 100644 index 00000000..65ec2ab5 --- /dev/null +++ b/src/compiler/provider.jl @@ -0,0 +1,15 @@ +struct DefaultProvider <: AbstractProvider + interp::CthulhuInterpreter +end +DefaultProvider(interp::AbstractInterpreter = NativeInterpreter()) = DefaultProvider(CthulhuInterpreter(interp)) + +get_abstract_interpreter(provider::DefaultProvider) = provider.interp +AbstractProvider(interp::CthulhuInterpreter) = DefaultProvider(interp) + +function should_regenerate_code_instance(provider::DefaultProvider, ci::CodeInstance) + return !haskey(provider.interp.unopt, ci) +end + +get_pc_remarks(provider::DefaultProvider, ci::CodeInstance) = get(provider.interp.remarks, ci, nothing) +get_pc_effects(provider::DefaultProvider, ci::CodeInstance) = get(provider.interp.effects, ci, nothing) +get_pc_excts(provider::DefaultProvider, ci::CodeInstance) = get(provider.interp.exception_types, ci, nothing) diff --git a/src/reflection.jl b/src/compiler/reflection.jl similarity index 98% rename from src/reflection.jl rename to src/compiler/reflection.jl index 014f30e8..301b8da2 100644 --- a/src/reflection.jl +++ b/src/compiler/reflection.jl @@ -326,7 +326,7 @@ function truncate_if_defaultargs!(tsn, mappings, meth) return tsn, mappings end -is_kw_dispatch(meth::Method) = meth.name == :kwcall || Base.unwrap_unionall(meth.sig).parameters[1] === typeof(Core.kwcall) || !isempty(Base.kwarg_decl(meth)) +is_kw_dispatch(meth::Method) = meth.name == :kwcall || unwrap_unionall(meth.sig).parameters[1] === typeof(Core.kwcall) || !isempty(Base.kwarg_decl(meth)) function tag_runtime(node::TypedSyntaxNode, info) node.runtime = isa(info, RTCallInfo) diff --git a/src/descend.jl b/src/descend.jl index dff996da..c48c13d6 100644 --- a/src/descend.jl +++ b/src/descend.jl @@ -60,7 +60,8 @@ function descend!(state::CthulhuState) mi::MethodInstance, ci::CodeInstance src = something(state.override, ci) - result = LookupResult(provider, src, config.optimize)::LookupResult + result = lookup(provider, src, config.optimize) + result === nothing && return @error "Descent into $mi failed" if config.jump_always def = state.mi.def @@ -192,7 +193,7 @@ function menu_callsites_from_source_node(callsite::Callsite, source_node) for info in callsite.info.callinfos ci = get_ci(info) mi = get_mi(ci) - p = Base.unwrap_unionall(mi.specTypes).parameters + p = unwrap_unionall(mi.specTypes).parameters if isa(source_node, TypedSyntax.MaybeTypedSyntaxNode) && length(p) == length(children(source_node)) + 1 new_node = copy(source_node) for (i, child) in enumerate(children(new_node)) @@ -206,7 +207,7 @@ function menu_callsites_from_source_node(callsite::Callsite, source_node) return callsites end -function source_slotnames(result::LookupResult) +function source_slotnames(result#=::LookupResult=#) result.src === nothing && return false return Base.sourceinfo_slotnames(result.src) end diff --git a/src/interface.jl b/src/interface.jl index 09c163af..91f4273f 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -9,6 +9,8 @@ display and callsite introspection features. This interface is still considered experimental at this time; future changes are to be expected, in which case we will do our best to communicate them in CHANGELOG.md. +# Extended help + Cthulhu relies mainly on the following types from Base: - `MethodInstance`, representing a specialization of a method. - `CodeInstance`, the high-level result of the Julia compilation pipeline. @@ -34,6 +36,8 @@ by a package is central enough that it is part of its API. In this case, this pa may define a constructor `Cthulhu.AbstractProvider(interp::SomeInterpreter)` to automatically pick the correct provider just by doing `@descend interp=SomeInterpreter() f(x)`. +## Interface + The interface for `AbstractProvider` requires a few methods to be defined. If it is associated with an `AbstractInterpreter` (see two paragraphs above), then only the following is required: - `run_type_inference(provider::SomeProvider, interp::SomeInterpreter, mi::MethodInstance)` to emit a `CodeInstance` @@ -42,6 +46,8 @@ with an `AbstractInterpreter` (see two paragraphs above), then only the followin - `OptimizedSource(provider::SomeProvider, interp::SomeInterpreter, result::InferenceResult)` (for uncached inference results, e.g. when looking up callsites emanating from concrete evaluation) - `InferredSource(provider::SomeProvider, interp::SomeInterpreter, ci::CodeInstance)` +If `Compiler` is used as a standard library, see the next section about the compiler integration to know +from which module these symbols should be taken. By default, an `AbstractProvider` is not associated with any particular `Compiler.AbstractInterpreter`, with `Cthulhu.get_abstract_interpreter(provider::SomeProvider)` returning `nothing`. In this case, @@ -49,14 +55,89 @@ the following methods are required: - `get_inference_world(provider::SomeProvider)`, returning the world in which the provider operates. Methods and bindings (including e.g. types and globals) defined after this world age should not be taken into account when providing the data Cthulhu needs. - `find_method_instance(provider::SomeProvider, tt::Type{<:Tuple}, world::UInt)`, to return the specialization of the matching method for `tt`, returning a `MethodInstance`. - `generate_code_instance(provider::SomeProvider, mi::MethodInstance)` to emit the `CodeInstance` - that holds compilation results to be later retrieved with `LookupResult`. -- `LookupResult(provider::SomeProvider, src, optimize::Bool)` to assemble most of the information + that holds compilation results to be later retrieved with `lookup`. +- `lookup(provider::SomeProvider, src, optimize::Bool)` to assemble most of the information that Cthulhu needs to display code and to provide callsites to select for further descent. `src` is the generated `CodeInstance` or any override emanating from `get_override(provider::SomeProvider, info::Cthulhu.CallInfo)` (more on that a few paragraphs below). - which by default may return `InferenceResult` (constant propagation) or `SemiConcreteCallInfo` (semi-concrete evaluation). This "override" replaces a `CodeInstance` for callsites - that are semantically associated with one but which have further information available, or for which the compiler decides to not cache one. - `find_caller_of(provider::SomeProvider, callee::Union{MethodInstance,Type}, mi::MethodInstance; allow_unspecialized::Bool=false)` to find occurrences of calls to `callee` in the provided method instance. This is the backbone of [`ascend`](@ref), and is not used for `descend`. +`AbstractProvider`s may additionally extend the following UI-related methods: +- `menu_commands(provider::SomeProvider)` to provide commands for the UI. You will most likely + want to add or remove commands to `default_menu_commands(provider)` to keep all or part of the + built-in commands. +- `is_command_enabled(provider::SomeProvider, state::CthulhuState, command::Command)` to + dynamically enable/disable commands based on e.g. which view we are in. You will most likely want to fall back to + `Cthulhu.is_default_command_enabled(provider, state, command)` to keep the behavior for default commands. +- `show_command(io::IO, provider::SomeProvider, state::CthulhuState, command::Command)` to customize + the display of commands in the terminal UI. The defaults should be good enough for most uses, however. + You may take a look at the source code to know how it interacts with other customizable entry points, + such as `style_for_command_key` and `value_for_command`. + +If you do not intend to provide a method that would be required for a specific UI component (for example, +you do not want to support displaying remarks, effects or exception types), you should extend +`Cthulhu.menu_commands(provider::SomeProvider)` and return a list of commands that excludes the +corresponding command. To that effect, you can use `Cthulhu.default_menu_commands(provider)` +and simply filter out the relevant command. + +If any of the relevant `CthulhuConfig` fields can be set via a UI command (which is the default), these should be implemented as well: +- `get_pc_remarks(provider::SomeProvider, ci::CodeInstance)` (required for the 'remarks' toggle) +- `get_pc_effects(provider::SomeProvider, ci::CodeInstance)` (required for the 'effects' toggle) +- `get_pc_excts(provider::SomeProvider, ci::CodeInstance)` (required for the 'exception types' toggle) +- `get_inlining_costs(provider::SomeProvider, mi::MethodInstance, src::Union{CodeInfo, IRCode})` + (required for the 'inlining costs' toggle) +or the corresponding fallbacks will give a warning by default when called. + +## Compiler integration + +Starting from Julia 1.12, the compiler is now defined as a standard library package. +In particular, Base will always use the Compiler that it was built with, but a user may have a custom +Compiler in their manifest that may be different than the one used in Base. By default (version 0.1.1), +the Compiler stdlib simply reexports the contents of `Base.Compiler`; therefore, usually, even though +`Compiler !== Base.Compiler`, `Compiler.AbstractInterpreter === Base.Compiler.AbstractInterpreter`. + +In the situation where `Compiler.AbstractInterpreter !== Base.Compiler.AbstractInterpreter` (which +can be easily triggered by `dev`ing a local Compiler path that is not github.com/Julialang/BaseCompiler.jl, +for instance), the Compiler integration in Cthulhu becomes a bit more complex. The Compiler extension (which +is virtually a no-op if `Compiler.AbstractInterpreter === Base.Compiler.AbstractInterpreter`) reincludes +`src/CthulhuCompiler.jl`, which in turn includes all compiler-related files under `src/compiler`. +At this point: +- `Cthulhu` contains the types and functions for its high-level interface (`AbstractProvider`, `descend`, + ...) as well as the integration with `Base.Compiler` (`DefaultProvider`, + `CthulhuInterpreter <: Base.Compiler.AbstractInterpreter`, ...). +- The Cthulhu extension, `CthulhuCompilerExt`, imports the high-level interface types and functions from + Cthulhu, and defines another compiler integration, this time with `Compiler` (the stdlib), + defining its own `DefaultProvider`, `CthulhuInterpreter <: Compiler.AbstractInterpreter`, etc. + +High-level types and functions are therefore always defined in `Cthulhu`, but those related to the compiler +integration (such as `InferredSource`, `OptimizedSource`, `run_type_inference`) will require either of `Cthulhu` or `CthulhuCompilerExt`, depending 1. on whether you want to use the Compiler stdlib, and 2. +on whether a Compiler stdlib with different implementations than `Base.Compiler` is actually used. To +automatically pick the correct one for your use case, you are advised to use +`get_module_for_compiler_integration(; use_compiler_stdlib)` (exported by default), which will return the +relevant module. + +For example, you may want to do the following: +```julia +using Cthulhu +const CompilerIntegration = get_module_for_compiler_integration(; use_compiler_stdlib = true) # true or false +import .CompilerIntegration: OptimizedSource, InferredSource, run_type_inference +using .CompilerIntegration: CthulhuInterpreter, LookupResult, get_effects, ir_to_src +``` + +Usually, a package defining its own `AbstractProvider` based on a `Compiler` implementation will choose +either of `Base.Compiler` or `Compiler`. +However, if, for some reason, your package wants to integrate with both compiler implementations, there is +nothing stopping you from duplicating your compiler integration, e.g. by having a single source file +included twice but in one context with +`const CompilerIntegration = get_module_for_compiler_integration(; use_compiler_stdlib = false)` +and in another with +`const CompilerIntegration = get_module_for_compiler_integration(; use_compiler_stdlib = true)`. +Note that if the latter returns `Cthulhu`, the integration shouldn't be duplicated, since it is implied that +`Compiler.AbstractInterpreter === Base.Compiler.AbstractInterpreter`. + +You may look at the tested implementations under `Cthulhu/test/providers` for inspiration. + +## Callsite information + Callsites are retrieved by inspecting `Compiler.CallInfo` data structures (which are currently distinct from `Cthulhu.CallInfo`, see the warning below), which are metadata semantically attached to expressions, relating one to the process that produced them. In most cases, expressions semantically related to a call to another function will have a `Compiler.CallInfo` attached @@ -71,7 +152,7 @@ Generally, this will be a `CodeInstance`, but it may be overriden with another s By default, the override will be either a `Compiler.SemiConcreteCallInfo` for semi-concrete evaluation, or a `Compiler.InferenceResult` for the results of constant propagation. -!!! warn +!!! warning `Compiler.CallInfo` and `Cthulhu.CallInfo` are currently distinct abstract types. This part of Cthulhu will likely be subject to change, and is not yet publicly available for customization by `AbstractProvider`s beyond its downstream use via `get_override`. @@ -83,37 +164,12 @@ Optionally, `AbstractProvider`s may modify default behavior by extending the fol - `get_override(provider::SomeProvider, info::Cthulhu.CallInfo)`: return a data structure that is more accurate than the `CodeInstance` associated with a given `Cthulhu.CallInfo`. -If any of the relevant `CthulhuConfig` fields may be set, these should be implemented as well -or will give a warning by default when called: -- `get_pc_remarks(provider::SomeProvider, ci::CodeInstance)` (required for the 'remarks' toggle) -- `get_pc_effects(provider::SomeProvider, ci::CodeInstance)` (required for the 'effects' toggle) -- `get_pc_excts(provider::SomeProvider, ci::CodeInstance)` (required for the 'exception types' toggle) -- `get_inlining_costs(provider::SomeProvider, mi::MethodInstance, src::Union{CodeInfo, IRCode})` - (required for the 'inlining costs' toggle) - -`AbstractProvider`s may additionally extend the following UI-related methods: -- `menu_commands(provider::SomeProvider)` to provide commands for the UI. You will most likely - want to add or remove commands to `default_menu_commands(provider)` to keep all or part of the - built-in commands. -- `is_command_enabled(provider::SomeProvider, state::CthulhuState, command::Command)` to - dynamically enable/disable commands based on e.g. which view we are in. You will most likely want to fall back to - `Cthulhu.is_default_command_enabled(provider, state, command)` to keep the behavior for default commands. -- `show_command(io::IO, provider::SomeProvider, state::CthulhuState, command::Command)` to customize - the display of commands in the terminal UI. The defaults should be good enough for most uses, however. - You may take a look at the source code to know how it interacts with other customizable entry points, - such as `style_for_command_key` and `value_for_command`. - -If you do not intend to provide a method that would be required for a specific UI component (for example, -you do not want to support displaying remarks, effects or exception types), you should extend -`Cthulhu.menu_commands(provider::SomeProvider)` and return a list of commands that excludes the -corresponding command. To that effect, you can use `Cthulhu.default_menu_commands(provider)` -and simply filter out the relevant command. """ abstract type AbstractProvider end get_abstract_interpreter(provider::AbstractProvider) = nothing -function CC.get_inference_world(provider::AbstractProvider) +function get_inference_world(provider::AbstractProvider) interp = get_abstract_interpreter(provider) interp !== nothing && return get_inference_world(interp) error(lazy"Not implemented for $provider") @@ -140,6 +196,17 @@ function generate_code_instance(provider::AbstractProvider, mi::MethodInstance) error(lazy"Not implemented for $provider") end +get_override(provider::AbstractProvider, @nospecialize(info)) = nothing + +function lookup(provider::AbstractProvider, src, optimize::Bool) + interp = get_abstract_interpreter(provider) + interp !== nothing && return lookup(provider, interp, src, optimize) + error(lazy""" + missing `$AbstractProvider` API: + `$(typeof(provider))` is required to implement the `$lookup(provider::$(typeof(provider)), src::$(typeof(src)), optimize::Bool)` interface. + """) +end + get_pc_remarks(provider::AbstractProvider, ci::CodeInstance) = @warn "Remarks could not be retrieved for $ci with provider type $(typeof(provider))" get_pc_effects(provider::AbstractProvider, ci::CodeInstance) = @@ -153,7 +220,7 @@ function find_caller_of(provider::AbstractProvider, callee::Union{MethodInstance error(lazy"Not implemented for $provider") end -function get_inlining_costs(provider::AbstractProvider, mi::MethodInstance, src::Union{CodeInfo, IRCode}) +function get_inlining_costs(provider::AbstractProvider, mi::MethodInstance, src#=::Union{CodeInfo, CC.IRCode}=#) interp = get_abstract_interpreter(provider) interp !== nothing && return get_inlining_costs(provider, interp, mi, src) return nothing @@ -164,49 +231,3 @@ function show_parameters(io::IO, provider::AbstractProvider) interp !== nothing && return show_parameters(io, provider, interp) @warn "Not implemented for provider type $(typeof(provider))" end - -function get_override(provider::AbstractProvider, @nospecialize(info)) - isa(info, ConstPropCallInfo) && return info.result - isa(info, SemiConcreteCallInfo) && return info - isa(info, OCCallInfo) && return get_override(provider, info.ci) - return nothing -end - -struct LookupResult - ir::Union{IRCode, Nothing} # used over `src` for callsite detection and printing - src::Union{CodeInfo, Nothing} # may be required (e.g. for LLVM and native module dumps) - rt - exct - infos::Vector{Any} - slottypes::Vector{Any} - effects::Effects - optimized::Bool - function LookupResult(ir, src, @nospecialize(rt), @nospecialize(exct), - infos, slottypes, effects, optimized) - ninfos = length(infos) - if src === nothing && ir === nothing - throw(ArgumentError("At least one of `src` or `ir` must have a value")) - end - if isa(ir, IRCode) && ninfos ≠ length(ir.stmts) - throw(ArgumentError("`ir` and `infos` are inconsistent with $(length(ir.stmts)) IR statements and $ninfos call infos")) - end - if isa(src, CodeInfo) - if !isa(src.ssavaluetypes, Vector{Any}) - throw(ArgumentError("`src.ssavaluetypes::$(typeof(src.ssavaluetypes))` must be a Vector{Any}")) - end - if !isa(ir, IRCode) && ninfos ≠ length(src.code) - throw(ArgumentError("`src` and `infos` are inconsistent with $(length(src.code)) code statements and $ninfos call infos")) - end - end - return new(ir, src, rt, exct, infos, slottypes, effects, optimized) - end -end - -function LookupResult(provider::AbstractProvider, src, optimize::Bool) - interp = get_abstract_interpreter(provider) - interp !== nothing && return LookupResult(provider, interp, src, optimize) - error(lazy""" - missing `$AbstractProvider` API: - `$(typeof(provider))` is required to implement the `$LookupResult(provider::$(typeof(provider)), src, optimize::Bool)` interface. - """) -end diff --git a/src/state.jl b/src/state.jl index 1dcd2f5d..93c8b7cb 100644 --- a/src/state.jl +++ b/src/state.jl @@ -18,6 +18,12 @@ function default_terminal() return term end +function cthulhu_source end +function cthulhu_typed end +function cthulhu_ast end +function cthulhu_llvm end +function cthulhu_native end + view_function(state::CthulhuState) = view_function(state.provider, state.config.view) function view_function(provider::AbstractProvider, view::Symbol) view === :source && return cthulhu_source @@ -154,11 +160,7 @@ end function bookmark_method(state::CthulhuState) push!(BOOKMARKS, Bookmark(state.provider, state.ci; state.config)) - mod = resolve_module(state.provider) - @info "The current `descend` state was saved for later use. You may access it with `$mod.BOOKMARKS[end]`." - if nameof(mod) === :CthulhuCompilerExt - @info "You can get the `CthulhuCompilerExt` module with `Base.get_extension(Cthulhu, :CthulhuCompilerExt)`" - end + @info "The current `descend` state was saved for later use. You may access it with `Cthulhu.BOOKMARKS[end]`." end function dump_parameters(state::CthulhuState) diff --git a/test/TestCodeViewSandbox.jl b/test/TestCodeViewSandbox.jl deleted file mode 100644 index e69de29b..00000000 diff --git a/test/provider_utils.jl b/test/provider_utils.jl index afe0ad16..35b83dd9 100644 --- a/test/provider_utils.jl +++ b/test/provider_utils.jl @@ -1,7 +1,8 @@ using Core.IR using Test -using .Cthulhu: CC, DefaultProvider, get_inference_world, find_method_instance, generate_code_instance, get_ci, get_override, LookupResult, find_callsites, Command, menu_commands, is_command_enabled, show_command, CthulhuState, PC2Effects, get_pc_effects, PC2Remarks, get_pc_remarks, PC2Excts, get_pc_excts, get_inlining_costs, show_parameters -using Cthulhu.Testing: VirtualTerminal, TestHarness, @run, wait_for, read_next, end_terminal_session, KEYS +using Cthulhu: descend, get_inference_world, find_method_instance, generate_code_instance, lookup, get_ci, get_override, find_callsites, Command, menu_commands, is_command_enabled, show_command, CthulhuState, get_pc_effects, get_pc_remarks, get_pc_excts, get_inlining_costs, show_parameters +using .CompilerIntegration: DefaultProvider, PC2Effects, PC2Remarks, PC2Excts, LookupResult +using Cthulhu.Testing: VirtualTerminal, TestHarness, @run, wait_for, read_next, end_terminal_session using Logging: with_logger, NullLogger function test_provider_api(provider, args...) @@ -11,9 +12,9 @@ function test_provider_api(provider, args...) @test isa(mi, MethodInstance) ci = generate_code_instance(provider, mi) @test isa(ci, CodeInstance) - result = LookupResult(provider, ci, false) + result = lookup(provider, ci, false) @test isa(result, LookupResult) - result = LookupResult(provider, ci, true) + result = lookup(provider, ci, true) @test isa(result, LookupResult) commands = menu_commands(provider) @@ -43,7 +44,7 @@ function test_provider_api(provider, args...) @test inlining_costs === nothing || isa(inlining_costs, Vector{Int}) end - result = LookupResult(provider, ci, false) + result = lookup(provider, ci, false) callsites, _ = find_callsites(provider, result, ci) @test length(callsites) ≥ ifelse(result.optimized, 1, 5) for callsite in callsites @@ -51,9 +52,9 @@ function test_provider_api(provider, args...) ci === nothing && continue override = get_override(provider, callsite.info) src = something(override, ci) - result = LookupResult(provider, src, false) + result = lookup(provider, src, false) @test isa(result, LookupResult) - result = LookupResult(provider, src, true) + result = lookup(provider, src, true) @test isa(result, LookupResult) end end @@ -75,10 +76,10 @@ function test_descend_for_provider(provider, args...; show = false) write(terminal, 'o') # optimize: on write(terminal, 'i') # inlining costs: on write(terminal, 'o') # optimize: off - write(terminal, KEYS[:enter]) + write(terminal, :enter) write(terminal, 'S') - write(terminal, KEYS[:up]) - write(terminal, KEYS[:enter]) + write(terminal, :up) + write(terminal, :enter) write(terminal, 'q') if show wait_for(harness.task) diff --git a/test/providers/CountingProviderModule.jl b/test/providers/CountingProviderModule.jl index 1e437528..f498eafe 100644 --- a/test/providers/CountingProviderModule.jl +++ b/test/providers/CountingProviderModule.jl @@ -1,10 +1,13 @@ module CountingProviderModule using Core.IR -import ..Cthulhu -using ..Cthulhu: CC, AbstractProvider, DefaultProvider, CthulhuInterpreter, generate_code_instance, Command, default_menu_commands, OptimizedSource, InferredSource, run_type_inference + +import ..CompilerIntegration +using ..CompilerIntegration: CC, DefaultProvider, CthulhuInterpreter, OptimizedSource, InferredSource, run_type_inference using .CC: InferenceResult +using Cthulhu: Cthulhu, AbstractProvider, generate_code_instance, Command, default_menu_commands + mutable struct CountingProvider <: AbstractProvider default::DefaultProvider count::Int @@ -32,13 +35,13 @@ function modify_count(key, name, value) Command(state -> state.provider.count += value, key, name, string(name), :actions) end -Cthulhu.run_type_inference(provider::CountingProvider, interp::CthulhuInterpreter, mi::MethodInstance) = +CompilerIntegration.run_type_inference(provider::CountingProvider, interp::CthulhuInterpreter, mi::MethodInstance) = run_type_inference(provider.default, interp, mi) -Cthulhu.OptimizedSource(provider::CountingProvider, interp::CthulhuInterpreter, ci::CodeInstance) = +CompilerIntegration.OptimizedSource(provider::CountingProvider, interp::CthulhuInterpreter, ci::CodeInstance) = OptimizedSource(provider.default, interp, ci) -Cthulhu.OptimizedSource(provider::CountingProvider, interp::CthulhuInterpreter, result::InferenceResult) = +CompilerIntegration.OptimizedSource(provider::CountingProvider, interp::CthulhuInterpreter, result::InferenceResult) = OptimizedSource(provider.default, interp, result) -Cthulhu.InferredSource(provider::CountingProvider, interp::CthulhuInterpreter, ci::CodeInstance) = +CompilerIntegration.InferredSource(provider::CountingProvider, interp::CthulhuInterpreter, ci::CodeInstance) = InferredSource(provider.default, interp, ci) export CountingProvider diff --git a/test/providers/ExternalProviderModule.jl b/test/providers/ExternalProviderModule.jl index 91cec392..7df5a2cc 100644 --- a/test/providers/ExternalProviderModule.jl +++ b/test/providers/ExternalProviderModule.jl @@ -1,10 +1,13 @@ module ExternalProviderModule using Core.IR -import ..Cthulhu -using ..Cthulhu: CC, AbstractProvider, generate_code_instance, Command, default_menu_commands, OptimizedSource, InferredSource, run_type_inference, LookupResult + +import ..CompilerIntegration +using ..CompilerIntegration: CC, ir_to_src, LookupResult, get_effects using .CC: InferenceResult, AbstractInterpreter, NativeInterpreter +using Cthulhu: Cthulhu, AbstractProvider, generate_code_instance, Command, default_menu_commands, cached_return_type, cached_exception_type + mutable struct ExternalOwner end struct InferredIR @@ -74,7 +77,10 @@ mutable struct ExternalProvider <: AbstractProvider ExternalProvider() = new(ExternalInterpreter()) end -CC.get_inference_world(provider::ExternalProvider) = CC.get_inference_world(provider.interp) +# Technically, in that case we'd just extend `get_abstract_interpreter`, +# but the point here is to satisfy the interface without it for testing. + +Cthulhu.get_inference_world(provider::ExternalProvider) = CC.get_inference_world(provider.interp) Cthulhu.find_method_instance(provider::ExternalProvider, @nospecialize(tt::Type{<:Tuple}), world::UInt) = Cthulhu.find_method_instance(provider, provider.interp, tt, world) @@ -85,21 +91,23 @@ function Cthulhu.generate_code_instance(provider::ExternalProvider, mi::MethodIn return ci end -function Cthulhu.LookupResult(provider::ExternalProvider, ci::CodeInstance, optimize::Bool #= ignored =#) +function Cthulhu.lookup(provider::ExternalProvider, ci::CodeInstance, optimize::Bool #= ignored =#) @assert isa(ci.inferred, InferredIR) ir = copy(ci.inferred.ir) - src = Cthulhu.ir_to_src(ir) + src = ir_to_src(ir) src.ssavaluetypes = copy(ir.stmts.type) src.min_world = @atomic ci.min_world src.max_world = @atomic ci.max_world - rt = Cthulhu.cached_return_type(ci) - exct = Cthulhu.cached_exception_type(ci) - effects = Cthulhu.get_effects(ci) + rt = cached_return_type(ci) + exct = cached_exception_type(ci) + effects = get_effects(ci) infos = copy(ir.stmts.info) return LookupResult(ir, src, rt, exct, infos, src.slottypes, effects, true #= optimized =#) end -Cthulhu.get_override(provider::ExternalProvider, @nospecialize(info)) = nothing +get_override(provider::ExternalProvider, info::Cthulhu.ConstPropCallInfo) = nothing +get_override(provider::ExternalProvider, info::Cthulhu.SemiConcreteCallInfo) = nothing +get_override(provider::ExternalProvider, info::Cthulhu.OCCallInfo) = nothing function Cthulhu.menu_commands(provider::ExternalProvider) commands = default_menu_commands(provider) diff --git a/test/providers/OverlayProviderModule.jl b/test/providers/OverlayProviderModule.jl index 09cf4138..52d5ceb3 100644 --- a/test/providers/OverlayProviderModule.jl +++ b/test/providers/OverlayProviderModule.jl @@ -1,9 +1,12 @@ module OverlayProviderModule # inspired by the Cthulhu integration at serenity4/SPIRV.jl/ext/SPIRVCthulhuExt.jl using Core.IR -import ..Cthulhu -using ..Cthulhu: CC, AbstractProvider, CthulhuInterpreter, generate_code_instance, Command, default_menu_commands, OptimizedSource, InferredSource, run_type_inference -using .CC: OverlayMethodTable, AbstractInterpreter, InferenceResult, InferenceParams, OptimizationParams + +import ..CompilerIntegration +using ..CompilerIntegration: CC, CthulhuInterpreter, OptimizedSource, InferredSource, run_type_inference +using .CC: InferenceResult, OverlayMethodTable, AbstractInterpreter, InferenceResult, InferenceParams, OptimizationParams + +using Cthulhu: Cthulhu, AbstractProvider ### Interpreter @@ -69,13 +72,13 @@ Cthulhu.get_abstract_interpreter(provider::OverlayProvider) = provider.interp # This only works so long as we don't rely on overriding compilation methods for `OverlayInterpreter` # (besides those from the `AbstractInterpreter` interface that are forwarded by `CthulhuInterpreter`). -Cthulhu.run_type_inference(provider::OverlayProvider, interp::OverlayInterpreter, mi::MethodInstance) = +CompilerIntegration.run_type_inference(provider::OverlayProvider, interp::OverlayInterpreter, mi::MethodInstance) = run_type_inference(provider, provider.cthulhu, mi) -Cthulhu.OptimizedSource(provider::OverlayProvider, interp::OverlayInterpreter, ci::CodeInstance) = +CompilerIntegration.OptimizedSource(provider::OverlayProvider, interp::OverlayInterpreter, ci::CodeInstance) = OptimizedSource(provider, provider.cthulhu, ci) -Cthulhu.OptimizedSource(provider::OverlayProvider, interp::OverlayInterpreter, result::InferenceResult) = +CompilerIntegration.OptimizedSource(provider::OverlayProvider, interp::OverlayInterpreter, result::InferenceResult) = OptimizedSource(provider, provider.cthulhu, result) -Cthulhu.InferredSource(provider::OverlayProvider, interp::OverlayInterpreter, ci::CodeInstance) = +CompilerIntegration.InferredSource(provider::OverlayProvider, interp::OverlayInterpreter, ci::CodeInstance) = InferredSource(provider, provider.cthulhu, ci) end diff --git a/test/runtests.jl b/test/runtests.jl index c30c984b..5df5b489 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -15,7 +15,7 @@ end @testset "Terminal tests" include("test_terminal.jl") @testset "AbstractInterpreter" include("test_AbstractInterpreter.jl") after = is_compiler_extension_loaded() - @assert before === after # make sure we don't mess up the test setup by loading Compiler durin tests + @assert before === after # make sure we don't mess up the test setup by loading Compiler during tests if !is_compiler_extension_loaded() @eval import Compiler # trigger the extension if is_compiler_extension_loaded() # allow extension to be disabled locally during development diff --git a/test/setup.jl b/test/setup.jl index 119711f0..05589f9a 100644 --- a/test/setup.jl +++ b/test/setup.jl @@ -1,5 +1,7 @@ using Test, InteractiveUtils -using .Cthulhu: AbstractProvider, CthulhuConfig, CthulhuState, find_method_instance, generate_code_instance, LookupResult +using Cthulhu: Cthulhu, AbstractProvider, CthulhuConfig, CONFIG, set_config, set_config!, CthulhuState, find_method_instance, generate_code_instance, lookup, find_callsites, get_effects, Callsite, get_mi, get_module_for_compiler_integration, is_compiler_loaded +const CompilerIntegration = get_module_for_compiler_integration(use_compiler_stdlib = is_compiler_loaded()) +using .CompilerIntegration: CC, DefaultProvider, Effects if isdefined(parentmodule(@__MODULE__), :VSCodeServer) using ..VSCodeServer end @@ -7,18 +9,18 @@ end # InteractiveUtils.@activate Compiler # use the Compiler.jl stdlib for the Base reflections too function cthulhu_info(@nospecialize(f), @nospecialize(tt=()); - optimize=true, interp=Cthulhu.CC.NativeInterpreter()) + optimize=true, interp=CC.NativeInterpreter()) provider = AbstractProvider(interp) mi = find_method_instance(provider, f, tt) ci = generate_code_instance(provider, mi) - result = LookupResult(provider, ci, optimize) + result = lookup(provider, ci, optimize) return provider, mi, ci, result end function find_callsites_by_ftt(@nospecialize(f), @nospecialize(TT=Tuple{}); optimize=true) provider, mi, ci, result = cthulhu_info(f, TT; optimize) - callsites, _ = Cthulhu.find_callsites(provider, result, ci) - @test all(c -> Cthulhu.get_effects(c) isa Cthulhu.Effects, callsites) + callsites, _ = find_callsites(provider, result, ci) + @test all(c -> get_effects(c) isa Effects, callsites) return callsites end @@ -26,5 +28,4 @@ macro find_callsites_by_ftt(ex0...) return InteractiveUtils.gen_call_with_extracted_types_and_kwargs(__module__, :find_callsites_by_ftt, ex0) end -get_mi(callsite::Cthulhu.Callsite) = Cthulhu.get_ci(callsite).def -get_method(callsite::Cthulhu.Callsite) = get_mi(callsite).def +get_method(callsite::Callsite) = get_mi(callsite).def diff --git a/test/test_Cthulhu.jl b/test/test_Cthulhu.jl index daba71a6..8505772a 100644 --- a/test/test_Cthulhu.jl +++ b/test/test_Cthulhu.jl @@ -3,13 +3,11 @@ module test_Cthulhu using Test, StaticArrays, Random using Core: Const -using Cthulhu: Cthulhu as _Cthulhu, callstring -const Cthulhu = _Cthulhu.CTHULHU_MODULE[] -using .Cthulhu: CC, DefaultProvider, CthulhuConfig, CONFIG, set_config, set_config! - include("setup.jl") include("irutils.jl") +using Cthulhu: callstring + # NOTE: From Julia version `v"1.12.0-DEV.1581"` onwards, Cthulhu uses the Compiler.jl stdlib. # Therefore, for queries on its data structures, use the utilities from `Compiler === Cthulhu.CC` # instead of those from `Base.Compiler === Core.Compiler`. diff --git a/test/test_codeview.jl b/test/test_codeview.jl index 9a025fd4..9655c7c7 100644 --- a/test/test_codeview.jl +++ b/test/test_codeview.jl @@ -3,9 +3,7 @@ module test_codeview using Test using Logging: NullLogger, with_logger -using Cthulhu: Cthulhu as _Cthulhu, is_compiler_loaded -const Cthulhu = _Cthulhu.CTHULHU_MODULE[] -using .Cthulhu: CthulhuState, view_function, CONFIG, set_config, cthulhu_typed +using Cthulhu: Cthulhu, is_compiler_loaded, CthulhuState, view_function, CONFIG, set_config, cthulhu_typed include("setup.jl") diff --git a/test/test_provider.jl b/test/test_provider.jl index 181b0ae4..80af7768 100644 --- a/test/test_provider.jl +++ b/test/test_provider.jl @@ -2,12 +2,11 @@ module test_provider using Test using Core.IR -using Cthulhu: CTHULHU_MODULE @eval module Impl -const Cthulhu = $(CTHULHU_MODULE[]) -using Cthulhu: descend -using .Cthulhu: DefaultProvider +using Cthulhu: Cthulhu, is_compiler_loaded, get_module_for_compiler_integration +const CompilerIntegration = get_module_for_compiler_integration(; use_compiler_stdlib = is_compiler_loaded()) +using .CompilerIntegration: DefaultProvider include("provider_utils.jl") include("providers/CountingProviderModule.jl") using .CountingProviderModule: CountingProvider diff --git a/test/test_terminal.jl b/test/test_terminal.jl index 2f41e3a7..68c617b2 100644 --- a/test/test_terminal.jl +++ b/test/test_terminal.jl @@ -4,8 +4,8 @@ using Core: Const using Test, REPL, LinearAlgebra using Cthulhu.Testing using Cthulhu.Testing: @run -using Cthulhu: Cthulhu as _Cthulhu, is_compiler_loaded, descend, descend_code_warntype, @descend, ascend -const Cthulhu = _Cthulhu.CTHULHU_MODULE[] +using Cthulhu: Cthulhu, get_module_for_compiler_integration, is_compiler_loaded, descend, descend_code_warntype, @descend, ascend +const CompilerIntegration = get_module_for_compiler_integration(use_compiler_stdlib = is_compiler_loaded()) if isdefined(parentmodule(@__MODULE__), :VSCodeServer) using ..VSCodeServer @@ -236,7 +236,7 @@ end @test end_terminal_session(harness) # descend with MethodInstances - mi = _Cthulhu.get_specialization(Definitions.callfmulti, Tuple{typeof(Ref{Any}(1))}) + mi = Cthulhu.get_specialization(Definitions.callfmulti, Tuple{typeof(Ref{Any}(1))}) terminal = VirtualTerminal() harness = @run terminal descend(mi; view=:typed, optimize=false, terminal) @@ -308,7 +308,7 @@ end @inline inner2(x) = 2*inner3(x) inner1(x) = -1*inner2(x) inner1(0x0123) - mi = _Cthulhu.get_specialization(inner3, Tuple{UInt16}) + mi = Cthulhu.get_specialization(inner3, Tuple{UInt16}) terminal = VirtualTerminal() harness = @run terminal ascend(terminal, mi; pagesize=11)