diff --git a/Compiler/src/typeinfer.jl b/Compiler/src/typeinfer.jl index ec1e62817106d..e3896870d82b8 100644 --- a/Compiler/src/typeinfer.jl +++ b/Compiler/src/typeinfer.jl @@ -560,12 +560,11 @@ function store_backedges(caller::CodeInstance, edges::SimpleVector) ccall(:jl_method_table_add_backedge, Cvoid, (Any, Any, Any), callee, item, caller) i += 2 continue - end - # `invoke` edge - if isa(callee, Method) + elseif isa(callee, Method) # ignore `Method`-edges (from e.g. failed `abstract_call_method`) i += 2 continue + # `invoke` edge elseif isa(callee, CodeInstance) callee = get_ci_mi(callee) end diff --git a/base/Base.jl b/base/Base.jl index 9119b3a482242..20b1636c29a8d 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -262,6 +262,7 @@ include("uuid.jl") include("pkgid.jl") include("toml_parser.jl") include("linking.jl") +include("staticdata.jl") include("loading.jl") # misc useful functions & macros diff --git a/base/loading.jl b/base/loading.jl index 024c4ceb356fd..4193aae13b96a 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -1280,17 +1280,20 @@ function _include_from_serialized(pkg::PkgId, path::String, ocachepath::Union{No sv = try if ocachepath !== nothing @debug "Loading object cache file $ocachepath for $(repr("text/plain", pkg))" - ccall(:jl_restore_package_image_from_file, Any, (Cstring, Any, Cint, Cstring, Cint), ocachepath, depmods, false, pkg.name, ignore_native) + ccall(:jl_restore_package_image_from_file, Ref{SimpleVector}, (Cstring, Any, Cint, Cstring, Cint), + ocachepath, depmods, #=completeinfo=#false, pkg.name, ignore_native) else @debug "Loading cache file $path for $(repr("text/plain", pkg))" - ccall(:jl_restore_incremental, Any, (Cstring, Any, Cint, Cstring), path, depmods, false, pkg.name) + ccall(:jl_restore_incremental, Ref{SimpleVector}, (Cstring, Any, Cint, Cstring), + path, depmods, #=completeinfo=#false, pkg.name) end finally lock(require_lock) end - if isa(sv, Exception) - return sv - end + + edges = sv[3]::Vector{Any} + ext_edges = sv[4]::Union{Nothing,Vector{Any}} + StaticData.insert_backedges(edges, ext_edges) restored = register_restored_modules(sv, pkg, path) @@ -4198,7 +4201,7 @@ function precompile(@nospecialize(argt::Type)) end # Variants that work for `invoke`d calls for which the signature may not be sufficient -precompile(mi::Core.MethodInstance, world::UInt=get_world_counter()) = +precompile(mi::MethodInstance, world::UInt=get_world_counter()) = (ccall(:jl_compile_method_instance, Cvoid, (Any, Ptr{Cvoid}, UInt), mi, C_NULL, world); return true) """ @@ -4214,7 +4217,7 @@ end function precompile(@nospecialize(argt::Type), m::Method) atype, sparams = ccall(:jl_type_intersection_with_env, Any, (Any, Any), argt, m.sig)::SimpleVector - mi = Core.Compiler.specialize_method(m, atype, sparams) + mi = Base.Compiler.specialize_method(m, atype, sparams) return precompile(mi) end diff --git a/base/staticdata.jl b/base/staticdata.jl new file mode 100644 index 0000000000000..79d81788cc16a --- /dev/null +++ b/base/staticdata.jl @@ -0,0 +1,296 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +module StaticData + +using Core: CodeInstance, MethodInstance +using Base: get_world_counter + +const WORLD_AGE_REVALIDATION_SENTINEL::UInt = 1 +const _jl_debug_method_invalidation = Ref{Union{Nothing,Vector{Any}}}(nothing) +debug_method_invalidation(onoff::Bool) = + _jl_debug_method_invalidation[] = onoff ? Any[] : nothing + +function get_ci_mi(codeinst::CodeInstance) + def = codeinst.def + if def isa Core.ABIOverride + return def.def + else + return def::MethodInstance + end +end + +# Restore backedges to external targets +# `edges` = [caller1, ...], the list of worklist-owned code instances internally +# `ext_ci_list` = [caller1, ...], the list of worklist-owned code instances externally +function insert_backedges(edges::Vector{Any}, ext_ci_list::Union{Nothing,Vector{Any}}) + # determine which CodeInstance objects are still valid in our image + # to enable any applicable new codes + stack = CodeInstance[] + visiting = IdDict{CodeInstance,Int}() + _insert_backedges(edges, stack, visiting) + if ext_ci_list !== nothing + _insert_backedges(ext_ci_list, stack, visiting, #=external=#true) + end +end + +function _insert_backedges(edges::Vector{Any}, stack::Vector{CodeInstance}, visiting::IdDict{CodeInstance,Int}, external::Bool=false) + for i = 1:length(edges) + codeinst = edges[i]::CodeInstance + verify_method_graph(codeinst, stack, visiting) + minvalid = codeinst.min_world + maxvalid = codeinst.max_world + if maxvalid ≥ minvalid + if get_world_counter() == maxvalid + # if this callee is still valid, add all the backedges + Base.Compiler.store_backedges(codeinst, codeinst.edges) + end + if get_world_counter() == maxvalid + maxvalid = typemax(UInt) + @atomic :monotonic codeinst.max_world = maxvalid + end + if external + caller = get_ci_mi(codeinst) + @assert isdefined(codeinst, :inferred) # See #53586, #53109 + inferred = @ccall jl_rettype_inferred( + codeinst.owner::Any, caller::Any, minvalid::UInt, maxvalid::UInt)::Any + if inferred !== nothing + # We already got a code instance for this world age range from + # somewhere else - we don't need this one. + else + @ccall jl_mi_cache_insert(caller::Any, codeinst::Any)::Cvoid + end + end + end + end +end + +function verify_method_graph(codeinst::CodeInstance, stack::Vector{CodeInstance}, visiting::IdDict{CodeInstance,Int}) + @assert isempty(stack); @assert isempty(visiting); + child_cycle, minworld, maxworld = verify_method(codeinst, stack, visiting) + @assert child_cycle == 0 + @assert isempty(stack); @assert isempty(visiting); + nothing +end + +# Test all edges relevant to a method: +# - Visit the entire call graph, starting from edges[idx] to determine if that method is valid +# - Implements Tarjan's SCC (strongly connected components) algorithm, simplified to remove the count variable +# and slightly modified with an early termination option once the computation reaches its minimum +function verify_method(codeinst::CodeInstance, stack::Vector{CodeInstance}, visiting::IdDict{CodeInstance,Int}) + world = codeinst.min_world + let max_valid2 = codeinst.max_world + if max_valid2 ≠ WORLD_AGE_REVALIDATION_SENTINEL + return 0, world, max_valid2 + end + end + current_world = get_world_counter() + local minworld::UInt, maxworld::UInt = 1, current_world + @assert get_ci_mi(codeinst).def isa Method + if haskey(visiting, codeinst) + return visiting[codeinst], minworld, maxworld + end + push!(stack, codeinst) + depth = length(stack) + visiting[codeinst] = depth + # TODO JL_TIMING(VERIFY_IMAGE, VERIFY_Methods) + callees = codeinst.edges + # verify current edges + if isempty(callees) + # quick return: no edges to verify (though we probably shouldn't have gotten here from WORLD_AGE_REVALIDATION_SENTINEL) + elseif maxworld == unsafe_load(cglobal(:jl_require_world, UInt)) + # if no new worlds were allocated since serializing the base module, then no new validation is worth doing right now either + minworld = maxworld + else + j = 1 + while j ≤ length(callees) + local min_valid2::UInt, max_valid2::UInt + edge = callees[j] + @assert !(edge isa Method) # `Method`-edge isn't allowed for the optimized one-edge format + if edge isa Core.BindingPartition + j += 1 + continue + end + if edge isa CodeInstance + edge = get_ci_mi(edge) + end + if edge isa MethodInstance + sig = typeintersect((edge.def::Method).sig, edge.specTypes) # TODO?? + min_valid2, max_valid2, matches = verify_call(sig, callees, j, 1, world) + j += 1 + elseif edge isa Int + sig = callees[j+1] + min_valid2, max_valid2, matches = verify_call(sig, callees, j+2, edge, world) + j += 2 + edge + edge = sig + else + callee = callees[j+1] + if callee isa Core.MethodTable # skip the legacy edge (missing backedge) + j += 2 + continue + end + if callee isa CodeInstance + callee = get_ci_mi(callee) + end + if callee isa MethodInstance + meth = callee.def::Method + else + meth = callee::Method + end + min_valid2, max_valid2 = verify_invokesig(edge, meth, world) + matches = nothing + j += 2 + end + if minworld < min_valid2 + minworld = min_valid2 + end + if maxworld > max_valid2 + maxworld = max_valid2 + end + invalidations = _jl_debug_method_invalidation[] + if max_valid2 ≠ typemax(UInt) && invalidations !== nothing + push!(invalidations, edge, "insert_backedges_callee", codeinst, matches) + end + if max_valid2 == 0 && invalidations === nothing + break + end + end + end + # verify recursive edges (if valid, or debugging) + cycle = depth + cause = codeinst + if maxworld ≠ 0 || _jl_debug_method_invalidation[] !== nothing + for j = 1:length(callees) + edge = callees[j] + if !(edge isa CodeInstance) + continue + end + callee = edge + local min_valid2::UInt, max_valid2::UInt + child_cycle, min_valid2, max_valid2 = verify_method(callee, stack, visiting) + if minworld < min_valid2 + minworld = min_valid2 + end + if minworld > max_valid2 + max_valid2 = 0 + end + if maxworld > max_valid2 + cause = callee + maxworld = max_valid2 + end + if max_valid2 == 0 + # found what we were looking for, so terminate early + break + elseif child_cycle ≠ 0 && child_cycle < cycle + # record the cycle will resolve at depth "cycle" + cycle = child_cycle + end + end + end + if maxworld ≠ 0 && cycle ≠ depth + return cycle, minworld, maxworld + end + # If we are the top of the current cycle, now mark all other parts of + # our cycle with what we found. + # Or if we found a failed edge, also mark all of the other parts of the + # cycle as also having a failed edge. + while length(stack) ≥ depth + child = pop!(stack) + if maxworld ≠ 0 + @atomic :monotonic child.min_world = minworld + end + @atomic :monotonic child.max_world = maxworld + @assert visiting[child] == length(stack) + 1 + delete!(visiting, child) + invalidations = _jl_debug_method_invalidation[] + if invalidations !== nothing && maxworld < current_world + push!(invalidations, child, "verify_methods", cause) + end + end + return 0, minworld, maxworld +end + +function verify_call(@nospecialize(sig), expecteds::Core.SimpleVector, i::Int, n::Int, world::UInt) + # verify that these edges intersect with the same methods as before + lim = _jl_debug_method_invalidation[] !== nothing ? Int(typemax(Int32)) : n + minworld = Ref{UInt}(1) + maxworld = Ref{UInt}(typemax(UInt)) + has_ambig = Ref{Int32}(0) + result = Base._methods_by_ftype(sig, nothing, lim, world, #=ambig=#false, minworld, maxworld, has_ambig) + if result === nothing + maxworld[] = 0 + else + # setdiff!(result, expected) + if length(result) ≠ n + maxworld[] = 0 + end + ins = 0 + for k = 1:length(result) + match = result[k]::Core.MethodMatch + local found = false + for j = 1:n + t = expecteds[i+j-1] + if t isa Method + meth = t + else + if t isa CodeInstance + t = get_ci_mi(t) + else + t = t::MethodInstance + end + meth = t.def::Method + end + if match.method == meth + found = true + break + end + end + if !found + # intersection has a new method or a method was + # deleted--this is now probably no good, just invalidate + # everything about it now + maxworld[] = 0 + if _jl_debug_method_invalidation[] === nothing + break + end + ins += 1 + result[ins] = match.method + end + end + if maxworld[] ≠ typemax(UInt) && _jl_debug_method_invalidation[] !== nothing + resize!(result, ins) + end + end + return minworld[], maxworld[], result +end + +function verify_invokesig(@nospecialize(invokesig), expected::Method, world::UInt) + @assert invokesig isa Type + local minworld::UInt, maxworld::UInt + if invokesig === expected.sig + # the invoke match is `expected` for `expected->sig`, unless `expected` is invalid + minworld = expected.primary_world + maxworld = expected.deleted_world + @assert minworld ≤ world + if maxworld < world + maxworld = 0 + end + else + minworld = 1 + maxworld = typemax(UInt) + mt = Base.get_methodtable(expected) + if mt === nothing + maxworld = 0 + else + matched, valid_worlds = Base.Compiler._findsup(invokesig, mt, world) + minworld, maxworld = valid_worlds.min_world, valid_worlds.max_world + if matched === nothing + maxworld = 0 + elseif matched.method != expected + maxworld = 0 + end + end + end + return minworld, maxworld +end + +end # module StaticData diff --git a/src/staticdata.c b/src/staticdata.c index 4124d3b481bb3..7fad87652b26a 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -89,7 +89,7 @@ External links: #include "julia_assert.h" static const size_t WORLD_AGE_REVALIDATION_SENTINEL = 0x1; -size_t jl_require_world = ~(size_t)0; +JL_DLLEXPORT size_t jl_require_world = ~(size_t)0; #include "staticdata_utils.c" #include "precompile_utils.c" @@ -4110,12 +4110,13 @@ static jl_value_t *jl_restore_package_image_from_stream(void* pkgimage_handle, i jl_atomic_store_release(&jl_world_counter, world); // now permit more methods to be added again JL_UNLOCK(&world_counter_lock); - // but one of those immediate users is going to be our cache insertions - jl_insert_backedges((jl_array_t*)edges, (jl_array_t*)new_ext_cis); // restore existing caches (needs to be last) + // reinit ccallables jl_reinit_ccallable(&ccallable_list, base, pkgimage_handle); arraylist_free(&ccallable_list); + jl_value_t *ext_edges = new_ext_cis ? (jl_value_t*)new_ext_cis : jl_nothing; + if (completeinfo) { cachesizes_sv = jl_alloc_svec(7); jl_svecset(cachesizes_sv, 0, jl_box_long(cachesizes.sysdata)); @@ -4125,12 +4126,11 @@ static jl_value_t *jl_restore_package_image_from_stream(void* pkgimage_handle, i jl_svecset(cachesizes_sv, 4, jl_box_long(cachesizes.reloclist)); jl_svecset(cachesizes_sv, 5, jl_box_long(cachesizes.gvarlist)); jl_svecset(cachesizes_sv, 6, jl_box_long(cachesizes.fptrlist)); - restored = (jl_value_t*)jl_svec(7, restored, init_order, extext_methods, - new_ext_cis ? (jl_value_t*)new_ext_cis : jl_nothing, - method_roots_list, edges, cachesizes_sv); + restored = (jl_value_t*)jl_svec(7, restored, init_order, edges, ext_edges, + extext_methods, method_roots_list, cachesizes_sv); } else { - restored = (jl_value_t*)jl_svec(2, restored, init_order); + restored = (jl_value_t*)jl_svec(4, restored, init_order, edges, ext_edges); } } } diff --git a/src/staticdata_utils.c b/src/staticdata_utils.c index 79d2909970d99..1985357321a3a 100644 --- a/src/staticdata_utils.c +++ b/src/staticdata_utils.c @@ -750,368 +750,6 @@ static void jl_copy_roots(jl_array_t *method_roots_list, uint64_t key) } } -static void verify_invokesig(jl_value_t *invokesig, jl_method_t *expected, size_t world, size_t *minworld, size_t *maxworld) -{ - assert(jl_is_type(invokesig)); - assert(jl_is_method(expected)); - if (jl_egal(invokesig, expected->sig)) { - // the invoke match is `expected` for `expected->sig`, unless `expected` is invalid - *minworld = jl_atomic_load_relaxed(&expected->primary_world); - *maxworld = jl_atomic_load_relaxed(&expected->deleted_world); - assert(*minworld <= world); - if (*maxworld < world) - *maxworld = 0; - } - else { - *minworld = 1; - *maxworld = ~(size_t)0; - jl_methtable_t *mt = jl_method_get_table(expected); - if ((jl_value_t*)mt == jl_nothing) { - *maxworld = 0; - } - else { - jl_value_t *matches = jl_gf_invoke_lookup_worlds(invokesig, (jl_value_t*)mt, world, minworld, maxworld); - if (matches == jl_nothing) { - *maxworld = 0; - } - else { - if (((jl_method_match_t*)matches)->method != expected) { - *maxworld = 0; - } - } - } - } -} - -static void verify_call(jl_value_t *sig, jl_svec_t *expecteds, size_t i, size_t n, size_t world, size_t *minworld, size_t *maxworld, jl_value_t **matches JL_REQUIRE_ROOTED_SLOT) -{ - // verify that these edges intersect with the same methods as before - *minworld = 1; - *maxworld = ~(size_t)0; - int ambig = 0; - // TODO: possibly need to included ambiguities too (for the optimizer correctness)? - jl_value_t *result = jl_matching_methods((jl_tupletype_t*)sig, jl_nothing, - _jl_debug_method_invalidation ? INT32_MAX : n, - 0, world, minworld, maxworld, &ambig); - *matches = result; - if (result == jl_nothing) { - *maxworld = 0; - } - else { - // setdiff!(result, expected) - size_t j, k, ins = 0; - if (jl_array_nrows(result) != n) { - *maxworld = 0; - } - for (k = 0; k < jl_array_nrows(result); k++) { - jl_method_t *match = ((jl_method_match_t*)jl_array_ptr_ref(result, k))->method; - for (j = 0; j < n; j++) { - jl_value_t *t = jl_svecref(expecteds, j + i); - if (jl_is_code_instance(t)) - t = (jl_value_t*)((jl_code_instance_t*)t)->def; - jl_method_t *meth; - if (jl_is_method(t)) - meth = (jl_method_t*)t; - else { - assert(jl_is_method_instance(t)); - meth = ((jl_method_instance_t*)t)->def.method; - } - if (match == meth) - break; - } - if (j == n) { - // intersection has a new method or a method was - // deleted--this is now probably no good, just invalidate - // everything about it now - *maxworld = 0; - if (!_jl_debug_method_invalidation) - break; - jl_array_ptr_set(result, ins++, match); - } - } - if (*maxworld != ~(size_t)0 && _jl_debug_method_invalidation) - jl_array_del_end((jl_array_t*)result, jl_array_nrows(result) - ins); - } -} - -// Test all edges relevant to a method: -//// Visit the entire call graph, starting from edges[idx] to determine if that method is valid -//// Implements Tarjan's SCC (strongly connected components) algorithm, simplified to remove the count variable -//// and slightly modified with an early termination option once the computation reaches its minimum -static int jl_verify_method(jl_code_instance_t *codeinst, size_t *minworld, size_t *maxworld, arraylist_t *stack, htable_t *visiting) -{ - size_t world = jl_atomic_load_relaxed(&codeinst->min_world); - size_t max_valid2 = jl_atomic_load_relaxed(&codeinst->max_world); - if (max_valid2 != WORLD_AGE_REVALIDATION_SENTINEL) { - *minworld = world; - *maxworld = max_valid2; - return 0; - } - *minworld = 1; - size_t current_world = jl_atomic_load_relaxed(&jl_world_counter); - *maxworld = current_world; - assert(jl_is_method_instance(jl_get_ci_mi(codeinst)) && jl_is_method(jl_get_ci_mi(codeinst)->def.method)); - void **bp = ptrhash_bp(visiting, codeinst); - if (*bp != HT_NOTFOUND) - return (char*)*bp - (char*)HT_NOTFOUND; // cycle idx - arraylist_push(stack, (void*)codeinst); - size_t depth = stack->len; - *bp = (char*)HT_NOTFOUND + depth; - JL_TIMING(VERIFY_IMAGE, VERIFY_Methods); - jl_svec_t *callees = jl_atomic_load_relaxed(&codeinst->edges); - assert(jl_is_svec((jl_value_t*)callees)); - // verify current edges - if (callees == jl_emptysvec) { - // quick return: no edges to verify (though we probably shouldn't have gotten here from WORLD_AGE_REVALIDATION_SENTINEL) - } - else if (*maxworld == jl_require_world) { - // if no new worlds were allocated since serializing the base module, then no new validation is worth doing right now either - *minworld = *maxworld; - } - else { - jl_value_t *loctag = NULL; - jl_value_t *sig = NULL; - jl_value_t *matches = NULL; - JL_GC_PUSH3(&loctag, &matches, &sig); - for (size_t j = 0; j < jl_svec_len(callees); ) { - jl_value_t *edge = jl_svecref(callees, j); - size_t min_valid2; - size_t max_valid2; - assert(!jl_is_method(edge)); // `Method`-edge isn't allowed for the optimized one-edge format - if (jl_is_binding_partition(edge)) { - j += 1; - continue; - } - if (jl_is_code_instance(edge)) - edge = (jl_value_t*)jl_get_ci_mi((jl_code_instance_t*)edge); - if (jl_is_method_instance(edge)) { - jl_method_instance_t *mi = (jl_method_instance_t*)edge; - sig = jl_type_intersection(mi->def.method->sig, (jl_value_t*)mi->specTypes); // TODO: ?? - verify_call(sig, callees, j, 1, world, &min_valid2, &max_valid2, &matches); - sig = NULL; - j += 1; - } - else if (jl_is_long(edge)) { - jl_value_t *sig = jl_svecref(callees, j + 1); - size_t nedges = jl_unbox_long(edge); - verify_call(sig, callees, j + 2, nedges, world, &min_valid2, &max_valid2, &matches); - j += 2 + nedges; - edge = sig; - } - else { - jl_method_instance_t *callee = (jl_method_instance_t*)jl_svecref(callees, j + 1); - jl_method_t *meth; - if (jl_is_mtable(callee)) { - // skip the legacy edge (missing backedge) - j += 2; - continue; - } - if (jl_is_code_instance(callee)) - callee = jl_get_ci_mi((jl_code_instance_t*)callee); - if (jl_is_method_instance(callee)) { - meth = callee->def.method; - } - else { - assert(jl_is_method(callee)); - meth = (jl_method_t*)callee; - } - verify_invokesig(edge, meth, world, &min_valid2, &max_valid2); - j += 2; - } - if (*minworld < min_valid2) - *minworld = min_valid2; - if (*maxworld > max_valid2) - *maxworld = max_valid2; - if (max_valid2 != ~(size_t)0 && _jl_debug_method_invalidation) { - jl_array_ptr_1d_push(_jl_debug_method_invalidation, edge); - loctag = jl_cstr_to_string("insert_backedges_callee"); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)codeinst); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, matches); - } - //jl_static_show((JL_STREAM*)ios_stderr, (jl_value_t*)edge); - //ios_puts(max_valid2 == ~(size_t)0 ? "valid\n" : "INVALID\n", ios_stderr); - if (max_valid2 == 0 && !_jl_debug_method_invalidation) - break; - } - JL_GC_POP(); - } - // verify recursive edges (if valid, or debugging) - size_t cycle = depth; - jl_code_instance_t *cause = codeinst; - if (*maxworld != 0 || _jl_debug_method_invalidation) { - for (size_t j = 0; j < jl_svec_len(callees); j++) { - jl_value_t *edge = jl_svecref(callees, j); - if (!jl_is_code_instance(edge)) - continue; - jl_code_instance_t *callee = (jl_code_instance_t*)edge; - size_t min_valid2; - size_t max_valid2; - size_t child_cycle = jl_verify_method(callee, &min_valid2, &max_valid2, stack, visiting); - if (*minworld < min_valid2) - *minworld = min_valid2; - if (*minworld > max_valid2) - max_valid2 = 0; - if (*maxworld > max_valid2) { - cause = callee; - *maxworld = max_valid2; - } - if (max_valid2 == 0) { - // found what we were looking for, so terminate early - break; - } - else if (child_cycle && child_cycle < cycle) { - // record the cycle will resolve at depth "cycle" - cycle = child_cycle; - } - } - } - if (*maxworld != 0 && cycle != depth) - return cycle; - // If we are the top of the current cycle, now mark all other parts of - // our cycle with what we found. - // Or if we found a failed edge, also mark all of the other parts of the - // cycle as also having a failed edge. - while (stack->len >= depth) { - jl_code_instance_t *child = (jl_code_instance_t*)arraylist_pop(stack); - if (jl_atomic_load_relaxed(&jl_n_threads) == 1) { - // a different thread might simultaneously come to a different, but equally valid, alternative result - assert(jl_atomic_load_relaxed(&child->max_world) == WORLD_AGE_REVALIDATION_SENTINEL); - assert(*minworld <= jl_atomic_load_relaxed(&child->min_world)); - } - if (*maxworld != 0) - jl_atomic_store_relaxed(&child->min_world, *minworld); - jl_atomic_store_relaxed(&child->max_world, *maxworld); - void **bp = ptrhash_bp(visiting, child); - assert(*bp == (char*)HT_NOTFOUND + stack->len + 1); - *bp = HT_NOTFOUND; - if (_jl_debug_method_invalidation && *maxworld < current_world) { - jl_value_t *loctag; - jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)child); - loctag = jl_cstr_to_string("verify_methods"); - JL_GC_PUSH1(&loctag); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)cause); - JL_GC_POP(); - } - } - //jl_static_show((JL_STREAM*)ios_stderr, (jl_value_t*)codeinst->def); - //ios_puts(max_valid == ~(size_t)0 ? "valid\n\n" : "INVALID\n\n", ios_stderr); - return 0; -} - -static void jl_verify_method_graph(jl_code_instance_t *codeinst, arraylist_t *stack, htable_t *visiting) -{ - size_t minworld; - size_t maxworld; - assert(stack->len == 0); - for (size_t i = 0, hsz = visiting->size; i < hsz; i++) - assert(visiting->table[i] == HT_NOTFOUND); - int child_cycle = jl_verify_method(codeinst, &minworld, &maxworld, stack, visiting); - assert(child_cycle == 0); (void)child_cycle; - assert(stack->len == 0); - for (size_t i = 0, hsz = visiting->size / 2; i < hsz; i++) { - assert(visiting->table[2 * i + 1] == HT_NOTFOUND); - visiting->table[2 * i] = HT_NOTFOUND; - } - if (jl_atomic_load_relaxed(&jl_n_threads) == 1) { // a different thread might simultaneously come to a different, but equally valid, alternative result - assert(maxworld == 0 || jl_atomic_load_relaxed(&codeinst->min_world) == minworld); - assert(jl_atomic_load_relaxed(&codeinst->max_world) == maxworld); - } -} - -// Restore backedges to external targets -// `edges` = [caller1, ...], the list of worklist-owned code instances internally -// `ext_ci_list` = [caller1, ...], the list of worklist-owned code instances externally -static void jl_insert_backedges(jl_array_t *edges, jl_array_t *ext_ci_list) -{ - // determine which CodeInstance objects are still valid in our image - // to enable any applicable new codes - arraylist_t stack; - arraylist_new(&stack, 0); - htable_t visiting; - htable_new(&visiting, 0); - for (size_t external = 0; external < (ext_ci_list ? 2 : 1); external++) { - if (external) - edges = ext_ci_list; - size_t nedges = jl_array_nrows(edges); - for (size_t i = 0; i < nedges; i++) { - jl_code_instance_t *codeinst = (jl_code_instance_t*)jl_array_ptr_ref(edges, i); - jl_svec_t *callees = jl_atomic_load_relaxed(&codeinst->edges); - jl_method_instance_t *caller = jl_get_ci_mi(codeinst); - jl_verify_method_graph(codeinst, &stack, &visiting); - size_t minvalid = jl_atomic_load_relaxed(&codeinst->min_world); - size_t maxvalid = jl_atomic_load_relaxed(&codeinst->max_world); - if (maxvalid >= minvalid) { - if (jl_atomic_load_relaxed(&jl_world_counter) == maxvalid) { - // if this callee is still valid, add all the backedges - for (size_t j = 0; j < jl_svec_len(callees); ) { - jl_value_t *edge = jl_svecref(callees, j); - if (jl_is_long(edge)) { - j += 2; // skip over signature and count but not methods - continue; - } - else if (jl_is_method(edge)) { - j += 1; - continue; - } - else if (jl_is_binding_partition(edge)) { - j += 1; - continue; - } - if (jl_is_code_instance(edge)) - edge = (jl_value_t*)((jl_code_instance_t*)edge)->def; - if (jl_is_method_instance(edge)) { - jl_method_instance_add_backedge((jl_method_instance_t*)edge, NULL, codeinst); - j += 1; - } - else { - jl_value_t *callee = jl_svecref(callees, j + 1); - if (jl_is_mtable(callee)) { - jl_methtable_t *mt = (jl_methtable_t*)callee; - jl_method_table_add_backedge(mt, edge, codeinst); - j += 2; - continue; - } - else if (jl_is_code_instance(callee)) { - callee = (jl_value_t*)((jl_code_instance_t*)callee)->def; - } - else if (jl_is_method(callee)) { - j += 2; - continue; - } - jl_method_instance_add_backedge((jl_method_instance_t*)callee, edge, codeinst); - j += 2; - } - } - } - if (jl_atomic_load_relaxed(&jl_world_counter) == maxvalid) { - maxvalid = ~(size_t)0; - jl_atomic_store_relaxed(&codeinst->max_world, maxvalid); - } - if (external) { - jl_value_t *owner = codeinst->owner; - JL_GC_PROMISE_ROOTED(owner); - - // See #53586, #53109 - assert(jl_atomic_load_relaxed(&codeinst->inferred)); - - if (jl_rettype_inferred(owner, caller, minvalid, maxvalid) != jl_nothing) { - // We already got a code instance for this world age range from somewhere else - we don't need - // this one. - } - else { - jl_mi_cache_insert(caller, codeinst); - } - } - } - } - } - - htable_free(&visiting); - arraylist_free(&stack); -} - static jl_value_t *read_verify_mod_list(ios_t *s, jl_array_t *depmods) { if (!jl_main_module->build_id.lo) { diff --git a/test/precompile.jl b/test/precompile.jl index 3b193e1facd7c..78a96250600a4 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -1004,9 +1004,9 @@ precompile_test_harness("code caching") do dir MA = getfield(@__MODULE__, StaleA) Base.eval(MA, :(nbits(::UInt8) = 8)) @eval using $StaleC - invalidations = ccall(:jl_debug_method_invalidation, Any, (Cint,), 1) + invalidations = Base.StaticData.debug_method_invalidation(true) @eval using $StaleB - ccall(:jl_debug_method_invalidation, Any, (Cint,), 0) + Base.StaticData.debug_method_invalidation(false) MB = getfield(@__MODULE__, StaleB) MC = getfield(@__MODULE__, StaleC) world = Base.get_world_counter() @@ -1820,12 +1820,14 @@ precompile_test_harness("PkgCacheInspector") do load_path end if ocachefile !== nothing - sv = ccall(:jl_restore_package_image_from_file, Any, (Cstring, Any, Cint, Cstring, Cint), ocachefile, depmods, true, "PCI", false) + sv = ccall(:jl_restore_package_image_from_file, Any, (Cstring, Any, Cint, Cstring, Cint), + ocachefile, depmods, #=completeinfo=#true, "PCI", false) else - sv = ccall(:jl_restore_incremental, Any, (Cstring, Any, Cint, Cstring), cachefile, depmods, true, "PCI") + sv = ccall(:jl_restore_incremental, Any, (Cstring, Any, Cint, Cstring), + cachefile, depmods, #=completeinfo=#true, "PCI") end - modules, init_order, external_methods, new_ext_cis, new_method_roots, external_targets, edges = sv + modules, init_order, edges, new_ext_cis, external_methods, new_method_roots, cache_sizes = sv m = only(external_methods).func::Method @test m.name == :repl_cmd && m.nargs < 2 @test new_ext_cis === nothing || any(new_ext_cis) do ci