From cf25cdef16426f831e6f34c6322c83ad7476545e Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Wed, 19 Feb 2025 19:32:00 +0900 Subject: [PATCH 1/2] make Cthulhu use Compiler.jl as stdlib optionally when it's loaded Using the Compiler stdlib does make things a bit tricky because we have to consider Compiler.jl's compatibility not just with the package itself, but also with the runtime system. In the past, we only had to worry about the former, so issues didn't come up as often (since using `Core.Compiler` guarantees that the compiler is compatible with the runtime system always). This commit changes Cthulhu back to using `Base.Compiler` until the user specifically loads the `Compiler.jl` standard library. This should reduce how often Cthulhu breaks (in normal use). When an user loads Compiler explicitly, then Cthulhu now load the CthulhuCompilerExt extension module, and wraps the implementations of exported APIs. --- Project.toml | 7 +- ext/CthulhuCompilerExt.jl | 15 + src/Cthulhu.jl | 896 +++----------------------------------- src/CthulhuBase.jl | 786 +++++++++++++++++++++++++++++++++ 4 files changed, 873 insertions(+), 831 deletions(-) create mode 100644 ext/CthulhuCompilerExt.jl create mode 100644 src/CthulhuBase.jl diff --git a/Project.toml b/Project.toml index 2f60d40a..6a169372 100644 --- a/Project.toml +++ b/Project.toml @@ -5,7 +5,6 @@ authors = ["Valentin Churavy and contributors"] [deps] CodeTracking = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2" -Compiler = "807dbc54-b67e-4c79-8afb-eafe4df6f2e1" FoldingTrees = "1eca21be-9b9b-4ed8-839a-6d8ae26b1781" InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" JuliaSyntax = "70703baa-626e-46a2-a12c-08ffd08c73b4" @@ -17,6 +16,12 @@ UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" Unicode = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" WidthLimitedIO = "b8c1c048-cf81-46c6-9da0-18c1d99e41f2" +[weakdeps] +Compiler = "807dbc54-b67e-4c79-8afb-eafe4df6f2e1" + +[extensions] +CthulhuCompilerExt = "Compiler" + [compat] CodeTracking = "0.5, 1" Compiler = "0.0.3" diff --git a/ext/CthulhuCompilerExt.jl b/ext/CthulhuCompilerExt.jl new file mode 100644 index 00000000..00cd63e6 --- /dev/null +++ b/ext/CthulhuCompilerExt.jl @@ -0,0 +1,15 @@ +module CthulhuCompilerExt + +@static if VERSION ≥ v"1.12.0-DEV.1581" + using Compiler: Compiler as CC + using Compiler.IRShow: IRShow + using Cthulhu: Cthulhu + + function __init__() + Cthulhu.CTHULHU_MODULE[] = @__MODULE__ + end + + include("../src/CthulhuBase.jl") +end + +end diff --git a/src/Cthulhu.jl b/src/Cthulhu.jl index 6b0f3246..e7b3e11d 100644 --- a/src/Cthulhu.jl +++ b/src/Cthulhu.jl @@ -1,118 +1,41 @@ module Cthulhu -Base.Experimental.@compiler_options compile=min optimize=1 - -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: MethodInstance, MethodMatch +export @descend, @descend_code_typed, @descend_code_warntype, + descend, descend_code_typed, descend_code_warntype, ascend + @static if VERSION ≥ v"1.12.0-DEV.1581" - using Compiler: Compiler as CC - using Compiler.IRShow: IRShow + const CC = Base.Compiler + const IRShow = Base.IRShow else const CC = Core.Compiler const IRShow = Base.IRShow end -using Core.IR -using .CC: AbstractInterpreter, ApplyCallInfo, CallInfo as CCCallInfo, ConstCallInfo, - EFFECTS_TOTAL, Effects, IncrementalCompact, InferenceParams, InferenceResult, - InferenceState, IRCode, LimitedAccuracy, MethodMatchInfo, MethodResultPure, - NativeInterpreter, NoCallInfo, OptimizationParams, OptimizationState, - UnionSplitApplyCallInfo, UnionSplitInfo, WorldRange, WorldView, - argextype, argtypes_to_type, compileable_specialization, ignorelimited, singleton_type, - specialize_method, sptypes_from_meth_instance, widenconst -using Base: @constprop, default_tt, isvarargtype, unwrapva, unwrap_unionall, rewrap_unionall -const mapany = Base.mapany - -const ArgTypes = Vector{Any} - -@static if VERSION ≥ v"1.11.0-DEV.1498" - import .CC: get_inference_world - using Base: get_world_counter -else - import .CC: get_world_counter, get_world_counter as get_inference_world -end - -Base.@kwdef mutable struct CthulhuConfig - enable_highlighter::Bool = false - highlighter::Cmd = `pygmentize -l` - asm_syntax::Symbol = :att - dead_code_elimination::Bool = true - pretty_ast::Bool = false - interruptexc::Bool = true - debuginfo::Symbol = :compact - optimize::Bool = true - iswarn::Bool = false - hide_type_stable::Bool = false - remarks::Bool = false - with_effects::Bool = false - exception_type::Bool = false - inline_cost::Bool = false - type_annotations::Bool = true - annotate_source::Bool = true # overrides optimize, although the current setting is preserved - inlay_types_vscode::Bool = true - diagnostics_vscode::Bool = true - jump_always::Bool = false -end -""" - Cthulhu.CONFIG - -# Options -- `enable_highlighter::Bool`: Use command line `highlighter` to syntax highlight - Julia, LLVM and native code. -- `highlighter::Cmd`: A command line program that receives "julia" as an argument and julia - code as stdin. Defaults to `$(CthulhuConfig().highlighter)`. -- `asm_syntax::Symbol`: Set the syntax of assembly code being used. - Defaults to `$(CthulhuConfig().asm_syntax)`. -- `dead_code_elimination::Bool`: Enable dead-code elimination for high-level Julia IR. - Defaults to `true`. DCE is known to be buggy and you may want to disable it if you - encounter errors. Please report such bugs with a MWE to Julia or Cthulhu. -- `pretty_ast::Bool`: Use a pretty printer for the ast dump. Defaults to `false`. -- `interruptexc::Bool`: Use -key to quit or ascend. Defaults to `false`. -- `debuginfo::Symbol`: Initial state of "debuginfo" toggle. Defaults to `:compact`. - Options:. `:none`, `:compact`, `:source` -- `optimize::Bool`: Initial state of "optimize" toggle. Defaults to `true`. -- `hide_type_stable::Bool`: Initial state of "hide_type_stable" toggle. Defaults to `false`. -- `iswarn::Bool`: Initial state of "warn" toggle. Defaults to `false`. -- `remarks::Bool` Initial state of "remarks" toggle. Defaults to `false`. -- `with_effects::Bool` Intial state of "effects" toggle. Defaults to `false`. -- `exception_type::Bool` `Intial state of "exception type" toggle. Defaults to `false`. -- `inline_cost::Bool` Initial state of "inlining costs" toggle. Defaults to `false`. -- `type_annotations::Bool` Initial state of "type annnotations" toggle. Defaults to `true`. -- `annotate_source::Bool` Initial state of "Source". Defaults to `true`. -- `inlay_types_vscode::Bool` Initial state of "vscode: inlay types" toggle. Defaults to `true` -- `diagnostics_vscode::Bool` Initial state of "Vscode: diagnostics" toggle. Defaults to `true` -- `jump_always::Bool` Initial state of "jump to source always" toggle. Defaults to `false`. -""" -const CONFIG = CthulhuConfig() +const CTHULHU_MODULE = Ref{Module}(@__MODULE__) -using Preferences -include("preferences.jl") __init__() = read_config!(CONFIG) -module DInfo - @enum DebugInfo none compact source -end -using .DInfo: DebugInfo -const AnyDebugInfo = Union{DebugInfo,Symbol} +include("CthulhuBase.jl") + +""" + @descend -include("interpreter.jl") -include("callsite.jl") -include("interface.jl") -include("reflection.jl") -include("ui.jl") -include("codeview.jl") -include("backedges.jl") +Evaluates the arguments to the function or macro call, determines their +types, and calls [`descend`](@ref) on the resulting expression. +See [`Cthulhu.CONFIG`](@ref) for options and their defaults. -export descend, @descend, descend_code_typed, descend_code_warntype, @descend_code_typed, @descend_code_warntype -export ascend +# Examples +```julia +julia> @descend sin(1) +[...] + +julia> @descend iswarn=false foo() +[...] +``` +""" +macro descend(ex0...) + InteractiveUtils.gen_call_with_extracted_types_and_kwargs(__module__, :descend, ex0) +end """ @descend_code_typed @@ -134,15 +57,6 @@ macro descend_code_typed(ex0...) InteractiveUtils.gen_call_with_extracted_types_and_kwargs(__module__, :descend_code_typed, ex0) end -""" - @interp - -For debugging. Returns a CthulhuInterpreter from the appropriate entrypoint. -""" -macro interp(ex0...) - InteractiveUtils.gen_call_with_extracted_types_and_kwargs(__module__, :mkinterp, ex0) -end - """ @descend_code_warntype @@ -170,23 +84,36 @@ macro descend_code_warntype(ex0...) end """ - @descend + descend(f, argtypes=Tuple{...}; kwargs...) + descend(tt::Type{<:Tuple}; kwargs...) + descend(Cthulhu.BOOKMARKS[i]) + descend(mi::MethodInstance; kwargs...) -Evaluates the arguments to the function or macro call, determines their -types, and calls [`descend`](@ref) on the resulting expression. -See [`Cthulhu.CONFIG`](@ref) for options and their defaults. +Given a function and a tuple-type, interactively explore the source code of functions +annotated with inferred types by descending into `invoke` statements. Type enter to select an +`invoke` to descend into, select `↩` to ascend, and press `q` or `control-c` to quit. +See [`Cthulhu.CONFIG`](@ref) for `kwargs` and their defaults. -# Examples +# Usage: ```julia -julia> @descend sin(1) +julia> descend(sin, (Int,)) [...] -julia> @descend iswarn=false foo() +julia> descend(sin, Tuple{Int}) +[...] + +julia> descend(Tuple{typeof(sin), Int}) +[...] + +julia> descend() do + T = rand() > 0.5 ? Int64 : Float64 + sin(rand(T)) + end [...] ``` """ -macro descend(ex0...) - InteractiveUtils.gen_call_with_extracted_types_and_kwargs(__module__, :descend, ex0) +function descend(@nospecialize(args...); @nospecialize(kwargs...)) + CTHULHU_MODULE[].descend_impl(args...; kwargs...) end """ @@ -218,8 +145,9 @@ julia> descend_code_typed() do [...] ``` """ -descend_code_typed(@nospecialize(args...); kwargs...) = - _descend_with_error_handling(args...; annotate_source=false, iswarn=false, kwargs...) +function descend_code_typed(@nospecialize(args...); kwargs...) + CTHULHU_MODULE[].descend_code_typed(args...; kwargs...) +end """ descend_code_warntype(f, argtypes=Tuple{...}; kwargs...) @@ -250,709 +178,10 @@ julia> descend_code_warntype() do [...] ``` """ -descend_code_warntype(@nospecialize(args...); kwargs...) = - _descend_with_error_handling(args...; annotate_source=false, iswarn=true, optimize=false, kwargs...) - -function _descend_with_error_handling(@nospecialize(f), @nospecialize(argtypes = default_tt(f)); kwargs...) - ft = Core.Typeof(f) - if isa(argtypes, Type) - u = unwrap_unionall(argtypes) - tt = rewrap_unionall(Tuple{ft, u.parameters...}, argtypes) - else - tt = Tuple{ft, argtypes...} - end - __descend_with_error_handling(tt; kwargs...) -end -_descend_with_error_handling(mi::MethodInstance; kwargs...) = - __descend_with_error_handling(mi; kwargs...) -_descend_with_error_handling(@nospecialize(tt::Type{<:Tuple}); kwargs...) = - __descend_with_error_handling(tt; kwargs...) -_descend_with_error_handling(interp::AbstractInterpreter, mi::MethodInstance; kwargs...) = - __descend_with_error_handling(interp, mi; kwargs...) -function __descend_with_error_handling(args...; terminal=default_terminal(), kwargs...) - @nospecialize - try - _descend(terminal, args...; kwargs...) - catch x - TypedSyntax.clear_all_vscode() - if x isa InterruptException - return nothing - else - rethrow(x) - end - end - return nothing +function descend_code_warntype(@nospecialize(args...); kwargs...) + CTHULHU_MODULE[].descend_code_warntype_impl(args...; kwargs...) end -default_terminal() = REPL.LineEdit.terminal(Base.active_repl) - -""" - descend(f, argtypes=Tuple{...}; kwargs...) - descend(tt::Type{<:Tuple}; kwargs...) - descend(Cthulhu.BOOKMARKS[i]) - descend(mi::MethodInstance; kwargs...) - -Given a function and a tuple-type, interactively explore the source code of functions -annotated with inferred types by descending into `invoke` statements. Type enter to select an -`invoke` to descend into, select `↩` to ascend, and press `q` or `control-c` to quit. -See [`Cthulhu.CONFIG`](@ref) for `kwargs` and their defaults. - -# Usage: -```julia -julia> descend(sin, (Int,)) -[...] - -julia> descend(sin, Tuple{Int}) -[...] - -julia> descend(Tuple{typeof(sin), Int}) -[...] - -julia> descend() do - T = rand() > 0.5 ? Int64 : Float64 - sin(rand(T)) - end -[...] -``` -""" -descend(@nospecialize(args...); kwargs...) = - _descend_with_error_handling(args...; iswarn=true, kwargs...) - -@static if VERSION ≥ v"1.11.0-DEV.207" - using .CC: cached_return_type -else -function cached_return_type(code::CodeInstance) - rettype = code.rettype - isdefined(code, :rettype_const) || return rettype - rettype_const = code.rettype_const - if isa(rettype_const, Vector{Any}) && !(Vector{Any} <: rettype) - return Core.PartialStruct(rettype, rettype_const) - elseif isa(rettype_const, Core.PartialOpaque) && rettype <: Core.OpaqueClosure - return rettype_const - elseif isa(rettype_const, CC.InterConditional) && !(CC.InterConditional <: rettype) - return rettype_const - else - return Const(rettype_const) - end -end -end - -function cached_exception_type(code::CodeInstance) - @static if VERSION ≥ v"1.11.0-DEV.945" - return code.exctype - else - return nothing - end -end - -get_effects(codeinst::CodeInstance) = CC.decode_effects(codeinst.ipo_purity_bits) -get_effects(codeinst::CodeInfo) = CC.decode_effects(codeinst.purity) -get_effects(src::InferredSource) = src.effects -get_effects(result::InferenceResult) = result.ipo_effects -get_effects(result::CC.ConstPropResult) = get_effects(result.result) -get_effects(result::CC.ConcreteResult) = result.effects -get_effects(result::CC.SemiConcreteResult) = result.effects - -# `@constprop :aggressive` here in order to make sure the constant propagation of `allow_no_src` -@constprop :aggressive function lookup(interp::CthulhuInterpreter, mi::MethodInstance, optimize::Bool; allow_no_src::Bool=false) - if optimize - return lookup_optimized(interp, mi, allow_no_src) - else - return lookup_unoptimized(interp, mi) - end -end - -function lookup_optimized(interp::CthulhuInterpreter, mi::MethodInstance, allow_no_src::Bool=false) - codeinst = interp.opt[mi] - rt = cached_return_type(codeinst) - exct = cached_exception_type(codeinst) - opt = codeinst.inferred - if opt !== nothing - opt = opt::OptimizedSource - src = CC.copy(opt.ir) - codeinf = opt.src - infos = src.stmts.info - slottypes = src.argtypes - elseif allow_no_src - # This doesn't showed up as covered, but it is (see the CI test with `coverage=false`). - # But with coverage on, the empty function body isn't empty due to :code_coverage_effect expressions. - codeinf = src = nothing - infos = [] - slottypes = Any[Base.unwrap_unionall(mi.specTypes).parameters...] - else - Core.eval(Main, quote - interp = $interp - mi = $mi - end) - error("couldn't find the source; inspect `Main.interp` and `Main.mi`") - end - effects = get_effects(codeinst) - return (; src, rt, exct, infos, slottypes, effects, codeinf) -end - -function lookup_unoptimized(interp::CthulhuInterpreter, mi::MethodInstance) - codeinf = src = copy(interp.unopt[mi].src) - (; rt, exct) = interp.unopt[mi] - infos = interp.unopt[mi].stmt_info - effects = get_effects(interp.unopt[mi]) - slottypes = src.slottypes - if isnothing(slottypes) - slottypes = Any[ Any for i = 1:length(src.slotflags) ] - end - return (; src, rt, exct, infos, slottypes, effects, codeinf) -end - -function lookup_constproped(interp::CthulhuInterpreter, override::InferenceResult, optimize::Bool) - if optimize - return lookup_constproped_optimized(interp, override) - else - return lookup_constproped_unoptimized(interp, override) - end -end - -function lookup_constproped_optimized(interp::CthulhuInterpreter, override::InferenceResult) - opt = override.src - if isa(opt, OptimizedSource) - # `(override::InferenceResult).src` might has been transformed to OptimizedSource already, - # e.g. when we switch from constant-prop' unoptimized source - src = CC.copy(opt.ir) - rt = override.result - exct = @static hasfield(InferenceResult, :exc_result) ? override.exc_result : nothing - infos = src.stmts.info - slottypes = src.argtypes - codeinf = opt.src - effects = opt.effects - return (; src, rt, exct, infos, slottypes, effects, codeinf) - else - # the source might be unavailable at this point, - # when a result is fully constant-folded etc. - return lookup(interp, override.linfo, true) - end -end - -function lookup_constproped_unoptimized(interp::CthulhuInterpreter, override::InferenceResult) - unopt = get(interp.unopt, override, nothing) - if unopt === nothing - unopt = interp.unopt[override.linfo] - end - codeinf = src = copy(unopt.src) - (; rt, exct) = unopt - infos = unopt.stmt_info - effects = get_effects(unopt) - slottypes = src.slottypes - if isnothing(slottypes) - slottypes = Any[ Any for i = 1:length(src.slotflags) ] - end - return (; src, rt, exct, infos, slottypes, effects, codeinf) -end - -function lookup_semiconcrete(interp::CthulhuInterpreter, override::SemiConcreteCallInfo, optimize::Bool) - src = CC.copy(override.ir) - rt = get_rt(override) - exct = Any # TODO - infos = src.stmts.info - slottypes = src.argtypes - effects = get_effects(override) - (; codeinf) = lookup(interp, get_mi(override), optimize) - return (; src, rt, exct, infos, slottypes, effects, codeinf) -end - -function get_override(@nospecialize(info)) - isa(info, ConstPropCallInfo) && return info.result - isa(info, SemiConcreteCallInfo) && return info - isa(info, OCCallInfo) && return get_override(info.ci) - return nothing -end - -## -# _descend is the main driver function. -# src/reflection.jl has the tools to discover methods -# src/ui.jl provides the user facing interface to which _descend responds -## -function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::AbstractCursor; - override::Union{Nothing,InferenceResult,SemiConcreteCallInfo} = nothing, - debuginfo::Union{Symbol,DebugInfo} = CONFIG.debuginfo, # default is compact debuginfo - optimize::Bool = CONFIG.optimize, # default is true - interruptexc::Bool = CONFIG.interruptexc, - iswarn::Bool = CONFIG.iswarn, # default is false - hide_type_stable::Union{Nothing,Bool} = CONFIG.hide_type_stable, - verbose::Union{Nothing,Bool} = nothing, - remarks::Bool = CONFIG.remarks&!CONFIG.optimize, # default is false - with_effects::Bool = CONFIG.with_effects, # default is false - exception_type::Bool = CONFIG.exception_type, # default is false - inline_cost::Bool = CONFIG.inline_cost&CONFIG.optimize, # default is false - type_annotations::Bool = CONFIG.type_annotations, # default is true - annotate_source::Bool = CONFIG.annotate_source, # default is true - inlay_types_vscode::Bool = CONFIG.inlay_types_vscode, # default is true - diagnostics_vscode::Bool = CONFIG.diagnostics_vscode, # default is true - jump_always::Bool = CONFIG.jump_always, # default is false - ) - - if isnothing(hide_type_stable) - hide_type_stable = something(verbose, false) - end - isnothing(verbose) || Base.depwarn("The `verbose` keyword argument to `Cthulhu.descend` is deprecated. Use `hide_type_stable` instead.") - if isa(debuginfo, Symbol) - debuginfo = getfield(DInfo, debuginfo)::DebugInfo - end - - is_cached(key::MethodInstance) = can_descend(interp, key, optimize & !annotate_source) - - menu_options = (; cursor = '•', scroll_wrap = true) - display_CI = true - view_cmd = cthulhu_typed - iostream = term.out_stream::IO - function additional_descend(new_mi::MethodInstance) - new_interp = CthulhuInterpreter(interp) - do_typeinf!(new_interp, new_mi) - _descend(term, new_interp, new_mi; - debuginfo, optimize, interruptexc, iswarn, hide_type_stable, remarks, - with_effects, exception_type, - inline_cost, type_annotations, annotate_source, - inlay_types_vscode, diagnostics_vscode) - end - custom_toggles = Cthulhu.custom_toggles(interp) - if !(custom_toggles isa Vector{CustomToggle}) - error(lazy""" - invalid `$AbstractInterpreter` API: - `$(Cthulhu.custom_toggles)(interp::$(typeof(interp))` is expected to return `Vector{CustomToggle}` object. - """) - end - while true - if isa(override, InferenceResult) - (; src, rt, exct, infos, slottypes, codeinf, effects) = lookup_constproped(interp, curs, override, optimize & !annotate_source) - elseif isa(override, SemiConcreteCallInfo) - (; src, rt, exct, infos, slottypes, codeinf, effects) = lookup_semiconcrete(interp, curs, override, optimize & !annotate_source) - else - if optimize && !annotate_source - codeinst = get_optimized_codeinst(interp, curs) - if codeinst.inferred === nothing - if isdefined(codeinst, :rettype_const) - # This was inferred to a pure constant - we have no code to show, - # but make something up that looks plausible. - callsites = Callsite[] - if display_CI - exct = @static VERSION ≥ v"1.11.0-DEV.945" ? codeinst.exct : nothing - callsite = Callsite(-1, MICallInfo(codeinst.def, codeinst.rettype, get_effects(codeinst), exct), :invoke) - println(iostream) - println(iostream, "│ ─ $callsite") - println(iostream, "│ return ", Const(codeinst.rettype_const)) - println(iostream) - end - @goto show_menu - else - @info """ - Inference discarded the source for this call because of recursion: - Cthulhu nevertheless is trying to retrieve the source for further inspection. - """ - additional_descend(get_mi(curs)) - break - end - end - end - (; src, rt, exct, infos, slottypes, effects, codeinf) = lookup(interp, curs, optimize & !annotate_source) - end - mi = get_mi(curs) - src = preprocess_ci!(src, mi, optimize & !annotate_source, CONFIG) - if (optimize & !annotate_source) || isa(src, IRCode) # optimization might have deleted some statements - infos = src.stmts.info - else - @assert length(src.code) == length(infos) - end - infkey = override isa InferenceResult ? override : mi - @static if VERSION ≥ v"1.11.0-DEV.1127" - pc2excts = exception_type ? get_excts(interp, infkey) : nothing - else - if exception_type - @warn "Statement-wise and call-wise exception type information is available only on v\"1.11.0-DEV.1127\" and later" - end - pc2excts = nothing - end - callsites, sourcenodes = find_callsites(interp, src, infos, mi, slottypes, optimize & !annotate_source, annotate_source, pc2excts) - - if jump_always - if isdefined(Main, :VSCodeServer) && Main.VSCodeServer isa Module && isdefined(Main.VSCodeServer, :openfile) - Main.VSCodeServer.openfile(whereis(mi.def::Method)...; preserve_focus=true) - else - edit(whereis(mi.def::Method)...) - end - end - - if display_CI - pc2remarks = remarks ? get_remarks(interp, infkey) : nothing - pc2effects = with_effects ? get_effects(interp, infkey) : nothing - printstyled(IOContext(iostream, :limit=>true), mi.def, '\n'; bold=true) - if debuginfo == DInfo.compact - str = let debuginfo=debuginfo, src=src, codeinf=codeinf, rt=rt, mi=mi, - iswarn=iswarn, hide_type_stable=hide_type_stable, - pc2remarks=pc2remarks, pc2effects=pc2effects, inline_cost=inline_cost, type_annotations=type_annotations - ioctx = IOContext(iostream, - :color => true, - :displaysize => displaysize(iostream), # displaysize doesn't propagate otherwise - :SOURCE_SLOTNAMES => codeinf === nothing ? false : Base.sourceinfo_slotnames(codeinf), - :with_effects => with_effects, - :exception_type => exception_type) - stringify(ioctx) do lambda_io - cthulhu_typed(lambda_io, debuginfo, annotate_source ? codeinf : src, rt, exct, effects, mi; - iswarn, optimize, hide_type_stable, - pc2remarks, pc2effects, pc2excts, - inline_cost, type_annotations, annotate_source, inlay_types_vscode, diagnostics_vscode, - jump_always, interp) - end - end - # eliminate trailing indentation (see first item in bullet list in PR #189) - rmatch = findfirst(r"\u001B\[90m\u001B\[(\d+)G( *)\u001B\[1G\u001B\[39m\u001B\[90m( *)\u001B\[39m$", str) - if rmatch !== nothing - str = str[begin:prevind(str, first(rmatch))] - end - print(iostream, str) - else - lambda_io = IOContext(iostream, - :SOURCE_SLOTNAMES => Base.sourceinfo_slotnames(codeinf), - :with_effects => with_effects, - :exception_type => exception_type) - cthulhu_typed(lambda_io, debuginfo, src, rt, exct, effects, mi; - iswarn, optimize, hide_type_stable, - pc2remarks, pc2effects, pc2excts, - inline_cost, type_annotations, annotate_source, inlay_types_vscode, diagnostics_vscode, - jump_always, interp) - end - view_cmd = cthulhu_typed - else - display_CI = true - end - - @label show_menu - - shown_callsites = annotate_source ? sourcenodes : callsites - menu = CthulhuMenu(shown_callsites, with_effects, exception_type, - optimize & !annotate_source, - iswarn&get(iostream, :color, false)::Bool, - hide_type_stable, custom_toggles; menu_options...) - usg = usage(view_cmd, annotate_source, optimize, iswarn, hide_type_stable, - debuginfo, remarks, with_effects, exception_type, inline_cost, - type_annotations, CONFIG.enable_highlighter, inlay_types_vscode, - diagnostics_vscode, jump_always, custom_toggles) - cid = request(term, usg, menu) - toggle = menu.toggle - - if toggle === nothing - if cid == length(callsites) + 1 - break - end - if cid == -1 - interruptexc ? throw(InterruptException()) : break - end - callsite = callsites[cid] - sourcenode = !isempty(sourcenodes) ? sourcenodes[cid] : nothing - - info = callsite.info - if info isa MultiCallInfo - show_sub_callsites = sub_callsites = let callsite=callsite - map(ci->Callsite(callsite.id, ci, callsite.head), info.callinfos) - end - if isempty(sub_callsites) - Core.eval(Main, quote - interp = $interp - mi = $mi - info = $info - end) - @error "Expected multiple callsites, but found none. Please fill an issue with a reproducing example." - continue - end - if sourcenode !== nothing - show_sub_callsites = let callsite=callsite - map(info.callinfos) do ci - p = Base.unwrap_unionall(get_mi(ci).specTypes).parameters - if isa(sourcenode, TypedSyntax.MaybeTypedSyntaxNode) && length(p) == length(JuliaSyntax.children(sourcenode)) + 1 - newnode = copy(sourcenode) - for (i, child) in enumerate(JuliaSyntax.children(newnode)) - child.typ = p[i+1] - end - newnode - else - Callsite(callsite.id, ci, callsite.head) - end - end - end - end - menu = CthulhuMenu(show_sub_callsites, with_effects, exception_type, - optimize & !annotate_source, false, false, custom_toggles; - sub_menu=true, menu_options...) - cid = request(term, "", menu) - if cid == length(sub_callsites) + 1 - continue - end - if cid == -1 - interruptexc ? throw(InterruptException()) : break - end - - callsite = sub_callsites[cid] - info = callsite.info - end - - # forcibly enter and inspect the frame, although the native interpreter gave up - if info isa UncachedCallInfo - @info """ - Inference didn't cache this call information because of imprecise analysis due to recursion: - Cthulhu nevertheless is trying to descend into it for further inspection. - """ - additional_descend(get_mi(info)::MethodInstance) - continue - elseif info isa TaskCallInfo - @info """ - Inference didn't analyze this call because it is a dynamic call: - Cthulhu nevertheless is trying to descend into it for further inspection. - """ - additional_descend(get_mi(info)::MethodInstance) - continue - elseif info isa RTCallInfo - @info """ - This is a runtime call. You cannot descend into it. - """ - @goto show_menu - end - - # recurse - next_cursor = navigate(curs, callsite)::Union{AbstractCursor,Nothing} - if next_cursor === nothing - continue - end - - _descend(term, interp, next_cursor; - override = get_override(info), debuginfo, - optimize, interruptexc, - iswarn, hide_type_stable, - remarks, with_effects, exception_type, inline_cost, - type_annotations, annotate_source, - inlay_types_vscode, diagnostics_vscode, - jump_always) - - elseif toggle === :warn - iswarn ⊻= true - elseif toggle === :with_effects - with_effects ⊻= true - elseif toggle === :exception_type - exception_type ⊻= true - elseif toggle === :hide_type_stable - hide_type_stable ⊻= true - elseif toggle === :inlay_types_vscode - inlay_types_vscode ⊻= true - TypedSyntax.clear_inlay_hints_vscode() - elseif toggle === :diagnostics_vscode - diagnostics_vscode ⊻= true - TypedSyntax.clear_diagnostics_vscode() - elseif toggle === :optimize - optimize ⊻= true - if !is_cached(get_mi(curs)) - @warn "Can't switch to post-optimization state, since this inference frame isn't cached." - optimize ⊻= true - end - if remarks && optimize - @warn "Disable optimization to see the inference remarks." - end - elseif toggle === :debuginfo - debuginfo = DebugInfo((Int(debuginfo) + 1) % 3) - elseif toggle === :remarks - remarks ⊻= true - if remarks && optimize - @warn "Disable optimization to see the inference remarks." - end - elseif toggle === :inline_cost - inline_cost ⊻= true - if inline_cost && !optimize - @warn "Enable optimization to see the inlining costs." - end - elseif toggle === :type_annotations - type_annotations ⊻= true - elseif toggle === :highlighter - CONFIG.enable_highlighter ⊻= true - if CONFIG.enable_highlighter - @info "Using syntax highlighter $(CONFIG.highlighter)." - else - @info "Turned off syntax highlighter for Julia, LLVM and native code." - end - display_CI = false - elseif toggle === :dump_params - @info "Dumping inference cache." - Core.show(mapany(((i, x),) -> (i, x.result, x.linfo), enumerate(get_inference_cache(interp)))) - Core.println() - display_CI = false - elseif toggle === :bookmark - push!(BOOKMARKS, Bookmark(mi, interp)) - @info "The method is pushed at the end of `Cthulhu.BOOKMARKS`." - display_CI = false - elseif toggle === :revise - # Call Revise.revise() without introducing a dependency on Revise - id = Base.PkgId(UUID("295af30f-e4ad-537b-8983-00126c2a3abe"), "Revise") - mod = get(Base.loaded_modules, id, nothing) - if mod !== nothing - revise = getfield(mod, :revise)::Function - revise() - mi = get_specialization(mi.specTypes)::MethodInstance - do_typeinf!(interp, mi) - curs = update_cursor(curs, mi) - else - @warn "Failed to load Revise." - end - elseif toggle === :edit - edit(whereis(mi.def::Method)...) - display_CI = false - elseif toggle === :jump_always - jump_always ⊻= true - elseif toggle === :typed - view_cmd = cthulhu_typed - annotate_source = false - display_CI = true - elseif toggle === :source - view_cmd = cthulhu_typed - annotate_source = true - display_CI = true - elseif toggle === :ast || toggle === :llvm || toggle === :native - view_cmd = CODEVIEWS[toggle] - world = get_inference_world(interp) - println(iostream) - @static if VERSION < v"1.12.0-DEV.669" - view_cmd(iostream, mi, optimize, debuginfo, world, CONFIG) - else - src = CC.typeinf_code(interp, mi, true) - view_cmd(iostream, mi, src, optimize, debuginfo, world, CONFIG) - end - display_CI = false - else - local i = findfirst(ct->ct.toggle === toggle, custom_toggles) - @assert i !== nothing - ct = custom_toggles[i] - onoff = ct.onoff ⊻= true - if onoff - curs = ct.callback_on(curs) - else - curs = ct.callback_off(curs) - end - if !(curs isa AbstractCursor) - local f = onoff ? "callback_on" : "callback_off" - error(lazy""" - invalid `$AbstractInterpreter` API: - `f` callback is expected to return `AbstractCursor` object. - """) - end - end - println(iostream) - end - - TypedSyntax.clear_all_vscode() -end - -function do_typeinf!(interp::AbstractInterpreter, mi::MethodInstance) - result = InferenceResult(mi) - @static if isdefined(CC, :engine_reserve) - ci = CC.engine_reserve(interp, mi) - result.ci = ci - end - # we may want to handle the case when `InferenceState(...)` returns `nothing`, - # which indicates code generation of a `@generated` has been failed, - # and show it in the UI in some way? - frame = InferenceState(result, #=cache_mode=#:global, interp)::InferenceState - CC.typeinf(interp, frame) - return nothing -end - -get_specialization(@nospecialize tt::Type{<:Tuple}) = specialize_method(Base._which(tt)) - -get_specialization(@nospecialize(f), @nospecialize(tt=default_tt(f))) = - get_specialization(Base.signature_type(f, tt)) - -function mkinterp(interp::AbstractInterpreter, @nospecialize(args...)) - interp′ = CthulhuInterpreter(interp) - mi = get_specialization(args...) - do_typeinf!(interp′, mi) - (interp′, mi) -end -mkinterp(@nospecialize(args...); interp::AbstractInterpreter=NativeInterpreter()) = mkinterp(interp, args...) - -_descend(interp::AbstractInterpreter, mi::MethodInstance; terminal=default_terminal(), kwargs...) = - _descend(terminal, interp, mi; kwargs...) -_descend(term::AbstractTerminal, interp::AbstractInterpreter, mi::MethodInstance; kwargs...) = - _descend(term, interp, AbstractCursor(interp, mi); kwargs...) -function _descend(@nospecialize(args...); - interp::AbstractInterpreter=NativeInterpreter(), kwargs...) - (interp′, mi) = mkinterp(interp, args...) - _descend(interp′, mi; kwargs...) -end -function _descend(term::AbstractTerminal, mi::MethodInstance; - interp::AbstractInterpreter=NativeInterpreter(), kwargs...) - interp′ = Cthulhu.CthulhuInterpreter(interp) - Cthulhu.do_typeinf!(interp′, mi) - _descend(term, interp′, mi; kwargs...) -end -function _descend(term::AbstractTerminal, @nospecialize(args...); - interp::AbstractInterpreter=NativeInterpreter(), kwargs...) - (interp′, mi) = mkinterp(interp, args...) - _descend(term, interp′, mi; kwargs...) -end - -descend_code_typed(b::Bookmark; kw...) = - _descend_with_error_handling(b.interp, b.mi; iswarn=false, kw...) - -descend_code_warntype(b::Bookmark; kw...) = - _descend_with_error_handling(b.interp, b.mi; iswarn=true, kw...) - -FoldingTrees.writeoption(buf::IO, data::Data, charsused::Int) = FoldingTrees.writeoption(buf, data.callstr, charsused) - -function ascend(term, mi; interp::AbstractInterpreter=NativeInterpreter(), kwargs...) - root = treelist(mi) - root === nothing && return - menu = TreeMenu(root) - choice = menu.current - while choice !== nothing - menu.chosen = false - choice = TerminalMenus.request(term, "Choose a call for analysis (q to quit):", menu; cursor=menu.currentidx) - browsecodetyped = true - if choice !== nothing - node = menu.current - mi = instance(node.data.nd) - if !isroot(node) - # Help user find the sites calling the parent - miparent = instance(node.parent.data.nd) - ulocs = find_caller_of(interp, miparent, mi; allow_unspecialized=true) - if !isempty(ulocs) - ulocs = [(k[1], maybe_fix_path(String(k[2])), k[3]) => v for (k, v) in ulocs] - strlocs = [string(" "^k[3] * '"', k[2], "\", ", k[1], ": lines ", v) for (k, v) in ulocs] - explain_inlining = length(ulocs) > 1 ? "(including inlined callers represented by indentation) " : "" - push!(strlocs, "Browse typed code") - linemenu = TerminalMenus.RadioMenu(strlocs; charset=:ascii) - browsecodetyped = false - choice2 = 1 - while choice2 != -1 - promptstr = sprint(miparent, explain_inlining; context=:color=>get(term, :color, false)) do iobuf, mip, exi - printstyled(iobuf, "\nOpen an editor at a possible caller of\n "; color=:light_cyan) - print(iobuf, miparent) - printstyled(iobuf, "\n$(explain_inlining)or browse typed code:"; color=:light_cyan) - end - choice2 = TerminalMenus.request(term, promptstr, linemenu; cursor=choice2) - if 0 < choice2 < length(strlocs) - loc, lines = ulocs[choice2] - edit(loc[2], first(lines)) - elseif choice2 == length(strlocs) - browsecodetyped = true - break - end - end - end - end - if !isa(mi, MethodInstance) - error("You can only descend into known calls. If you tried to descend into a runtime-dispatched signature, try its caller instead.") - end - # The main application of `ascend` is finding cases of non-inferrability, so the - # warn highlighting is useful. - interp′ = CthulhuInterpreter(interp) - do_typeinf!(interp′, mi) - browsecodetyped && _descend(term, interp′, mi; annotate_source=true, iswarn=true, optimize=false, interruptexc=false, kwargs...) - end - end -end -ascend(mi; kwargs...) = ascend(default_terminal(), mi; kwargs...) - """ ascend(mi::MethodInstance; kwargs...) ascend(bt; kwargs...) @@ -960,7 +189,9 @@ ascend(mi; kwargs...) = ascend(default_terminal(), mi; kwargs...) Follow a chain of calls (either through a backtrace `bt` or the backedges of a `MethodInstance` `mi`), with the option to `descend` into intermediate calls. `kwargs` are passed to [`descend`](@ref). """ -ascend +function ascend(@nospecialize(args...); kwargs...) + CTHULHU_MODULE[].ascend_impl(args...; kwargs...) +end using PrecompileTools @setup_workload begin @@ -968,14 +199,19 @@ using PrecompileTools input = Base.link_pipe!(Pipe(), reader_supports_async=true, writer_supports_async=true) term = REPL.Terminals.TTYTerminal("dumb", input.out, devnull, devnull) write(input.in, 'q') - @compile_workload begin - descend(gcd, (Int, Int); terminal=term) - # declare we are done with streams - close(input.in) - end + + @compile_workload descend(gcd, (Int, Int); terminal=term) + + # @static if VERSION ≥ v"1.12.0-DEV.1581" + # using Compiler + # @compile_workload descend(gcd, (Int, Int); terminal=term) + # end + + # declare we are done with streams + close(input.in) catch err @error "Errorred while running the precompile workload, the package may or may not work but latency will be long" exeption=(err,catch_backtrace()) end end -end +end # module Cthulhu diff --git a/src/CthulhuBase.jl b/src/CthulhuBase.jl new file mode 100644 index 00000000..21ade3cf --- /dev/null +++ b/src/CthulhuBase.jl @@ -0,0 +1,786 @@ +Base.Experimental.@compiler_options compile=min optimize=1 + +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: MethodInstance, MethodMatch +using Core.IR +using .CC: AbstractInterpreter, ApplyCallInfo, CallInfo as CCCallInfo, ConstCallInfo, + EFFECTS_TOTAL, Effects, IncrementalCompact, InferenceParams, InferenceResult, + InferenceState, IRCode, LimitedAccuracy, MethodMatchInfo, MethodResultPure, + NativeInterpreter, NoCallInfo, OptimizationParams, OptimizationState, + UnionSplitApplyCallInfo, UnionSplitInfo, WorldRange, WorldView, + argextype, argtypes_to_type, compileable_specialization, ignorelimited, singleton_type, + specialize_method, sptypes_from_meth_instance, widenconst +using Base: @constprop, default_tt, isvarargtype, unwrapva, unwrap_unionall, rewrap_unionall +const mapany = Base.mapany + +const ArgTypes = Vector{Any} + +@static if VERSION ≥ v"1.11.0-DEV.1498" + import .CC: get_inference_world + using Base: get_world_counter +else + import .CC: get_world_counter, get_world_counter as get_inference_world +end + +Base.@kwdef mutable struct CthulhuConfig + enable_highlighter::Bool = false + highlighter::Cmd = `pygmentize -l` + asm_syntax::Symbol = :att + dead_code_elimination::Bool = true + pretty_ast::Bool = false + interruptexc::Bool = true + debuginfo::Symbol = :compact + optimize::Bool = true + iswarn::Bool = false + hide_type_stable::Bool = false + remarks::Bool = false + with_effects::Bool = false + exception_type::Bool = false + inline_cost::Bool = false + type_annotations::Bool = true + annotate_source::Bool = true # overrides optimize, although the current setting is preserved + inlay_types_vscode::Bool = true + diagnostics_vscode::Bool = true + jump_always::Bool = false +end + +""" + Cthulhu.CONFIG + +# Options +- `enable_highlighter::Bool`: Use command line `highlighter` to syntax highlight + Julia, LLVM and native code. +- `highlighter::Cmd`: A command line program that receives "julia" as an argument and julia + code as stdin. Defaults to `$(CthulhuConfig().highlighter)`. +- `asm_syntax::Symbol`: Set the syntax of assembly code being used. + Defaults to `$(CthulhuConfig().asm_syntax)`. +- `dead_code_elimination::Bool`: Enable dead-code elimination for high-level Julia IR. + Defaults to `true`. DCE is known to be buggy and you may want to disable it if you + encounter errors. Please report such bugs with a MWE to Julia or Cthulhu. +- `pretty_ast::Bool`: Use a pretty printer for the ast dump. Defaults to `false`. +- `interruptexc::Bool`: Use -key to quit or ascend. Defaults to `false`. +- `debuginfo::Symbol`: Initial state of "debuginfo" toggle. Defaults to `:compact`. + Options:. `:none`, `:compact`, `:source` +- `optimize::Bool`: Initial state of "optimize" toggle. Defaults to `true`. +- `hide_type_stable::Bool`: Initial state of "hide_type_stable" toggle. Defaults to `false`. +- `iswarn::Bool`: Initial state of "warn" toggle. Defaults to `false`. +- `remarks::Bool` Initial state of "remarks" toggle. Defaults to `false`. +- `with_effects::Bool` Intial state of "effects" toggle. Defaults to `false`. +- `exception_type::Bool` `Intial state of "exception type" toggle. Defaults to `false`. +- `inline_cost::Bool` Initial state of "inlining costs" toggle. Defaults to `false`. +- `type_annotations::Bool` Initial state of "type annnotations" toggle. Defaults to `true`. +- `annotate_source::Bool` Initial state of "Source". Defaults to `true`. +- `inlay_types_vscode::Bool` Initial state of "vscode: inlay types" toggle. Defaults to `true` +- `diagnostics_vscode::Bool` Initial state of "Vscode: diagnostics" toggle. Defaults to `true` +- `jump_always::Bool` Initial state of "jump to source always" toggle. Defaults to `false`. +""" +const CONFIG = CthulhuConfig() + +using Preferences +include("preferences.jl") + +module DInfo + @enum DebugInfo none compact source +end +using .DInfo: DebugInfo +const AnyDebugInfo = Union{DebugInfo,Symbol} + +include("interpreter.jl") +include("callsite.jl") +include("interface.jl") +include("reflection.jl") +include("ui.jl") +include("codeview.jl") +include("backedges.jl") + +""" + @interp + +For debugging. Returns a CthulhuInterpreter from the appropriate entrypoint. +""" +macro interp(ex0...) + InteractiveUtils.gen_call_with_extracted_types_and_kwargs(__module__, :mkinterp, ex0) +end + +descend_code_typed_impl(@nospecialize(args...); kwargs...) = + _descend_with_error_handling(args...; annotate_source=false, iswarn=false, kwargs...) + +descend_code_warntype_impl(@nospecialize(args...); kwargs...) = + _descend_with_error_handling(args...; annotate_source=false, iswarn=true, optimize=false, kwargs...) + +function _descend_with_error_handling(@nospecialize(f), @nospecialize(argtypes = default_tt(f)); kwargs...) + ft = Core.Typeof(f) + if isa(argtypes, Type) + u = unwrap_unionall(argtypes) + tt = rewrap_unionall(Tuple{ft, u.parameters...}, argtypes) + else + tt = Tuple{ft, argtypes...} + end + __descend_with_error_handling(tt; kwargs...) +end +_descend_with_error_handling(mi::MethodInstance; kwargs...) = + __descend_with_error_handling(mi; kwargs...) +_descend_with_error_handling(@nospecialize(tt::Type{<:Tuple}); kwargs...) = + __descend_with_error_handling(tt; kwargs...) +_descend_with_error_handling(interp::AbstractInterpreter, mi::MethodInstance; kwargs...) = + __descend_with_error_handling(interp, mi; kwargs...) +function __descend_with_error_handling(args...; terminal=default_terminal(), kwargs...) + @nospecialize + try + _descend(terminal, args...; kwargs...) + catch x + TypedSyntax.clear_all_vscode() + if x isa InterruptException + return nothing + else + rethrow(x) + end + end + return nothing +end + +default_terminal() = REPL.LineEdit.terminal(Base.active_repl) + +descend_impl(@nospecialize(args...); kwargs...) = + _descend_with_error_handling(args...; iswarn=true, kwargs...) + +@static if VERSION ≥ v"1.11.0-DEV.207" + using .CC: cached_return_type +else +function cached_return_type(code::CodeInstance) + rettype = code.rettype + isdefined(code, :rettype_const) || return rettype + rettype_const = code.rettype_const + if isa(rettype_const, Vector{Any}) && !(Vector{Any} <: rettype) + return Core.PartialStruct(rettype, rettype_const) + elseif isa(rettype_const, Core.PartialOpaque) && rettype <: Core.OpaqueClosure + return rettype_const + elseif isa(rettype_const, CC.InterConditional) && !(CC.InterConditional <: rettype) + return rettype_const + else + return Const(rettype_const) + end +end +end + +function cached_exception_type(code::CodeInstance) + @static if VERSION ≥ v"1.11.0-DEV.945" + return code.exctype + else + return nothing + end +end + +get_effects(codeinst::CodeInstance) = CC.decode_effects(codeinst.ipo_purity_bits) +get_effects(codeinst::CodeInfo) = CC.decode_effects(codeinst.purity) +get_effects(src::InferredSource) = src.effects +get_effects(result::InferenceResult) = result.ipo_effects +get_effects(result::CC.ConstPropResult) = get_effects(result.result) +get_effects(result::CC.ConcreteResult) = result.effects +get_effects(result::CC.SemiConcreteResult) = result.effects + +# `@constprop :aggressive` here in order to make sure the constant propagation of `allow_no_src` +@constprop :aggressive function lookup(interp::CthulhuInterpreter, mi::MethodInstance, optimize::Bool; allow_no_src::Bool=false) + if optimize + return lookup_optimized(interp, mi, allow_no_src) + else + return lookup_unoptimized(interp, mi) + end +end + +function lookup_optimized(interp::CthulhuInterpreter, mi::MethodInstance, allow_no_src::Bool=false) + codeinst = interp.opt[mi] + rt = cached_return_type(codeinst) + exct = cached_exception_type(codeinst) + opt = codeinst.inferred + if opt !== nothing + opt = opt::OptimizedSource + src = CC.copy(opt.ir) + codeinf = opt.src + infos = src.stmts.info + slottypes = src.argtypes + elseif allow_no_src + # This doesn't showed up as covered, but it is (see the CI test with `coverage=false`). + # But with coverage on, the empty function body isn't empty due to :code_coverage_effect expressions. + codeinf = src = nothing + infos = [] + slottypes = Any[Base.unwrap_unionall(mi.specTypes).parameters...] + else + Core.eval(Main, quote + interp = $interp + mi = $mi + end) + error("couldn't find the source; inspect `Main.interp` and `Main.mi`") + end + effects = get_effects(codeinst) + return (; src, rt, exct, infos, slottypes, effects, codeinf) +end + +function lookup_unoptimized(interp::CthulhuInterpreter, mi::MethodInstance) + codeinf = src = copy(interp.unopt[mi].src) + (; rt, exct) = interp.unopt[mi] + infos = interp.unopt[mi].stmt_info + effects = get_effects(interp.unopt[mi]) + slottypes = src.slottypes + if isnothing(slottypes) + slottypes = Any[ Any for i = 1:length(src.slotflags) ] + end + return (; src, rt, exct, infos, slottypes, effects, codeinf) +end + +function lookup_constproped(interp::CthulhuInterpreter, override::InferenceResult, optimize::Bool) + if optimize + return lookup_constproped_optimized(interp, override) + else + return lookup_constproped_unoptimized(interp, override) + end +end + +function lookup_constproped_optimized(interp::CthulhuInterpreter, override::InferenceResult) + opt = override.src + if isa(opt, OptimizedSource) + # `(override::InferenceResult).src` might has been transformed to OptimizedSource already, + # e.g. when we switch from constant-prop' unoptimized source + src = CC.copy(opt.ir) + rt = override.result + exct = @static hasfield(InferenceResult, :exc_result) ? override.exc_result : nothing + infos = src.stmts.info + slottypes = src.argtypes + codeinf = opt.src + effects = opt.effects + return (; src, rt, exct, infos, slottypes, effects, codeinf) + else + # the source might be unavailable at this point, + # when a result is fully constant-folded etc. + return lookup(interp, override.linfo, true) + end +end + +function lookup_constproped_unoptimized(interp::CthulhuInterpreter, override::InferenceResult) + unopt = get(interp.unopt, override, nothing) + if unopt === nothing + unopt = interp.unopt[override.linfo] + end + codeinf = src = copy(unopt.src) + (; rt, exct) = unopt + infos = unopt.stmt_info + effects = get_effects(unopt) + slottypes = src.slottypes + if isnothing(slottypes) + slottypes = Any[ Any for i = 1:length(src.slotflags) ] + end + return (; src, rt, exct, infos, slottypes, effects, codeinf) +end + +function lookup_semiconcrete(interp::CthulhuInterpreter, override::SemiConcreteCallInfo, optimize::Bool) + src = CC.copy(override.ir) + rt = get_rt(override) + exct = Any # TODO + infos = src.stmts.info + slottypes = src.argtypes + effects = get_effects(override) + (; codeinf) = lookup(interp, get_mi(override), optimize) + return (; src, rt, exct, infos, slottypes, effects, codeinf) +end + +function get_override(@nospecialize(info)) + isa(info, ConstPropCallInfo) && return info.result + isa(info, SemiConcreteCallInfo) && return info + isa(info, OCCallInfo) && return get_override(info.ci) + return nothing +end + +## +# _descend is the main driver function. +# src/reflection.jl has the tools to discover methods +# src/ui.jl provides the user facing interface to which _descend responds +## +function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::AbstractCursor; + override::Union{Nothing,InferenceResult,SemiConcreteCallInfo} = nothing, + debuginfo::Union{Symbol,DebugInfo} = CONFIG.debuginfo, # default is compact debuginfo + optimize::Bool = CONFIG.optimize, # default is true + interruptexc::Bool = CONFIG.interruptexc, + iswarn::Bool = CONFIG.iswarn, # default is false + hide_type_stable::Union{Nothing,Bool} = CONFIG.hide_type_stable, + verbose::Union{Nothing,Bool} = nothing, + remarks::Bool = CONFIG.remarks&!CONFIG.optimize, # default is false + with_effects::Bool = CONFIG.with_effects, # default is false + exception_type::Bool = CONFIG.exception_type, # default is false + inline_cost::Bool = CONFIG.inline_cost&CONFIG.optimize, # default is false + type_annotations::Bool = CONFIG.type_annotations, # default is true + annotate_source::Bool = CONFIG.annotate_source, # default is true + inlay_types_vscode::Bool = CONFIG.inlay_types_vscode, # default is true + diagnostics_vscode::Bool = CONFIG.diagnostics_vscode, # default is true + jump_always::Bool = CONFIG.jump_always, # default is false + ) + + if isnothing(hide_type_stable) + hide_type_stable = something(verbose, false) + end + isnothing(verbose) || Base.depwarn("The `verbose` keyword argument to `Cthulhu.descend` is deprecated. Use `hide_type_stable` instead.") + if isa(debuginfo, Symbol) + debuginfo = getfield(DInfo, debuginfo)::DebugInfo + end + + is_cached(key::MethodInstance) = can_descend(interp, key, optimize & !annotate_source) + + menu_options = (; cursor = '•', scroll_wrap = true) + display_CI = true + view_cmd = cthulhu_typed + iostream = term.out_stream::IO + function additional_descend(new_mi::MethodInstance) + new_interp = CthulhuInterpreter(interp) + do_typeinf!(new_interp, new_mi) + _descend(term, new_interp, new_mi; + debuginfo, optimize, interruptexc, iswarn, hide_type_stable, remarks, + with_effects, exception_type, + inline_cost, type_annotations, annotate_source, + inlay_types_vscode, diagnostics_vscode) + end + custom_toggles = Cthulhu.custom_toggles(interp) + if !(custom_toggles isa Vector{CustomToggle}) + error(lazy""" + invalid `$AbstractInterpreter` API: + `$(Cthulhu.custom_toggles)(interp::$(typeof(interp))` is expected to return `Vector{CustomToggle}` object. + """) + end + while true + if isa(override, InferenceResult) + (; src, rt, exct, infos, slottypes, codeinf, effects) = lookup_constproped(interp, curs, override, optimize & !annotate_source) + elseif isa(override, SemiConcreteCallInfo) + (; src, rt, exct, infos, slottypes, codeinf, effects) = lookup_semiconcrete(interp, curs, override, optimize & !annotate_source) + else + if optimize && !annotate_source + codeinst = get_optimized_codeinst(interp, curs) + if codeinst.inferred === nothing + if isdefined(codeinst, :rettype_const) + # This was inferred to a pure constant - we have no code to show, + # but make something up that looks plausible. + callsites = Callsite[] + if display_CI + exct = @static VERSION ≥ v"1.11.0-DEV.945" ? codeinst.exct : nothing + callsite = Callsite(-1, MICallInfo(codeinst.def, codeinst.rettype, get_effects(codeinst), exct), :invoke) + println(iostream) + println(iostream, "│ ─ $callsite") + println(iostream, "│ return ", Const(codeinst.rettype_const)) + println(iostream) + end + @goto show_menu + else + @info """ + Inference discarded the source for this call because of recursion: + Cthulhu nevertheless is trying to retrieve the source for further inspection. + """ + additional_descend(get_mi(curs)) + break + end + end + end + (; src, rt, exct, infos, slottypes, effects, codeinf) = lookup(interp, curs, optimize & !annotate_source) + end + mi = get_mi(curs) + src = preprocess_ci!(src, mi, optimize & !annotate_source, CONFIG) + if (optimize & !annotate_source) || isa(src, IRCode) # optimization might have deleted some statements + infos = src.stmts.info + else + @assert length(src.code) == length(infos) + end + infkey = override isa InferenceResult ? override : mi + @static if VERSION ≥ v"1.11.0-DEV.1127" + pc2excts = exception_type ? get_excts(interp, infkey) : nothing + else + if exception_type + @warn "Statement-wise and call-wise exception type information is available only on v\"1.11.0-DEV.1127\" and later" + end + pc2excts = nothing + end + callsites, sourcenodes = find_callsites(interp, src, infos, mi, slottypes, optimize & !annotate_source, annotate_source, pc2excts) + + if jump_always + if isdefined(Main, :VSCodeServer) && Main.VSCodeServer isa Module && isdefined(Main.VSCodeServer, :openfile) + Main.VSCodeServer.openfile(whereis(mi.def::Method)...; preserve_focus=true) + else + edit(whereis(mi.def::Method)...) + end + end + + if display_CI + pc2remarks = remarks ? get_remarks(interp, infkey) : nothing + pc2effects = with_effects ? get_effects(interp, infkey) : nothing + printstyled(IOContext(iostream, :limit=>true), mi.def, '\n'; bold=true) + if debuginfo == DInfo.compact + str = let debuginfo=debuginfo, src=src, codeinf=codeinf, rt=rt, mi=mi, + iswarn=iswarn, hide_type_stable=hide_type_stable, + pc2remarks=pc2remarks, pc2effects=pc2effects, inline_cost=inline_cost, type_annotations=type_annotations + ioctx = IOContext(iostream, + :color => true, + :displaysize => displaysize(iostream), # displaysize doesn't propagate otherwise + :SOURCE_SLOTNAMES => codeinf === nothing ? false : Base.sourceinfo_slotnames(codeinf), + :with_effects => with_effects, + :exception_type => exception_type) + stringify(ioctx) do lambda_io + cthulhu_typed(lambda_io, debuginfo, annotate_source ? codeinf : src, rt, exct, effects, mi; + iswarn, optimize, hide_type_stable, + pc2remarks, pc2effects, pc2excts, + inline_cost, type_annotations, annotate_source, inlay_types_vscode, diagnostics_vscode, + jump_always, interp) + end + end + # eliminate trailing indentation (see first item in bullet list in PR #189) + rmatch = findfirst(r"\u001B\[90m\u001B\[(\d+)G( *)\u001B\[1G\u001B\[39m\u001B\[90m( *)\u001B\[39m$", str) + if rmatch !== nothing + str = str[begin:prevind(str, first(rmatch))] + end + print(iostream, str) + else + lambda_io = IOContext(iostream, + :SOURCE_SLOTNAMES => Base.sourceinfo_slotnames(codeinf), + :with_effects => with_effects, + :exception_type => exception_type) + cthulhu_typed(lambda_io, debuginfo, src, rt, exct, effects, mi; + iswarn, optimize, hide_type_stable, + pc2remarks, pc2effects, pc2excts, + inline_cost, type_annotations, annotate_source, inlay_types_vscode, diagnostics_vscode, + jump_always, interp) + end + view_cmd = cthulhu_typed + else + display_CI = true + end + + @label show_menu + + shown_callsites = annotate_source ? sourcenodes : callsites + menu = CthulhuMenu(shown_callsites, with_effects, exception_type, + optimize & !annotate_source, + iswarn&get(iostream, :color, false)::Bool, + hide_type_stable, custom_toggles; menu_options...) + usg = usage(view_cmd, annotate_source, optimize, iswarn, hide_type_stable, + debuginfo, remarks, with_effects, exception_type, inline_cost, + type_annotations, CONFIG.enable_highlighter, inlay_types_vscode, + diagnostics_vscode, jump_always, custom_toggles) + cid = request(term, usg, menu) + toggle = menu.toggle + + if toggle === nothing + if cid == length(callsites) + 1 + break + end + if cid == -1 + interruptexc ? throw(InterruptException()) : break + end + callsite = callsites[cid] + sourcenode = !isempty(sourcenodes) ? sourcenodes[cid] : nothing + + info = callsite.info + if info isa MultiCallInfo + show_sub_callsites = sub_callsites = let callsite=callsite + map(ci->Callsite(callsite.id, ci, callsite.head), info.callinfos) + end + if isempty(sub_callsites) + Core.eval(Main, quote + interp = $interp + mi = $mi + info = $info + end) + @error "Expected multiple callsites, but found none. Please fill an issue with a reproducing example." + continue + end + if sourcenode !== nothing + show_sub_callsites = let callsite=callsite + map(info.callinfos) do ci + p = Base.unwrap_unionall(get_mi(ci).specTypes).parameters + if isa(sourcenode, TypedSyntax.MaybeTypedSyntaxNode) && length(p) == length(JuliaSyntax.children(sourcenode)) + 1 + newnode = copy(sourcenode) + for (i, child) in enumerate(JuliaSyntax.children(newnode)) + child.typ = p[i+1] + end + newnode + else + Callsite(callsite.id, ci, callsite.head) + end + end + end + end + menu = CthulhuMenu(show_sub_callsites, with_effects, exception_type, + optimize & !annotate_source, false, false, custom_toggles; + sub_menu=true, menu_options...) + cid = request(term, "", menu) + if cid == length(sub_callsites) + 1 + continue + end + if cid == -1 + interruptexc ? throw(InterruptException()) : break + end + + callsite = sub_callsites[cid] + info = callsite.info + end + + # forcibly enter and inspect the frame, although the native interpreter gave up + if info isa UncachedCallInfo + @info """ + Inference didn't cache this call information because of imprecise analysis due to recursion: + Cthulhu nevertheless is trying to descend into it for further inspection. + """ + additional_descend(get_mi(info)::MethodInstance) + continue + elseif info isa TaskCallInfo + @info """ + Inference didn't analyze this call because it is a dynamic call: + Cthulhu nevertheless is trying to descend into it for further inspection. + """ + additional_descend(get_mi(info)::MethodInstance) + continue + elseif info isa RTCallInfo + @info """ + This is a runtime call. You cannot descend into it. + """ + @goto show_menu + end + + # recurse + next_cursor = navigate(curs, callsite)::Union{AbstractCursor,Nothing} + if next_cursor === nothing + continue + end + + _descend(term, interp, next_cursor; + override = get_override(info), debuginfo, + optimize, interruptexc, + iswarn, hide_type_stable, + remarks, with_effects, exception_type, inline_cost, + type_annotations, annotate_source, + inlay_types_vscode, diagnostics_vscode, + jump_always) + + elseif toggle === :warn + iswarn ⊻= true + elseif toggle === :with_effects + with_effects ⊻= true + elseif toggle === :exception_type + exception_type ⊻= true + elseif toggle === :hide_type_stable + hide_type_stable ⊻= true + elseif toggle === :inlay_types_vscode + inlay_types_vscode ⊻= true + TypedSyntax.clear_inlay_hints_vscode() + elseif toggle === :diagnostics_vscode + diagnostics_vscode ⊻= true + TypedSyntax.clear_diagnostics_vscode() + elseif toggle === :optimize + optimize ⊻= true + if !is_cached(get_mi(curs)) + @warn "Can't switch to post-optimization state, since this inference frame isn't cached." + optimize ⊻= true + end + if remarks && optimize + @warn "Disable optimization to see the inference remarks." + end + elseif toggle === :debuginfo + debuginfo = DebugInfo((Int(debuginfo) + 1) % 3) + elseif toggle === :remarks + remarks ⊻= true + if remarks && optimize + @warn "Disable optimization to see the inference remarks." + end + elseif toggle === :inline_cost + inline_cost ⊻= true + if inline_cost && !optimize + @warn "Enable optimization to see the inlining costs." + end + elseif toggle === :type_annotations + type_annotations ⊻= true + elseif toggle === :highlighter + CONFIG.enable_highlighter ⊻= true + if CONFIG.enable_highlighter + @info "Using syntax highlighter $(CONFIG.highlighter)." + else + @info "Turned off syntax highlighter for Julia, LLVM and native code." + end + display_CI = false + elseif toggle === :dump_params + @info "Dumping inference cache." + Core.show(mapany(((i, x),) -> (i, x.result, x.linfo), enumerate(get_inference_cache(interp)))) + Core.println() + display_CI = false + elseif toggle === :bookmark + push!(BOOKMARKS, Bookmark(mi, interp)) + @info "The method is pushed at the end of `Cthulhu.BOOKMARKS`." + display_CI = false + elseif toggle === :revise + # Call Revise.revise() without introducing a dependency on Revise + id = Base.PkgId(UUID("295af30f-e4ad-537b-8983-00126c2a3abe"), "Revise") + mod = get(Base.loaded_modules, id, nothing) + if mod !== nothing + revise = getfield(mod, :revise)::Function + revise() + mi = get_specialization(mi.specTypes)::MethodInstance + do_typeinf!(interp, mi) + curs = update_cursor(curs, mi) + else + @warn "Failed to load Revise." + end + elseif toggle === :edit + edit(whereis(mi.def::Method)...) + display_CI = false + elseif toggle === :jump_always + jump_always ⊻= true + elseif toggle === :typed + view_cmd = cthulhu_typed + annotate_source = false + display_CI = true + elseif toggle === :source + view_cmd = cthulhu_typed + annotate_source = true + display_CI = true + elseif toggle === :ast || toggle === :llvm || toggle === :native + view_cmd = CODEVIEWS[toggle] + world = get_inference_world(interp) + println(iostream) + @static if VERSION < v"1.12.0-DEV.669" + view_cmd(iostream, mi, optimize, debuginfo, world, CONFIG) + else + src = CC.typeinf_code(interp, mi, true) + view_cmd(iostream, mi, src, optimize, debuginfo, world, CONFIG) + end + display_CI = false + else + local i = findfirst(ct->ct.toggle === toggle, custom_toggles) + @assert i !== nothing + ct = custom_toggles[i] + onoff = ct.onoff ⊻= true + if onoff + curs = ct.callback_on(curs) + else + curs = ct.callback_off(curs) + end + if !(curs isa AbstractCursor) + local f = onoff ? "callback_on" : "callback_off" + error(lazy""" + invalid `$AbstractInterpreter` API: + `f` callback is expected to return `AbstractCursor` object. + """) + end + end + println(iostream) + end + + TypedSyntax.clear_all_vscode() +end + +function do_typeinf!(interp::AbstractInterpreter, mi::MethodInstance) + result = InferenceResult(mi) + @static if isdefined(CC, :engine_reserve) + ci = CC.engine_reserve(interp, mi) + result.ci = ci + end + # we may want to handle the case when `InferenceState(...)` returns `nothing`, + # which indicates code generation of a `@generated` has been failed, + # and show it in the UI in some way? + frame = InferenceState(result, #=cache_mode=#:global, interp)::InferenceState + CC.typeinf(interp, frame) + return nothing +end + +get_specialization(@nospecialize tt::Type{<:Tuple}) = specialize_method(Base._which(tt)) + +get_specialization(@nospecialize(f), @nospecialize(tt=default_tt(f))) = + get_specialization(Base.signature_type(f, tt)) + +function mkinterp(interp::AbstractInterpreter, @nospecialize(args...)) + interp′ = CthulhuInterpreter(interp) + mi = get_specialization(args...) + do_typeinf!(interp′, mi) + (interp′, mi) +end +mkinterp(@nospecialize(args...); interp::AbstractInterpreter=NativeInterpreter()) = mkinterp(interp, args...) + +_descend(interp::AbstractInterpreter, mi::MethodInstance; terminal=default_terminal(), kwargs...) = + _descend(terminal, interp, mi; kwargs...) +_descend(term::AbstractTerminal, interp::AbstractInterpreter, mi::MethodInstance; kwargs...) = + _descend(term, interp, AbstractCursor(interp, mi); kwargs...) +function _descend(@nospecialize(args...); + interp::AbstractInterpreter=NativeInterpreter(), kwargs...) + (interp′, mi) = mkinterp(interp, args...) + _descend(interp′, mi; kwargs...) +end +function _descend(term::AbstractTerminal, mi::MethodInstance; + interp::AbstractInterpreter=NativeInterpreter(), kwargs...) + interp′ = Cthulhu.CthulhuInterpreter(interp) + Cthulhu.do_typeinf!(interp′, mi) + _descend(term, interp′, mi; kwargs...) +end +function _descend(term::AbstractTerminal, @nospecialize(args...); + interp::AbstractInterpreter=NativeInterpreter(), kwargs...) + (interp′, mi) = mkinterp(interp, args...) + _descend(term, interp′, mi; kwargs...) +end +descend_code_typed_impl(b::Bookmark; kw...) = + _descend_with_error_handling(b.interp, b.mi; iswarn=false, kw...) +descend_code_warntype_impl(b::Bookmark; kw...) = + _descend_with_error_handling(b.interp, b.mi; iswarn=true, kw...) + +FoldingTrees.writeoption(buf::IO, data::Data, charsused::Int) = FoldingTrees.writeoption(buf, data.callstr, charsused) + +function ascend_impl(term, mi; interp::AbstractInterpreter=NativeInterpreter(), kwargs...) + root = treelist(mi) + root === nothing && return + menu = TreeMenu(root) + choice = menu.current + while choice !== nothing + menu.chosen = false + choice = TerminalMenus.request(term, "Choose a call for analysis (q to quit):", menu; cursor=menu.currentidx) + browsecodetyped = true + if choice !== nothing + node = menu.current + mi = instance(node.data.nd) + if !isroot(node) + # Help user find the sites calling the parent + miparent = instance(node.parent.data.nd) + ulocs = find_caller_of(interp, miparent, mi; allow_unspecialized=true) + if !isempty(ulocs) + ulocs = [(k[1], maybe_fix_path(String(k[2])), k[3]) => v for (k, v) in ulocs] + strlocs = [string(" "^k[3] * '"', k[2], "\", ", k[1], ": lines ", v) for (k, v) in ulocs] + explain_inlining = length(ulocs) > 1 ? "(including inlined callers represented by indentation) " : "" + push!(strlocs, "Browse typed code") + linemenu = TerminalMenus.RadioMenu(strlocs; charset=:ascii) + browsecodetyped = false + choice2 = 1 + while choice2 != -1 + promptstr = sprint(miparent, explain_inlining; context=:color=>get(term, :color, false)) do iobuf, mip, exi + printstyled(iobuf, "\nOpen an editor at a possible caller of\n "; color=:light_cyan) + print(iobuf, miparent) + printstyled(iobuf, "\n$(explain_inlining)or browse typed code:"; color=:light_cyan) + end + choice2 = TerminalMenus.request(term, promptstr, linemenu; cursor=choice2) + if 0 < choice2 < length(strlocs) + loc, lines = ulocs[choice2] + edit(loc[2], first(lines)) + elseif choice2 == length(strlocs) + browsecodetyped = true + break + end + end + end + end + if !isa(mi, MethodInstance) + error("You can only descend into known calls. If you tried to descend into a runtime-dispatched signature, try its caller instead.") + end + # The main application of `ascend` is finding cases of non-inferrability, so the + # warn highlighting is useful. + interp′ = CthulhuInterpreter(interp) + do_typeinf!(interp′, mi) + browsecodetyped && _descend(term, interp′, mi; annotate_source=true, iswarn=true, optimize=false, interruptexc=false, kwargs...) + end + end +end +ascend_impl(mi; kwargs...) = ascend(default_terminal(), mi; kwargs...) From 2c04ee007a2289c0396a831d1365a60eb9aa9276 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Thu, 20 Feb 2025 03:01:33 +0900 Subject: [PATCH 2/2] temp: don't load Compiler.jl stdlib for now --- Project.toml | 2 +- ext/CthulhuCompilerExt.jl | 1 + src/CthulhuBase.jl | 2 +- test/setup.jl | 6 +++--- test/test_Cthulhu.jl | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Project.toml b/Project.toml index 6a169372..7323a2fd 100644 --- a/Project.toml +++ b/Project.toml @@ -24,7 +24,7 @@ CthulhuCompilerExt = "Compiler" [compat] CodeTracking = "0.5, 1" -Compiler = "0.0.3" +Compiler = "0.0.3, 1.12.0" FoldingTrees = "1" InteractiveUtils = "1.9" JuliaSyntax = "0.4" diff --git a/ext/CthulhuCompilerExt.jl b/ext/CthulhuCompilerExt.jl index 00cd63e6..13f422b5 100644 --- a/ext/CthulhuCompilerExt.jl +++ b/ext/CthulhuCompilerExt.jl @@ -6,6 +6,7 @@ module CthulhuCompilerExt using Cthulhu: Cthulhu function __init__() + CC.activate!(; reflection=true) Cthulhu.CTHULHU_MODULE[] = @__MODULE__ end diff --git a/src/CthulhuBase.jl b/src/CthulhuBase.jl index 21ade3cf..e3284237 100644 --- a/src/CthulhuBase.jl +++ b/src/CthulhuBase.jl @@ -345,7 +345,7 @@ function _descend(term::AbstractTerminal, interp::AbstractInterpreter, curs::Abs inline_cost, type_annotations, annotate_source, inlay_types_vscode, diagnostics_vscode) end - custom_toggles = Cthulhu.custom_toggles(interp) + custom_toggles = (@__MODULE__).custom_toggles(interp) if !(custom_toggles isa Vector{CustomToggle}) error(lazy""" invalid `$AbstractInterpreter` API: diff --git a/test/setup.jl b/test/setup.jl index 498ebcd2..ef796c6e 100644 --- a/test/setup.jl +++ b/test/setup.jl @@ -3,9 +3,9 @@ if isdefined(parentmodule(@__MODULE__), :VSCodeServer) using ..VSCodeServer end -@static if VERSION ≥ v"1.12.0-DEV.1581" -InteractiveUtils.@activate Compiler # use the Compiler.jl stdlib for the Base reflections too -end +# @static if VERSION ≥ v"1.12.0-DEV.1581" +# InteractiveUtils.@activate Compiler # use the Compiler.jl stdlib for the Base reflections too +# end function cthulhu_info(@nospecialize(f), @nospecialize(tt=()); optimize=true, interp=Cthulhu.CC.NativeInterpreter()) diff --git a/test/test_Cthulhu.jl b/test/test_Cthulhu.jl index e013adbc..ef7abf89 100644 --- a/test/test_Cthulhu.jl +++ b/test/test_Cthulhu.jl @@ -1,7 +1,7 @@ module test_Cthulhu using Test, Cthulhu, StaticArrays, Random -const CC = Cthulhu.CC +const CC = Cthulhu.CTHULHU_MODULE[].CC include("setup.jl") include("irutils.jl")