diff --git a/base/errorshow.jl b/base/errorshow.jl index 05c6c6d787ecd..2f925c11b29ca 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -176,7 +176,7 @@ function showerror(io::IO, ex::InexactError) Experimental.show_error_hints(io, ex) end -typesof(args...) = Tuple{Any[ Core.Typeof(a) for a in args ]...} +typesof(@nospecialize args...) = Tuple{Any[ Core.Typeof(args[i]) for i in 1:length(args) ]...} function print_with_compare(io::IO, @nospecialize(a::DataType), @nospecialize(b::DataType), color::Symbol) if a.name === b.name diff --git a/src/ast.c b/src/ast.c index 3b4b3e5d78f7d..51cf262ad9ff2 100644 --- a/src/ast.c +++ b/src/ast.c @@ -962,7 +962,7 @@ static jl_value_t *jl_invoke_julia_macro(jl_array_t *args, jl_module_t *inmodule jl_value_t *result; JL_TRY { margs[0] = jl_toplevel_eval(*ctx, margs[0]); - jl_method_instance_t *mfunc = jl_method_lookup(margs, nargs, 1, world); + jl_method_instance_t *mfunc = jl_method_lookup(margs, nargs, world); JL_GC_PROMISE_ROOTED(mfunc); if (mfunc == NULL) { jl_method_error(margs[0], &margs[1], nargs, world); diff --git a/src/builtins.c b/src/builtins.c index 9c9fec857f9da..3ea207cf7d55a 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -990,8 +990,7 @@ JL_CALLABLE(jl_f_applicable) { JL_NARGSV(applicable, 1); size_t world = jl_get_ptls_states()->world_age; - return jl_method_lookup(args, nargs, 1, world) != NULL ? - jl_true : jl_false; + return jl_method_lookup(args, nargs, world) != NULL ? jl_true : jl_false; } JL_CALLABLE(jl_f_invoke) diff --git a/src/datatype.c b/src/datatype.c index 0260281c7470b..d22551895d99a 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -49,6 +49,7 @@ JL_DLLEXPORT jl_methtable_t *jl_new_method_table(jl_sym_t *name, jl_module_t *mo mt->name = jl_demangle_typename(name); mt->module = module; mt->defs = jl_nothing; + mt->leafcache = (jl_array_t*)jl_an_empty_vec_any; mt->cache = jl_nothing; mt->max_args = 0; mt->kwsorter = NULL; diff --git a/src/dump.c b/src/dump.c index 7cb4ffc47d092..35303932bc161 100644 --- a/src/dump.c +++ b/src/dump.c @@ -2237,9 +2237,6 @@ STATIC_INLINE jl_value_t *verify_type(jl_value_t *v) JL_NOTSAFEPOINT } #endif -jl_datatype_t *jl_lookup_cache_type_(jl_datatype_t *type); -void jl_cache_type_(jl_datatype_t *type); - static jl_datatype_t *jl_recache_type(jl_datatype_t *dt) JL_GC_DISABLED { jl_datatype_t *t; // the type after unique'ing diff --git a/src/gf.c b/src/gf.c index ca51b5f24917b..38a91c281fdbd 100644 --- a/src/gf.c +++ b/src/gf.c @@ -231,7 +231,8 @@ jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_a m->sig = (jl_value_t*)jl_anytuple_type; m->slot_syms = jl_an_empty_string; - JL_GC_PUSH1(&m); + jl_typemap_entry_t *newentry = NULL; + JL_GC_PUSH2(&m, &newentry); jl_method_instance_t *mi = jl_get_specialized(m, (jl_value_t*)jl_anytuple_type, jl_emptysvec); m->unspecialized = mi; jl_gc_wb(m, mi); @@ -242,8 +243,10 @@ jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_a codeinst->invoke = jl_fptr_args; jl_methtable_t *mt = dt->name->mt; - jl_typemap_insert(&mt->cache, (jl_value_t*)mt, jl_anytuple_type, - NULL, jl_emptysvec, (jl_value_t*)mi, 0, &lambda_cache, 1, ~(size_t)0); + newentry = jl_typemap_alloc(jl_anytuple_type, NULL, jl_emptysvec, + (jl_value_t*)mi, 1, ~(size_t)0); + jl_typemap_insert(&mt->cache, (jl_value_t*)mt, newentry, 0, &lambda_cache); + mt->frozen = 1; JL_GC_POP(); return dt; @@ -418,11 +421,11 @@ static void foreach_mtable_in_module( jl_module_t *m, void (*visit)(jl_methtable_t *mt, void *env), void *env, - jl_array_t *visited) + jl_array_t **visited) { size_t i; void **table = m->bindings.table; - jl_eqtable_put(visited, (jl_value_t*)m, jl_true, NULL); + *visited = jl_eqtable_put(*visited, (jl_value_t*)m, jl_true, NULL); for (i = 1; i < m->bindings.size; i += 2) { if (table[i] != HT_NOTFOUND) { jl_binding_t *b = (jl_binding_t*)table[i]; @@ -440,7 +443,7 @@ static void foreach_mtable_in_module( else if (jl_is_module(v)) { jl_module_t *child = (jl_module_t*)v; if (child != m && child->parent == m && child->name == b->name && - !jl_eqtable_get(visited, v, NULL)) { + !jl_eqtable_get(*visited, v, NULL)) { // this is the original/primary binding for the submodule foreach_mtable_in_module(child, visit, env, visited); } @@ -463,11 +466,11 @@ void jl_foreach_reachable_mtable(void (*visit)(jl_methtable_t *mt, void *env), v jl_module_t *m = (jl_module_t*)jl_array_ptr_ref(mod_array, i); assert(jl_is_module(m)); if (!jl_eqtable_get(visited, (jl_value_t*)m, NULL)) - foreach_mtable_in_module(m, visit, env, visited); + foreach_mtable_in_module(m, visit, env, &visited); } } else { - foreach_mtable_in_module(jl_main_module, visit, env, visited); + foreach_mtable_in_module(jl_main_module, visit, env, &visited); } JL_GC_POP(); } @@ -475,8 +478,10 @@ void jl_foreach_reachable_mtable(void (*visit)(jl_methtable_t *mt, void *env), v static void reset_mt_caches(jl_methtable_t *mt, void *env) { // removes all method caches - if (mt->defs != jl_nothing) // make sure not to reset builtin functions + if (mt->defs != jl_nothing) { // make sure not to reset builtin functions + mt->leafcache = (jl_array_t*)jl_an_empty_vec_any; mt->cache = jl_nothing; + } jl_typemap_visitor(mt->defs, get_method_unspec_list, env); } @@ -543,9 +548,10 @@ jl_value_t *jl_nth_slot_type(jl_value_t *sig, size_t i) // return 1; //} -static jl_value_t *ml_matches(jl_typemap_t *ml, int offs, +static jl_value_t *ml_matches(jl_methtable_t *mt, int offs, jl_tupletype_t *type, int lim, int include_ambiguous, - size_t world, size_t *min_valid, size_t *max_valid); + size_t world, size_t *min_valid, size_t *max_valid, + int cache_result); // get the compilation signature specialization for this method static void jl_compilation_sig( @@ -930,17 +936,36 @@ JL_DLLEXPORT int jl_isa_compileable_sig( return 1; } +static inline jl_typemap_entry_t *lookup_leafcache(jl_array_t *leafcache JL_PROPAGATES_ROOT, jl_value_t *tt, size_t world) JL_NOTSAFEPOINT +{ + jl_typemap_entry_t *entry = (jl_typemap_entry_t*)jl_eqtable_get(leafcache, (jl_value_t*)tt, NULL); + if (entry) { + do { + if (entry->min_world <= world && world <= entry->max_world) + return entry; + entry = entry->next; + } while ((jl_value_t*)entry != jl_nothing); + } + return NULL; +} + static jl_method_instance_t *cache_method( jl_methtable_t *mt, jl_typemap_t **cache, jl_value_t *parent JL_PROPAGATES_ROOT, jl_tupletype_t *tt, // the original tupletype of the signature jl_method_t *definition, - size_t world, + size_t world, size_t min_valid, size_t max_valid, jl_svec_t *sparams) { // caller must hold the mt->writelock // short-circuit (now that we hold the lock) if this entry is already present int8_t offs = mt ? jl_cachearg_offset(mt) : 1; { // scope block + if (mt) { + jl_array_t *leafcache = jl_atomic_load_relaxed(&mt->leafcache); + jl_typemap_entry_t *entry = lookup_leafcache(leafcache, (jl_value_t*)tt, world); + if (entry) + return entry->func.linfo; + } struct jl_typemap_assoc search = {(jl_value_t*)tt, world, NULL, 0, ~(size_t)0}; jl_typemap_entry_t *entry = jl_typemap_assoc_by_type(*cache, &search, offs, /*subtype*/1); if (entry && entry->func.value) @@ -971,12 +996,12 @@ static jl_method_instance_t *cache_method( jl_tupletype_t *cachett = tt; jl_svec_t* guardsigs = jl_emptysvec; - size_t min_valid = 1; - size_t max_valid = ~(size_t)0; if (!cache_with_orig && mt) { // now examine what will happen if we chose to use this sig in the cache // TODO: should we first check `compilationsig <: definition`? - temp = ml_matches(mt->defs, 0, compilationsig, MAX_UNSPECIALIZED_CONFLICTS, 1, world, &min_valid, &max_valid); + size_t min_valid2 = 1; + size_t max_valid2 = ~(size_t)0; + temp = ml_matches(mt, 0, compilationsig, MAX_UNSPECIALIZED_CONFLICTS, 1, world, &min_valid2, &max_valid2, 0); int guards = 0; if (temp == jl_false) { cache_with_orig = 1; @@ -1027,22 +1052,14 @@ static jl_method_instance_t *cache_method( } } } - if (cache_with_orig) { - min_valid = 1; - max_valid = ~(size_t)0; - } - else { + if (!cache_with_orig) { // determined above that there's no ambiguity in also using compilationsig as the cacheablesig + min_valid = min_valid2; + max_valid = max_valid2; cachett = compilationsig; } } - if (cache_with_orig && mt) { - // now examine defs to determine the min/max-valid range for this lookup result - (void)ml_matches(mt->defs, 0, cachett, -1, 0, world, &min_valid, &max_valid); - } - assert(mt == NULL || min_valid > 1); - // now scan `cachett` and ensure that `Type{T}` in the cache will be matched exactly by `typeof(T)` // and also reduce the complexity of rejecting this entry in the cache // by replacing non-simple types with jl_any_type to build a new `type` @@ -1072,32 +1089,39 @@ static jl_method_instance_t *cache_method( temp2 = (jl_value_t*)simplett; } - // short-circuit if this exact entry is already present - // to avoid adding a new duplicate copy of it - if (cachett != tt && simplett == NULL) { - struct jl_typemap_assoc search = {(jl_value_t*)cachett, min_valid, NULL, 0, ~(size_t)0}; + // short-circuit if an existing entry is already present + // that satisfies our requirements + if (cachett != tt) { + struct jl_typemap_assoc search = {(jl_value_t*)cachett, world, NULL, 0, ~(size_t)0}; jl_typemap_entry_t *entry = jl_typemap_assoc_by_type(*cache, &search, offs, /*subtype*/1); - if (entry && (jl_value_t*)entry->simplesig == jl_nothing) { - if (jl_egal((jl_value_t*)guardsigs, (jl_value_t*)entry->guardsigs)) { - // just update the existing entry to reflect new knowledge - if (entry->min_world > min_valid) - entry->min_world = min_valid; - if (entry->max_world < max_valid) - entry->max_world = max_valid; - if (entry->func.linfo == NULL) { - entry->func.linfo = newmeth; - jl_gc_wb(entry, newmeth); - } - assert(entry->func.linfo == newmeth); - JL_GC_POP(); - return newmeth; - } + if (entry && jl_egal((jl_value_t*)entry->simplesig, simplett ? (jl_value_t*)simplett : jl_nothing) && + jl_egal((jl_value_t*)guardsigs, (jl_value_t*)entry->guardsigs)) { + JL_GC_POP(); + return entry->func.linfo; } } - jl_typemap_insert(cache, parent, cachett, simplett, guardsigs, - (jl_value_t*)newmeth, offs, &lambda_cache, - min_valid, max_valid); + jl_typemap_entry_t *newentry = jl_typemap_alloc(cachett, simplett, guardsigs, (jl_value_t*)newmeth, min_valid, max_valid); + temp = (jl_value_t*)newentry; + if (mt && cachett == tt && simplett == NULL && jl_svec_len(guardsigs) == 0) { + if (!jl_has_free_typevars((jl_value_t*)tt) && jl_lookup_cache_type_(tt) == NULL) { + // if this type isn't normally in the cache, force it in there now + // anyways so that we can depend on it as a token (especially since + // we just cached it in memory as this method signature anyways) + JL_LOCK(&typecache_lock); + if (jl_lookup_cache_type_(tt) == NULL) + jl_cache_type_(tt); + JL_UNLOCK(&typecache_lock); // Might GC + } + jl_typemap_entry_t *old = (jl_typemap_entry_t*)jl_eqtable_get(mt->leafcache, (jl_value_t*)tt, jl_nothing); + newentry->next = old; + jl_gc_wb(newentry, old); + mt->leafcache = jl_eqtable_put(mt->leafcache, (jl_value_t*)tt, (jl_value_t*)newentry, NULL); + jl_gc_wb(mt, mt->leafcache); + } + else { + jl_typemap_insert(cache, parent, newentry, offs, &lambda_cache); + } JL_GC_POP(); return newmeth; @@ -1171,12 +1195,20 @@ static jl_typemap_entry_t *jl_typemap_morespecific_by_type(jl_typemap_entry_t *f return candidate; } -static jl_method_instance_t *jl_mt_assoc_by_type(jl_methtable_t *mt, jl_datatype_t *tt, int mt_cache, size_t world) +static jl_method_instance_t *jl_mt_assoc_by_type(jl_methtable_t *mt, jl_datatype_t *tt, size_t world) { // caller must hold the mt->writelock + assert(tt->isdispatchtuple || tt->hasfreetypevars); + if (tt->isdispatchtuple) { + jl_array_t *leafcache = jl_atomic_load_relaxed(&mt->leafcache); + jl_typemap_entry_t *entry = lookup_leafcache(leafcache, (jl_value_t*)tt, world); + if (entry) + return entry->func.linfo; + } + struct jl_typemap_assoc search = {(jl_value_t*)tt, world, NULL, 0, ~(size_t)0}; jl_typemap_entry_t *entry = jl_typemap_assoc_by_type(mt->cache, &search, jl_cachearg_offset(mt), /*subtype*/1); - if (entry && entry->func.value) + if (entry) return entry->func.linfo; jl_method_instance_t *nf = NULL; @@ -1189,17 +1221,7 @@ static jl_method_instance_t *jl_mt_assoc_by_type(jl_methtable_t *mt, jl_datatype entry = jl_typemap_morespecific_by_type(entry, (jl_value_t*)tt, &search.env, world); if (entry != NULL) { jl_method_t *m = entry->func.method; - jl_svec_t *env = search.env; - if (!mt_cache) { - intptr_t nspec = (mt == jl_type_type_mt ? m->nargs + 1 : mt->max_args + 2); - jl_compilation_sig(tt, env, m, nspec, &newparams); - if (newparams) - tt = jl_apply_tuple_type(newparams); - nf = jl_specializations_get_linfo(m, (jl_value_t*)tt, env); - } - else { - nf = cache_method(mt, &mt->cache, (jl_value_t*)mt, tt, m, world, env); - } + nf = cache_method(mt, &mt->cache, (jl_value_t*)mt, tt, m, world, search.min_valid, search.max_valid, search.env); } } JL_GC_POP(); @@ -1686,15 +1708,22 @@ JL_DLLEXPORT void jl_method_table_disable(jl_methtable_t *mt, jl_method_t *metho (void)check_ambiguous_matches(mt->defs, methodentry, check_disabled_ambiguous_visitor); // drop this method from mt->cache struct invalidate_mt_env mt_cache_env; - mt_cache_env.max_world = methodentry->max_world - 1; + mt_cache_env.max_world = methodentry->max_world; mt_cache_env.shadowed = (jl_value_t*)method; jl_typemap_visitor(mt->cache, invalidate_mt_cache, (void*)&mt_cache_env); + jl_array_t *leafcache = mt->leafcache; + size_t i, l = jl_array_len(leafcache); + for (i = 1; i < l; i += 2) { + jl_value_t *l = jl_array_ptr_ref(leafcache, i); + if (l && l != jl_nothing) + invalidate_mt_cache((jl_typemap_entry_t*)l, (void*)&mt_cache_env); + } // Invalidate the backedges - jl_svec_t *specializations = methodentry->func.method->specializations; int invalidated = 0; + jl_svec_t *specializations = methodentry->func.method->specializations; jl_value_t *loctag = NULL; JL_GC_PUSH1(&loctag); - size_t i, l = jl_svec_len(specializations); + l = jl_svec_len(specializations); for (i = 0; i < l; i++) { jl_method_instance_t *mi = (jl_method_instance_t*)jl_svecref(specializations, i); if (mi) { @@ -1729,7 +1758,8 @@ JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method size_t max_world = method->primary_world - 1; int invalidated = 0; jl_value_t *loctag = NULL; // debug info for invalidation - JL_GC_PUSH2(&oldvalue, &loctag); + jl_typemap_entry_t *newentry = NULL; + JL_GC_PUSH3(&oldvalue, &newentry, &loctag); JL_LOCK(&mt->writelock); // first delete the existing entry (we'll disable it later) struct jl_typemap_assoc search = {(jl_value_t*)type, method->primary_world, NULL, 0, ~(size_t)0}; @@ -1739,9 +1769,9 @@ JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method // TODO: just append our new entry right here } // then add our new entry - jl_typemap_entry_t *newentry = jl_typemap_insert(&mt->defs, (jl_value_t*)mt, - (jl_tupletype_t*)type, simpletype, jl_emptysvec, (jl_value_t*)method, 0, &method_defs, - method->primary_world, method->deleted_world); + newentry = jl_typemap_alloc((jl_tupletype_t*)type, simpletype, jl_emptysvec, + (jl_value_t*)method, method->primary_world, method->deleted_world); + jl_typemap_insert(&mt->defs, (jl_value_t*)mt, newentry, 0, &method_defs); oldvalue = check_ambiguous_matches(mt->defs, newentry, check_ambiguous_visitor); if (oldentry) { oldvalue = oldentry->func.value; @@ -1780,9 +1810,17 @@ JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method mt_cache_env.max_world = max_world; mt_cache_env.shadowed = oldvalue; jl_typemap_visitor(mt->cache, invalidate_mt_cache, (void*)&mt_cache_env); - //TODO: if it's small, might it be better to drop it all too? + jl_array_t *leafcache = mt->leafcache; + size_t i, l = jl_array_len(leafcache); + for (i = 1; i < l; i += 2) { + jl_value_t *l = jl_array_ptr_ref(leafcache, i); + if (l && l != jl_nothing) + invalidate_mt_cache((jl_typemap_entry_t*)l, (void*)&mt_cache_env); + } + //TODO: if it's small, might it be better to drop it all? //if (mt != jl_type_type_mt) { // mt->cache = jl_nothing; + // mt->leafcache = jl_an_empty_vec_any; //} jl_value_t **d; @@ -1866,17 +1904,21 @@ jl_tupletype_t *arg_type_tuple(jl_value_t *arg1, jl_value_t **args, size_t nargs return jl_inst_arg_tuple_type(arg1, args, nargs, 1); } -jl_method_instance_t *jl_method_lookup(jl_value_t **args, size_t nargs, int cache, size_t world) +jl_method_instance_t *jl_method_lookup(jl_value_t **args, size_t nargs, size_t world) { assert(nargs > 0 && "expected caller to handle this case"); jl_methtable_t *mt = jl_gf_mtable(args[0]); jl_typemap_entry_t *entry = jl_typemap_assoc_exact(mt->cache, args[0], &args[1], nargs, jl_cachearg_offset(mt), world); if (entry) return entry->func.linfo; - JL_LOCK(&mt->writelock); jl_tupletype_t *tt = arg_type_tuple(args[0], &args[1], nargs); + jl_array_t *leafcache = jl_atomic_load_relaxed(&mt->leafcache); + entry = lookup_leafcache(leafcache, (jl_value_t*)tt, world); + if (entry) + return entry->func.linfo; JL_GC_PUSH1(&tt); - jl_method_instance_t *sf = jl_mt_assoc_by_type(mt, tt, cache, world); + JL_LOCK(&mt->writelock); + jl_method_instance_t *sf = jl_mt_assoc_by_type(mt, tt, world); JL_GC_POP(); JL_UNLOCK(&mt->writelock); return sf; @@ -1899,7 +1941,7 @@ JL_DLLEXPORT jl_value_t *jl_matching_methods(jl_tupletype_t *types, int lim, int jl_methtable_t *mt = jl_method_table_for(unw); if ((jl_value_t*)mt == jl_nothing) return jl_false; // indeterminate - ml_matches can't deal with this case - return ml_matches(mt->defs, 0, types, lim, include_ambiguous, world, min_valid, max_valid); + return ml_matches(mt, 0, types, lim, include_ambiguous, world, min_valid, max_valid, 1); } jl_method_instance_t *jl_get_unspecialized(jl_method_instance_t *method JL_PROPAGATES_ROOT) @@ -2038,7 +2080,13 @@ jl_method_instance_t *jl_get_specialization1(jl_tupletype_t *types JL_PROPAGATES return NULL; // find if exactly 1 method matches (issue #7302) - jl_value_t *matches = jl_matching_methods(types, 1, 1, world, min_valid, max_valid); + size_t min_valid2 = 1; + size_t max_valid2 = ~(size_t)0; + jl_value_t *matches = jl_matching_methods(types, 1, 1, world, &min_valid2, &max_valid2); + if (*min_valid < min_valid2) + *min_valid = min_valid2; + if (*max_valid > max_valid2) + *max_valid = max_valid2; if (matches == jl_false || jl_array_len(matches) != 1) return NULL; jl_tupletype_t *tt = NULL; @@ -2059,7 +2107,7 @@ jl_method_instance_t *jl_get_specialization1(jl_tupletype_t *types JL_PROPAGATES // inject it there now if we think it will be // used via dispatch later (e.g. because it was hinted via a call to `precompile`) JL_LOCK(&mt->writelock); - nf = cache_method(mt, &mt->cache, (jl_value_t*)mt, ti, m, world, env); + nf = cache_method(mt, &mt->cache, (jl_value_t*)mt, ti, m, world, min_valid2, max_valid2, env); JL_UNLOCK(&mt->writelock); } else { @@ -2346,16 +2394,23 @@ STATIC_INLINE jl_method_instance_t *jl_lookup_generic_(jl_value_t *F, jl_value_t LOOP_BODY(3); #undef LOOP_BODY i = 4; - // if no method was found in the associative cache, check the full cache + jl_tupletype_t *tt = NULL; + int64_t last_alloc; if (i == 4) { + // if no method was found in the associative cache, check the full cache JL_TIMING(METHOD_LOOKUP_FAST); mt = jl_gf_mtable(F); entry = jl_typemap_assoc_exact(mt->cache, F, args, nargs, jl_cachearg_offset(mt), world); + if (entry == NULL) { + last_alloc = jl_options.malloc_log ? jl_gc_diff_total_bytes() : 0; + tt = arg_type_tuple(F, args, nargs); + jl_array_t *leafcache = jl_atomic_load_relaxed(&mt->leafcache); + entry = lookup_leafcache(leafcache, (jl_value_t*)tt, world); + } if (entry && entry->isleafsig && entry->simplesig == (void*)jl_nothing && entry->guardsigs == jl_emptysvec) { // put the entry into the cache if it's valid for a leafsig lookup, // using pick_which to slightly randomize where it ends up call_cache[cache_idx[++pick_which[cache_idx[0]] & 3]] = entry; - goto have_entry; } } @@ -2365,13 +2420,12 @@ STATIC_INLINE jl_method_instance_t *jl_lookup_generic_(jl_value_t *F, jl_value_t mfunc = entry->func.linfo; } else { - int64_t last_alloc = jl_options.malloc_log ? jl_gc_diff_total_bytes() : 0; + JL_GC_PUSH1(&tt); + assert(tt); JL_LOCK(&mt->writelock); // cache miss case JL_TIMING(METHOD_LOOKUP_SLOW); - jl_tupletype_t *tt = arg_type_tuple(F, args, nargs); - JL_GC_PUSH1(&tt); - mfunc = jl_mt_assoc_by_type(mt, tt, /*cache*/1, world); + mfunc = jl_mt_assoc_by_type(mt, tt, world); JL_GC_POP(); JL_UNLOCK(&mt->writelock); if (jl_options.malloc_log) @@ -2482,7 +2536,7 @@ static jl_value_t *jl_gf_invoke_by_method(jl_method_t *method, jl_value_t *gf, j if (method->invokes == NULL) method->invokes = jl_nothing; - mfunc = cache_method(NULL, &method->invokes, (jl_value_t*)method, tt, method, 1, tpenv); + mfunc = cache_method(NULL, &method->invokes, (jl_value_t*)method, tt, method, 1, 1, ~(size_t)0, tpenv); JL_UNLOCK(&method->writelock); JL_GC_POP(); if (jl_options.malloc_log) @@ -2532,7 +2586,7 @@ JL_DLLEXPORT jl_value_t *jl_get_invoke_lambda(jl_typemap_entry_t *entry, jl_valu method->invokes = jl_nothing; jl_method_instance_t *mfunc = cache_method(NULL, &method->invokes, (jl_value_t*)method, - (jl_tupletype_t*)tt, method, 1, tpenv); + (jl_tupletype_t*)tt, method, 1, 1, ~(size_t)0, tpenv); JL_GC_POP(); JL_UNLOCK(&method->writelock); return (jl_value_t*)mfunc; @@ -2778,10 +2832,14 @@ static int ml_matches_visitor(jl_typemap_entry_t *ml, struct typemap_intersectio // // Returns a match as an array of svec(argtypes, static_params, Method). // See below for the meaning of lim. -static jl_value_t *ml_matches(jl_typemap_t *defs, int offs, +static jl_value_t *ml_matches(jl_methtable_t *mt, int offs, jl_tupletype_t *type, int lim, int include_ambiguous, - size_t world, size_t *min_valid, size_t *max_valid) + size_t world, size_t *min_valid, size_t *max_valid, + int cache_result) { + jl_typemap_t *defs = mt->defs; + if (defs == jl_nothing) // special-case: ignore builtin functions + return jl_an_empty_vec_any; jl_value_t *unw = jl_unwrap_unionall((jl_value_t*)type); assert(jl_is_datatype(unw)); size_t l = jl_svec_len(((jl_datatype_t*)unw)->parameters); @@ -2803,10 +2861,54 @@ static jl_value_t *ml_matches(jl_typemap_t *defs, int offs, env.world = world; env.min_valid = *min_valid; env.max_valid = *max_valid; - struct jl_typemap_assoc search = {(jl_value_t*)type, world, jl_emptysvec, env.min_valid, env.max_valid}; + struct jl_typemap_assoc search = {(jl_value_t*)type, world, jl_emptysvec, 1, ~(size_t)0}; JL_GC_PUSH5(&env.t, &env.matc, &env.match.env, &search.env, &env.match.ti); + + if (((jl_datatype_t*)unw)->isdispatchtuple) { + jl_array_t *leafcache = jl_atomic_load_relaxed(&mt->leafcache); + jl_typemap_entry_t *entry = lookup_leafcache(leafcache, (jl_value_t*)type, world); + if (entry) { + jl_method_instance_t *mi = entry->func.linfo; + jl_method_t *meth = mi->def.method; + if (jl_egal((jl_value_t*)type, mi->specTypes)) { + env.match.env = mi->sparam_vals; + env.match.ti = mi->specTypes; + } + else { + // TODO: should we use jl_subtype_env instead (since we know that `type <: meth->sig` by transitivity) + env.match.ti = jl_type_intersection_env((jl_value_t*)type, (jl_value_t*)meth->sig, &env.match.env); + } + env.matc = jl_svec(3, env.match.ti, env.match.env, meth); + env.t = (jl_value_t*)jl_alloc_vec_any(1); + jl_array_ptr_set(env.t, 0, env.matc); + if (*min_valid < entry->min_world) + *min_valid = entry->min_world; + if (*max_valid > entry->max_world) + *max_valid = entry->max_world; + JL_GC_POP(); + return env.t; + } + } + if (((jl_datatype_t*)unw)->isdispatchtuple) { + jl_typemap_entry_t *entry = jl_typemap_assoc_by_type(mt->cache, &search, jl_cachearg_offset(mt), /*subtype*/1); + if (entry && (((jl_datatype_t*)unw)->isdispatchtuple || entry->guardsigs == jl_emptysvec)) { + jl_method_instance_t *mi = entry->func.linfo; + jl_method_t *meth = mi->def.method; + // TODO: should we use jl_subtype_env instead (since we know that `type <: meth->sig` by transitivity) + env.match.ti = jl_type_intersection_env((jl_value_t*)type, (jl_value_t*)meth->sig, &env.match.env); + env.matc = jl_svec(3, env.match.ti, env.match.env, meth); + env.t = (jl_value_t*)jl_alloc_vec_any(1); + jl_array_ptr_set(env.t, 0, env.matc); + *min_valid = entry->min_world; + *max_valid = entry->max_world; + JL_GC_POP(); + return env.t; + } + } htable_new(&env.visited, 0); if (((jl_datatype_t*)unw)->isdispatchtuple) { + search.min_valid = env.min_valid; + search.max_valid = env.max_valid; jl_typemap_entry_t *ml = jl_typemap_assoc_by_type(defs, &search, offs, /*subtype*/1); env.min_valid = search.min_valid; env.max_valid = search.max_valid; @@ -2821,6 +2923,14 @@ static jl_value_t *ml_matches(jl_typemap_t *defs, int offs, jl_typemap_intersection_visitor(defs, offs, &env.match); } htable_free(&env.visited); + if (cache_result && ((jl_datatype_t*)unw)->isdispatchtuple) { // cache_result parameter keeps this from being recursive + if (env.t != jl_false && jl_array_len(env.t) == 1) { + env.matc = (jl_svec_t*)jl_array_ptr_ref(env.t, 0); + jl_method_t *meth = (jl_method_t*)jl_svecref(env.matc, 2); + jl_svec_t *tpenv = (jl_svec_t*)jl_svecref(env.matc, 1); + cache_method(mt, &mt->cache, (jl_value_t*)mt, type, meth, world, env.min_valid, env.max_valid, tpenv); + } + } JL_GC_POP(); *min_valid = env.min_valid; *max_valid = env.max_valid; diff --git a/src/iddict.c b/src/iddict.c index 6bec76563d07d..4245ce61a5f80 100644 --- a/src/iddict.c +++ b/src/iddict.c @@ -37,7 +37,11 @@ static int jl_table_assign_bp(jl_array_t **pa, jl_value_t *key, jl_value_t *val) jl_array_t *a = *pa; size_t orig, index, iter, empty_slot; size_t newsz, sz = hash_size(a); - assert(sz >= 1); + if (sz == 0) { + a = jl_alloc_vec_any(HT_N_INLINE); + sz = hash_size(a); + *pa = a; + } size_t maxprobe = max_probe(sz); void **tab = (void **)a->data; @@ -108,7 +112,8 @@ static int jl_table_assign_bp(jl_array_t **pa, jl_value_t *key, jl_value_t *val) jl_value_t **jl_table_peek_bp(jl_array_t *a, jl_value_t *key) JL_NOTSAFEPOINT { size_t sz = hash_size(a); - assert(sz >= 1); + if (sz == 0) + return NULL; size_t maxprobe = max_probe(sz); void **tab = (void **)a->data; uint_t hv = keyhash(key); diff --git a/src/init.c b/src/init.c index 415c4b0316e8c..8345d288e31db 100644 --- a/src/init.c +++ b/src/init.c @@ -721,7 +721,6 @@ void _julia_init(JL_IMAGE_SEARCH rel) else { jl_init_types(); jl_init_codegen(); - jl_an_empty_vec_any = (jl_value_t*)jl_alloc_vec_any(0); // used internally } jl_init_tasks(); diff --git a/src/jltypes.c b/src/jltypes.c index bba9362e9a64f..0e9b911f7d22d 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -822,17 +822,9 @@ static void cache_insert_type_linear(jl_datatype_t *type, ssize_t insert_at) #ifndef NDEBUG static int is_cacheable(jl_datatype_t *type) { - // only cache types whose behavior will not depend on the identities - // of contained TypeVars - assert(jl_is_datatype(type)); - jl_svec_t *t = type->parameters; - if (jl_svec_len(t) == 0 && jl_emptytuple_type != NULL) // Tuple{} is the only type eligible for this that doesn't have parameters - return 0; - // cache abstract types with no free type vars - if (jl_is_abstracttype(type)) - return !jl_has_free_typevars((jl_value_t*)type); - // ... or concrete types - return jl_is_concrete_type((jl_value_t*)type); + // ensure cache only contains types whose behavior will not depend on the + // identities of contained TypeVars + return !jl_has_free_typevars((jl_value_t*)type); } #endif @@ -1943,18 +1935,19 @@ void jl_init_types(void) JL_GC_DISABLED jl_methtable_type->name->mt = jl_nonfunction_mt; jl_methtable_type->super = jl_any_type; jl_methtable_type->parameters = jl_emptysvec; - jl_methtable_type->name->names = jl_perm_symsvec(11, "name", "defs", - "cache", "max_args", + jl_methtable_type->name->names = jl_perm_symsvec(12, "name", "defs", + "leafcache", "cache", "max_args", "kwsorter", "module", "backedges", "", "", "offs", ""); - jl_methtable_type->types = jl_svec(11, jl_symbol_type, jl_any_type, jl_any_type, jl_any_type/*jl_long*/, + jl_methtable_type->types = jl_svec(12, jl_symbol_type, jl_any_type, jl_any_type, + jl_any_type, jl_any_type/*jl_long*/, jl_any_type, jl_any_type/*module*/, jl_any_type/*any vector*/, jl_any_type/*long*/, jl_any_type/*int32*/, jl_any_type/*uint8*/, jl_any_type/*uint8*/); jl_methtable_type->instance = NULL; jl_methtable_type->abstract = 0; jl_methtable_type->mutabl = 1; - jl_methtable_type->ninitialized = 4; + jl_methtable_type->ninitialized = 5; jl_precompute_memoized_dt(jl_methtable_type, 1); jl_symbol_type->name = jl_new_typename_in(jl_symbol("Symbol"), core); @@ -2152,6 +2145,9 @@ void jl_init_types(void) JL_GC_DISABLED jl_array_symbol_type = jl_apply_type2((jl_value_t*)jl_array_type, (jl_value_t*)jl_symbol_type, jl_box_long(1)); jl_array_uint8_type = jl_apply_type2((jl_value_t*)jl_array_type, (jl_value_t*)jl_uint8_type, jl_box_long(1)); jl_array_int32_type = jl_apply_type2((jl_value_t*)jl_array_type, (jl_value_t*)jl_int32_type, jl_box_long(1)); + jl_an_empty_vec_any = (jl_value_t*)jl_alloc_vec_any(0); // used internally + jl_nonfunction_mt->leafcache = (jl_array_t*)jl_an_empty_vec_any; + jl_type_type_mt->leafcache = (jl_array_t*)jl_an_empty_vec_any; jl_expr_type = jl_new_datatype(jl_symbol("Expr"), core, @@ -2466,18 +2462,18 @@ void jl_init_types(void) JL_GC_DISABLED jl_svecset(jl_typename_type->types, 1, jl_module_type); jl_svecset(jl_typename_type->types, 6, jl_long_type); jl_svecset(jl_typename_type->types, 3, jl_type_type); - jl_svecset(jl_methtable_type->types, 3, jl_long_type); - jl_svecset(jl_methtable_type->types, 5, jl_module_type); - jl_svecset(jl_methtable_type->types, 6, jl_array_any_type); + jl_svecset(jl_methtable_type->types, 4, jl_long_type); + jl_svecset(jl_methtable_type->types, 6, jl_module_type); + jl_svecset(jl_methtable_type->types, 7, jl_array_any_type); #ifdef __LP64__ - jl_svecset(jl_methtable_type->types, 7, jl_int64_type); // unsigned long - jl_svecset(jl_methtable_type->types, 8, jl_int64_type); // uint32_t plus alignment + jl_svecset(jl_methtable_type->types, 8, jl_int64_type); // unsigned long + jl_svecset(jl_methtable_type->types, 9, jl_int64_type); // uint32_t plus alignment #else - jl_svecset(jl_methtable_type->types, 7, jl_int32_type); // DWORD - jl_svecset(jl_methtable_type->types, 8, jl_int32_type); // uint32_t + jl_svecset(jl_methtable_type->types, 8, jl_int32_type); // DWORD + jl_svecset(jl_methtable_type->types, 9, jl_int32_type); // uint32_t #endif - jl_svecset(jl_methtable_type->types, 9, jl_uint8_type); jl_svecset(jl_methtable_type->types, 10, jl_uint8_type); + jl_svecset(jl_methtable_type->types, 11, jl_uint8_type); jl_svecset(jl_method_type->types, 13, jl_method_instance_type); jl_svecset(jl_method_instance_type->types, 5, jl_code_instance_type); jl_svecset(jl_code_instance_type->types, 8, jl_voidpointer_type); diff --git a/src/julia.h b/src/julia.h index a6b10f84287a3..1d61b2ca961eb 100644 --- a/src/julia.h +++ b/src/julia.h @@ -537,6 +537,7 @@ typedef struct _jl_methtable_t { JL_DATA_TYPE jl_sym_t *name; // sometimes a hack used by serialization to handle kwsorter jl_typemap_t *defs; + jl_array_t *leafcache; jl_typemap_t *cache; intptr_t max_args; // max # of non-vararg arguments in a signature jl_value_t *kwsorter; // keyword argument sorter function diff --git a/src/julia_internal.h b/src/julia_internal.h index 61123c4b4423d..1fee22750c418 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -462,6 +462,8 @@ jl_datatype_t *jl_new_uninitialized_datatype(void); void jl_precompute_memoized_dt(jl_datatype_t *dt, int cacheable); jl_datatype_t *jl_wrap_Type(jl_value_t *t); // x -> Type{x} jl_value_t *jl_wrap_vararg(jl_value_t *t, jl_value_t *n); +jl_datatype_t *jl_lookup_cache_type_(jl_datatype_t *type); +void jl_cache_type_(jl_datatype_t *type); void jl_assign_bits(void *dest, jl_value_t *bits) JL_NOTSAFEPOINT; void set_nth_field(jl_datatype_t *st, void *v, size_t i, jl_value_t *rhs) JL_NOTSAFEPOINT; jl_expr_t *jl_exprn(jl_sym_t *head, size_t n); @@ -482,7 +484,7 @@ int jl_is_toplevel_only_expr(jl_value_t *e) JL_NOTSAFEPOINT; jl_value_t *jl_call_scm_on_ast(const char *funcname, jl_value_t *expr, jl_module_t *inmodule); void jl_linenumber_to_lineinfo(jl_code_info_t *ci, jl_value_t *name); -jl_method_instance_t *jl_method_lookup(jl_value_t **args, size_t nargs, int cache, size_t world); +jl_method_instance_t *jl_method_lookup(jl_value_t **args, size_t nargs, size_t world); jl_value_t *jl_gf_invoke(jl_value_t *types, jl_value_t *f, jl_value_t **args, size_t nargs); jl_method_instance_t *jl_lookup_generic(jl_value_t **args, uint32_t nargs, uint32_t callsite, size_t world) JL_ALWAYS_LEAFTYPE; JL_DLLEXPORT jl_value_t *jl_matching_methods(jl_tupletype_t *types, int lim, int include_ambiguous, @@ -1048,13 +1050,12 @@ struct jl_typemap_info { jl_datatype_t **jl_contains; // the type that is being put in this }; -jl_typemap_entry_t *jl_typemap_insert(jl_typemap_t **cache, - jl_value_t *parent JL_PROPAGATES_ROOT, - jl_tupletype_t *type, - jl_tupletype_t *simpletype, jl_svec_t *guardsigs, - jl_value_t *newvalue, int8_t offs, - const struct jl_typemap_info *tparams, - size_t min_world, size_t max_world); +void jl_typemap_insert(jl_typemap_t **cache, jl_value_t *parent, + jl_typemap_entry_t *newrec, int8_t offs, + const struct jl_typemap_info *tparams); +jl_typemap_entry_t *jl_typemap_alloc( + jl_tupletype_t *type, jl_tupletype_t *simpletype, jl_svec_t *guardsigs, + jl_value_t *newvalue, size_t min_world, size_t max_world); struct jl_typemap_assoc { // inputs diff --git a/src/precompile.c b/src/precompile.c index 13ce3d92d7d1c..4aede27d7c1eb 100644 --- a/src/precompile.c +++ b/src/precompile.c @@ -314,12 +314,6 @@ static void jl_compile_all_defs(void) JL_GC_POP(); } -static int precompile_enq_all_cache__(jl_typemap_entry_t *l, void *closure) -{ - jl_array_ptr_1d_push((jl_array_t*)closure, (jl_value_t*)l->func.linfo); - return 1; -} - static int precompile_enq_specialization_(jl_method_instance_t *mi, void *closure) { assert(jl_is_method_instance(mi)); @@ -370,7 +364,6 @@ static int precompile_enq_all_specializations__(jl_typemap_entry_t *def, void *c static void precompile_enq_all_specializations_(jl_methtable_t *mt, void *env) { jl_typemap_visitor(mt->defs, precompile_enq_all_specializations__, env); - jl_typemap_visitor(mt->cache, precompile_enq_all_cache__, env); } void jl_compile_now(jl_method_instance_t *mi); diff --git a/src/typemap.c b/src/typemap.c index 849ae6a8e2e02..8b0bf64305fdd 100644 --- a/src/typemap.c +++ b/src/typemap.c @@ -872,18 +872,34 @@ static void jl_typemap_level_insert_( jl_typemap_list_insert_(map, &cache->linear, (jl_value_t*)cache, newrec, tparams); } -jl_typemap_entry_t *jl_typemap_insert(jl_typemap_t **cache, jl_value_t *parent, - jl_tupletype_t *type, - jl_tupletype_t *simpletype, jl_svec_t *guardsigs, - jl_value_t *newvalue, int8_t offs, - const struct jl_typemap_info *tparams, - size_t min_world, size_t max_world) +jl_typemap_entry_t *jl_typemap_alloc( + jl_tupletype_t *type, jl_tupletype_t *simpletype, jl_svec_t *guardsigs, + jl_value_t *newvalue, size_t min_world, size_t max_world) { jl_ptls_t ptls = jl_get_ptls_states(); assert(min_world > 0 && max_world > 0); if (!simpletype) simpletype = (jl_tupletype_t*)jl_nothing; jl_value_t *ttype = jl_unwrap_unionall((jl_value_t*)type); + assert(jl_is_tuple_type(ttype)); + // compute the complexity of this type signature + int isva = jl_is_va_tuple((jl_datatype_t*)ttype); + int issimplesig = !jl_is_unionall(type); // a TypeVar environment needs a complex matching test + int isleafsig = issimplesig && !isva; // entirely leaf types don't need to be sorted + size_t i, l; + for (i = 0, l = jl_nparams(ttype); i < l && issimplesig; i++) { + jl_value_t *decl = jl_tparam(ttype, i); + if (jl_is_kind(decl)) + isleafsig = 0; // Type{} may have a higher priority than a kind + else if (jl_is_type_type(decl)) + isleafsig = 0; // Type{} may need special processing to compute the match + else if (jl_is_vararg_type(decl)) + isleafsig = 0; // makes iteration easier when the endpoints are the same + else if (decl == (jl_value_t*)jl_any_type) + isleafsig = 0; // Any needs to go in the general cache + else if (!jl_is_concrete_type(decl)) // anything else needs to go through the general subtyping test + isleafsig = issimplesig = 0; + } jl_typemap_entry_t *newrec = (jl_typemap_entry_t*)jl_gc_alloc(ptls, sizeof(jl_typemap_entry_t), @@ -895,32 +911,19 @@ jl_typemap_entry_t *jl_typemap_insert(jl_typemap_t **cache, jl_value_t *parent, newrec->next = (jl_typemap_entry_t*)jl_nothing; newrec->min_world = min_world; newrec->max_world = max_world; - // compute the complexity of this type signature - newrec->va = jl_is_va_tuple((jl_datatype_t*)ttype); - newrec->issimplesig = !jl_is_unionall(type); // a TypeVar environment needs a complex matching test - newrec->isleafsig = newrec->issimplesig && !newrec->va; // entirely leaf types don't need to be sorted - JL_GC_PUSH1(&newrec); - assert(jl_is_tuple_type(ttype)); - size_t i, l; - for (i = 0, l = jl_nparams(ttype); i < l && newrec->issimplesig; i++) { - jl_value_t *decl = jl_tparam(ttype, i); - if (jl_is_kind(decl)) - newrec->isleafsig = 0; // Type{} may have a higher priority than a kind - else if (jl_is_type_type(decl)) - newrec->isleafsig = 0; // Type{} may need special processing to compute the match - else if (jl_is_vararg_type(decl)) - newrec->isleafsig = 0; // makes iteration easier when the endpoints are the same - else if (decl == (jl_value_t*)jl_any_type) - newrec->isleafsig = 0; // Any needs to go in the general cache - else if (!jl_is_concrete_type(decl)) // anything else needs to go through the general subtyping test - newrec->isleafsig = newrec->issimplesig = 0; - } - // TODO: assert that guardsigs == jl_emptysvec && simplesig == jl_nothing if isleafsig and optimize with that knowledge? - jl_typemap_insert_generic(*cache, cache, parent, newrec, offs, tparams); - JL_GC_POP(); + newrec->va = isva; + newrec->issimplesig = issimplesig; + newrec->isleafsig = isleafsig; return newrec; } +void jl_typemap_insert(jl_typemap_t **cache, jl_value_t *parent, + jl_typemap_entry_t *newrec, int8_t offs, + const struct jl_typemap_info *tparams) +{ + jl_typemap_insert_generic(*cache, cache, parent, newrec, offs, tparams); +} + static void jl_typemap_list_insert_sorted( jl_typemap_t *map, jl_typemap_entry_t **pml, jl_value_t *parent, jl_typemap_entry_t *newrec, const struct jl_typemap_info *tparams) diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index a16565bba3751..4007122aee41d 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -395,7 +395,7 @@ end # Returns the return type. example: get_type(:(Base.strip("", ' ')), Main) returns (String, true) function try_get_type(sym::Expr, fn::Module) val, found = get_value(sym, fn) - found && return Base.typesof(val).parameters[1], found + found && return Core.Typeof(val), found if sym.head === :call # getfield call is special cased as the evaluation of getfield provides good type information, # is inexpensive and it is also performed in the complete_symbol function. @@ -403,7 +403,7 @@ function try_get_type(sym::Expr, fn::Module) if isa(a1,GlobalRef) && isconst(a1.mod,a1.name) && isdefined(a1.mod,a1.name) && eval(a1) === Core.getfield val, found = get_value_getfield(sym, Main) - return found ? Base.typesof(val).parameters[1] : Any, found + return found ? Core.Typeof(val) : Any, found end return get_type_call(sym) elseif sym.head === :thunk @@ -430,7 +430,7 @@ end function get_type(sym, fn::Module) val, found = get_value(sym, fn) - return found ? Base.typesof(val).parameters[1] : Any, found + return found ? Core.Typeof(val) : Any, found end # Method completion on function call expression that look like :(max(1)) diff --git a/test/channels.jl b/test/channels.jl index 0eb1f589f4f5c..d4d8cf6e2e67d 100644 --- a/test/channels.jl +++ b/test/channels.jl @@ -390,6 +390,7 @@ end t = Timer(0) do t tc[] += 1 end + Libc.systemsleep(0.005) @test isopen(t) Base.process_events() @test !isopen(t) @@ -402,6 +403,7 @@ end t = Timer(0) do t tc[] += 1 end + Libc.systemsleep(0.005) @test isopen(t) close(t) @test !isopen(t) diff --git a/test/reflection.jl b/test/reflection.jl index 4b77f2e2ac058..23a6ff0dfbe69 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -507,7 +507,6 @@ f18888() = nothing let world = Core.Compiler.get_world_counter() m = first(methods(f18888, Tuple{})) - @test isempty(m.specializations) ft = typeof(f18888) code_typed(f18888, Tuple{}; optimize=false)