From 9938126b911da2400d101002f3fe65586a1154ed Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Tue, 9 Mar 2021 21:54:30 -0500 Subject: [PATCH] Switch callsite detection to use stmt info (#128) Rips out all the heuristics that are duplicating the analysis that inference already does to figure out what is getting called. This should be much more precise and general. We may want to revisit the whole CallInfo setup in the future to maybe use the stmt info more directly, but this should be good for the moment. --- src/Cthulhu.jl | 100 ++++++++++++++++-- src/callsite.jl | 31 +++++- src/codeview.jl | 13 ++- src/interpreter.jl | 44 +++++++- src/reflection.jl | 257 +++++++++++++++++---------------------------- test/runtests.jl | 61 ++++------- 6 files changed, 284 insertions(+), 222 deletions(-) diff --git a/src/Cthulhu.jl b/src/Cthulhu.jl index a6c0b104..20e90aed 100644 --- a/src/Cthulhu.jl +++ b/src/Cthulhu.jl @@ -55,6 +55,15 @@ macro descend_code_typed(ex0...) InteractiveUtils.gen_call_with_extracted_types_and_kwargs(__module__, :descend_code_typed, ex0) end +""" + @interp + +For debugging. Returns a CthulhuInterpreter from the appropriate entrypoint. +""" +macro interp(ex0...) + InteractiveUtils.gen_call_with_extracted_types_and_kwargs(__module__, :mkinterp, ex0) +end + """ @descend_code_warntype @@ -141,12 +150,47 @@ const descend = descend_code_typed descend(ci::CthulhuInterpreter, mi::MethodInstance; kwargs...) = _descend(ci, mi; iswarn=false, interruptexc=false, kwargs...) +function codeinst_rt(code::CodeInstance) + rettype = code.rettype + if isdefined(code, :rettype_const) + rettype_const = code.rettype_const + if isa(rettype_const, Vector{Any}) && !(Vector{Any} <: rettype) + return Core.PartialStruct(rettype, rettype_const) + elseif rettype <: Core.OpaqueClosure && isa(rettype_const, Core.PartialOpaque) + return rettype_const + elseif isa(rettype_const, Core.InterConditional) + return rettype_const + else + return Const(rettype_const) + end + else + return rettype + end +end + +function lookup(interp::CthulhuInterpreter, mi::MethodInstance, optimize::Bool) + if !optimize + codeinf = copy(interp.unopt[mi].src) + rt = interp.unopt[mi].rt + infos = interp.unopt[mi].stmt_infos + slottypes = codeinf.slottypes + else + codeinst = interp.opt[mi] + ir = Core.Compiler.copy(codeinst.inferred.ir) + codeinf = ir + rt = codeinst_rt(codeinst) + infos = ir.stmts.info + slottypes = ir.argtypes + end + (codeinf, rt, infos, slottypes) +end + ## # _descend is the main driver function. # src/reflection.jl has the tools to discover methods # src/ui.jl provides the user facing interface to which _descend responds ## -function _descend(interp::CthulhuInterpreter, mi::MethodInstance; iswarn::Bool, params=current_params(), optimize::Bool=true, interruptexc::Bool=true, verbose=true, kwargs...) +function _descend(interp::CthulhuInterpreter, mi::MethodInstance; override::Union{Nothing, InferenceResult} = nothing, iswarn::Bool, params=current_params(), optimize::Bool=true, interruptexc::Bool=true, verbose=true, kwargs...) debuginfo = true if :debuginfo in keys(kwargs) selected = kwargs[:debuginfo] @@ -157,13 +201,44 @@ function _descend(interp::CthulhuInterpreter, mi::MethodInstance; iswarn::Bool, display_CI = true while true debuginfo_key = debuginfo ? :source : :none - codeinf = copy(optimize ? interp.opt[mi].inferred : interp.unopt[mi].src) - rt = optimize ? interp.opt[mi].rettype : interp.unopt[mi].rt - preprocess_ci!(codeinf, mi, optimize, CONFIG) - callsites = find_callsites(codeinf, mi, codeinf.slottypes; params=params, kwargs...) - display_CI && cthulu_typed(stdout, debuginfo_key, codeinf, rt, mi, iswarn, verbose) - display_CI = true + if override === nothing && optimize && interp.opt[mi].inferred === nothing + # This was inferred to a pure constant - we have no code to show, + # but make something up that looks plausible. + if display_CI + println(stdout) + println(stdout, "│ ─ $(string(Callsite(-1, MICallInfo(mi, interp.opt[mi].rettype), :invoke)))") + println(stdout, "│ return ", Const(interp.opt[mi].rettype_const)) + println(stdout) + end + callsites = Callsite[] + else + if override !== nothing + if !haskey(interp.unopt, override) + Core.eval(Core.Main, :(the_issue = $override)) + end + codeinf = optimize ? override.src : interp.unopt[override].src + rt = optimize ? override.result : interp.unopt[override].rt + if optimize + let os = codeinf + codeinf = Core.Compiler.copy(codeinf.ir) + infos = codeinf.stmts.info + slottypes = codeinf.argtypes + end + else + codeinf = copy(codeinf) + infos = interp.unopt[override].stmt_infos + slottypes = codeinf.slottypes + end + else + (codeinf, rt, infos, slottypes) = lookup(interp, mi, optimize) + end + preprocess_ci!(codeinf, mi, optimize, CONFIG) + callsites = find_callsites(codeinf, infos, mi, slottypes; params=params, kwargs...) + + display_CI && cthulu_typed(stdout, debuginfo_key, codeinf, rt, mi, iswarn, verbose) + display_CI = true + end TerminalMenus.config(cursor = '•', scroll = :wrap) menu = CthulhuMenu(callsites, optimize) @@ -213,7 +288,9 @@ function _descend(interp::CthulhuInterpreter, mi::MethodInstance; iswarn::Bool, continue end - _descend(interp, next_mi; params=params, optimize=optimize, + _descend(interp, next_mi; + override = isa(callsite.info, ConstPropCallInfo) ? callsite.info.result : nothing, + params=params, optimize=optimize, iswarn=iswarn, debuginfo=debuginfo_key, interruptexc=interruptexc, verbose=verbose, kwargs...) elseif toggle === :warn @@ -272,12 +349,17 @@ function do_typeinf!(interp, mi) return nothing end -function _descend(@nospecialize(F), @nospecialize(TT); params=current_params(), kwargs...) +function mkinterp(@nospecialize(F), @nospecialize(TT)) ci = CthulhuInterpreter() sigt = Base.signature_type(F, TT) match = Base._which(sigt) mi = Core.Compiler.specialize_method(match) do_typeinf!(ci, mi) + (ci, mi) +end + +function _descend(@nospecialize(F), @nospecialize(TT); params=current_params(), kwargs...) + (ci, mi) = mkinterp(F, TT) _descend(ci, mi; params=params, kwargs...) end diff --git a/src/callsite.jl b/src/callsite.jl index b7389ec5..e04280db 100644 --- a/src/callsite.jl +++ b/src/callsite.jl @@ -2,11 +2,14 @@ using Unicode abstract type CallInfo; end +using Core.Compiler: MethodMatch + # Call could be resolved to a singular MI struct MICallInfo <: CallInfo mi::MethodInstance rt end +MICallInfo(match::MethodMatch, rt) = MICallInfo(Core.Compiler.specialize_method(match), rt) get_mi(ci::MICallInfo) = ci.mi # Failed @@ -49,12 +52,25 @@ struct TaskCallInfo <: CallInfo end get_mi(tci::TaskCallInfo) = get_mi(tci.ci) +# OpaqueClosure CallInfo +struct OCCallInfo <: CallInfo + ci::MICallInfo +end +OCCallInfo(mi::MethodInstance, rt) = OCCallInfo(MICallInfo(mi, rt)) +get_mi(tci::OCCallInfo) = get_mi(tci.ci) + # Special handling for ReturnTypeCall struct ReturnTypeCallInfo <: CallInfo called_mi::CallInfo end get_mi(rtci::ReturnTypeCallInfo) = get_mi(rtci.called_mi) +struct ConstPropCallInfo <: CallInfo + mi::CallInfo + result::InferenceResult +end +get_mi(cpci::ConstPropCallInfo) = cpci.result.linfo + # CUDA callsite struct CuCallInfo <: CallInfo cumi::MICallInfo @@ -100,7 +116,7 @@ function Base.print(io::TextWidthLimiter, s::String) end function headstring(@nospecialize(T)) - T = Base.unwrapva(T) + T = widenconst(Base.unwrapva(T)) if T isa Union || T === Union{} return string(T) elseif T isa UnionAll @@ -164,6 +180,13 @@ function show_callinfo(limiter, ci::Union{MultiCallInfo, FailedCallInfo, Generat __show_limited(limiter, name, tt, rt) end +function show_callinfo(limiter, ci::ConstPropCallInfo) + # XXX: The first argument could be const-overriden too + name = ci.result.linfo.def.name + tt = ci.result.argtypes[2:end] + __show_limited(limiter, name, tt, ci.mi.rt) +end + function Base.show(io::IO, c::Callsite) limit = get(io, :limit, false)::Bool cols = limit ? (displaysize(io)::Tuple{Int,Int})[2] : typemax(Int) @@ -200,6 +223,12 @@ function Base.show(io::IO, c::Callsite) print(limiter, " = cucall < ") show_callinfo(limiter, c.info.cumi) print(limiter, " >") + elseif isa(c.info, ConstPropCallInfo) + print(limiter, " = < constprop > ") + show_callinfo(limiter, c.info) + elseif isa(c.info, OCCallInfo) + print(limiter, " = < opaque closure call > ") + show_callinfo(limiter, c.info.ci) end end diff --git a/src/codeview.jl b/src/codeview.jl index 44b3d460..45430cf3 100644 --- a/src/codeview.jl +++ b/src/codeview.jl @@ -61,7 +61,7 @@ function cthulhu_warntype(io::IO, src, rettype, debuginfo, stable_code) debuginfo = Base.IRShow.debuginfo(debuginfo) lineprinter = Base.IRShow.__debuginfo[debuginfo] lambda_io::IOContext = io - if src.slotnames !== nothing + if hasfield(typeof(src), :slotnames) && src.slotnames !== nothing slotnames = Base.sourceinfo_slotnames(src) lambda_io = IOContext(lambda_io, :SOURCE_SLOTNAMES => slotnames) show_variables(io, src, slotnames) @@ -69,8 +69,13 @@ function cthulhu_warntype(io::IO, src, rettype, debuginfo, stable_code) print(io, "Body") InteractiveUtils.warntype_type_printer(io, rettype, true) println(io) - ir_printer = stable_code ? Base.IRShow.show_ir : show_ir - ir_printer(lambda_io, src, lineprinter(src), InteractiveUtils.warntype_type_printer) + if isa(src, IRCode) + show(io, src) + # XXX this doesn't properly show warntype + else + ir_printer = stable_code ? Base.IRShow.show_ir : show_ir + ir_printer(lambda_io, src, lineprinter(src), InteractiveUtils.warntype_type_printer) + end return nothing end @@ -81,6 +86,8 @@ function cthulu_typed(io::IO, debuginfo_key, CI, rettype, mi, iswarn, stable_cod if iswarn cthulhu_warntype(io, CI, rettype, debuginfo_key, stable_code) + elseif isa(CI, IRCode) + show(io, CI) else show(io, CI, debuginfo = debuginfo_key) end diff --git a/src/interpreter.jl b/src/interpreter.jl index b2e743b8..e075e011 100644 --- a/src/interpreter.jl +++ b/src/interpreter.jl @@ -1,5 +1,6 @@ using Core.Compiler: AbstractInterpreter, NativeInterpreter, InferenceState, - OptimizationState, CodeInfo, CodeInstance, InferenceResult + OptimizationState, CodeInfo, CodeInstance, InferenceResult, WorldRange, + IRCode, SSAValue, inlining_policy struct InferredSource src::CodeInfo @@ -7,10 +8,15 @@ struct InferredSource rt::Any end +struct OptimizedSource + ir::IRCode + isinlineable::Bool +end + mutable struct CthulhuInterpreter <: AbstractInterpreter native::NativeInterpreter - unopt::Dict{MethodInstance, InferredSource} + unopt::Dict{Union{MethodInstance, InferenceResult}, InferredSource} opt::Dict{MethodInstance, CodeInstance} msgs::Dict{MethodInstance, Vector{Pair{Int, String}}} @@ -18,8 +24,8 @@ end CthulhuInterpreter() = CthulhuInterpreter( NativeInterpreter(), - Dict{MethodInstance, CodeInfo}(), - Dict{MethodInstance, CodeInfo}(), + Dict{MethodInstance, InferredSource}(), + Dict{MethodInstance, CodeInstance}(), Dict{MethodInstance, Vector{Tuple{Int, String}}}() ) @@ -46,6 +52,7 @@ Core.Compiler.setindex!(a::Dict, b, c) = setindex!(a, b, c) Core.Compiler.may_optimize(ei::CthulhuInterpreter) = true Core.Compiler.may_compress(ei::CthulhuInterpreter) = false Core.Compiler.may_discard_trees(ei::CthulhuInterpreter) = false +Core.Compiler.verbose_stmt_info(ei::CthulhuInterpreter) = true function Core.Compiler.add_remark!(ei::CthulhuInterpreter, sv::InferenceState, msg) push!(get!(ei.msgs, sv.linfo, Tuple{Int, String}[]), @@ -54,10 +61,37 @@ end function Core.Compiler.finish(state::InferenceState, ei::CthulhuInterpreter) r = invoke(Core.Compiler.finish, Tuple{InferenceState, AbstractInterpreter}, state, ei) - ei.unopt[state.linfo] = InferredSource( + ei.unopt[Core.Compiler.any(state.result.overridden_by_const) ? state.result : state.linfo] = InferredSource( copy(isa(state.src, OptimizationState) ? state.src.src : state.src), copy(state.stmt_info), state.result.result) return r end + +function Core.Compiler.transform_result_for_cache(interp::CthulhuInterpreter, linfo::MethodInstance, + valid_worlds::Core.Compiler.WorldRange, @nospecialize(inferred_result)) + if isa(inferred_result, OptimizationState) + opt = inferred_result + if isdefined(opt, :ir) + return OptimizedSource(opt.ir, opt.src.inlineable) + end + end + return inferred_result +end + +function Core.Compiler.inlining_policy(interp::CthulhuInterpreter) + function (src) + @assert isa(src, OptimizedSource) + return src.isinlineable ? src.ir : nothing + end +end + +function Core.Compiler.finish!(interp::CthulhuInterpreter, caller::InferenceResult) + if isa(caller.src, OptimizationState) + opt = caller.src + if isdefined(opt, :ir) + caller.src = OptimizedSource(opt.ir, opt.src.inlineable) + end + end +end diff --git a/src/reflection.jl b/src/reflection.jl index 956c4121..0203e39e 100644 --- a/src/reflection.jl +++ b/src/reflection.jl @@ -3,7 +3,9 @@ ## using Base.Meta -using .Compiler: widenconst, argextype, Const +using .Compiler: widenconst, argextype, Const, MethodMatchInfo, + UnionSplitApplyCallInfo, UnionSplitInfo, ConstCallInfo, + MethodResultPure, ApplyCallInfo const is_return_type = Core.Compiler.is_return_type const sptypes_from_meth_instance = Core.Compiler.sptypes_from_meth_instance @@ -35,179 +37,91 @@ function transform(::Val{:CuFunction}, callsite, callexpr, CI, mi, slottypes; pa return Callsite(callsite.id, CuCallInfo(callinfo(Tuple{widenconst(ft), tt.val.parameters...}, Nothing, params=params)), callsite.head) end -function find_callsites(CI::Core.CodeInfo, mi::Core.MethodInstance, slottypes; params=current_params(), multichoose::Bool=false, kwargs...) +function find_callsites(CI::Union{Core.CodeInfo, IRCode}, stmt_info::Union{Vector, Nothing}, mi::Core.MethodInstance, slottypes; params=current_params(), multichoose::Bool=false, kwargs...) sptypes = sptypes_from_meth_instance(mi) callsites = Callsite[] - function process_return_type(id, c, rt) - callinfo = nothing - is_call = isexpr(c, :call) - arg_base = is_call ? 0 : 1 - length(c.args) == (arg_base + 3) || return nothing - ft = argextype(c.args[arg_base + 2], CI, sptypes, slottypes) - if isa(ft, Const) - ft = ft.val - end - if isa(ft, Function) - ft = typeof(ft) - end - argTs = argextype(c.args[arg_base + 3], CI, sptypes, slottypes) - if isa(argTs, Const) - sig = Tuple{widenconst(ft), argTs.val.parameters...} - miinner = multichoose ? choose_method_instance(sig) : first_method_instance(sig) - if miinner !== nothing - callinfo = MICallInfo(miinner, rt.val) - else - callinfo = FailedCallInfo(sig, rt) + for (id, c) in enumerate(isa(CI, IRCode) ? CI.stmts.inst : CI.code) + callsite = nothing + isa(c, Expr) || continue + if stmt_info !== nothing + info = stmt_info[id] + if info !== nothing + rt = argextype(SSAValue(id), CI, sptypes, slottypes) + was_return_type = false + if isa(info, MethodResultPure) + # TODO: We could annotate this in the UI + continue + end + if isa(info, Core.Compiler.ReturnTypeCallInfo) + info = info.info + was_return_type = true + end + function process_info(info) + if isa(info, MethodMatchInfo) + if info.results === missing + return [] + end + + matches = info.results.matches + return map(match->MICallInfo(match, rt), matches) + elseif isa(info, UnionSplitInfo) + return mapreduce(process_info, vcat, info.matches) + elseif isa(info, UnionSplitApplyCallInfo) + return mapreduce(process_info, vcat, info.infos) + elseif isa(info, ApplyCallInfo) + # XXX: This could probably use its own info. For now, + # we ignore any implicit iterate calls. + return process_info(info.call) + elseif isa(info, ConstCallInfo) + result = info.result + return [ConstPropCallInfo(MICallInfo(result.linfo, rt), result)] + elseif isdefined(Compiler, :OpaqueClosureCallInfo) && isa(info, Compiler.OpaqueClosureCallInfo) + return [OCCallInfo(Core.Compiler.specialize_method(info.match), rt)] + elseif isdefined(Compiler, :OpaqueClosureCreateInfo) && isa(info, Compiler.OpaqueClosureCreateInfo) + # TODO: Add ability to descend into OCs at creation site + return [] + elseif info == false + return [] + else + @show CI + @show c + @show info + error() + end + end + callinfos = process_info(info) + if !was_return_type && isempty(callinfos) + continue + end + callsite = let + if length(callinfos) == 1 + callinfo = callinfos[1] + else + types = mapany(arg -> widenconst(argextype(arg, CI, sptypes, slottypes)), c.args) + callinfo = MultiCallInfo(Core.Compiler.argtypes_to_type(types), rt, callinfos) + end + if was_return_type + callinfo = ReturnTypeCallInfo(callinfo) + end + Callsite(id, callinfo, c.head) + end end - else - callinfo = FailedCallInfo(Base.signature_type(ft, argTs), rt) end - return Callsite(id, ReturnTypeCallInfo(callinfo), c.head) - end - function maybefixsplat(t, arg) - if isa(arg, Core.SSAValue) - arg = CI.ssavaluetypes[arg.id] - if isa(arg, Core.Compiler.Const) - arg = arg.val - end - end - if isa(arg, Tuple) - # Redo the type analysis in case there are any DataTypes in the tuple - return Tuple{map(arg -> widenconst(argextype(arg, CI, sptypes, slottypes)), arg)...} - end - return t - end - for (id, c) in enumerate(CI.code) - if c isa Expr - callsite = nothing + if callsite === nothing && c isa Expr if c.head === :(=) c = c.args[2] (c isa Expr) || continue end if c.head === :invoke - rt = CI.ssavaluetypes[id] + rt = argextype(SSAValue(id), CI, sptypes, slottypes) at = argextype(c.args[2], CI, sptypes, slottypes) if isa(at, Const) && is_return_type(at.val) callsite = process_return_type(id, c, rt) else callsite = Callsite(id, MICallInfo(c.args[1], rt), c.head) end - mi = get_mi(callsite) - if nameof(mi.def.module) === :CUDAnative && mi.def.name === :cufunction - callsite = transform(Val(:CuFunction), callsite, c, CI, mi, slottypes; params=params, kwargs...) - elseif callsite.info isa MICallInfo - argtypes_ssa = map(c.args[3:end]) do a - if isa(a, Core.SSAValue) - a = CI.ssavaluetypes[a.id] - return unwrapconst(a) - elseif isa(a, SlotOrArgument) - a = slottypes[_id(a)] - return unwrapconst(a) - elseif isa(a, QuoteNode) - return Core.Typeof(a.value) - end - Core.Typeof(a) - end - sig_ssa = Tuple{Base.tuple_type_head(mi.def.sig), argtypes_ssa...} - if sig_ssa !== mi.def.sig - sig_callinfo = callinfo(sig_ssa, rt) - if get_mi(sig_callinfo) !== mi - callsite = Callsite(id, DeoptimizedCallInfo(sig_callinfo, callsite.info), c.head) - end - end - end - elseif c.head === :call - rt = CI.ssavaluetypes[id] - types = mapany(arg -> widenconst(argextype(arg, CI, sptypes, slottypes)), c.args) - - # Look through _apply - ok = true - while types[1] === typeof(Core._apply) - new_types = Any[types[2]] - for (i, t) in enumerate(types[3:end]) - if i == 1 && t <: Tuple - t = maybefixsplat(t, c.args[3]) - end - if !(t <: Tuple) || t isa Union - ok = false - break - end - append!(new_types, t.parameters) - end - ok || break - types = new_types - end - ok || continue - - # Look through _apply_iterate - if types[1] === typeof(Core._apply_iterate) - ok = true - new_types = Any[types[3]] - for i = 4:length(types) - t = types[i] - if i == 4 && t <: Tuple - t = maybefixsplat(t, c.args[4]) - end - if t <: AbstractArray - if hasmethod(length, (Type{t},)) - for i = 1:length(t) - push!(new_types, eltype(t)) - end - else - push!(new_types, Vararg{eltype(t)}) - i == length(types) || (ok = false) - end - continue - end - if !(t <: Tuple) || t isa Union - ok = false - break - end - append!(new_types, Base.unwrap_unionall(t).parameters) - end - ok || continue - types = new_types - end - - # Filter out builtin functions and intrinsic function - if types[1] <: Core.Builtin || types[1] <: Core.IntrinsicFunction - continue - end - - if isdefined(types[1], :instance) && is_return_type(types[1].instance) - callsite = process_return_type(id, c, rt) - elseif types[1] isa Union - # Union{typeof(sin), typeof(cos)} - fts = Any[] - function thatcher(u) - if u isa Union - thatcher(u.a) - thatcher(u.b) - else - push!(fts, u) - end - end - thatcher(types[1]) - sigs = let types=types - mapany(ft-> Any[ft, types[2:end]...], fts) - end - cis = CallInfo[callinfo(Tuple{t...}, rt, params=params) for t in sigs] - callsite = Callsite(id, MultiCallInfo(Tuple{types...}, rt, cis), c.head) - else - ft = Base.unwrap_unionall(types[1]) - name = ft.name - ci = if nameof(name.module) === :CUDAnative && name.name === Symbol("#kw##cufunction") - ft = types[4] - # XXX: Simplify - tt = types[5].parameters[1].parameters - CuCallInfo(callinfo(Tuple{widenconst(ft), tt...}, Nothing, params=params)) - else - callinfo(Tuple{types...}, rt, params=params) - end - callsite = Callsite(id, ci, c.head) - end elseif c.head === :foreigncall # special handling of jl_new_task length(c.args) > 0 || continue @@ -221,10 +135,17 @@ function find_callsites(CI::Core.CodeInfo, mi::Core.MethodInstance, slottypes; p end end end + end - if callsite !== nothing - push!(callsites, callsite) + if callsite !== nothing + if callsite.info isa MICallInfo + mi = get_mi(callsite) + if nameof(mi.def.module) === :CUDAnative && mi.def.name === :cufunction + callsite = transform(Val(:CuFunction), callsite, c, CI, mi, slottypes; params=params, kwargs...) + end end + + push!(callsites, callsite) end end return callsites @@ -234,13 +155,18 @@ function dce!(ci, mi) argtypes = Core.Compiler.matching_cache_argtypes(mi, nothing)[1] ir = Compiler.inflate_ir(ci, sptypes_from_meth_instance(mi), argtypes) + dce!(ir, mi) + Core.Compiler.replace_code_newstyle!(ci, ir, length(argtypes)-1) +end + +function dce!(ir::IRCode, mi) compact = Core.Compiler.IncrementalCompact(ir, true) # Just run through the iterator without any processing Core.Compiler.foreach(x -> nothing, compact) ir = Core.Compiler.finish(compact) - Core.Compiler.replace_code_newstyle!(ci, ir, length(argtypes)-1) end + function do_typeinf_slottypes(mi::Core.Compiler.MethodInstance, run_optimizer::Bool, interp::Core.Compiler.AbstractInterpreter) ccall(:jl_typeinf_begin, Cvoid, ()) result = Core.Compiler.InferenceResult(mi) @@ -257,7 +183,7 @@ function do_typeinf_slottypes(mi::Core.Compiler.MethodInstance, run_optimizer::B return (frame.src, result.result, frame.slottypes) end -function preprocess_ci!(ci, mi, optimize, config::CthulhuConfig) +function preprocess_ci!(ci::CodeInfo, mi, optimize, config::CthulhuConfig) if optimize && config.dead_code_elimination # if the optimizer hasn't run, the IR hasn't been converted # to SSA form yet and dce is not legal @@ -266,6 +192,15 @@ function preprocess_ci!(ci, mi, optimize, config::CthulhuConfig) end end +function preprocess_ci!(ir::IRCode, mi, optimize, config::CthulhuConfig) + if optimize && config.dead_code_elimination + # if the optimizer hasn't run, the IR hasn't been converted + # to SSA form yet and dce is not legal + ir = dce!(ir, mi) + ir = dce!(ir, mi) + end +end + const CompilerParams = Core.Compiler.NativeInterpreter current_params() = CompilerParams() diff --git a/test/runtests.jl b/test/runtests.jl index 6bdd5011..87d8844f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -23,20 +23,15 @@ function firstassigned(specializations) end function process(@nospecialize(f), @nospecialize(TT); optimize=true) - mi = Cthulhu.first_method_instance(f, TT) - (ci, rt, slottypes) = Cthulhu.do_typeinf_slottypes(mi, optimize, Cthulhu.current_params()) + (interp, mi) = Cthulhu.mkinterp(f, TT) + (ci, rt, infos, slottypes) = Cthulhu.lookup(interp, mi, optimize) Cthulhu.preprocess_ci!(ci, mi, optimize, Cthulhu.CthulhuConfig(dead_code_elimination=true)) - ci, mi, rt, slottypes + ci, infos, mi, rt, slottypes end function find_callsites_by_ftt(@nospecialize(f), @nospecialize(TT); optimize=true) - ci, mi, _, slottypes = process(f, TT; optimize=optimize) - callsites = Cthulhu.find_callsites(ci, mi, slottypes) -end - -# Testing that we don't have spurious calls from `Type` -let callsites = find_callsites_by_ftt(Base.throw_boundserror, Tuple{UnitRange{Int64},Int64}) - @test length(callsites) == 1 + ci, infos, mi, _, slottypes = process(f, TT; optimize=optimize) + callsites = Cthulhu.find_callsites(ci, infos, mi, slottypes) end function test() @@ -104,17 +99,20 @@ end g(x) = @inbounds f(x) h(x) = f(x) -let (CI, _, _, _) = process(g, Tuple{Vector{Float64}}) - @test length(CI.code) == 3 +let (CI, _, _, _, _) = process(g, Tuple{Vector{Float64}}) + @test length(CI.stmts) == 3 end -let (CI, _, _, _) = process(h, Tuple{Vector{Float64}}) - @test length(CI.code) == 2 +let (CI, _, _, _, _) = process(h, Tuple{Vector{Float64}}) + @test length(CI.stmts) == 2 end end -f(a, b) = a + b -let callsites = find_callsites_by_ftt(f, Tuple{Any, Any}) +# Something with many methods, but enough to be under the match limit +g_matches(a::Int, b::Int) = a+b +g_matches(a::Float64, b::Float64) = a+b +f_matches(a, b) = g_matches(a, b) +let callsites = find_callsites_by_ftt(f_matches, Tuple{Any, Any}; optimize=false) @test length(callsites) == 1 callinfo = callsites[1].info @test callinfo isa Cthulhu.MultiCallInfo @@ -128,7 +126,8 @@ let callsites = find_callsites_by_ftt(return_type_failure, Tuple{Float64}, optim callinfo = callsites[1].info @test callinfo isa Cthulhu.ReturnTypeCallInfo callinfo = callinfo.called_mi - @test callinfo isa Cthulhu.FailedCallInfo + @test callinfo isa Cthulhu.MultiCallInfo + @test length(callinfo.callinfos) == 0 end # tasks @@ -231,30 +230,6 @@ let callsites = find_callsites_by_ftt(like_cat, Tuple{Val{3}, Vararg{Matrix{Floa @test mi.specTypes.parameters[4] === Type{Float32} end -@testset "deoptimized calls" begin - @noinline function f(@nospecialize(t)) - s = 0 - for k in t - s += k - end - return s - end - g() = f((1, 2, 3)) - - callsites = find_callsites_by_ftt(g, Tuple{}) - infotypes = [typeof(x.info) for x in callsites] - @test infotypes == [Cthulhu.DeoptimizedCallInfo] - - @noinline f2(@nospecialize(a), @nospecialize(b), @nospecialize(c)) = - (read("/dev/null"); nothing) - h2() = __undef__::Int - g2(a) = f2(a, h2(), 3) - - callsites = find_callsites_by_ftt(g2, Tuple{Int}) - infotypes = [typeof(x.info) for x in callsites] - @test infotypes == [Cthulhu.DeoptimizedCallInfo] -end - @testset "warntype variables" begin src, rettype = code_typed(identity, (Any,); optimize=false)[1] io = IOBuffer() @@ -345,10 +320,10 @@ function foo() T = rand() > 0.5 ? Int64 : Float64 sum(rand(T, 100)) end -ci, mi, rt, st = process(foo, Tuple{}); +ci, infos, mi, rt, slottypes = process(foo, Tuple{}); io = IOBuffer() Cthulhu.cthulu_typed(io, :none, ci, rt, mi, true, false) str = String(take!(io)) print(str) # test by bounding the number of lines printed -@test count(isequal('\n'), str) <= 50 +@test_broken count(isequal('\n'), str) <= 50