From c31710a7d93c84b0e1f79c7d9c7ba7bca948ba10 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Thu, 21 Nov 2024 09:10:18 -0500 Subject: [PATCH] Make Expr(:invoke) target be a CodeInstance, not MethodInstance (#54899) This changes our IR representation to use a CodeInstance directly as the invoke function target to specify the ABI in its entirety, instead of just the MethodInstance (specifically for the rettype). That allows removing the lookup call at that point to decide upon the ABI. It is based around the idea that eventually we now keep track of these anyways to form a graph of the inferred edge data, for use later in validation anyways (instead of attempting to invert the backedges graph in staticdata_utils.c), so we might as well use the same target type for the :invoke call representation also now. --- Compiler/src/abstractinterpretation.jl | 11 ++--- Compiler/src/ssair/EscapeAnalysis.jl | 5 ++- Compiler/src/ssair/inlining.jl | 57 +++++++++++++++----------- Compiler/src/ssair/irinterp.jl | 16 +++++--- Compiler/src/ssair/passes.jl | 12 +++--- Compiler/src/ssair/show.jl | 8 +++- Compiler/src/typeinfer.jl | 21 +++++++--- Compiler/test/inline.jl | 16 ++++---- Compiler/test/irutils.jl | 2 +- base/essentials.jl | 2 + base/reflection.jl | 17 +------- src/aotcompile.cpp | 13 +++--- src/cgutils.cpp | 3 +- src/codegen-stubs.c | 2 +- src/codegen.cpp | 34 ++++++--------- src/gf.c | 2 +- src/init.c | 16 +++++++- src/interpreter.c | 5 ++- src/julia.h | 4 -- src/julia_internal.h | 3 +- src/opaque_closure.c | 5 ++- src/precompile_utils.c | 5 ++- stdlib/REPL/src/precompile.jl | 36 ++++------------ 23 files changed, 151 insertions(+), 144 deletions(-) diff --git a/Compiler/src/abstractinterpretation.jl b/Compiler/src/abstractinterpretation.jl index ef08183ee59dd..64181f685e665 100644 --- a/Compiler/src/abstractinterpretation.jl +++ b/Compiler/src/abstractinterpretation.jl @@ -323,11 +323,11 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(fun if mi === nothing || !const_prop_methodinstance_heuristic(interp, mi, arginfo, sv) csig = get_compileable_sig(method, sig, match.sparams) if csig !== nothing && (!seenall || csig !== sig) # corresponds to whether the first look already looked at this, so repeating abstract_call_method is not useful + #println(sig, " changed to ", csig, " for ", method) sp_ = ccall(:jl_type_intersection_with_env, Any, (Any, Any), csig, method.sig)::SimpleVector - if match.sparams === sp_[2] - mresult = abstract_call_method(interp, method, csig, match.sparams, multiple_matches, StmtInfo(false, false), sv)::Future - isready(mresult) || return false # wait for mresult Future to resolve off the callstack before continuing - end + sparams = sp_[2]::SimpleVector + mresult = abstract_call_method(interp, method, csig, sparams, multiple_matches, StmtInfo(false, false), sv)::Future + isready(mresult) || return false # wait for mresult Future to resolve off the callstack before continuing end end end @@ -1365,7 +1365,8 @@ function const_prop_call(interp::AbstractInterpreter, pop!(callstack) return nothing end - inf_result.ci_as_edge = codeinst_as_edge(interp, frame) + existing_edge = result.edge + inf_result.ci_as_edge = codeinst_as_edge(interp, frame, existing_edge) @assert frame.frameid != 0 && frame.cycleid == frame.frameid @assert frame.parentid == sv.frameid @assert inf_result.result !== nothing diff --git a/Compiler/src/ssair/EscapeAnalysis.jl b/Compiler/src/ssair/EscapeAnalysis.jl index a8c450f5bb9e0..47a7840628bb5 100644 --- a/Compiler/src/ssair/EscapeAnalysis.jl +++ b/Compiler/src/ssair/EscapeAnalysis.jl @@ -1068,7 +1068,10 @@ end # escape statically-resolved call, i.e. `Expr(:invoke, ::MethodInstance, ...)` function escape_invoke!(astate::AnalysisState, pc::Int, args::Vector{Any}) - mi = first(args)::MethodInstance + mi = first(args) + if !(mi isa MethodInstance) + mi = (mi::CodeInstance).def # COMBAK get escape info directly from CI instead? + end first_idx, last_idx = 2, length(args) add_liveness_changes!(astate, pc, args, first_idx, last_idx) # TODO inspect `astate.ir.stmts[pc][:info]` and use const-prop'ed `InferenceResult` if available diff --git a/Compiler/src/ssair/inlining.jl b/Compiler/src/ssair/inlining.jl index 02b58b518a72a..0c0d14bf8f25a 100644 --- a/Compiler/src/ssair/inlining.jl +++ b/Compiler/src/ssair/inlining.jl @@ -38,7 +38,7 @@ struct SomeCase end struct InvokeCase - invoke::MethodInstance + invoke::Union{CodeInstance,MethodInstance} effects::Effects info::CallInfo end @@ -764,8 +764,9 @@ function rewrite_apply_exprargs!(todo::Vector{Pair{Int,Any}}, return new_argtypes end -function compileable_specialization(mi::MethodInstance, effects::Effects, +function compileable_specialization(code::Union{MethodInstance,CodeInstance}, effects::Effects, et::InliningEdgeTracker, @nospecialize(info::CallInfo), state::InliningState) + mi = code isa CodeInstance ? code.def : code mi_invoke = mi method, atype, sparams = mi.def::Method, mi.specTypes, mi.sparam_vals if OptimizationParams(state.interp).compilesig_invokes @@ -773,10 +774,10 @@ function compileable_specialization(mi::MethodInstance, effects::Effects, new_atype === nothing && return nothing if atype !== new_atype sp_ = ccall(:jl_type_intersection_with_env, Any, (Any, Any), new_atype, method.sig)::SimpleVector - if sparams === sp_[2]::SimpleVector - mi_invoke = specialize_method(method, new_atype, sparams) - mi_invoke === nothing && return nothing - end + sparams = sp_[2]::SimpleVector + mi_invoke = specialize_method(method, new_atype, sparams) + mi_invoke === nothing && return nothing + code = mi_invoke end else # If this caller does not want us to optimize calls to use their @@ -786,8 +787,15 @@ function compileable_specialization(mi::MethodInstance, effects::Effects, return nothing end end - add_inlining_edge!(et, mi_invoke) # to the dispatch lookup - return InvokeCase(mi_invoke, effects, info) + # prefer using a CodeInstance gotten from the cache, since that is where the invoke target should get compiled to normally + # TODO: can this code be gotten directly from inference sometimes? + code = get(code_cache(state), mi_invoke, nothing) + if !isa(code, CodeInstance) + #println("missing code for ", mi_invoke, " for ", mi) + code = mi_invoke + end + add_inlining_edge!(et, code) # to the code and edges + return InvokeCase(code, effects, info) end struct InferredResult @@ -844,18 +852,18 @@ function resolve_todo(mi::MethodInstance, result::Union{Nothing,InferenceResult, src = @atomic :monotonic inferred_result.inferred effects = decode_effects(inferred_result.ipo_purity_bits) edge = inferred_result - else # there is no cached source available, bail out + else # there is no cached source available for this, but there might be code for the compilation sig return compileable_specialization(mi, Effects(), et, info, state) end # the duplicated check might have been done already within `analyze_method!`, but still # we need it here too since we may come here directly using a constant-prop' result if !OptimizationParams(state.interp).inlining || is_stmt_noinline(flag) - return compileable_specialization(edge.def, effects, et, info, state) + return compileable_specialization(edge, effects, et, info, state) end src_inlining_policy(state.interp, src, info, flag) || - return compileable_specialization(edge.def, effects, et, info, state) + return compileable_specialization(edge, effects, et, info, state) add_inlining_edge!(et, edge) if inferred_result isa CodeInstance @@ -1423,7 +1431,8 @@ end function semiconcrete_result_item(result::SemiConcreteResult, @nospecialize(info::CallInfo), flag::UInt32, state::InliningState) - mi = result.edge.def + code = result.edge + mi = code.def et = InliningEdgeTracker(state) if (!OptimizationParams(state.interp).inlining || is_stmt_noinline(flag) || @@ -1431,10 +1440,10 @@ function semiconcrete_result_item(result::SemiConcreteResult, # a `@noinline`-declared method when it's marked as `@constprop :aggressive`. # Suppress the inlining here (unless inlining is requested at the callsite). (is_declared_noinline(mi.def::Method) && !is_stmt_inline(flag))) - return compileable_specialization(mi, result.effects, et, info, state) + return compileable_specialization(code, result.effects, et, info, state) end src_inlining_policy(state.interp, result.ir, info, flag) || - return compileable_specialization(mi, result.effects, et, info, state) + return compileable_specialization(code, result.effects, et, info, state) add_inlining_edge!(et, result.edge) preserve_local_sources = OptimizationParams(state.interp).preserve_local_sources @@ -1466,7 +1475,7 @@ may_inline_concrete_result(result::ConcreteResult) = function concrete_result_item(result::ConcreteResult, @nospecialize(info::CallInfo), state::InliningState) if !may_inline_concrete_result(result) et = InliningEdgeTracker(state) - return compileable_specialization(result.edge.def, result.effects, et, info, state) + return compileable_specialization(result.edge, result.effects, et, info, state) end @assert result.effects === EFFECTS_TOTAL return ConstantCase(quoted(result.result), result.edge) @@ -1522,11 +1531,7 @@ function handle_modifyop!_call!(ir::IRCode, idx::Int, stmt::Expr, info::ModifyOp match = info.results[1]::MethodMatch match.fully_covers || return nothing edge = info.edges[1] - if edge === nothing - edge = specialize_method(match) - else - edge = edge.def - end + edge === nothing && return nothing case = compileable_specialization(edge, Effects(), InliningEdgeTracker(state), info, state) case === nothing && return nothing stmt.head = :invoke_modify @@ -1564,8 +1569,11 @@ function handle_finalizer_call!(ir::IRCode, idx::Int, stmt::Expr, info::Finalize # `Core.Compiler` data structure into the global cache item1 = cases[1].item if isa(item1, InliningTodo) - push!(stmt.args, true) - push!(stmt.args, item1.mi) + code = get(code_cache(state), item1.mi, nothing) # COMBAK: this seems like a bad design, can we use stmt_info instead to store the correct info? + if code isa CodeInstance + push!(stmt.args, true) + push!(stmt.args, code) + end elseif isa(item1, InvokeCase) push!(stmt.args, false) push!(stmt.args, item1.invoke) @@ -1578,7 +1586,10 @@ end function handle_invoke_expr!(todo::Vector{Pair{Int,Any}}, ir::IRCode, idx::Int, stmt::Expr, @nospecialize(info::CallInfo), flag::UInt32, sig::Signature, state::InliningState) - mi = stmt.args[1]::MethodInstance + mi = stmt.args[1] + if !(mi isa MethodInstance) + mi = (mi::CodeInstance).def + end case = resolve_todo(mi, info, flag, state) handle_single_case!(todo, ir, idx, stmt, case, false) return nothing diff --git a/Compiler/src/ssair/irinterp.jl b/Compiler/src/ssair/irinterp.jl index dd5c907d3c25f..e96d27a85bc37 100644 --- a/Compiler/src/ssair/irinterp.jl +++ b/Compiler/src/ssair/irinterp.jl @@ -33,11 +33,15 @@ end function abstract_eval_invoke_inst(interp::AbstractInterpreter, inst::Instruction, irsv::IRInterpretationState) stmt = inst[:stmt] - mi = stmt.args[1]::MethodInstance - world = frame_world(irsv) - mi_cache = WorldView(code_cache(interp), world) - code = get(mi_cache, mi, nothing) - code === nothing && return Pair{Any,Tuple{Bool,Bool}}(nothing, (false, false)) + ci = stmt.args[1] + if ci isa MethodInstance + world = frame_world(irsv) + mi_cache = WorldView(code_cache(interp), world) + code = get(mi_cache, ci, nothing) + code === nothing && return Pair{Any,Tuple{Bool,Bool}}(nothing, (false, false)) + else + code = ci::CodeInstance + end argtypes = collect_argtypes(interp, stmt.args[2:end], StatementState(nothing, false), irsv) argtypes === nothing && return Pair{Any,Tuple{Bool,Bool}}(Bottom, (false, false)) return concrete_eval_invoke(interp, code, argtypes, irsv) @@ -160,7 +164,7 @@ function reprocess_instruction!(interp::AbstractInterpreter, inst::Instruction, result isa Future && (result = result[]) (; rt, effects) = result add_flag!(inst, flags_for_effects(effects)) - elseif head === :invoke + elseif head === :invoke # COMBAK: || head === :invoke_modifyfield (similar to call, but for args[2:end]) rt, (nothrow, noub) = abstract_eval_invoke_inst(interp, inst, irsv) if nothrow add_flag!(inst, IR_FLAG_NOTHROW) diff --git a/Compiler/src/ssair/passes.jl b/Compiler/src/ssair/passes.jl index dad4a09a3e710..e61f3207fc07a 100644 --- a/Compiler/src/ssair/passes.jl +++ b/Compiler/src/ssair/passes.jl @@ -1302,7 +1302,7 @@ function sroa_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing) # at the end of the intrinsic. Detect that here. if length(stmt.args) == 4 && stmt.args[4] === nothing # constant case - elseif length(stmt.args) == 5 && stmt.args[4] isa Bool && stmt.args[5] isa MethodInstance + elseif length(stmt.args) == 5 && stmt.args[4] isa Bool && stmt.args[5] isa Core.CodeInstance # inlining case else continue @@ -1522,9 +1522,9 @@ end # NOTE we resolve the inlining source here as we don't want to serialize `Core.Compiler` # data structure into the global cache (see the comment in `handle_finalizer_call!`) function try_inline_finalizer!(ir::IRCode, argexprs::Vector{Any}, idx::Int, - mi::MethodInstance, @nospecialize(info::CallInfo), inlining::InliningState, + code::CodeInstance, @nospecialize(info::CallInfo), inlining::InliningState, attach_after::Bool) - code = get(code_cache(inlining), mi, nothing) + mi = code.def et = InliningEdgeTracker(inlining) if code isa CodeInstance if use_const_api(code) @@ -1671,11 +1671,11 @@ function try_resolve_finalizer!(ir::IRCode, alloc_idx::Int, finalizer_idx::Int, if inline === nothing # No code in the function - Nothing to do else - mi = finalizer_stmt.args[5]::MethodInstance - if inline::Bool && try_inline_finalizer!(ir, argexprs, loc, mi, info, inlining, attach_after) + ci = finalizer_stmt.args[5]::CodeInstance + if inline::Bool && try_inline_finalizer!(ir, argexprs, loc, ci, info, inlining, attach_after) # the finalizer body has been inlined else - newinst = add_flag(NewInstruction(Expr(:invoke, mi, argexprs...), Nothing), flag) + newinst = add_flag(NewInstruction(Expr(:invoke, ci, argexprs...), Nothing), flag) insert_node!(ir, loc, newinst, attach_after) end end diff --git a/Compiler/src/ssair/show.jl b/Compiler/src/ssair/show.jl index b9ed220d59453..7d7b182655db7 100644 --- a/Compiler/src/ssair/show.jl +++ b/Compiler/src/ssair/show.jl @@ -92,11 +92,14 @@ function print_stmt(io::IO, idx::Int, @nospecialize(stmt), code::Union{IRCode,Co print(io, ", ") print(io, stmt.typ) print(io, ")") - elseif isexpr(stmt, :invoke) && length(stmt.args) >= 2 && isa(stmt.args[1], MethodInstance) + elseif isexpr(stmt, :invoke) && length(stmt.args) >= 2 && isa(stmt.args[1], Union{MethodInstance,CodeInstance}) stmt = stmt::Expr # TODO: why is this here, and not in Base.show_unquoted printstyled(io, " invoke "; color = :light_black) - mi = stmt.args[1]::Core.MethodInstance + mi = stmt.args[1] + if !(mi isa Core.MethodInstance) + mi = (mi::Core.CodeInstance).def + end show_unquoted(io, stmt.args[2], indent) print(io, "(") # XXX: this is wrong if `sig` is not a concretetype method @@ -110,6 +113,7 @@ function print_stmt(io::IO, idx::Int, @nospecialize(stmt), code::Union{IRCode,Co end join(io, (print_arg(i) for i = 3:length(stmt.args)), ", ") print(io, ")") + # TODO: if we have a CodeInstance, should we print that rettype info here, which may differ (wider or narrower than the ssavaluetypes) elseif isexpr(stmt, :call) && length(stmt.args) >= 1 && label_dynamic_calls ft = maybe_argextype(stmt.args[1], code, sptypes) f = singleton_type(ft) diff --git a/Compiler/src/typeinfer.jl b/Compiler/src/typeinfer.jl index 544c5d5739795..83ec0271ea474 100644 --- a/Compiler/src/typeinfer.jl +++ b/Compiler/src/typeinfer.jl @@ -449,9 +449,10 @@ function finishinfer!(me::InferenceState, interp::AbstractInterpreter) maybe_validate_code(me.linfo, me.src, "inferred") # finish populating inference results into the CodeInstance if possible, and maybe cache that globally for use elsewhere - if isdefined(result, :ci) && !limited_ret + if isdefined(result, :ci) result_type = result.result - @assert !(result_type === nothing || result_type isa LimitedAccuracy) + result_type isa LimitedAccuracy && (result_type = result_type.typ) + @assert !(result_type === nothing) if isa(result_type, Const) rettype_const = result_type.val const_flags = is_result_constabi_eligible(result) ? 0x3 : 0x2 @@ -760,16 +761,24 @@ function MethodCallResult(::AbstractInterpreter, sv::AbsIntState, method::Method return MethodCallResult(rt, exct, effects, edge, edgecycle, edgelimited, volatile_inf_result) end -# allocate a dummy `edge::CodeInstance` to be added by `add_edges!` -function codeinst_as_edge(interp::AbstractInterpreter, sv::InferenceState) +# allocate a dummy `edge::CodeInstance` to be added by `add_edges!`, reusing an existing_edge if possible +# TODO: fill this in fully correctly (currently IPO info such as effects and return types are lost) +function codeinst_as_edge(interp::AbstractInterpreter, sv::InferenceState, @nospecialize existing_edge) mi = sv.linfo - owner = cache_owner(interp) min_world, max_world = first(sv.world.valid_worlds), last(sv.world.valid_worlds) if max_world >= get_world_counter() max_world = typemax(UInt) end edges = Core.svec(sv.edges...) - ci = CodeInstance(mi, owner, Any, Any, nothing, nothing, zero(Int32), + if existing_edge isa CodeInstance + # return an existing_edge, if the existing edge has more restrictions already (more edges and narrower worlds) + if existing_edge.min_world >= min_world && + existing_edge.max_world <= max_world && + existing_edge.edges == edges + return existing_edge + end + end + ci = CodeInstance(mi, cache_owner(interp), Any, Any, nothing, nothing, zero(Int32), min_world, max_world, zero(UInt32), nothing, zero(UInt8), nothing, edges) if max_world == typemax(UInt) # if we can record all of the backedges in the global reverse-cache, diff --git a/Compiler/test/inline.jl b/Compiler/test/inline.jl index 9d828fb7a4cfd..5dbf0a01db4a8 100644 --- a/Compiler/test/inline.jl +++ b/Compiler/test/inline.jl @@ -121,7 +121,7 @@ f29083(;μ,σ) = μ + σ*randn() g29083() = f29083(μ=2.0,σ=0.1) let c = code_typed(g29083, ())[1][1].code # make sure no call to kwfunc remains - @test !any(e->(isa(e,Expr) && (e.head === :invoke && e.args[1].def.name === :kwfunc)), c) + @test !any(e->(isa(e,Expr) && (e.head === :invoke && e.args[1].def.def.name === :kwfunc)), c) end @testset "issue #19122: [no]inline of short func. def. with return type annotation" begin @@ -723,7 +723,7 @@ mktempdir() do dir ci, rt = only(code_typed(issue42246)) if any(ci.code) do stmt Meta.isexpr(stmt, :invoke) && - stmt.args[1].def.name === nameof(IOBuffer) + stmt.args[1].def.def.name === nameof(IOBuffer) end exit(0) else @@ -1797,7 +1797,7 @@ end isinvokemodify(y) = @nospecialize(x) -> isinvokemodify(y, x) isinvokemodify(sym::Symbol, @nospecialize(x)) = isinvokemodify(mi->mi.def.name===sym, x) -isinvokemodify(pred::Function, @nospecialize(x)) = isexpr(x, :invoke_modify) && pred(x.args[1]::MethodInstance) +isinvokemodify(pred::Function, @nospecialize(x)) = isexpr(x, :invoke_modify) && pred((x.args[1]::CodeInstance).def) mutable struct Atomic{T} @atomic x::T @@ -2131,7 +2131,7 @@ let src = code_typed1((Type,)) do x end @test count(src.code) do @nospecialize x isinvoke(:no_compile_sig_invokes, x) && - (x.args[1]::MethodInstance).specTypes == Tuple{typeof(no_compile_sig_invokes),Any} + (x.args[1]::Core.CodeInstance).def.specTypes == Tuple{typeof(no_compile_sig_invokes),Any} end == 1 end let src = code_typed1((Type,); interp=NoCompileSigInvokes()) do x @@ -2139,7 +2139,7 @@ let src = code_typed1((Type,); interp=NoCompileSigInvokes()) do x end @test count(src.code) do @nospecialize x isinvoke(:no_compile_sig_invokes, x) && - (x.args[1]::MethodInstance).specTypes == Tuple{typeof(no_compile_sig_invokes),Type} + (x.args[1]::Core.CodeInstance).def.specTypes == Tuple{typeof(no_compile_sig_invokes),Type} end == 1 end # test the union split case @@ -2148,7 +2148,7 @@ let src = code_typed1((Union{DataType,UnionAll},)) do x end @test count(src.code) do @nospecialize x isinvoke(:no_compile_sig_invokes, x) && - (x.args[1]::MethodInstance).specTypes == Tuple{typeof(no_compile_sig_invokes),Any} + (x.args[1]::Core.CodeInstance).def.specTypes == Tuple{typeof(no_compile_sig_invokes),Any} end == 2 end let src = code_typed1((Union{DataType,UnionAll},); interp=NoCompileSigInvokes()) do x @@ -2156,11 +2156,11 @@ let src = code_typed1((Union{DataType,UnionAll},); interp=NoCompileSigInvokes()) end @test count(src.code) do @nospecialize x isinvoke(:no_compile_sig_invokes, x) && - (x.args[1]::MethodInstance).specTypes == Tuple{typeof(no_compile_sig_invokes),DataType} + (x.args[1]::Core.CodeInstance).def.specTypes == Tuple{typeof(no_compile_sig_invokes),DataType} end == 1 @test count(src.code) do @nospecialize x isinvoke(:no_compile_sig_invokes, x) && - (x.args[1]::MethodInstance).specTypes == Tuple{typeof(no_compile_sig_invokes),UnionAll} + (x.args[1]::Core.CodeInstance).def.specTypes == Tuple{typeof(no_compile_sig_invokes),UnionAll} end == 1 end diff --git a/Compiler/test/irutils.jl b/Compiler/test/irutils.jl index 95525d2f2fe5a..50b3a858d89dc 100644 --- a/Compiler/test/irutils.jl +++ b/Compiler/test/irutils.jl @@ -38,7 +38,7 @@ end # check if `x` is a statically-resolved call of a function whose name is `sym` isinvoke(y) = @nospecialize(x) -> isinvoke(y, x) isinvoke(sym::Symbol, @nospecialize(x)) = isinvoke(mi->mi.def.name===sym, x) -isinvoke(pred::Function, @nospecialize(x)) = isexpr(x, :invoke) && pred(x.args[1]::MethodInstance) +isinvoke(pred::Function, @nospecialize(x)) = isexpr(x, :invoke) && pred((x.args[1]::CodeInstance).def) fully_eliminated(@nospecialize args...; retval=(@__FILE__), kwargs...) = fully_eliminated(code_typed1(args...; kwargs...); retval) diff --git a/base/essentials.jl b/base/essentials.jl index 5683120df8d51..3574116261968 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -1050,6 +1050,7 @@ call obsolete versions of a function `f`. Prior to Julia 1.9, this function was not exported, and was called as `Base.invokelatest`. """ function invokelatest(@nospecialize(f), @nospecialize args...; kwargs...) + @inline kwargs = merge(NamedTuple(), kwargs) if isempty(kwargs) return Core._call_latest(f, args...) @@ -1084,6 +1085,7 @@ of [`invokelatest`](@ref). world age refers to system state unrelated to the main Julia session. """ function invoke_in_world(world::UInt, @nospecialize(f), @nospecialize args...; kwargs...) + @inline kwargs = Base.merge(NamedTuple(), kwargs) if isempty(kwargs) return Core._call_in_world(world, f, args...) diff --git a/base/reflection.jl b/base/reflection.jl index 1b8ed9413a35b..9246b4cb0ac71 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -248,30 +248,17 @@ struct CodegenParams """ trim::Cint - """ - A pointer of type - - typedef jl_value_t *(*jl_codeinstance_lookup_t)(jl_method_instance_t *mi JL_PROPAGATES_ROOT, - size_t min_world, size_t max_world); - - that may be used by external compilers as a callback to look up the code instance corresponding - to a particular method instance. - """ - lookup::Ptr{Cvoid} - function CodegenParams(; track_allocations::Bool=true, code_coverage::Bool=true, prefer_specsig::Bool=false, gnu_pubnames::Bool=true, debug_info_kind::Cint = default_debug_info_kind(), debug_info_level::Cint = Cint(JLOptions().debug_level), safepoint_on_entry::Bool=true, - gcstack_arg::Bool=true, use_jlplt::Bool=true, trim::Cint=Cint(0), - lookup::Ptr{Cvoid}=unsafe_load(cglobal(:jl_rettype_inferred_addr, Ptr{Cvoid}))) + gcstack_arg::Bool=true, use_jlplt::Bool=true, trim::Cint=Cint(0)) return new( Cint(track_allocations), Cint(code_coverage), Cint(prefer_specsig), Cint(gnu_pubnames), debug_info_kind, debug_info_level, Cint(safepoint_on_entry), - Cint(gcstack_arg), Cint(use_jlplt), Cint(trim), - lookup) + Cint(gcstack_arg), Cint(use_jlplt), Cint(trim)) end end diff --git a/src/aotcompile.cpp b/src/aotcompile.cpp index 583a8201587f7..4b3f1f1171ded 100644 --- a/src/aotcompile.cpp +++ b/src/aotcompile.cpp @@ -289,21 +289,22 @@ static void makeSafeName(GlobalObject &G) G.setName(StringRef(SafeName.data(), SafeName.size())); } -jl_code_instance_t *jl_ci_cache_lookup(const jl_cgparams_t &cgparams, jl_method_instance_t *mi, size_t world) +static jl_code_instance_t *jl_ci_cache_lookup(jl_method_instance_t *mi, size_t world, jl_codeinstance_lookup_t lookup) { ++CICacheLookups; - jl_value_t *ci = cgparams.lookup(mi, world, world); + jl_value_t *ci = lookup(mi, world, world); JL_GC_PROMISE_ROOTED(ci); jl_code_instance_t *codeinst = NULL; if (ci != jl_nothing && jl_atomic_load_relaxed(&((jl_code_instance_t *)ci)->inferred) != jl_nothing) { codeinst = (jl_code_instance_t*)ci; } else { - if (cgparams.lookup != jl_rettype_inferred_addr) { + if (lookup != jl_rettype_inferred_addr) { // XXX: This will corrupt and leak a lot of memory which may be very bad jl_error("Refusing to automatically run type inference with custom cache lookup."); } else { + // XXX: SOURCE_MODE_ABI is wrong here (not sufficient) codeinst = jl_type_infer(mi, world, SOURCE_MODE_ABI); /* Even if this codeinst is ordinarily not cacheable, we need to force * it into the cache here, since it was explicitly requested and is @@ -440,13 +441,15 @@ static void compile_workqueue(jl_codegen_params_t ¶ms, CompilationPolicy pol // `_imaging_mode` controls if raw pointers can be embedded (e.g. the code will be loaded into the same session). // `_external_linkage` create linkages between pkgimages. extern "C" JL_DLLEXPORT_CODEGEN -void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvmmod, const jl_cgparams_t *cgparams, int _policy, int _imaging_mode, int _external_linkage, size_t _world) +void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvmmod, const jl_cgparams_t *cgparams, int _policy, int _imaging_mode, int _external_linkage, size_t _world, jl_codeinstance_lookup_t lookup) { JL_TIMING(NATIVE_AOT, NATIVE_Create); ++CreateNativeCalls; CreateNativeMax.updateMax(jl_array_nrows(methods)); if (cgparams == NULL) cgparams = &jl_default_cgparams; + if (lookup == NULL) + lookup = &jl_rettype_inferred_native; jl_native_code_desc_t *data = new jl_native_code_desc_t; CompilationPolicy policy = (CompilationPolicy) _policy; bool imaging = imaging_default() || _imaging_mode == 1; @@ -511,7 +514,7 @@ void *jl_create_native_impl(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvm // then we want to compile and emit this if (jl_atomic_load_relaxed(&mi->def.method->primary_world) <= this_world && this_world <= jl_atomic_load_relaxed(&mi->def.method->deleted_world)) { // find and prepare the source code to compile - jl_code_instance_t *codeinst = jl_ci_cache_lookup(*cgparams, mi, this_world); + jl_code_instance_t *codeinst = jl_ci_cache_lookup(mi, this_world, lookup); if (jl_options.trim != JL_TRIM_NO && !codeinst) { // If we're building a small image, we need to compile everything // to ensure that we have all the information we need. diff --git a/src/cgutils.cpp b/src/cgutils.cpp index a166b0a2c4800..157d253ba4f21 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -4485,8 +4485,7 @@ static int compare_cgparams(const jl_cgparams_t *a, const jl_cgparams_t *b) (a->debug_info_kind == b->debug_info_kind) && (a->safepoint_on_entry == b->safepoint_on_entry) && (a->gcstack_arg == b->gcstack_arg) && - (a->use_jlplt == b->use_jlplt) && - (a->lookup == b->lookup); + (a->use_jlplt == b->use_jlplt); } #endif diff --git a/src/codegen-stubs.c b/src/codegen-stubs.c index 98ac063ba36d6..fe50af3f8e84d 100644 --- a/src/codegen-stubs.c +++ b/src/codegen-stubs.c @@ -70,7 +70,7 @@ JL_DLLEXPORT size_t jl_jit_total_bytes_fallback(void) return 0; } -JL_DLLEXPORT void *jl_create_native_fallback(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvmmod, const jl_cgparams_t *cgparams, int _policy, int _imaging_mode, int _external_linkage, size_t _world) UNAVAILABLE +JL_DLLEXPORT void *jl_create_native_fallback(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvmmod, const jl_cgparams_t *cgparams, int _policy, int _imaging_mode, int _external_linkage, size_t _world, jl_codeinstance_lookup_t lookup) UNAVAILABLE JL_DLLEXPORT void jl_dump_compiles_fallback(void *s) { diff --git a/src/codegen.cpp b/src/codegen.cpp index 85d791052484c..e3225a1a7dec2 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -5496,10 +5496,19 @@ static jl_cgval_t emit_invoke(jl_codectx_t &ctx, const jl_cgval_t &lival, ArrayR bool handled = false; jl_cgval_t result; if (lival.constant) { - jl_method_instance_t *mi = (jl_method_instance_t*)lival.constant; + jl_method_instance_t *mi; + jl_value_t *ci = nullptr; + if (jl_is_method_instance(lival.constant)) { + mi = (jl_method_instance_t*)lival.constant; + } + else { + ci = lival.constant; + assert(jl_is_code_instance(ci)); + mi = ((jl_code_instance_t*)ci)->def; + } assert(jl_is_method_instance(mi)); if (mi == ctx.linfo) { - // handle self-recursion specially + // handle self-recursion specially (TODO: assuming ci is a valid invoke for mi?) jl_returninfo_t::CallingConv cc = jl_returninfo_t::CallingConv::Boxed; FunctionType *ft = ctx.f->getFunctionType(); StringRef protoname = ctx.f->getName(); @@ -5514,8 +5523,7 @@ static jl_cgval_t emit_invoke(jl_codectx_t &ctx, const jl_cgval_t &lival, ArrayR } } else { - jl_value_t *ci = ctx.params->lookup(mi, ctx.min_world, ctx.max_world); - if (ci != jl_nothing) { + if (ci) { jl_code_instance_t *codeinst = (jl_code_instance_t*)ci; auto invoke = jl_atomic_load_acquire(&codeinst->invoke); // check if we know how to handle this specptr @@ -10343,24 +10351,8 @@ int jl_opaque_ptrs_set = 0; extern "C" void jl_init_llvm(void) { - jl_default_cgparams = { - /* track_allocations */ 1, - /* code_coverage */ 1, - /* prefer_specsig */ 0, -#ifdef _OS_WINDOWS_ - /* gnu_pubnames */ 0, -#else - /* gnu_pubnames */ 1, -#endif - /* debug_info_kind */ (int) DICompileUnit::DebugEmissionKind::FullDebug, - /* debug_info_level */ (int) jl_options.debug_level, - /* safepoint_on_entry */ 1, - /* gcstack_arg */ 1, - /* use_jlplt*/ 1, - /* trim */ 0, - /* lookup */ jl_rettype_inferred_addr }; jl_page_size = jl_getpagesize(); - jl_default_debug_info_kind = (int) DICompileUnit::DebugEmissionKind::FullDebug; + jl_default_debug_info_kind = jl_default_cgparams.debug_info_kind = (int) DICompileUnit::DebugEmissionKind::FullDebug; jl_default_cgparams.debug_info_level = (int) jl_options.debug_level; InitializeNativeTarget(); InitializeNativeTargetAsmPrinter(); diff --git a/src/gf.c b/src/gf.c index 97a50cd8339a7..90b874d614b0c 100644 --- a/src/gf.c +++ b/src/gf.c @@ -3196,7 +3196,7 @@ JL_DLLEXPORT int jl_compile_hint(jl_tupletype_t *types) if (mi == NULL) return 0; JL_GC_PROMISE_ROOTED(mi); - jl_compile_method_instance(mi, NULL, world); + jl_compile_method_instance(mi, types, world); return 1; } diff --git a/src/init.c b/src/init.c index b3ca33344d258..1cd14e8556cc6 100644 --- a/src/init.c +++ b/src/init.c @@ -722,7 +722,21 @@ static void restore_fp_env(void) static NOINLINE void _finish_julia_init(JL_IMAGE_SEARCH rel, jl_ptls_t ptls, jl_task_t *ct); JL_DLLEXPORT int jl_default_debug_info_kind; -JL_DLLEXPORT jl_cgparams_t jl_default_cgparams; +JL_DLLEXPORT jl_cgparams_t jl_default_cgparams = { + /* track_allocations */ 1, + /* code_coverage */ 1, + /* prefer_specsig */ 0, +#ifdef _OS_WINDOWS_ + /* gnu_pubnames */ 0, +#else + /* gnu_pubnames */ 1, +#endif + /* debug_info_kind */ 0, // later DICompileUnit::DebugEmissionKind::FullDebug, + /* debug_info_level */ 0, // later jl_options.debug_level, + /* safepoint_on_entry */ 1, + /* gcstack_arg */ 1, + /* use_jlplt*/ 1, + /* trim */ 0 }; static void init_global_mutexes(void) { JL_MUTEX_INIT(&jl_modules_mutex, "jl_modules_mutex"); diff --git a/src/interpreter.c b/src/interpreter.c index cf2ae1a0d9f44..49a3afed14f0c 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -135,8 +135,9 @@ static jl_value_t *do_invoke(jl_value_t **args, size_t nargs, interpreter_state size_t i; for (i = 1; i < nargs; i++) argv[i-1] = eval_value(args[i], s); - jl_method_instance_t *meth = (jl_method_instance_t*)args[0]; - assert(jl_is_method_instance(meth)); + jl_value_t *c = args[0]; + assert(jl_is_code_instance(c) || jl_is_method_instance(c)); + jl_method_instance_t *meth = jl_is_method_instance(c) ? (jl_method_instance_t*)c : ((jl_code_instance_t*)c)->def; jl_value_t *result = jl_invoke(argv[0], nargs == 2 ? NULL : &argv[1], nargs - 2, meth); JL_GC_POP(); return result; diff --git a/src/julia.h b/src/julia.h index 87979f75e8d80..944fd3c43a297 100644 --- a/src/julia.h +++ b/src/julia.h @@ -2650,8 +2650,6 @@ JL_DLLEXPORT void jl_set_safe_restore(jl_jmp_buf *) JL_NOTSAFEPOINT; // codegen interface ---------------------------------------------------------- // The root propagation here doesn't have to be literal, but callers should // ensure that the return value outlives the MethodInstance -typedef jl_value_t *(*jl_codeinstance_lookup_t)(jl_method_instance_t *mi JL_PROPAGATES_ROOT, - size_t min_world, size_t max_world); typedef struct { int track_allocations; // can we track allocations? int code_coverage; // can we measure coverage? @@ -2667,8 +2665,6 @@ typedef struct { int use_jlplt; // Whether to use the Julia PLT mechanism or emit symbols directly int trim; // can we emit dynamic dispatches? - // Cache access. Default: jl_rettype_inferred_native. - jl_codeinstance_lookup_t lookup; } jl_cgparams_t; extern JL_DLLEXPORT int jl_default_debug_info_kind; extern JL_DLLEXPORT jl_cgparams_t jl_default_cgparams; diff --git a/src/julia_internal.h b/src/julia_internal.h index aadcbfdc94038..cd101533f1b8d 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -1946,7 +1946,8 @@ JL_DLLIMPORT jl_value_t *jl_dump_fptr_asm(uint64_t fptr, char emit_mc, const cha JL_DLLIMPORT jl_value_t *jl_dump_function_ir(jl_llvmf_dump_t *dump, char strip_ir_metadata, char dump_module, const char *debuginfo); JL_DLLIMPORT jl_value_t *jl_dump_function_asm(jl_llvmf_dump_t *dump, char emit_mc, const char* asm_variant, const char *debuginfo, char binary, char raw); -JL_DLLIMPORT void *jl_create_native(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvmmod, const jl_cgparams_t *cgparams, int policy, int imaging_mode, int cache, size_t world); +typedef jl_value_t *(*jl_codeinstance_lookup_t)(jl_method_instance_t *mi JL_PROPAGATES_ROOT, size_t min_world, size_t max_world); +JL_DLLIMPORT void *jl_create_native(jl_array_t *methods, LLVMOrcThreadSafeModuleRef llvmmod, const jl_cgparams_t *cgparams, int policy, int imaging_mode, int cache, size_t world, jl_codeinstance_lookup_t lookup); JL_DLLIMPORT void jl_dump_native(void *native_code, const char *bc_fname, const char *unopt_bc_fname, const char *obj_fname, const char *asm_fname, ios_t *z, ios_t *s, jl_emission_params_t *params); diff --git a/src/opaque_closure.c b/src/opaque_closure.c index 65773f88a3951..e3334c037f5a9 100644 --- a/src/opaque_closure.c +++ b/src/opaque_closure.c @@ -53,7 +53,8 @@ static jl_opaque_closure_t *new_opaque_closure(jl_tupletype_t *argt, jl_value_t jl_method_instance_t *mi = NULL; if (source->source) { mi = jl_specializations_get_linfo(source, sigtype, jl_emptysvec); - } else { + } + else { mi = (jl_method_instance_t *)jl_atomic_load_relaxed(&source->specializations); if (!jl_subtype(sigtype, mi->specTypes)) { jl_error("sigtype mismatch in optimized opaque closure"); @@ -116,7 +117,7 @@ static jl_opaque_closure_t *new_opaque_closure(jl_tupletype_t *argt, jl_value_t // OC wrapper methods are not world dependent and have no edges or other info ci = jl_get_method_inferred(mi_generic, selected_rt, 1, ~(size_t)0, NULL, NULL); if (!jl_atomic_load_acquire(&ci->invoke)) - jl_compile_codeinst(ci); + jl_compile_codeinst(ci); // confusing this actually calls jl_emit_oc_wrapper and never actually compiles ci (which would be impossible) specptr = jl_atomic_load_relaxed(&ci->specptr.fptr); } jl_opaque_closure_t *oc = (jl_opaque_closure_t*)jl_gc_alloc(ct->ptls, sizeof(jl_opaque_closure_t), oc_type); diff --git a/src/precompile_utils.c b/src/precompile_utils.c index d008cd26a28e9..81c60ba70d29f 100644 --- a/src/precompile_utils.c +++ b/src/precompile_utils.c @@ -278,7 +278,8 @@ static void *jl_precompile_(jl_array_t *m, int external_linkage) } } void *native_code = jl_create_native(m2, NULL, NULL, 0, 1, external_linkage, - jl_atomic_load_acquire(&jl_world_counter)); + jl_atomic_load_acquire(&jl_world_counter), + NULL); JL_GC_POP(); return native_code; } @@ -389,7 +390,7 @@ static void *jl_precompile_trimmed(size_t world) jl_cgparams_t params = jl_default_cgparams; params.trim = jl_options.trim; void *native_code = jl_create_native(m, NULL, ¶ms, 0, /* imaging */ 1, 0, - world); + world, NULL); JL_GC_POP(); return native_code; } diff --git a/stdlib/REPL/src/precompile.jl b/stdlib/REPL/src/precompile.jl index f7961a205e0b1..daa01f626aeab 100644 --- a/stdlib/REPL/src/precompile.jl +++ b/stdlib/REPL/src/precompile.jl @@ -142,13 +142,13 @@ function repl_workload() # wait for the definitive prompt before start writing to the TTY check_errors(readuntil(output_copy, JULIA_PROMPT)) write(debug_output, "\n#### REPL STARTED ####\n") - sleep(0.1) + sleep(0.01) check_errors(readavailable(output_copy)) # Input our script precompile_lines = split(repl_script::String, '\n'; keepempty=false) curr = 0 for l in precompile_lines - sleep(0.1) + sleep(0.01) # try to let a bit of output accumulate before reading again curr += 1 # consume any other output bytesavailable(output_copy) > 0 && check_errors(readavailable(output_copy)) @@ -168,7 +168,7 @@ function repl_workload() occursin(PKG_PROMPT, strbuf) && break occursin(SHELL_PROMPT, strbuf) && break occursin(HELP_PROMPT, strbuf) && break - sleep(0.1) + sleep(0.01) # try to let a bit of output accumulate before reading again end notify(repl_init_event) check_errors(strbuf) @@ -187,37 +187,15 @@ function repl_workload() nothing end -# Copied from PrecompileTools.jl let - function check_edges(node) - parentmi = node.mi_info.mi - for child in node.children - childmi = child.mi_info.mi - if !(isdefined(childmi, :backedges) && parentmi ∈ childmi.backedges) - precompile(childmi.specTypes) - end - check_edges(child) - end - end - if Base.generating_output() && Base.JLOptions().use_pkgimages != 0 - Core.Compiler.Timings.reset_timings() - Core.Compiler.__set_measure_typeinf(true) - try - repl_workload() - finally - Core.Compiler.__set_measure_typeinf(false) - Core.Compiler.Timings.close_current_timer() - end - roots = Core.Compiler.Timings._timings[1].children - for child in roots - precompile(child.mi_info.mi.specTypes) - check_edges(child) - end + repl_workload() precompile(Tuple{typeof(Base.setindex!), Base.Dict{Any, Any}, Any, Int}) precompile(Tuple{typeof(Base.delete!), Base.Set{Any}, String}) precompile(Tuple{typeof(Base.:(==)), Char, String}) - precompile(Tuple{typeof(Base.reseteof), Base.TTY}) + #for child in copy(Base.newly_inferred) + # precompile((child::Base.CodeInstance).def) + #end end end