From de0d501909de7090bf408bdbea3d8317cf18a79c Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 3 Jun 2025 19:45:21 +0000 Subject: [PATCH] better handling of missing backedges Manage a single dictionary (keyed by TypeName) instead of scattering this info into each TypeName scattered across the system. This makes it much easier to scan the whole table when required and to split it up better, so that all kwcalls and all constructors don't end up stuck into just one table. While not enormous (or even the largest) just using the REPL and Pkg, they are clearly larger than intended for a linear scan: ``` julia> length(Type.body.name.backedges) 1024 julia> length(typeof(Core.kwcall).name.backedges) 196 julia> length(typeof(convert).name.backedges) 1510 ``` --- src/datatype.c | 2 +- src/gf.c | 331 +++++++++++++++++++++++++++---------------- src/jltypes.c | 30 ++-- src/julia.h | 2 +- src/julia_internal.h | 2 - src/method.c | 38 ----- src/staticdata.c | 44 ++++-- test/misc.jl | 4 +- 8 files changed, 257 insertions(+), 196 deletions(-) diff --git a/src/datatype.c b/src/datatype.c index eb25907647157..2df2bdb2aaa71 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -62,6 +62,7 @@ JL_DLLEXPORT jl_methtable_t *jl_new_method_table(jl_sym_t *name, jl_module_t *mo mt->cache = mc; mt->name = name; mt->module = module; + mt->backedges = (jl_genericmemory_t*)jl_an_empty_memory_any; JL_GC_POP(); return mt; } @@ -88,7 +89,6 @@ JL_DLLEXPORT jl_typename_t *jl_new_typename_in(jl_sym_t *name, jl_module_t *modu tn->partial = NULL; tn->atomicfields = NULL; tn->constfields = NULL; - tn->backedges = NULL; tn->max_methods = 0; jl_atomic_store_relaxed(&tn->max_args, 0); jl_atomic_store_relaxed(&tn->cache_entry_count, 0); diff --git a/src/gf.c b/src/gf.c index 8205cf70b99c3..57dc2760eecac 100644 --- a/src/gf.c +++ b/src/gf.c @@ -777,55 +777,117 @@ JL_DLLEXPORT int jl_mi_try_insert(jl_method_instance_t *mi JL_ROOTING_ARGUMENT, return ret; } -static int foreach_typename_in_module( - jl_module_t *m, - int (*visit)(jl_typename_t *tn, void *env), - void *env) +enum top_typename_facts { + EXACTLY_ANY = 1 << 0, + HAVE_TYPE = 1 << 1, + EXACTLY_TYPE = 1 << 2, + HAVE_FUNCTION = 1 << 3, + EXACTLY_FUNCTION = 1 << 4, + HAVE_KWCALL = 1 << 5, + EXACTLY_KWCALL = 1 << 6, + SHORT_TUPLE = 1 << 7, +}; + +static void foreach_top_nth_typename(void (*f)(jl_typename_t*, int, void*), jl_value_t *a JL_PROPAGATES_ROOT, int n, unsigned *facts, void *env) { - jl_svec_t *table = jl_atomic_load_relaxed(&m->bindings); - for (size_t i = 0; i < jl_svec_len(table); i++) { - jl_binding_t *b = (jl_binding_t*)jl_svecref(table, i); - if ((void*)b == jl_nothing) - break; - jl_sym_t *name = b->globalref->name; - jl_value_t *v = jl_get_latest_binding_value_if_const(b); - if (v) { - jl_value_t *uw = jl_unwrap_unionall(v); - if (jl_is_datatype(uw)) { - jl_typename_t *tn = ((jl_datatype_t*)uw)->name; - if (tn->module == m && tn->name == name && tn->wrapper == v) { - // this is the original/primary binding for the type (name/wrapper) - if (!visit(((jl_datatype_t*)uw)->name, env)) - return 0; - } + if (jl_is_datatype(a)) { + if (n <= 0) { + jl_datatype_t *dt = ((jl_datatype_t*)a); + if (dt->name == jl_type_typename) { // key Type{T} on T instead of Type + *facts |= HAVE_TYPE; + foreach_top_nth_typename(f, jl_tparam0(a), -1, facts, env); } - else if (jl_is_module(v)) { - jl_module_t *child = (jl_module_t*)v; - if (child != m && child->parent == m && child->name == name) { - // this is the original/primary binding for the submodule - if (!foreach_typename_in_module(child, visit, env)) - return 0; + else if (dt == jl_function_type) { + if (n == -1) // key Type{>:Function} as Type instead of Function + *facts |= EXACTLY_TYPE; // HAVE_TYPE is already set + else + *facts |= HAVE_FUNCTION | EXACTLY_FUNCTION; + } + else if (dt == jl_any_type) { + if (n == -1) // key Type{>:Any} and kinds as Type instead of Any + *facts |= EXACTLY_TYPE; // HAVE_TYPE is already set + else + *facts |= EXACTLY_ANY; + } + else if (dt == jl_kwcall_type) { + if (n == -1) // key Type{>:typeof(kwcall)} as exactly kwcall + *facts |= EXACTLY_KWCALL; + else + *facts |= HAVE_KWCALL; + } + else { + while (1) { + jl_datatype_t *super = dt->super; + if (super == jl_function_type) { + *facts |= HAVE_FUNCTION; + break; + } + if (super == jl_any_type || super->super == dt) + break; + dt = super; } + f(dt->name, 1, env); } } - table = jl_atomic_load_relaxed(&m->bindings); - } + else if (jl_is_tuple_type(a)) { + if (jl_nparams(a) >= n) + foreach_top_nth_typename(f, jl_tparam(a, n - 1), 0, facts, env); + else + *facts |= SHORT_TUPLE; + } + } + else if (jl_is_typevar(a)) { + foreach_top_nth_typename(f, ((jl_tvar_t*)a)->ub, n, facts, env); + } + else if (jl_is_unionall(a)) { + foreach_top_nth_typename(f, ((jl_unionall_t*)a)->body, n, facts, env); + } + else if (jl_is_uniontype(a)) { + jl_uniontype_t *u = (jl_uniontype_t*)a; + foreach_top_nth_typename(f, u->a, n, facts, env); + foreach_top_nth_typename(f, u->b, n, facts, env); + } +} + +// Inspect type `argtypes` for all backedge keys that might be relevant to it, splitting it +// up on some commonly observed patterns to make a better distribution. +// (It could do some of that balancing automatically, but for now just hard-codes kwcall.) +// Along the way, record some facts about what was encountered, so that those additional +// calls can be added later if needed for completeness. +// The `int explct` argument instructs the caller if the callback is due to an exactly +// encountered type or if it rather encountered a subtype. +// This is not capable of walking to all top-typenames for an explicitly encountered +// Function or Any, so the caller a fallback that can scan the entire in that case. +// We do not de-duplicate calls when encountering a Union. +static int jl_foreach_top_typename_for(void (*f)(jl_typename_t*, int, void*), jl_value_t *argtypes JL_PROPAGATES_ROOT, int all_subtypes, void *env) +{ + unsigned facts = 0; + foreach_top_nth_typename(f, argtypes, 1, &facts, env); + if (facts & HAVE_KWCALL) { + // split kwcall on the 3rd argument instead, using the same logic + unsigned kwfacts = 0; + foreach_top_nth_typename(f, argtypes, 3, &kwfacts, env); + // copy kwfacts to original facts + if (kwfacts & SHORT_TUPLE) + kwfacts |= (all_subtypes ? EXACTLY_ANY : EXACTLY_KWCALL); + facts |= kwfacts; + } + if (all_subtypes && (facts & (EXACTLY_FUNCTION | EXACTLY_TYPE | EXACTLY_ANY))) + // flag that we have an explct match than is necessitating a full table scan + return 0; + // or inform caller of only which supertypes are applicable + if (facts & HAVE_FUNCTION) + f(jl_function_type->name, facts & EXACTLY_FUNCTION ? 1 : 0, env); + if (facts & HAVE_TYPE) + f(jl_type_typename, facts & EXACTLY_TYPE ? 1 : 0, env); + if (facts & (HAVE_KWCALL | EXACTLY_KWCALL)) + f(jl_kwcall_type->name, facts & EXACTLY_KWCALL ? 1 : 0, env); + f(jl_any_type->name, facts & EXACTLY_ANY ? 1 : 0, env); return 1; } -static int jl_foreach_reachable_typename(int (*visit)(jl_typename_t *tn, void *env), jl_array_t *mod_array, void *env) -{ - for (size_t i = 0; i < jl_array_nrows(mod_array); i++) { - jl_module_t *m = (jl_module_t*)jl_array_ptr_ref(mod_array, i); - assert(jl_is_module(m)); - if (m->parent == m) // some toplevel modules (really just Base) aren't actually - if (!foreach_typename_in_module(m, visit, env)) - return 0; - } - return 1; -} -int foreach_mtable_in_module( +static int foreach_mtable_in_module( jl_module_t *m, int (*visit)(jl_methtable_t *mt, void *env), void *env) @@ -2102,41 +2164,56 @@ JL_DLLEXPORT void jl_method_instance_add_backedge(jl_method_instance_t *callee, } +static int jl_foreach_top_typename_for(void (*f)(jl_typename_t*, int, void*), jl_value_t *argtypes JL_PROPAGATES_ROOT, int all_subtypes, void *env); + struct _typename_add_backedge { jl_value_t *typ; jl_value_t *caller; }; -static void _typename_add_backedge(jl_typename_t *tn, void *env0) +static void _typename_add_backedge(jl_typename_t *tn, int explct, void *env0) { struct _typename_add_backedge *env = (struct _typename_add_backedge*)env0; JL_GC_PROMISE_ROOTED(env->typ); JL_GC_PROMISE_ROOTED(env->caller); - if (jl_atomic_load_relaxed(&allow_new_worlds)) { - if (!tn->backedges) { - // lazy-init the backedges array - tn->backedges = jl_alloc_vec_any(2); - jl_gc_wb(tn, tn->backedges); - jl_array_ptr_set(tn->backedges, 0, env->typ); - jl_array_ptr_set(tn->backedges, 1, env->caller); + if (!explct) + return; + jl_genericmemory_t *allbackedges = jl_method_table->backedges; + jl_array_t *backedges = (jl_array_t*)jl_eqtable_get(allbackedges, (jl_value_t*)tn, NULL); + if (backedges == NULL) { + backedges = jl_alloc_vec_any(2); + JL_GC_PUSH1(&backedges); + jl_array_del_end(backedges, 2); + jl_genericmemory_t *newtable = jl_eqtable_put(allbackedges, (jl_value_t*)tn, (jl_value_t*)backedges, NULL); + JL_GC_POP(); + if (newtable != allbackedges) { + jl_method_table->backedges = newtable; + jl_gc_wb(jl_method_table, newtable); } - else { - // check if the edge is already present and avoid adding a duplicate - size_t i, l = jl_array_nrows(tn->backedges); - // reuse an already cached instance of this type, if possible - // TODO: use jl_cache_type_(tt) like cache_method does, instead of this linear scan? - for (i = 1; i < l; i += 2) { - if (jl_array_ptr_ref(tn->backedges, i) != env->caller) { - if (jl_types_equal(jl_array_ptr_ref(tn->backedges, i - 1), env->typ)) { - env->typ = jl_array_ptr_ref(tn->backedges, i - 1); - break; - } - } + } + // check if the edge is already present and avoid adding a duplicate + size_t i, l = jl_array_nrows(backedges); + // reuse an already cached instance of this type, if possible + // TODO: use jl_cache_type_(tt) like cache_method does, instead of this linear scan? + // TODO: use as_global_root and de-dup edges array too + for (i = 1; i < l; i += 2) { + if (jl_array_ptr_ref(backedges, i) == env->caller) { + if (jl_types_equal(jl_array_ptr_ref(backedges, i - 1), env->typ)) { + env->typ = jl_array_ptr_ref(backedges, i - 1); + return; // this edge already recorded } - jl_array_ptr_1d_push(tn->backedges, env->typ); - jl_array_ptr_1d_push(tn->backedges, env->caller); } } + for (i = 1; i < l; i += 2) { + if (jl_array_ptr_ref(backedges, i) != env->caller) { + if (jl_types_equal(jl_array_ptr_ref(backedges, i - 1), env->typ)) { + env->typ = jl_array_ptr_ref(backedges, i - 1); + break; + } + } + } + jl_array_ptr_1d_push(backedges, env->typ); + jl_array_ptr_1d_push(backedges, env->caller); } // add a backedge from a non-existent signature to caller @@ -2146,11 +2223,13 @@ JL_DLLEXPORT void jl_method_table_add_backedge(jl_value_t *typ, jl_code_instance if (!jl_atomic_load_relaxed(&allow_new_worlds)) return; // try to pick the best cache(s) for this typ edge - struct _typename_add_backedge env = {typ, (jl_value_t*)caller}; - jl_methcache_t *mc = jl_method_table->cache; + jl_methtable_t *mt = jl_method_table; + jl_methcache_t *mc = mt->cache; JL_LOCK(&mc->writelock); - if (jl_atomic_load_relaxed(&allow_new_worlds)) - jl_foreach_top_typename_for(_typename_add_backedge, typ, &env); + if (jl_atomic_load_relaxed(&allow_new_worlds)) { + struct _typename_add_backedge env = {typ, (jl_value_t*)caller}; + jl_foreach_top_typename_for(_typename_add_backedge, typ, 0, &env); + } JL_UNLOCK(&mc->writelock); } @@ -2164,65 +2243,66 @@ struct _typename_invalidate_backedge { int invalidated; }; -static void _typename_invalidate_backedges(jl_typename_t *tn, void *env0) +static void _typename_invalidate_backedges(jl_typename_t *tn, int explct, void *env0) { struct _typename_invalidate_backedge *env = (struct _typename_invalidate_backedge*)env0; JL_GC_PROMISE_ROOTED(env->type); JL_GC_PROMISE_ROOTED(env->isect); // isJuliaType considers jl_value_t** to be a julia object too JL_GC_PROMISE_ROOTED(env->isect2); // isJuliaType considers jl_value_t** to be a julia object too - if (tn->backedges) { - jl_value_t **backedges = jl_array_ptr_data(tn->backedges); - size_t i, na = jl_array_nrows(tn->backedges); - size_t ins = 0; - for (i = 1; i < na; i += 2) { - jl_value_t *backedgetyp = backedges[i - 1]; - JL_GC_PROMISE_ROOTED(backedgetyp); - int missing = 0; - if (jl_type_intersection2(backedgetyp, (jl_value_t*)env->type, env->isect, env->isect2)) { - // See if the intersection was actually already fully - // covered, but that the new method is ambiguous. - // -> no previous method: now there is one, need to update the missing edge - // -> one+ previously matching method(s): - // -> more specific then all of them: need to update the missing edge - // -> some may have been ambiguous: now there is a replacement - // -> some may have been called: now there is a replacement (also will be detected in the loop later) - // -> less specific or ambiguous with any one of them: can ignore the missing edge (not missing) - // -> some may have been ambiguous: still are - // -> some may have been called: they may be partly replaced (will be detected in the loop later) - // c.f. `is_replacing`, which is a similar query, but with an existing method match to compare against - missing = 1; - for (size_t j = 0; j < env->n; j++) { - jl_method_t *m = env->d[j]; - JL_GC_PROMISE_ROOTED(m); - if (jl_subtype(*env->isect, m->sig) || (*env->isect2 && jl_subtype(*env->isect2, m->sig))) { - // We now know that there actually was a previous - // method for this part of the type intersection. - if (!jl_type_morespecific(env->type, m->sig)) { - missing = 0; - break; - } + jl_array_t *backedges = (jl_array_t*)jl_eqtable_get(jl_method_table->backedges, (jl_value_t*)tn, NULL); + if (backedges == NULL) + return; + jl_value_t **d = jl_array_ptr_data(backedges); + size_t i, na = jl_array_nrows(backedges); + size_t ins = 0; + for (i = 1; i < na; i += 2) { + jl_value_t *backedgetyp = d[i - 1]; + JL_GC_PROMISE_ROOTED(backedgetyp); + int missing = 0; + if (jl_type_intersection2(backedgetyp, (jl_value_t*)env->type, env->isect, env->isect2)) { + // See if the intersection was actually already fully + // covered, but that the new method is ambiguous. + // -> no previous method: now there is one, need to update the missing edge + // -> one+ previously matching method(s): + // -> more specific then all of them: need to update the missing edge + // -> some may have been ambiguous: now there is a replacement + // -> some may have been called: now there is a replacement (also will be detected in the loop later) + // -> less specific or ambiguous with any one of them: can ignore the missing edge (not missing) + // -> some may have been ambiguous: still are + // -> some may have been called: they may be partly replaced (will be detected in the loop later) + // c.f. `is_replacing`, which is a similar query, but with an existing method match to compare against + missing = 1; + for (size_t j = 0; j < env->n; j++) { + jl_method_t *m = env->d[j]; + JL_GC_PROMISE_ROOTED(m); + if (jl_subtype(*env->isect, m->sig) || (*env->isect2 && jl_subtype(*env->isect2, m->sig))) { + // We now know that there actually was a previous + // method for this part of the type intersection. + if (!jl_type_morespecific(env->type, m->sig)) { + missing = 0; + break; } } } - *env->isect = *env->isect2 = NULL; - if (missing) { - jl_code_instance_t *backedge = (jl_code_instance_t*)backedges[i]; - JL_GC_PROMISE_ROOTED(backedge); - invalidate_code_instance(backedge, env->max_world, 0); - env->invalidated = 1; - if (_jl_debug_method_invalidation) - jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)backedgetyp); - } - else { - backedges[ins++] = backedges[i - 1]; - backedges[ins++] = backedges[i - 0]; - } } - if (ins == 0) - tn->backedges = NULL; - else - jl_array_del_end(tn->backedges, na - ins); + *env->isect = *env->isect2 = NULL; + if (missing) { + jl_code_instance_t *backedge = (jl_code_instance_t*)d[i]; + JL_GC_PROMISE_ROOTED(backedge); + invalidate_code_instance(backedge, env->max_world, 0); + env->invalidated = 1; + if (_jl_debug_method_invalidation) + jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)backedgetyp); + } + else { + d[ins++] = d[i - 1]; + d[ins++] = d[i - 0]; + } } + if (ins == 0) + jl_eqtable_pop(jl_method_table->backedges, (jl_value_t*)tn, NULL, NULL); + else if (na != ins) + jl_array_del_end(backedges, na - ins); } struct invalidate_mt_env { @@ -2390,14 +2470,6 @@ static int erase_all_backedges(jl_methtable_t *mt, void *env) return jl_typemap_visitor(jl_atomic_load_relaxed(&mt->defs), erase_method_backedges, env); } -static int erase_all_mc_backedges(jl_typename_t *tn, void *env) -{ - tn->backedges = NULL; - return 1; -} - -static int jl_foreach_reachable_typename(int (*visit)(jl_typename_t *tn, void *env), jl_array_t *mod_array, void *env); - JL_DLLEXPORT void jl_disable_new_worlds(void) { if (jl_generating_output()) @@ -2410,7 +2482,7 @@ JL_DLLEXPORT void jl_disable_new_worlds(void) jl_foreach_reachable_mtable(erase_all_backedges, mod_array, (void*)NULL); JL_LOCK(&jl_method_table->cache->writelock); - jl_foreach_reachable_typename(erase_all_mc_backedges, mod_array, (void*)NULL); + jl_method_table->backedges = (jl_genericmemory_t*)jl_an_empty_memory_any; JL_UNLOCK(&jl_method_table->cache->writelock); JL_GC_POP(); } @@ -2590,7 +2662,16 @@ void jl_method_table_activate(jl_typemap_entry_t *newentry) jl_methcache_t *mc = jl_method_table->cache; JL_LOCK(&mc->writelock); struct _typename_invalidate_backedge typename_env = {type, &isect, &isect2, d, n, max_world, invalidated}; - jl_foreach_top_typename_for(_typename_invalidate_backedges, type, &typename_env); + if (!jl_foreach_top_typename_for(_typename_invalidate_backedges, type, 1, &typename_env)) { + // if the new method cannot be split into exact backedges, scan the whole table for anything that might be affected + jl_genericmemory_t *allbackedges = jl_method_table->backedges; + for (size_t i = 0, n = allbackedges->length; i < n; i += 2) { + jl_value_t *tn = jl_genericmemory_ptr_ref(allbackedges, i); + jl_value_t *backedges = jl_genericmemory_ptr_ref(allbackedges, i+1); + if (tn && tn != jl_nothing && backedges) + _typename_invalidate_backedges((jl_typename_t*)tn, 0, &typename_env); + } + } invalidated |= typename_env.invalidated; if (oldmi && jl_array_nrows(oldmi)) { // search mc->cache and leafcache and drop anything that might overlap with the new method diff --git a/src/jltypes.c b/src/jltypes.c index 7d731082bd786..9b664ce25861c 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3004,23 +3004,22 @@ void jl_init_types(void) JL_GC_DISABLED jl_typename_type->name->wrapper = (jl_value_t*)jl_typename_type; jl_typename_type->super = jl_any_type; jl_typename_type->parameters = jl_emptysvec; - jl_typename_type->name->n_uninitialized = 19 - 2; - jl_typename_type->name->names = jl_perm_symsvec(19, "name", "module", "singletonname", + jl_typename_type->name->n_uninitialized = 18 - 2; + jl_typename_type->name->names = jl_perm_symsvec(18, "name", "module", "singletonname", "names", "atomicfields", "constfields", "wrapper", "Typeofwrapper", "cache", "linearcache", - "backedges", "partial", - "hash", "max_args", "n_uninitialized", + "partial", "hash", "max_args", "n_uninitialized", "flags", // "abstract", "mutable", "mayinlinealloc", "cache_entry_count", "max_methods", "constprop_heuristic"); - const static uint32_t typename_constfields[1] = { 0b0001101000001001011 }; // TODO: put back atomicfields and constfields in this list - const static uint32_t typename_atomicfields[1] = { 0b0010010001110000000 }; + const static uint32_t typename_constfields[1] = { 0b000110100001001011 }; // TODO: put back atomicfields and constfields in this list + const static uint32_t typename_atomicfields[1] = { 0b001001001110000000 }; jl_typename_type->name->constfields = typename_constfields; jl_typename_type->name->atomicfields = typename_atomicfields; jl_precompute_memoized_dt(jl_typename_type, 1); - jl_typename_type->types = jl_svec(19, jl_symbol_type, jl_any_type /*jl_module_type*/, jl_symbol_type, + jl_typename_type->types = jl_svec(18, jl_symbol_type, jl_any_type /*jl_module_type*/, jl_symbol_type, jl_simplevector_type, jl_any_type/*jl_voidpointer_type*/, jl_any_type/*jl_voidpointer_type*/, - jl_type_type, jl_type_type, jl_simplevector_type, jl_simplevector_type, + jl_type_type, jl_simplevector_type, jl_simplevector_type, jl_methcache_type, jl_any_type, jl_any_type /*jl_long_type*/, jl_any_type /*jl_int32_type*/, @@ -3046,13 +3045,13 @@ void jl_init_types(void) JL_GC_DISABLED jl_methtable_type->super = jl_any_type; jl_methtable_type->parameters = jl_emptysvec; jl_methtable_type->name->n_uninitialized = 0; - jl_methtable_type->name->names = jl_perm_symsvec(4, "defs", "cache", "name", "module"); - const static uint32_t methtable_constfields[1] = { 0b1110 }; - const static uint32_t methtable_atomicfields[1] = { 0b0001 }; + jl_methtable_type->name->names = jl_perm_symsvec(5, "defs", "cache", "name", "module", "backedges"); + const static uint32_t methtable_constfields[1] = { 0b01110 }; + const static uint32_t methtable_atomicfields[1] = { 0b00001 }; jl_methtable_type->name->constfields = methtable_constfields; jl_methtable_type->name->atomicfields = methtable_atomicfields; jl_precompute_memoized_dt(jl_methtable_type, 1); - jl_methtable_type->types = jl_svec(4, jl_any_type, jl_methcache_type, jl_symbol_type, jl_any_type /*jl_module_type*/); + jl_methtable_type->types = jl_svec(5, jl_any_type, jl_methcache_type, jl_symbol_type, jl_any_type /*jl_module_type*/, jl_any_type); jl_symbol_type->name = jl_new_typename_in(jl_symbol("Symbol"), core, 0, 1); jl_symbol_type->name->wrapper = (jl_value_t*)jl_symbol_type; @@ -3378,6 +3377,7 @@ void jl_init_types(void) JL_GC_DISABLED core = jl_core_module; jl_method_table->module = core; jl_atomic_store_relaxed(&jl_method_table->cache->leafcache, (jl_genericmemory_t*)jl_an_empty_memory_any); + jl_method_table->backedges = (jl_genericmemory_t*)jl_an_empty_memory_any; jl_atomic_store_relaxed(&core->bindingkeyset, (jl_genericmemory_t*)jl_an_empty_memory_any); // export own name, so "using Foo" makes "Foo" itself visible jl_set_initial_const(core, core->name, (jl_value_t*)core, 1); @@ -3842,13 +3842,13 @@ void jl_init_types(void) JL_GC_DISABLED jl_svecset(jl_typename_type->types, 5, jl_voidpointer_type); jl_svecset(jl_typename_type->types, 6, jl_type_type); jl_svecset(jl_typename_type->types, 7, jl_type_type); - jl_svecset(jl_typename_type->types, 12, jl_long_type); + jl_svecset(jl_typename_type->types, 11, jl_long_type); + jl_svecset(jl_typename_type->types, 12, jl_int32_type); jl_svecset(jl_typename_type->types, 13, jl_int32_type); - jl_svecset(jl_typename_type->types, 14, jl_int32_type); + jl_svecset(jl_typename_type->types, 14, jl_uint8_type); jl_svecset(jl_typename_type->types, 15, jl_uint8_type); jl_svecset(jl_typename_type->types, 16, jl_uint8_type); jl_svecset(jl_typename_type->types, 17, jl_uint8_type); - jl_svecset(jl_typename_type->types, 18, jl_uint8_type); jl_svecset(jl_methcache_type->types, 2, jl_long_type); // voidpointer jl_svecset(jl_methcache_type->types, 3, jl_long_type); // uint32_t plus alignment jl_svecset(jl_methtable_type->types, 3, jl_module_type); diff --git a/src/julia.h b/src/julia.h index 6c1c8af0a788b..3dd2088ecf207 100644 --- a/src/julia.h +++ b/src/julia.h @@ -520,7 +520,6 @@ typedef struct { _Atomic(jl_value_t*) Typeofwrapper; // cache for Type{wrapper} _Atomic(jl_svec_t*) cache; // sorted array _Atomic(jl_svec_t*) linearcache; // unsorted array - jl_array_t *backedges; // uncovered (sig => caller::CodeInstance) pairs with this type as the function jl_array_t *partial; // incomplete instantiations of this type intptr_t hash; _Atomic(int32_t) max_args; // max # of non-vararg arguments in a signature with this type as the function @@ -882,6 +881,7 @@ typedef struct _jl_methtable_t { jl_methcache_t *cache; jl_sym_t *name; // sometimes used for debug printing jl_module_t *module; // sometimes used for debug printing + jl_genericmemory_t *backedges; // IdDict{top typenames, Vector{uncovered (sig => caller::CodeInstance)}} } jl_methtable_t; typedef struct { diff --git a/src/julia_internal.h b/src/julia_internal.h index 24fb7fdb05f90..ed5aea6c80aef 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -857,7 +857,6 @@ jl_expr_t *jl_exprn(jl_sym_t *head, size_t n); jl_function_t *jl_new_generic_function(jl_sym_t *name, jl_module_t *module, size_t new_world); jl_function_t *jl_new_generic_function_with_supertype(jl_sym_t *name, jl_module_t *module, jl_datatype_t *st, size_t new_world); int jl_foreach_reachable_mtable(int (*visit)(jl_methtable_t *mt, void *env), jl_array_t *mod_array, void *env); -int foreach_mtable_in_module(jl_module_t *m, int (*visit)(jl_methtable_t *mt, void *env), void *env); void jl_init_main_module(void); JL_DLLEXPORT int jl_is_submodule(jl_module_t *child, jl_module_t *parent) JL_NOTSAFEPOINT; jl_array_t *jl_get_loaded_modules(void); @@ -939,7 +938,6 @@ JL_DLLEXPORT jl_methtable_t *jl_method_get_table( jl_method_t *method JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_methcache_t *jl_method_get_cache( jl_method_t *method JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT; -void jl_foreach_top_typename_for(void (*f)(jl_typename_t*, void*), jl_value_t *argtypes JL_PROPAGATES_ROOT, void *env); JL_DLLEXPORT int jl_pointer_egal(jl_value_t *t); JL_DLLEXPORT jl_value_t *jl_nth_slot_type(jl_value_t *sig JL_PROPAGATES_ROOT, size_t i) JL_NOTSAFEPOINT; diff --git a/src/method.c b/src/method.c index 77863b27e24b6..eba485ff9eb69 100644 --- a/src/method.c +++ b/src/method.c @@ -1154,39 +1154,6 @@ JL_DLLEXPORT jl_value_t *jl_declare_const_gf(jl_module_t *mod, jl_sym_t *name) return gf; } -static void foreach_top_nth_typename(void (*f)(jl_typename_t*, void*), jl_value_t *a JL_PROPAGATES_ROOT, int n, void *env) -{ - if (jl_is_datatype(a)) { - if (n == 0) { - jl_datatype_t *dt = ((jl_datatype_t*)a); - jl_typename_t *tn = NULL; - while (1) { - if (dt != jl_any_type && dt != jl_function_type) - tn = dt->name; - if (dt->super == dt) - break; - dt = dt->super; - } - if (tn) - f(tn, env); - } - else if (jl_is_tuple_type(a)) { - if (jl_nparams(a) >= n) - foreach_top_nth_typename(f, jl_tparam(a, n - 1), 0, env); - } - } - else if (jl_is_typevar(a)) { - foreach_top_nth_typename(f, ((jl_tvar_t*)a)->ub, n, env); - } - else if (jl_is_unionall(a)) { - foreach_top_nth_typename(f, ((jl_unionall_t*)a)->body, n, env); - } - else if (jl_is_uniontype(a)) { - jl_uniontype_t *u = (jl_uniontype_t*)a; - foreach_top_nth_typename(f, u->a, n, env); - foreach_top_nth_typename(f, u->b, n, env); - } -} // get the MethodTable for dispatch, or `nothing` if cannot be determined JL_DLLEXPORT jl_methtable_t *jl_method_table_for(jl_value_t *argtypes JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT @@ -1200,11 +1167,6 @@ JL_DLLEXPORT jl_methcache_t *jl_method_cache_for(jl_value_t *argtypes JL_PROPAGA return jl_method_table->cache; } -void jl_foreach_top_typename_for(void (*f)(jl_typename_t*, void*), jl_value_t *argtypes JL_PROPAGATES_ROOT, void *env) -{ - foreach_top_nth_typename(f, argtypes, 1, env); -} - jl_methcache_t *jl_kwmethod_cache_for(jl_value_t *argtypes JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT { return jl_method_table->cache; diff --git a/src/staticdata.c b/src/staticdata.c index 92e7f494ad35d..3df8e7414420c 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -867,20 +867,30 @@ static void jl_insert_into_serialization_queue(jl_serializer_state *s, jl_value_ assert(!jl_object_in_image((jl_value_t*)tn->module)); assert(!jl_object_in_image((jl_value_t*)tn->wrapper)); } + } + if (jl_is_mtable(v)) { + jl_methtable_t *mt = (jl_methtable_t*)v; // Any back-edges will be re-validated and added by staticdata.jl, so // drop them from the image here if (s->incremental || jl_options.trim || jl_options.strip_ir) { - record_field_change((jl_value_t**)&tn->backedges, NULL); + record_field_change((jl_value_t**)&mt->backedges, jl_an_empty_memory_any); } else { // don't recurse into all backedges memory (yet) - jl_value_t *backedges = get_replaceable_field((jl_value_t**)&tn->backedges, 1); - if (backedges) { - jl_queue_for_serialization_(s, (jl_value_t*)((jl_array_t*)backedges)->ref.mem, 0, 1); - for (size_t i = 0, n = jl_array_nrows(backedges); i < n; i += 2) { - jl_value_t *t = jl_array_ptr_ref(backedges, i); - assert(!jl_is_code_instance(t)); - jl_queue_for_serialization(s, t); + jl_value_t *allbackedges = get_replaceable_field((jl_value_t**)&mt->backedges, 1); + jl_queue_for_serialization_(s, allbackedges, 0, 1); + for (size_t i = 0, n = ((jl_genericmemory_t*)allbackedges)->length; i < n; i += 2) { + jl_value_t *tn = jl_genericmemory_ptr_ref(allbackedges, i); + jl_queue_for_serialization(s, tn); + jl_value_t *backedges = jl_genericmemory_ptr_ref(allbackedges, i + 1); + if (backedges && backedges != jl_nothing) { + jl_queue_for_serialization_(s, (jl_value_t*)((jl_array_t*)backedges)->ref.mem, 0, 1); + jl_queue_for_serialization(s, backedges); + for (size_t i = 0, n = jl_array_nrows(backedges); i < n; i += 2) { + jl_value_t *t = jl_array_ptr_ref(backedges, i); + assert(!jl_is_code_instance(t)); + jl_queue_for_serialization(s, t); + } } } } @@ -2573,8 +2583,6 @@ static void jl_prune_mi_backedges(jl_array_t *backedges) static void jl_prune_tn_backedges(jl_array_t *backedges) { - if (backedges == NULL) - return; size_t i = 0, ins = 0, n = jl_array_nrows(backedges); for (i = 1; i < n; i += 2) { jl_value_t *ci = jl_array_ptr_ref(backedges, i); @@ -2586,6 +2594,15 @@ static void jl_prune_tn_backedges(jl_array_t *backedges) jl_array_del_end(backedges, n - ins); } +static void jl_prune_mt_backedges(jl_genericmemory_t *allbackedges) +{ + for (size_t i = 0, n = allbackedges->length; i < n; i += 2) { + jl_value_t *tn = jl_genericmemory_ptr_ref(allbackedges, i); + jl_value_t *backedges = jl_genericmemory_ptr_ref(allbackedges, i + 1); + if (tn && tn != jl_nothing && backedges) + jl_prune_tn_backedges((jl_array_t*)backedges); + } +} static void jl_prune_binding_backedges(jl_array_t *backedges) { @@ -3240,8 +3257,6 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, jl_prune_type_cache_hash(jl_atomic_load_relaxed(&tn->cache))); jl_gc_wb(tn, jl_atomic_load_relaxed(&tn->cache)); jl_prune_type_cache_linear(jl_atomic_load_relaxed(&tn->linearcache)); - jl_value_t *backedges = get_replaceable_field((jl_value_t**)&tn->backedges, 1); - jl_prune_tn_backedges((jl_array_t*)backedges); } else if (jl_is_method_instance(v)) { jl_method_instance_t *mi = (jl_method_instance_t*)v; @@ -3253,6 +3268,11 @@ static void jl_save_system_image_to_stream(ios_t *f, jl_array_t *mod_array, jl_value_t *backedges = get_replaceable_field((jl_value_t**)&b->backedges, 1); jl_prune_binding_backedges((jl_array_t*)backedges); } + else if (jl_is_mtable(v)) { + jl_methtable_t *mt = (jl_methtable_t*)v; + jl_value_t *backedges = get_replaceable_field((jl_value_t**)&mt->backedges, 1); + jl_prune_mt_backedges((jl_genericmemory_t*)backedges); + } } } diff --git a/test/misc.jl b/test/misc.jl index d72ad5b58ad12..2b87a2ad26f0f 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -1632,10 +1632,10 @@ end let errs = IOBuffer() run(`$(Base.julia_cmd()) -e ' using Test - @test isdefined(Type.body.name, :backedges) + @test !isempty(Core.GlobalMethods.backedges) Base.Experimental.disable_new_worlds() @test_throws "disable_new_worlds" @eval f() = 1 - @test !isdefined(Type.body.name, :backedges) + @test isempty(Core.GlobalMethods.backedges) @test_throws "disable_new_worlds" Base.delete_method(which(+, (Int, Int))) @test 1+1 == 2 using Dates