From ea6a465488d6a2266cd63f316dffe790d31266ba Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Thu, 3 Apr 2025 16:15:01 -0400 Subject: [PATCH 1/7] trim: Add `Core.finalizer` support This adds the changes required to guess the MethodInstance that the runtime will eventually invoke for this call, enqueue it for codegen, and then verify its presence in the verifier. --- Compiler/src/typeinfer.jl | 54 +++++++++++++++++++++++++++++++++++--- Compiler/src/verifytrim.jl | 14 ++++++++-- base/runtime_internals.jl | 8 +++++- src/gf.c | 1 + 4 files changed, 71 insertions(+), 6 deletions(-) diff --git a/Compiler/src/typeinfer.jl b/Compiler/src/typeinfer.jl index d2709259c8824..f5433ddb9f4a2 100644 --- a/Compiler/src/typeinfer.jl +++ b/Compiler/src/typeinfer.jl @@ -1374,8 +1374,29 @@ function typeinf_type(interp::AbstractInterpreter, mi::MethodInstance) return ci.rettype end +# Resolve a call, as described by `argtype` to a single matching +# Method and return a compilable MethodInstance for the call, if +# it will be runtime-dispatched to exactly that MethodInstance +function compileable_specialization_for_call(interp::AbstractInterpreter, @nospecialize(argtype)) + matches = findall(argtype, method_table(interp); limit = 1) + matches === nothing && return nothing + length(matches.matches) == 0 && return nothing + match = only(matches.matches) + + compileable_atype = get_compileable_sig(match.method, match.spec_types, match.sparams) + compileable_atype === nothing && return nothing + if match.spec_types !== compileable_atype + sp_ = ccall(:jl_type_intersection_with_env, Any, (Any, Any), compileable_atype, method.sig)::SimpleVector + sparams = sp_[2]::SimpleVector + mi = specialize_method(match.method, compileable_atype, sparams) + else + mi = specialize_method(match.method, compileable_atype, match.sparams) + end + return mi +end + # collect a list of all code that is needed along with CodeInstance to codegen it fully -function collectinvokes!(wq::Vector{CodeInstance}, ci::CodeInfo) +function collectinvokes!(wq::Vector{CodeInstance}, ci::CodeInfo, sptypes::Vector{VarState}) src = ci.code for i = 1:length(src) stmt = src[i] @@ -1384,6 +1405,31 @@ function collectinvokes!(wq::Vector{CodeInstance}, ci::CodeInfo) edge = stmt.args[1] edge isa CodeInstance && isdefined(edge, :inferred) && push!(wq, edge) end + + if isexpr(stmt, :call) + farg = stmt.args[1] + !applicable(argextype, farg, ci, sptypes) && continue # TODO: Why is this failing during bootstrap + ftyp = widenconst(argextype(farg, ci, sptypes)) + if ftyp <: Builtin + # TODO: Make interp elsewhere + interp = NativeInterpreter(Base.get_world_counter()) + if Core.finalizer isa ftyp && length(stmt.args) == 3 + finalizer = argextype(stmt.args[2], ci, sptypes) + obj = argextype(stmt.args[3], ci, sptypes) + atype = argtypes_to_type(Any[finalizer, obj]) + + mi = compileable_specialization_for_call(interp, atype) + mi === nothing && continue + + if mi.def.primary_world <= Base.get_world_counter() <= mi.def.deleted_world + ci = typeinf_ext(interp, mi, SOURCE_MODE_GET_SOURCE) + # TODO: separate workqueue for NativeInterpreter + ci isa CodeInstance && push!(wq, ci) + end + # push!(wq, mi) + end + end + end # TODO: handle other StmtInfo like @cfunction and OpaqueClosure? end end @@ -1420,8 +1466,9 @@ function add_codeinsts_to_jit!(interp::AbstractInterpreter, ci, source_mode::UIn end end push!(inspected, callee) - collectinvokes!(tocompile, src) mi = get_ci_mi(callee) + sptypes = sptypes_from_meth_instance(mi) + collectinvokes!(tocompile, src, sptypes) if iszero(ccall(:jl_mi_cache_has_ci, Cint, (Any, Any), mi, callee)) cached = ccall(:jl_get_ci_equiv, Any, (Any, UInt), callee, get_inference_world(interp))::CodeInstance if cached === callee @@ -1519,7 +1566,8 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_m end push!(inspected, callee) if src isa CodeInfo - collectinvokes!(tocompile, src) + sptypes = sptypes_from_meth_instance(mi) + collectinvokes!(tocompile, src, sptypes) # try to reuse an existing CodeInstance from before to avoid making duplicates in the cache if iszero(ccall(:jl_mi_cache_has_ci, Cint, (Any, Any), mi, callee)) cached = ccall(:jl_get_ci_equiv, Any, (Any, UInt), callee, this_world)::CodeInstance diff --git a/Compiler/src/verifytrim.jl b/Compiler/src/verifytrim.jl index 9b49c4b4500c1..298c52113d3a6 100644 --- a/Compiler/src/verifytrim.jl +++ b/Compiler/src/verifytrim.jl @@ -199,6 +199,8 @@ function verify_codeinstance!(codeinst::CodeInstance, codeinfo::CodeInfo, inspec if !may_dispatch(ftyp) continue end + # TODO: Make interp elsewhere + interp = NativeInterpreter(Base.get_world_counter()) if Core._apply_iterate isa ftyp if length(stmt.args) >= 3 # args[1] is _apply_iterate object @@ -219,9 +221,17 @@ function verify_codeinstance!(codeinst::CodeInstance, codeinfo::CodeInfo, inspec end elseif Core.finalizer isa ftyp if length(stmt.args) == 3 - # TODO: check that calling `args[1](args[2])` is defined before warning + finalizer = argextype(stmt.args[2], ci, sptypes) + obj = argextype(stmt.args[3], ci, sptypes) + atype = argtypes_to_type(Any[finalizer, obj]) + + mi = compileable_specialization_for_call(interp, atype) + if mi !== nothing + ci = get(caches, mi, nothing) + ci isa CodeInstance && continue + end + error = "unresolved finalizer registered" - warn = true end else error = "unresolved call to builtin" diff --git a/base/runtime_internals.jl b/base/runtime_internals.jl index d8c90b41842c8..9aae6b2a2b78c 100644 --- a/base/runtime_internals.jl +++ b/base/runtime_internals.jl @@ -1622,12 +1622,18 @@ end is_nospecialized(method::Method) = method.nospecialize ≠ 0 is_nospecializeinfer(method::Method) = method.nospecializeinfer && is_nospecialized(method) + +""" +Return MethodInstance corresponding to `atype` and `sparams`. + +No widening / narrowing / compileable-normalization of `atype` is performed. +""" function specialize_method(method::Method, @nospecialize(atype), sparams::SimpleVector; preexisting::Bool=false) @inline if isa(atype, UnionAll) atype, sparams = normalize_typevars(method, atype, sparams) end - if is_nospecializeinfer(method) + if is_nospecializeinfer(method) # TODO: this shouldn't be here atype = get_nospecializeinfer_sig(method, atype, sparams) end if preexisting diff --git a/src/gf.c b/src/gf.c index b130fbe05ca6d..a42c73e618e83 100644 --- a/src/gf.c +++ b/src/gf.c @@ -3211,6 +3211,7 @@ JL_DLLEXPORT jl_method_instance_t *jl_method_match_to_mi(jl_method_match_t *matc } // compile-time method lookup +// intersect types with the MT, and return a single compileable specialization that covers the intersection. jl_method_instance_t *jl_get_specialization1(jl_tupletype_t *types, size_t world, int mt_cache) { if (jl_has_free_typevars((jl_value_t*)types)) From e4e86a2a6e7442405749526545d254a55a0ec5a5 Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Fri, 4 Apr 2025 15:15:57 -0400 Subject: [PATCH 2/7] some fixes --- Compiler/src/typeinfer.jl | 2 +- Compiler/src/verifytrim.jl | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Compiler/src/typeinfer.jl b/Compiler/src/typeinfer.jl index f5433ddb9f4a2..d79007949d0f9 100644 --- a/Compiler/src/typeinfer.jl +++ b/Compiler/src/typeinfer.jl @@ -1386,7 +1386,7 @@ function compileable_specialization_for_call(interp::AbstractInterpreter, @nospe compileable_atype = get_compileable_sig(match.method, match.spec_types, match.sparams) compileable_atype === nothing && return nothing if match.spec_types !== compileable_atype - sp_ = ccall(:jl_type_intersection_with_env, Any, (Any, Any), compileable_atype, method.sig)::SimpleVector + sp_ = ccall(:jl_type_intersection_with_env, Any, (Any, Any), compileable_atype, match.method.sig)::SimpleVector sparams = sp_[2]::SimpleVector mi = specialize_method(match.method, compileable_atype, sparams) else diff --git a/Compiler/src/verifytrim.jl b/Compiler/src/verifytrim.jl index 298c52113d3a6..19d8bcd623275 100644 --- a/Compiler/src/verifytrim.jl +++ b/Compiler/src/verifytrim.jl @@ -1,6 +1,6 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -import ..Compiler: verify_typeinf_trim +import ..Compiler: verify_typeinf_trim, NativeInterpreter, argtypes_to_type, compileable_specialization_for_call using ..Compiler: # operators @@ -221,8 +221,8 @@ function verify_codeinstance!(codeinst::CodeInstance, codeinfo::CodeInfo, inspec end elseif Core.finalizer isa ftyp if length(stmt.args) == 3 - finalizer = argextype(stmt.args[2], ci, sptypes) - obj = argextype(stmt.args[3], ci, sptypes) + finalizer = argextype(stmt.args[2], codeinfo, sptypes) + obj = argextype(stmt.args[3], codeinfo, sptypes) atype = argtypes_to_type(Any[finalizer, obj]) mi = compileable_specialization_for_call(interp, atype) From d34fb93e4a2837b04a6662295ed9550f8f281005 Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Fri, 4 Apr 2025 15:55:13 -0400 Subject: [PATCH 3/7] typeinfer: add separate compile workqueue for native / call_latest code --- Compiler/src/typeinfer.jl | 210 ++++++++++++++++++++++++-------------- 1 file changed, 131 insertions(+), 79 deletions(-) diff --git a/Compiler/src/typeinfer.jl b/Compiler/src/typeinfer.jl index d79007949d0f9..c3a6e40da937c 100644 --- a/Compiler/src/typeinfer.jl +++ b/Compiler/src/typeinfer.jl @@ -1378,6 +1378,12 @@ end # Method and return a compilable MethodInstance for the call, if # it will be runtime-dispatched to exactly that MethodInstance function compileable_specialization_for_call(interp::AbstractInterpreter, @nospecialize(argtype)) + mt = ccall(:jl_method_table_for, Any, (Any,), argtype) + if mt === nothing + # this would require scanning all method tables, so give up instead + return nothing + end + matches = findall(argtype, method_table(interp); limit = 1) matches === nothing && return nothing length(matches.matches) == 0 && return nothing @@ -1392,18 +1398,44 @@ function compileable_specialization_for_call(interp::AbstractInterpreter, @nospe else mi = specialize_method(match.method, compileable_atype, match.sparams) end + return mi end +const QueueItems = Union{CodeInstance,MethodInstance,SimpleVector} + +struct CompilationQueue + tocompile::Vector{QueueItems} + inspected::IdSet{QueueItems} + interp::Union{AbstractInterpreter,Nothing} + + CompilationQueue(; + interp::Union{AbstractInterpreter,Nothing} + ) = new(QueueItems[], IdSet{QueueItems}(), interp) + + CompilationQueue(queue::CompilationQueue; + interp::Union{AbstractInterpreter,Nothing} + ) = new(empty!(queue.tocompile), empty!(queue.inspected), interp) +end + +Base.push!(queue::CompilationQueue, item) = push!(queue.tocompile, item) +Base.append!(queue::CompilationQueue, items) = append!(queue.tocompile, items) +Base.pop!(queue::CompilationQueue) = pop!(queue.tocompile) +Base.empty!(queue::CompilationQueue) = (empty!(queue.tocompile); empty!(queue.inspected)) +markinspected!(queue::CompilationQueue, item) = push!(queue.inspected, item) +isinspected(queue::CompilationQueue, item) = item in queue.inspected +Base.isempty(queue::CompilationQueue) = isempty(queue.tocompile) + # collect a list of all code that is needed along with CodeInstance to codegen it fully -function collectinvokes!(wq::Vector{CodeInstance}, ci::CodeInfo, sptypes::Vector{VarState}) +function collectinvokes!(workqueue::CompilationQueue, ci::CodeInfo, sptypes::Vector{VarState}; + invokelatest_queue::Union{CompilationQueue,Nothing} = nothing) src = ci.code for i = 1:length(src) stmt = src[i] isexpr(stmt, :(=)) && (stmt = stmt.args[2]) if isexpr(stmt, :invoke) || isexpr(stmt, :invoke_modify) edge = stmt.args[1] - edge isa CodeInstance && isdefined(edge, :inferred) && push!(wq, edge) + edge isa CodeInstance && isdefined(edge, :inferred) && push!(workqueue, edge) end if isexpr(stmt, :call) @@ -1411,22 +1443,19 @@ function collectinvokes!(wq::Vector{CodeInstance}, ci::CodeInfo, sptypes::Vector !applicable(argextype, farg, ci, sptypes) && continue # TODO: Why is this failing during bootstrap ftyp = widenconst(argextype(farg, ci, sptypes)) if ftyp <: Builtin - # TODO: Make interp elsewhere - interp = NativeInterpreter(Base.get_world_counter()) if Core.finalizer isa ftyp && length(stmt.args) == 3 finalizer = argextype(stmt.args[2], ci, sptypes) obj = argextype(stmt.args[3], ci, sptypes) atype = argtypes_to_type(Any[finalizer, obj]) - mi = compileable_specialization_for_call(interp, atype) - mi === nothing && continue + invokelatest_queue === nothing && continue + let workqueue = invokelatest_queue + # make a best-effort attempt to enqueue the relevant code for the finalizer + mi = compileable_specialization_for_call(workqueue.interp, atype) + mi === nothing && continue - if mi.def.primary_world <= Base.get_world_counter() <= mi.def.deleted_world - ci = typeinf_ext(interp, mi, SOURCE_MODE_GET_SOURCE) - # TODO: separate workqueue for NativeInterpreter - ci isa CodeInstance && push!(wq, ci) + push!(workqueue, mi) end - # push!(wq, mi) end end end @@ -1439,14 +1468,13 @@ function add_codeinsts_to_jit!(interp::AbstractInterpreter, ci, source_mode::UIn ci isa CodeInstance && !ci_has_invoke(ci) || return ci codegen = codegen_cache(interp) codegen === nothing && return ci - inspected = IdSet{CodeInstance}() - tocompile = Vector{CodeInstance}() - push!(tocompile, ci) - while !isempty(tocompile) + workqueue = CompilationQueue(; interp) + push!(workqueue, ci) + while !isempty(workqueue) # ci_has_real_invoke(ci) && return ci # optimization: cease looping if ci happens to get compiled (not just jl_fptr_wait_for_compiled, but fully jl_is_compiled_codeinst) - callee = pop!(tocompile) + callee = pop!(workqueue) ci_has_invoke(callee) && continue - callee in inspected && continue + isinspected(workqueue, callee) && continue src = get(codegen, callee, nothing) if !isa(src, CodeInfo) src = @atomic :monotonic callee.inferred @@ -1454,26 +1482,26 @@ function add_codeinsts_to_jit!(interp::AbstractInterpreter, ci, source_mode::UIn src = _uncompressed_ir(callee, src) end if !isa(src, CodeInfo) - newcallee = typeinf_ext(interp, callee.def, source_mode) # always SOURCE_MODE_ABI + newcallee = typeinf_ext(workqueue.interp, callee.def, source_mode) # always SOURCE_MODE_ABI if newcallee isa CodeInstance callee === ci && (ci = newcallee) # ci stopped meeting the requirements after typeinf_ext last checked, try again with newcallee - push!(tocompile, newcallee) + push!(workqueue, newcallee) end if newcallee !== callee - push!(inspected, callee) + markinspected!(workqueue, callee) end continue end end - push!(inspected, callee) + markinspected!(workqueue, callee) mi = get_ci_mi(callee) sptypes = sptypes_from_meth_instance(mi) - collectinvokes!(tocompile, src, sptypes) + collectinvokes!(workqueue, src, sptypes) if iszero(ccall(:jl_mi_cache_has_ci, Cint, (Any, Any), mi, callee)) - cached = ccall(:jl_get_ci_equiv, Any, (Any, UInt), callee, get_inference_world(interp))::CodeInstance + cached = ccall(:jl_get_ci_equiv, Any, (Any, UInt), callee, get_inference_world(workqueue.interp))::CodeInstance if cached === callee # make sure callee is gc-rooted and cached, as required by jl_add_codeinst_to_jit - code_cache(interp)[mi] = callee + code_cache(workqueue.interp)[mi] = callee else # use an existing CI from the cache, if there is available one that is compatible callee === ci && (ci = cached) @@ -1497,57 +1525,45 @@ function typeinf_ext_toplevel(mi::MethodInstance, world::UInt, source_mode::UInt return typeinf_ext_toplevel(interp, mi, source_mode) end -# This is a bridge for the C code calling `jl_typeinf_func()` on set of Method matches -# The trim_mode can be any of: -const TRIM_NO = 0 -const TRIM_SAFE = 1 -const TRIM_UNSAFE = 2 -const TRIM_UNSAFE_WARN = 3 -function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_mode::Int) - inspected = IdSet{CodeInstance}() - tocompile = Vector{CodeInstance}() - codeinfos = [] - # first compute the ABIs of everything - latest = true # whether this_world == world_counter() - for this_world in reverse(sort!(worlds)) - interp = NativeInterpreter( - this_world; - inf_params = InferenceParams(; force_enable_inference = trim_mode != TRIM_NO) - ) - for i = 1:length(methods) - # each item in this list is either a MethodInstance indicating something - # to compile, or an svec(rettype, sig) describing a C-callable alias to create. - item = methods[i] - if item isa MethodInstance - # if this method is generally visible to the current compilation world, - # and this is either the primary world, or not applicable in the primary world - # then we want to compile and emit this - if item.def.primary_world <= this_world <= item.def.deleted_world - ci = typeinf_ext(interp, item, SOURCE_MODE_GET_SOURCE) - ci isa CodeInstance && push!(tocompile, ci) - end - elseif item isa SimpleVector && latest - (rt::Type, sig::Type) = item - # make a best-effort attempt to enqueue the relevant code for the ccallable - ptr = ccall(:jl_get_specialization1, - #= MethodInstance =# Ptr{Cvoid}, (Any, Csize_t, Cint), - sig, this_world, #= mt_cache =# 0) - if ptr !== C_NULL - mi = unsafe_pointer_to_objref(ptr)::MethodInstance - ci = typeinf_ext(interp, mi, SOURCE_MODE_GET_SOURCE) - ci isa CodeInstance && push!(tocompile, ci) - end - # additionally enqueue the ccallable entrypoint / adapter, which implicitly - # invokes the above ci - push!(codeinfos, item) +function compile!(codeinfos::Vector{Any}, workqueue::CompilationQueue; + invokelatest_queue::Union{CompilationQueue,Nothing} = nothing, +) + interp = workqueue.interp + world = get_inference_world(interp) + while !isempty(workqueue) + item = pop!(workqueue) + # each item in this list is either a MethodInstance indicating something + # to compile, or an svec(rettype, sig) describing a C-callable alias to create. + if item isa MethodInstance + isinspected(workqueue, item) && continue + # if this method is generally visible to the current compilation world, + # and this is either the primary world, or not applicable in the primary world + # then we want to compile and emit this + if item.def.primary_world <= world <= item.def.deleted_world + ci = typeinf_ext(interp, item, SOURCE_MODE_GET_SOURCE) + ci isa CodeInstance && push!(workqueue, ci) end - end - while !isempty(tocompile) - callee = pop!(tocompile) - callee in inspected && continue - # now make sure everything has source code, if desired + markinspected!(workqueue, item) + elseif item isa SimpleVector + invokelatest_queue === nothing && continue + (rt::Type, sig::Type) = item + # make a best-effort attempt to enqueue the relevant code for the ccallable + ptr = ccall(:jl_get_specialization1, + #= MethodInstance =# Ptr{Cvoid}, (Any, Csize_t, Cint), + sig, world, #= mt_cache =# 0) + if ptr !== C_NULL + mi = unsafe_pointer_to_objref(ptr)::MethodInstance + ci = typeinf_ext(interp, mi, SOURCE_MODE_GET_SOURCE) + ci isa CodeInstance && push!(invokelatest_queue, ci) + end + # additionally enqueue the ccallable entrypoint / adapter, which implicitly + # invokes the above ci + push!(codeinfos, item) + elseif item isa CodeInstance + callee = item + isinspected(workqueue, callee) && continue mi = get_ci_mi(callee) - def = mi.def + # now make sure everything has source code, if desired if use_const_api(callee) src = codeinfo_for_const(interp, mi, callee.rettype_const) else @@ -1556,21 +1572,21 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_m newcallee = typeinf_ext(interp, mi, SOURCE_MODE_GET_SOURCE) if newcallee isa CodeInstance @assert use_const_api(newcallee) || haskey(interp.codegen, newcallee) - push!(tocompile, newcallee) + push!(workqueue, newcallee) end if newcallee !== callee - push!(inspected, callee) + markinspected!(workqueue, callee) end continue end end - push!(inspected, callee) + markinspected!(workqueue, callee) if src isa CodeInfo sptypes = sptypes_from_meth_instance(mi) - collectinvokes!(tocompile, src, sptypes) + collectinvokes!(workqueue, src, sptypes; invokelatest_queue) # try to reuse an existing CodeInstance from before to avoid making duplicates in the cache if iszero(ccall(:jl_mi_cache_has_ci, Cint, (Any, Any), mi, callee)) - cached = ccall(:jl_get_ci_equiv, Any, (Any, UInt), callee, this_world)::CodeInstance + cached = ccall(:jl_get_ci_equiv, Any, (Any, UInt), callee, world)::CodeInstance if cached === callee code_cache(interp)[mi] = callee else @@ -1581,9 +1597,45 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_m push!(codeinfos, callee) push!(codeinfos, src) end + else @assert false "unexpected item in queue" end + end + return codeinfos +end + +# This is a bridge for the C code calling `jl_typeinf_func()` on set of Method matches +# The trim_mode can be any of: +const TRIM_NO = 0 +const TRIM_SAFE = 1 +const TRIM_UNSAFE = 2 +const TRIM_UNSAFE_WARN = 3 +function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_mode::Int) + inf_params = InferenceParams(; force_enable_inference = trim_mode != TRIM_NO) + invokelatest_queue = CompilationQueue(; + interp = NativeInterpreter(get_world_counter(); inf_params) + ) + codeinfos = [] + is_latest_world = true # whether this_world == world_counter() + workqueue = CompilationQueue(; interp = nothing) + for (i, this_world) in enumerate(sort!(worlds)) + workqueue = CompilationQueue(workqueue; + interp = NativeInterpreter(this_world; inf_params) + ) + + append!(workqueue, methods) + + is_latest_world = (i == length(worlds)) + if is_latest_world + # Provide the `invokelatest` queue so that we trigger "best-effort" code generation + # for, e.g., finalizers and cfunction. + # + # The queue is intentionally aliased, to handle e.g. a `finalizer` calling `Core.finalizer` + # (it will enqueue into itself and immediately drain) + compile!(codeinfos, workqueue; invokelatest_queue = workqueue) + else + compile!(codeinfos, workqueue) end - latest = false end + if trim_mode != TRIM_NO && trim_mode != TRIM_UNSAFE verify_typeinf_trim(codeinfos, trim_mode == TRIM_UNSAFE_WARN) end From 27a8799793e7b4584ff8dfd4cb008451a0c03a0b Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Tue, 8 Apr 2025 22:09:58 -0400 Subject: [PATCH 4/7] Make `empty!(::BitSet)` work during bootstrap --- base/idset.jl | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/base/idset.jl b/base/idset.jl index c2f26b528020d..963da8e7c0f92 100644 --- a/base/idset.jl +++ b/base/idset.jl @@ -92,8 +92,17 @@ function sizehint!(s::IdSet, newsz) nothing end +function _zero!(a::Memory{<:BitInteger}) + t = @_gc_preserve_begin a + p = unsafe_convert(Ptr{Cvoid}, a) + T = eltype(a) + memset(p, 0x0, (sizeof(T) * length(a)) % UInt) + @_gc_preserve_end t + return a +end + function empty!(s::IdSet) - fill!(s.idxs, 0x00) + _zero!(s.idxs) list = s.list for i = 1:s.max _unsetindex!(list, i) From ce56baa3b54dde4e3dc96da5571daf5841d4295f Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Fri, 11 Apr 2025 20:39:15 -0400 Subject: [PATCH 5/7] Collect invoke only for `ftyp === typeof(finalizer)` Otherwise this will match `ftyp === Builtin`, which is pretty silly since that could just as well be any other built-in. --- Compiler/src/typeinfer.jl | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/Compiler/src/typeinfer.jl b/Compiler/src/typeinfer.jl index c3a6e40da937c..bf1cc0b1220e8 100644 --- a/Compiler/src/typeinfer.jl +++ b/Compiler/src/typeinfer.jl @@ -1438,25 +1438,27 @@ function collectinvokes!(workqueue::CompilationQueue, ci::CodeInfo, sptypes::Vec edge isa CodeInstance && isdefined(edge, :inferred) && push!(workqueue, edge) end + invokelatest_queue === nothing && continue if isexpr(stmt, :call) farg = stmt.args[1] !applicable(argextype, farg, ci, sptypes) && continue # TODO: Why is this failing during bootstrap ftyp = widenconst(argextype(farg, ci, sptypes)) - if ftyp <: Builtin - if Core.finalizer isa ftyp && length(stmt.args) == 3 - finalizer = argextype(stmt.args[2], ci, sptypes) - obj = argextype(stmt.args[3], ci, sptypes) - atype = argtypes_to_type(Any[finalizer, obj]) - - invokelatest_queue === nothing && continue - let workqueue = invokelatest_queue - # make a best-effort attempt to enqueue the relevant code for the finalizer - mi = compileable_specialization_for_call(workqueue.interp, atype) - mi === nothing && continue - - push!(workqueue, mi) - end - end + + if ftyp === typeof(Core.finalizer) && length(stmt.args) == 3 + finalizer = argextype(stmt.args[2], ci, sptypes) + obj = argextype(stmt.args[3], ci, sptypes) + atype = argtypes_to_type(Any[finalizer, obj]) + else + # No dynamic dispatch to resolve / enqueue + continue + end + + let workqueue = invokelatest_queue + # make a best-effort attempt to enqueue the relevant code for the finalizer + mi = compileable_specialization_for_call(workqueue.interp, atype) + mi === nothing && continue + + push!(workqueue, mi) end end # TODO: handle other StmtInfo like @cfunction and OpaqueClosure? From d54649f57af9240a4968b00ad900d535981e497d Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Wed, 16 Apr 2025 20:03:33 +0000 Subject: [PATCH 6/7] typeinfer: Work around inference bug See https://github.com/JuliaLang/julia/issues/58143 for details. --- Compiler/src/typeinfer.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Compiler/src/typeinfer.jl b/Compiler/src/typeinfer.jl index bf1cc0b1220e8..9a3b312346225 100644 --- a/Compiler/src/typeinfer.jl +++ b/Compiler/src/typeinfer.jl @@ -1618,14 +1618,12 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_m codeinfos = [] is_latest_world = true # whether this_world == world_counter() workqueue = CompilationQueue(; interp = nothing) - for (i, this_world) in enumerate(sort!(worlds)) + for this_world in reverse!(sort!(worlds)) workqueue = CompilationQueue(workqueue; interp = NativeInterpreter(this_world; inf_params) ) append!(workqueue, methods) - - is_latest_world = (i == length(worlds)) if is_latest_world # Provide the `invokelatest` queue so that we trigger "best-effort" code generation # for, e.g., finalizers and cfunction. @@ -1636,6 +1634,7 @@ function typeinf_ext_toplevel(methods::Vector{Any}, worlds::Vector{UInt}, trim_m else compile!(codeinfos, workqueue) end + is_latest_world = false end if trim_mode != TRIM_NO && trim_mode != TRIM_UNSAFE From 0578395d9c980f496b188a383cc9266bce86d689 Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Wed, 16 Apr 2025 21:50:43 +0000 Subject: [PATCH 7/7] Adjust `--trim` tests for new functionality --- Compiler/test/verifytrim.jl | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/Compiler/test/verifytrim.jl b/Compiler/test/verifytrim.jl index 2462b50b0559a..ad32224be7e15 100644 --- a/Compiler/test/verifytrim.jl +++ b/Compiler/test/verifytrim.jl @@ -18,31 +18,42 @@ let infos = Any[] @test isempty(parents) end +make_cfunction() = @cfunction(+, Float64, (Int64,Int64)) + # use TRIM_UNSAFE to bypass verifier inside typeinf_ext_toplevel -let infos = typeinf_ext_toplevel(Any[Core.svec(Base.SecretBuffer, Tuple{Type{Base.SecretBuffer}})], [Base.get_world_counter()], TRIM_UNSAFE) - @test_broken length(infos) > 4 +let infos = typeinf_ext_toplevel(Any[Core.svec(Ptr{Cvoid}, Tuple{typeof(make_cfunction)})], [Base.get_world_counter()], TRIM_UNSAFE) errors, parents = get_verify_typeinf_trim(infos) @test_broken isempty(errors) # missing cfunction - #resize!(infos, 4) - #errors, = get_verify_typeinf_trim(infos) desc = only(errors) @test desc.first desc = desc.second @test desc isa CallMissing - @test occursin("finalizer", desc.desc) + @test occursin("cfunction", desc.desc) repr = sprint(verify_print_error, desc, parents) @test occursin( - r"""^unresolved finalizer registered from statement \(Core.finalizer\)\(Base.final_shred!, %new\(\)::Base.SecretBuffer\)::Nothing + r"""^unresolved cfunction from statement \$\(Expr\(:cfunction, Ptr{Nothing}, :\(\$\(QuoteNode\(\+\)\)\), Float64, :\(svec\(Int64, Int64\)::Core.SimpleVector\), :\(:ccall\)\)\)::Ptr{Nothing} Stacktrace: - \[1\] finalizer\(f::typeof\(Base.final_shred!\), o::Base.SecretBuffer\) - @ Base gcutils.jl:(\d+) \[inlined\] - \[2\] Base.SecretBuffer\(; sizehint::Int\d\d\) - @ Base secretbuffer.jl:(\d+) \[inlined\] - \[3\] Base.SecretBuffer\(\) - @ Base secretbuffer.jl:(\d+) + \[1\] make_cfunction\(\)""", repr) + + resize!(infos, 1) + @test infos[1] isa Core.SimpleVector && infos[1][1] isa Type && infos[1][2] isa Type + errors, parents = get_verify_typeinf_trim(infos) + desc = only(errors) + @test !desc.first + desc = desc.second + @test desc isa CCallableMissing + @test desc.rt == Ptr{Cvoid} + @test desc.sig == Tuple{typeof(make_cfunction)} + @test occursin("unresolved ccallable", desc.desc) + repr = sprint(verify_print_error, desc, parents) + @test repr == "unresolved ccallable for Tuple{$(typeof(make_cfunction))} => Ptr{Nothing}\n\n" +end - $""", repr) +let infos = typeinf_ext_toplevel(Any[Core.svec(Base.SecretBuffer, Tuple{Type{Base.SecretBuffer}})], [Base.get_world_counter()], TRIM_UNSAFE) + @test length(infos) > 4 + errors, parents = get_verify_typeinf_trim(infos) + @test isempty(errors) resize!(infos, 1) @test infos[1] isa Core.SimpleVector && infos[1][1] isa Type && infos[1][2] isa Type