From 1e1ba0759adfdf8c92ca00beffcf06bf03b2afa9 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Wed, 6 Aug 2025 14:37:40 -0400 Subject: [PATCH] compiler: consolidate staticdata.jl and invalidations.jl into Compiler proper We actually might want a `Base.CompilerUtils` module so that the `Compiler` package itself is smaller and less reliant on internal APIs, but for now just move these into a submodule of Compiler. These are supposed to call back into (and be called by) inference quite heavily in the future, so it makes more sense (and is necessary for the world-age) for them to actually be part of inference. --- Compiler/src/Compiler.jl | 14 ++++-- .../src/bindinginvalidations.jl | 41 ++++++----------- base/staticdata.jl => Compiler/src/reinfer.jl | 45 +++++++++++-------- Compiler/src/typeinfer.jl | 7 +++ base/Base.jl | 3 -- base/Base_compiler.jl | 2 +- base/c.jl | 20 ++++----- base/loading.jl | 2 +- sysimage.mk | 1 - test/precompile.jl | 4 +- 10 files changed, 71 insertions(+), 68 deletions(-) rename base/invalidation.jl => Compiler/src/bindinginvalidations.jl (87%) rename base/staticdata.jl => Compiler/src/reinfer.jl (94%) diff --git a/Compiler/src/Compiler.jl b/Compiler/src/Compiler.jl index e1c167e57ed08..4f3076a806512 100644 --- a/Compiler/src/Compiler.jl +++ b/Compiler/src/Compiler.jl @@ -48,8 +48,8 @@ using Base: @_foldable_meta, @_gc_preserve_begin, @_gc_preserve_end, @nospeciali PARTITION_KIND_GLOBAL, PARTITION_KIND_UNDEF_CONST, PARTITION_KIND_BACKDATED_CONST, PARTITION_KIND_DECLARED, PARTITION_FLAG_DEPWARN, Base, BitVector, Bottom, Callable, DataTypeFieldDesc, - EffectsOverride, Filter, Generator, IteratorSize, JLOptions, NUM_EFFECTS_OVERRIDES, - OneTo, Ordering, RefValue, SizeUnknown, _NAMEDTUPLE_NAME, + EffectsOverride, Filter, Generator, NUM_EFFECTS_OVERRIDES, + OneTo, Ordering, RefValue, _NAMEDTUPLE_NAME, _array_for, _bits_findnext, _methods_by_ftype, _uniontypes, all, allocatedinline, any, argument_datatype, binding_kind, cconvert, copy_exprargs, datatype_arrayelem, datatype_fieldcount, datatype_fieldtypes, datatype_layoutsize, datatype_nfields, @@ -64,9 +64,11 @@ using Base: @_foldable_meta, @_gc_preserve_begin, @_gc_preserve_end, @nospeciali partition_restriction, quoted, rename_unionall, rewrap_unionall, specialize_method, structdiff, tls_world_age, unconstrain_vararg_length, unionlen, uniontype_layout, uniontypes, unsafe_convert, unwrap_unionall, unwrapva, vect, widen_diagonal, - _uncompressed_ir, maybe_add_binding_backedge!, datatype_min_ninitialized, + _uncompressed_ir, datatype_min_ninitialized, partialstruct_init_undefs, fieldcount_noerror, _eval_import, _eval_using, - get_ci_mi + get_ci_mi, get_methodtable, morespecific, specializations, has_image_globalref, + PARTITION_MASK_KIND, PARTITION_KIND_GUARD, PARTITION_FLAG_EXPORTED, PARTITION_FLAG_DEPRECATED, + BINDING_FLAG_ANY_IMPLICIT_EDGES, is_some_implicit, IteratorSize, SizeUnknown, get_require_world, JLOptions using Base.Order @@ -188,6 +190,10 @@ include("bootstrap.jl") include("reflection_interface.jl") include("opaque_closure.jl") +baremodule ReinferUtils end +include(ReinferUtils, "reinfer.jl") +include(ReinferUtils, "bindinginvalidations.jl") + macro __SOURCE_FILE__() __source__.file === nothing && return nothing return QuoteNode(__source__.file::Symbol) diff --git a/base/invalidation.jl b/Compiler/src/bindinginvalidations.jl similarity index 87% rename from base/invalidation.jl rename to Compiler/src/bindinginvalidations.jl index 0a44449748c2f..ce76e5a54a6da 100644 --- a/base/invalidation.jl +++ b/Compiler/src/bindinginvalidations.jl @@ -1,26 +1,17 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -struct GlobalRefIterator - mod::Module -end -IteratorSize(::Type{GlobalRefIterator}) = SizeUnknown() -globalrefs(mod::Module) = GlobalRefIterator(mod) - -function iterate(gri::GlobalRefIterator, i = 1) - m = gri.mod - table = ccall(:jl_module_get_bindings, Ref{SimpleVector}, (Any,), m) - i > length(table) && return nothing - b = table[i] - b === nothing && return iterate(gri, i+1) - return ((b::Core.Binding).globalref, i+1) -end +using ..Compiler: _uncompressed_ir, specializations, get_ci_mi, convert, unsafe_load, cglobal, generating_output, has_image_globalref, + PARTITION_MASK_KIND, PARTITION_KIND_GUARD, PARTITION_FLAG_EXPORTED, PARTITION_FLAG_DEPRECATED, + BINDING_FLAG_ANY_IMPLICIT_EDGES, binding_kind, partition_restriction, is_some_imported, + is_some_binding_imported, is_some_implicit, SizeUnknown, maybe_add_binding_backedge!, walk_binding_partition, abstract_eval_partition_load, userefs +using .Core: SimpleVector, CodeInfo function foreachgr(visit, src::CodeInfo) stmts = src.code for i = 1:length(stmts) stmt = stmts[i] isa(stmt, GlobalRef) && visit(stmt) - for ur in Compiler.userefs(stmt) + for ur in userefs(stmt) arg = ur[] isa(arg, GlobalRef) && visit(arg) end @@ -35,7 +26,7 @@ function anygr(visit, src::CodeInfo) visit(stmt) && return true continue end - for ur in Compiler.userefs(stmt) + for ur in userefs(stmt) arg = ur[] isa(arg, GlobalRef) && visit(arg) && return true end @@ -69,7 +60,7 @@ function invalidate_method_for_globalref!(gr::GlobalRef, method::Method, invalid src = _uncompressed_ir(method) invalidate_all = should_invalidate_code_for_globalref(gr, src) end - if invalidate_all && !Base.generating_output() + if invalidate_all && !generating_output() @atomic method.did_scan_source |= 0x4 end invalidated_any = false @@ -99,15 +90,15 @@ export_affecting_partition_flags(bpart::Core.BindingPartition) = function invalidate_code_for_globalref!(b::Core.Binding, invalidated_bpart::Core.BindingPartition, new_bpart::Core.BindingPartition, new_max_world::UInt) gr = b.globalref - (_, (ib, ibpart)) = Compiler.walk_binding_partition(b, invalidated_bpart, new_max_world) - (_, (nb, nbpart)) = Compiler.walk_binding_partition(b, new_bpart, new_max_world+1) + (_, (ib, ibpart)) = walk_binding_partition(b, invalidated_bpart, new_max_world) + (_, (nb, nbpart)) = walk_binding_partition(b, new_bpart, new_max_world+1) # `abstract_eval_partition_load` is the maximum amount of information that inference # reads from a binding partition. If this information does not change - we do not need to # invalidate any code that inference created, because we know that the result will not change. need_to_invalidate_code = - Compiler.abstract_eval_partition_load(nothing, ib, ibpart) !== - Compiler.abstract_eval_partition_load(nothing, nb, nbpart) + abstract_eval_partition_load(nothing, ib, ibpart) !== + abstract_eval_partition_load(nothing, nb, nbpart) need_to_invalidate_export = export_affecting_partition_flags(invalidated_bpart) !== export_affecting_partition_flags(new_bpart) @@ -173,12 +164,6 @@ end invalidate_code_for_globalref!(gr::GlobalRef, invalidated_bpart::Core.BindingPartition, new_bpart::Core.BindingPartition, new_max_world::UInt) = invalidate_code_for_globalref!(convert(Core.Binding, gr), invalidated_bpart, new_bpart, new_max_world) -function maybe_add_binding_backedge!(b::Core.Binding, edge::Union{Method, CodeInstance}) - meth = isa(edge, Method) ? edge : get_ci_mi(edge).def - ccall(:jl_maybe_add_binding_backedge, Cint, (Any, Any, Any), b, edge, meth) - return nothing -end - function binding_was_invalidated(b::Core.Binding) # At least one partition is required for invalidation !isdefined(b, :partitions) && return false @@ -206,7 +191,7 @@ function scan_new_method!(method::Method, image_backedges_only::Bool) end function scan_new_methods!(extext_methods::Vector{Any}, internal_methods::Vector{Any}, image_backedges_only::Bool) - if image_backedges_only && Base.generating_output(true) + if image_backedges_only && generating_output(true) # Replacing image bindings is forbidden during incremental precompilation - skip backedge insertion return end diff --git a/base/staticdata.jl b/Compiler/src/reinfer.jl similarity index 94% rename from base/staticdata.jl rename to Compiler/src/reinfer.jl index a9ba58f3d82aa..6e543db6f21cb 100644 --- a/base/staticdata.jl +++ b/Compiler/src/reinfer.jl @@ -1,10 +1,13 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +using ..Compiler.Base +using ..Compiler: _findsup, store_backedges, JLOptions, get_world_counter, + _methods_by_ftype, get_methodtable, get_ci_mi, should_instrument, + morespecific, RefValue, get_require_world, Vector, IdDict using .Core: CodeInstance, MethodInstance -using .Base: JLOptions, Compiler, get_world_counter, _methods_by_ftype, get_methodtable, get_ci_mi, morespecific const WORLD_AGE_REVALIDATION_SENTINEL::UInt = 1 -const _jl_debug_method_invalidation = Ref{Union{Nothing,Vector{Any}}}(nothing) +const _jl_debug_method_invalidation = RefValue{Union{Nothing,Vector{Any}}}(nothing) debug_method_invalidation(onoff::Bool) = _jl_debug_method_invalidation[] = onoff ? Any[] : nothing @@ -70,7 +73,7 @@ function insert_backedges(edges::Vector{Any}, ext_ci_list::Union{Nothing,Vector{ # determine which CodeInstance objects are still valid in our image # to enable any applicable new codes backedges_only = unsafe_load(cglobal(:jl_first_image_replacement_world, UInt)) == typemax(UInt) - Base.scan_new_methods!(extext_methods, internal_methods, backedges_only) + scan_new_methods!(extext_methods, internal_methods, backedges_only) workspace = VerifyMethodWorkspace() _insert_backedges(edges, workspace) if ext_ci_list !== nothing @@ -135,23 +138,23 @@ function needs_instrumentation(codeinst::CodeInstance, mi::MethodInstance, def:: if JLOptions().code_coverage != 0 || JLOptions().malloc_log != 0 # test if the code needs to run with instrumentation, in which case we cannot use existing generated code if isdefined(def, :debuginfo) ? # generated_only functions do not have debuginfo, so fall back to considering their codeinst debuginfo though this may be slower and less reliable - Compiler.should_instrument(def.module, def.debuginfo) : - isdefined(codeinst, :debuginfo) && Compiler.should_instrument(def.module, codeinst.debuginfo) + should_instrument(def.module, def.debuginfo) : + isdefined(codeinst, :debuginfo) && should_instrument(def.module, codeinst.debuginfo) return true end gensig = gen_staged_sig(def, mi) if gensig !== nothing # if this is defined by a generator, try to consider forcing re-running the generators too, to add coverage for them - minworld = Ref{UInt}(1) - maxworld = Ref{UInt}(typemax(UInt)) - has_ambig = Ref{Int32}(0) + minworld = RefValue{UInt}(1) + maxworld = RefValue{UInt}(typemax(UInt)) + has_ambig = RefValue{Int32}(0) result = _methods_by_ftype(gensig, nothing, -1, validation_world, #=ambig=#false, minworld, maxworld, has_ambig) if result !== nothing for k = 1:length(result) match = result[k]::Core.MethodMatch genmethod = match.method # no, I refuse to refuse to recurse into your cursed generated function generators and will only test one level deep here - if isdefined(genmethod, :debuginfo) && Compiler.should_instrument(genmethod.module, genmethod.debuginfo) + if isdefined(genmethod, :debuginfo) && should_instrument(genmethod.module, genmethod.debuginfo) return true end end @@ -194,7 +197,7 @@ function verify_method(codeinst::CodeInstance, validation_world::UInt, workspace continue end - minworld, maxworld = Base.get_require_world(), validation_world + minworld, maxworld = get_require_world(), validation_world if haskey(workspace.visiting, initial.codeinst) workspace.result_states[current_depth] = VerifyMethodResultState(workspace.visiting[initial.codeinst], minworld, maxworld) @@ -209,7 +212,7 @@ function verify_method(codeinst::CodeInstance, validation_world::UInt, workspace # Check for invalidation of GlobalRef edges if (initial.def.did_scan_source & 0x1) == 0x0 backedges_only = unsafe_load(cglobal(:jl_first_image_replacement_world, UInt)) == typemax(UInt) - Base.scan_new_method!(initial.def, backedges_only) + scan_new_method!(initial.def, backedges_only) end if (initial.def.did_scan_source & 0x4) != 0x0 maxworld = 0 @@ -220,7 +223,7 @@ function verify_method(codeinst::CodeInstance, validation_world::UInt, workspace end # Process all non-CodeInstance edges - if !isempty(initial.callees) && maxworld != Base.get_require_world() + if !isempty(initial.callees) && maxworld != get_require_world() matches = [] j = 1 while j <= length(initial.callees) @@ -248,7 +251,7 @@ function verify_method(codeinst::CodeInstance, validation_world::UInt, workspace j += 1 min_valid2 = minworld max_valid2 = maxworld - if !Base.binding_was_invalidated(edge) + if !binding_was_invalidated(edge) if isdefined(edge, :partitions) min_valid2 = edge.partitions.min_world max_valid2 = edge.partitions.max_world @@ -333,7 +336,7 @@ function verify_method(codeinst::CodeInstance, validation_world::UInt, workspace end @atomic :monotonic child.max_world = result.result_maxworld if result.result_maxworld == validation_world && validation_world == get_world_counter() - Compiler.store_backedges(child, child.edges) + store_backedges(child, child.edges) end @assert workspace.visiting[child] == length(workspace.stack) + 1 delete!(workspace.visiting, child) @@ -559,9 +562,9 @@ function verify_call(@nospecialize(sig), expecteds::Core.SimpleVector, i::Int, n end # next, compare the current result of ml_matches to the old result lim = _jl_debug_method_invalidation[] !== nothing ? Int(typemax(Int32)) : n - minworld = Ref{UInt}(1) - maxworld = Ref{UInt}(typemax(UInt)) - has_ambig = Ref{Int32}(0) + minworld = RefValue{UInt}(1) + maxworld = RefValue{UInt}(typemax(UInt)) + has_ambig = RefValue{Int32}(0) result = _methods_by_ftype(sig, nothing, lim, world, #=ambig=#false, minworld, maxworld, has_ambig) if result === nothing empty!(matches) @@ -626,7 +629,7 @@ function verify_invokesig(@nospecialize(invokesig), expected::Method, world::UIn minworld = 1 maxworld = 0 else - matched, valid_worlds = Compiler._findsup(invokesig, mt, world) + matched, valid_worlds = _findsup(invokesig, mt, world) minworld, maxworld = valid_worlds.min_world, valid_worlds.max_world if matched === nothing maxworld = 0 @@ -641,3 +644,9 @@ function verify_invokesig(@nospecialize(invokesig), expected::Method, world::UIn end return minworld, maxworld end + +# Wrapper to call insert_backedges in typeinf_world for external calls +function insert_backedges_typeinf(edges::Vector{Any}, ext_ci_list::Union{Nothing,Vector{Any}}, extext_methods::Vector{Any}, internal_methods::Vector{Any}) + args = Any[insert_backedges, edges, ext_ci_list, extext_methods, internal_methods] + return ccall(:jl_call_in_typeinf_world, Any, (Ptr{Any}, Cint), args, length(args)) +end diff --git a/Compiler/src/typeinfer.jl b/Compiler/src/typeinfer.jl index 9849d22a9ce67..2d54ad696b6bf 100644 --- a/Compiler/src/typeinfer.jl +++ b/Compiler/src/typeinfer.jl @@ -741,6 +741,13 @@ function Base.iterate(it::ForwardToBackedgeIterator, i::Int = 1) end # record the backedges + +function maybe_add_binding_backedge!(b::Core.Binding, edge::Union{Method, CodeInstance}) + meth = isa(edge, Method) ? edge : get_ci_mi(edge).def + ccall(:jl_maybe_add_binding_backedge, Cint, (Any, Any, Any), b, edge, meth) + return nothing +end + function store_backedges(caller::CodeInstance, edges::SimpleVector) isa(get_ci_mi(caller).def, Method) || return # don't add backedges to toplevel method instance diff --git a/base/Base.jl b/base/Base.jl index 9d510b5c5d47c..a9d458cb5e9ee 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -267,9 +267,6 @@ include("uuid.jl") include("pkgid.jl") include("toml_parser.jl") include("linking.jl") -module StaticData -include("staticdata.jl") -end include("loading.jl") # BinaryPlatforms, used by Artifacts. Needs `Sort`. diff --git a/base/Base_compiler.jl b/base/Base_compiler.jl index ef448a02a15e9..5d786a325940a 100644 --- a/base/Base_compiler.jl +++ b/base/Base_compiler.jl @@ -348,7 +348,6 @@ using .Order include("coreir.jl") include("module.jl") -include("invalidation.jl") BUILDROOT::String = "" DATAROOT::String = "" @@ -377,6 +376,7 @@ process_sysimg_args!() function isready end include(strcat(DATAROOT, "julia/Compiler/src/Compiler.jl")) +using .Compiler.ReinferUtils: ReinferUtils, invalidate_code_for_globalref! const _return_type = Compiler.return_type diff --git a/base/c.jl b/base/c.jl index 69ea3adf24404..8f0f28d5775f5 100644 --- a/base/c.jl +++ b/base/c.jl @@ -226,16 +226,16 @@ function expand_ccallable(name, rt, def) else f = :(typeof($f)) end - at = map(sig.args[2:end]) do a - if isa(a,Expr) && a.head === :(::) - a.args[end] - else - :Any - end - end + at = Any[let a = sig.args[i] + if isa(a,Expr) && a.head === :(::) + a.args[end] + else + :Any + end + end for i in 2:length(sig.args)] return quote @__doc__ $(esc(def)) - _ccallable($name, $(esc(rt)), $(Expr(:curly, :Tuple, esc(f), map(esc, at)...))) + _ccallable($name, $(esc(rt)), $(Expr(:curly, :Tuple, esc(f), map!(esc, at, at)...))) end end end @@ -349,7 +349,7 @@ function ccall_macro_parse(exprs) end # add any varargs if necessary nreq = 0 - if !isnothing(varargs) + if varargs !== nothing if length(args) == 0 throw(ArgumentError("C ABI prohibits vararg without one required argument")) end @@ -389,7 +389,7 @@ function ccall_macro_lower(convention, func, rettype, types, args, gc_safe, nreq return Expr(:block, statements..., Expr(:call, :ccall, func, cconv, esc(rettype), - Expr(:tuple, map(esc, types)...), map(esc, args)...)) + Expr(:tuple, map!(esc, types, types)...), map!(esc, args, args)...)) end """ diff --git a/base/loading.jl b/base/loading.jl index 33799805e0601..a74a0556d8d25 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -1288,7 +1288,7 @@ function _include_from_serialized(pkg::PkgId, path::String, ocachepath::Union{No extext_methods = sv[5]::Vector{Any} internal_methods = sv[6]::Vector{Any} Compiler.@zone "CC: INSERT_BACKEDGES" begin - StaticData.insert_backedges(edges, ext_edges, extext_methods, internal_methods) + ReinferUtils.insert_backedges_typeinf(edges, ext_edges, extext_methods, internal_methods) end restored = register_restored_modules(sv, pkg, path) diff --git a/sysimage.mk b/sysimage.mk index e98dceb6a0128..296a137c12fcc 100644 --- a/sysimage.mk +++ b/sysimage.mk @@ -57,7 +57,6 @@ COMPILER_SRCS := $(addprefix $(JULIAHOME)/, \ base/int.jl \ base/indices.jl \ base/iterators.jl \ - base/invalidation.jl \ base/module.jl \ base/namedtuple.jl \ base/ntuple.jl \ diff --git a/test/precompile.jl b/test/precompile.jl index 8e091692b68fd..3c8a60cb5dc66 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -1055,9 +1055,9 @@ precompile_test_harness("code caching") do dir const glbi = LogBindingInvalidation(2.0) end) @eval using $StaleC - invalidations = Base.StaticData.debug_method_invalidation(true) + invalidations = Base.ReinferUtils.debug_method_invalidation(true) @eval using $StaleB - Base.StaticData.debug_method_invalidation(false) + Base.ReinferUtils.debug_method_invalidation(false) invokelatest() do MB = getfield(@__MODULE__, StaleB) MC = getfield(@__MODULE__, StaleC)