diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 81d0f06608a31..29c4a7e6e477a 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -347,7 +347,7 @@ function maybe_compress_codeinfo(interp::AbstractInterpreter, linfo::MethodInsta return ci end if may_discard_trees(interp) - cache_the_tree = ci.inferred && (is_inlineable(ci) || isa_compileable_sig(linfo.specTypes, def)) + cache_the_tree = ci.inferred && (is_inlineable(ci) || isa_compileable_sig(linfo.specTypes, linfo.sparam_vals, def)) else cache_the_tree = true end diff --git a/base/compiler/utilities.jl b/base/compiler/utilities.jl index 2915870ae2ea5..007aaa31fdd55 100644 --- a/base/compiler/utilities.jl +++ b/base/compiler/utilities.jl @@ -152,8 +152,8 @@ function get_compileable_sig(method::Method, @nospecialize(atype), sparams::Simp mt, atype, sparams, method) end -isa_compileable_sig(@nospecialize(atype), method::Method) = - !iszero(ccall(:jl_isa_compileable_sig, Int32, (Any, Any), atype, method)) +isa_compileable_sig(@nospecialize(atype), sparams::SimpleVector, method::Method) = + !iszero(ccall(:jl_isa_compileable_sig, Int32, (Any, Any, Any), atype, sparams, method)) # eliminate UnionAll vars that might be degenerate due to having identical bounds, # or a concrete upper bound and appearing covariantly. @@ -200,7 +200,12 @@ function specialize_method(method::Method, @nospecialize(atype), sparams::Simple if compilesig new_atype = get_compileable_sig(method, atype, sparams) new_atype === nothing && return nothing - atype = new_atype + if atype !== new_atype + sp_ = ccall(:jl_type_intersection_with_env, Any, (Any, Any), new_atype, method.sig)::SimpleVector + if sparams === sp_[2]::SimpleVector + atype = new_atype + end + end end if preexisting # check cached specializations diff --git a/src/gf.c b/src/gf.c index df50b42216006..6d705f15a482c 100644 --- a/src/gf.c +++ b/src/gf.c @@ -606,6 +606,46 @@ jl_value_t *jl_nth_slot_type(jl_value_t *sig, size_t i) JL_NOTSAFEPOINT // return 1; //} +static jl_value_t *inst_varargp_in_env(jl_value_t *decl, jl_svec_t *sparams) +{ + jl_value_t *unw = jl_unwrap_unionall(decl); + jl_value_t *vm = jl_tparam(unw, jl_nparams(unw) - 1); + assert(jl_is_vararg(vm)); + int nsp = jl_svec_len(sparams); + if (nsp > 0 && jl_has_free_typevars(vm)) { + JL_GC_PUSH1(&vm); + assert(jl_subtype_env_size(decl) == nsp); + vm = jl_instantiate_type_in_env(vm, (jl_unionall_t*)decl, jl_svec_data(sparams)); + assert(jl_is_vararg(vm)); + // rewrap_unionall(lastdeclt, sparams) if any sparams isa TypeVar + // for example, `Tuple{Vararg{Union{Nothing,Int,Val{T}}}} where T` + // and the user called it with `Tuple{Vararg{Union{Nothing,Int},N}}`, then T is unbound + jl_value_t **sp = jl_svec_data(sparams); + while (jl_is_unionall(decl)) { + jl_tvar_t *v = (jl_tvar_t*)*sp; + if (jl_is_typevar(v)) { + // must unwrap and re-wrap Vararg object explicitly here since jl_type_unionall handles it differently + jl_value_t *T = ((jl_vararg_t*)vm)->T; + jl_value_t *N = ((jl_vararg_t*)vm)->N; + int T_has_tv = T && jl_has_typevar(T, v); + int N_has_tv = N && jl_has_typevar(N, v); // n.b. JL_VARARG_UNBOUND check means this should be false + assert(!N_has_tv || N == (jl_value_t*)v); + if (T_has_tv) + vm = jl_type_unionall(v, T); + if (N_has_tv) + N = NULL; + vm = (jl_value_t*)jl_wrap_vararg(vm, N); // this cannot throw for these inputs + } + sp++; + decl = ((jl_unionall_t*)decl)->body; + nsp--; + } + assert(nsp == 0); + JL_GC_POP(); + } + return vm; +} + static jl_value_t *ml_matches(jl_methtable_t *mt, jl_tupletype_t *type, int lim, int include_ambiguous, int intersections, size_t world, int cache_result, @@ -634,16 +674,19 @@ static void jl_compilation_sig( assert(jl_is_tuple_type(tt)); size_t i, np = jl_nparams(tt); size_t nargs = definition->nargs; // == jl_nparams(jl_unwrap_unionall(decl)); + jl_value_t *type_i = NULL; + JL_GC_PUSH1(&type_i); for (i = 0; i < np; i++) { jl_value_t *elt = jl_tparam(tt, i); jl_value_t *decl_i = jl_nth_slot_type(decl, i); + type_i = jl_rewrap_unionall(decl_i, decl); size_t i_arg = (i < nargs - 1 ? i : nargs - 1); - if (jl_is_kind(decl_i)) { + if (jl_is_kind(type_i)) { // if we can prove the match was against the kind (not a Type) // we want to put that in the cache instead if (!*newparams) *newparams = jl_svec_copy(tt->parameters); - elt = decl_i; + elt = type_i; jl_svecset(*newparams, i, elt); } else if (jl_is_type_type(elt)) { @@ -652,7 +695,7 @@ static void jl_compilation_sig( // and the result of matching the type signature // needs to be restricted to the concrete type 'kind' jl_value_t *kind = jl_typeof(jl_tparam0(elt)); - if (jl_subtype(kind, decl_i) && !jl_subtype((jl_value_t*)jl_type_type, decl_i)) { + if (jl_subtype(kind, type_i) && !jl_subtype((jl_value_t*)jl_type_type, type_i)) { // if we can prove the match was against the kind (not a Type) // it's simpler (and thus better) to put that cache instead if (!*newparams) *newparams = jl_svec_copy(tt->parameters); @@ -664,7 +707,7 @@ static void jl_compilation_sig( // not triggered for isdispatchtuple(tt), this attempts to handle // some cases of adapting a random signature into a compilation signature // if we get a kind, where we don't expect to accept one, widen it to something more expected (Type{T}) - if (!(jl_subtype(elt, decl_i) && !jl_subtype((jl_value_t*)jl_type_type, decl_i))) { + if (!(jl_subtype(elt, type_i) && !jl_subtype((jl_value_t*)jl_type_type, type_i))) { if (!*newparams) *newparams = jl_svec_copy(tt->parameters); elt = (jl_value_t*)jl_type_type; jl_svecset(*newparams, i, elt); @@ -703,7 +746,7 @@ static void jl_compilation_sig( jl_svecset(*newparams, i, jl_type_type); } else if (jl_is_type_type(elt)) { // elt isa Type{T} - if (very_general_type(decl_i)) { + if (!jl_has_free_typevars(decl_i) && very_general_type(type_i)) { /* Here's a fairly simple heuristic: if this argument slot's declared type is general (Type or Any), @@ -742,15 +785,13 @@ static void jl_compilation_sig( */ if (!*newparams) *newparams = jl_svec_copy(tt->parameters); if (i < nargs || !definition->isva) { - jl_value_t *di = jl_type_intersection(decl_i, (jl_value_t*)jl_type_type); + jl_value_t *di = jl_type_intersection(type_i, (jl_value_t*)jl_type_type); assert(di != (jl_value_t*)jl_bottom_type); // issue #11355: DataType has a UID and so would take precedence in the cache if (jl_is_kind(di)) jl_svecset(*newparams, i, (jl_value_t*)jl_type_type); else jl_svecset(*newparams, i, di); - // TODO: recompute static parameter values, so in extreme cases we - // can give `T=Type` instead of `T=Type{Type{Type{...`. /* make editors happy:}}} */ } else { jl_svecset(*newparams, i, (jl_value_t*)jl_type_type); @@ -759,14 +800,15 @@ static void jl_compilation_sig( } int notcalled_func = (i_arg > 0 && i_arg <= 8 && !(definition->called & (1 << (i_arg - 1))) && + !jl_has_free_typevars(decl_i) && jl_subtype(elt, (jl_value_t*)jl_function_type)); - if (notcalled_func && (decl_i == (jl_value_t*)jl_any_type || - decl_i == (jl_value_t*)jl_function_type || - (jl_is_uniontype(decl_i) && // Base.Callable - ((((jl_uniontype_t*)decl_i)->a == (jl_value_t*)jl_function_type && - ((jl_uniontype_t*)decl_i)->b == (jl_value_t*)jl_type_type) || - (((jl_uniontype_t*)decl_i)->b == (jl_value_t*)jl_function_type && - ((jl_uniontype_t*)decl_i)->a == (jl_value_t*)jl_type_type))))) { + if (notcalled_func && (type_i == (jl_value_t*)jl_any_type || + type_i == (jl_value_t*)jl_function_type || + (jl_is_uniontype(type_i) && // Base.Callable + ((((jl_uniontype_t*)type_i)->a == (jl_value_t*)jl_function_type && + ((jl_uniontype_t*)type_i)->b == (jl_value_t*)jl_type_type) || + (((jl_uniontype_t*)type_i)->b == (jl_value_t*)jl_function_type && + ((jl_uniontype_t*)type_i)->a == (jl_value_t*)jl_type_type))))) { // and attempt to despecialize types marked Function, Callable, or Any // when called with a subtype of Function but is not called if (!*newparams) *newparams = jl_svec_copy(tt->parameters); @@ -779,15 +821,9 @@ static void jl_compilation_sig( // supertype of any other method signatures. so far we are conservative // and the types we find should be bigger. if (jl_nparams(tt) >= nspec && jl_va_tuple_kind((jl_datatype_t*)decl) == JL_VARARG_UNBOUND) { - jl_svec_t *limited = jl_alloc_svec(nspec); - JL_GC_PUSH1(&limited); if (!*newparams) *newparams = tt->parameters; - size_t i; - for (i = 0; i < nspec - 1; i++) { - jl_svecset(limited, i, jl_svecref(*newparams, i)); - } - jl_value_t *lasttype = jl_svecref(*newparams, i - 1); - // if all subsequent arguments are subtypes of lasttype, specialize + type_i = jl_svecref(*newparams, nspec - 2); + // if all subsequent arguments are subtypes of type_i, specialize // on that instead of decl. for example, if decl is // (Any...) // and type is @@ -795,53 +831,41 @@ static void jl_compilation_sig( // then specialize as (Symbol...), but if type is // (Symbol, Int32, Expr) // then specialize as (Any...) - size_t j = i; + size_t j = nspec - 1; int all_are_subtypes = 1; for (; j < jl_svec_len(*newparams); j++) { jl_value_t *paramj = jl_svecref(*newparams, j); if (jl_is_vararg(paramj)) paramj = jl_unwrap_vararg(paramj); - if (!jl_subtype(paramj, lasttype)) { + if (!jl_subtype(paramj, type_i)) { all_are_subtypes = 0; break; } } if (all_are_subtypes) { // avoid Vararg{Type{Type{...}}} - if (jl_is_type_type(lasttype) && jl_is_type_type(jl_tparam0(lasttype))) - lasttype = (jl_value_t*)jl_type_type; - jl_svecset(limited, i, jl_wrap_vararg(lasttype, (jl_value_t*)NULL)); + if (jl_is_type_type(type_i) && jl_is_type_type(jl_tparam0(type_i))) + type_i = (jl_value_t*)jl_type_type; + type_i = (jl_value_t*)jl_wrap_vararg(type_i, (jl_value_t*)NULL); // this cannot throw for these inputs } else { - jl_value_t *unw = jl_unwrap_unionall(decl); - jl_value_t *lastdeclt = jl_tparam(unw, jl_nparams(unw) - 1); - assert(jl_is_vararg(lastdeclt)); - int nsp = jl_svec_len(sparams); - if (nsp > 0 && jl_has_free_typevars(lastdeclt)) { - assert(jl_subtype_env_size(decl) == nsp); - lastdeclt = jl_instantiate_type_in_env(lastdeclt, (jl_unionall_t*)decl, jl_svec_data(sparams)); - // TODO: rewrap_unionall(lastdeclt, sparams) if any sparams isa TypeVar??? - // TODO: if we made any replacements above, sparams may now be incorrect - } - jl_svecset(limited, i, lastdeclt); + type_i = inst_varargp_in_env(decl, sparams); } + jl_svec_t *limited = jl_alloc_svec(nspec); + size_t i; + for (i = 0; i < nspec - 1; i++) { + jl_svecset(limited, i, jl_svecref(*newparams, i)); + } + jl_svecset(limited, i, type_i); *newparams = limited; - // now there is a problem: the widened signature is more - // general than just the given arguments, so it might conflict - // with another definition that doesn't have cache instances yet. - // to fix this, we insert guard cache entries for all intersections - // of this signature and definitions. those guard entries will - // supersede this one in conflicted cases, alerting us that there - // should actually be a cache miss. - // TODO: the above analysis assumes that there will never - // be a call attempted that should throw a no-method error - JL_GC_POP(); } + JL_GC_POP(); } // compute whether this type signature is a possible return value from jl_compilation_sig given a concrete-type for `tt` JL_DLLEXPORT int jl_isa_compileable_sig( jl_tupletype_t *type, + jl_svec_t *sparams, jl_method_t *definition) { jl_value_t *decl = definition->sig; @@ -873,18 +897,20 @@ JL_DLLEXPORT int jl_isa_compileable_sig( jl_methtable_t *kwmt = mt == jl_kwcall_mt ? jl_kwmethod_table_for(decl) : mt; if ((jl_value_t*)mt != jl_nothing) { // try to refine estimate of min and max - if (kwmt && kwmt != jl_type_type_mt && kwmt != jl_nonfunction_mt && kwmt != jl_kwcall_mt) + if (kwmt != NULL && kwmt != jl_type_type_mt && kwmt != jl_nonfunction_mt && kwmt != jl_kwcall_mt) + // new methods may be added, increasing nspec_min later nspec_min = jl_atomic_load_relaxed(&kwmt->max_args) + 2 + 2 * (mt == jl_kwcall_mt); else + // nspec is always nargs+1, regardless of the other contents of these mt nspec_max = nspec_min; } - int isbound = (jl_va_tuple_kind((jl_datatype_t*)decl) == JL_VARARG_UNBOUND); + int isunbound = (jl_va_tuple_kind((jl_datatype_t*)decl) == JL_VARARG_UNBOUND); if (jl_is_vararg(jl_tparam(type, np - 1))) { - if (!isbound || np < nspec_min || np > nspec_max) + if (!isunbound || np < nspec_min || np > nspec_max) return 0; } else { - if (np < nargs - 1 || (isbound && np >= nspec_max)) + if (np < nargs - 1 || (isunbound && np >= nspec_max)) return 0; } } @@ -892,74 +918,87 @@ JL_DLLEXPORT int jl_isa_compileable_sig( return 0; } + jl_value_t *type_i = NULL; + JL_GC_PUSH1(&type_i); for (i = 0; i < np; i++) { jl_value_t *elt = jl_tparam(type, i); - jl_value_t *decl_i = jl_nth_slot_type((jl_value_t*)decl, i); size_t i_arg = (i < nargs - 1 ? i : nargs - 1); if (jl_is_vararg(elt)) { - elt = jl_unwrap_vararg(elt); - if (jl_has_free_typevars(decl_i)) { - // TODO: in this case, answer semi-conservatively that these varargs are always compilable - // we don't have the ability to get sparams, so deciding if elt - // is a potential result of jl_instantiate_type_in_env for decl_i - // for any sparams that is consistent with the rest of the arguments - // seems like it would be extremely difficult - // and hopefully the upstream code probably gave us something reasonable - continue; - } - else if (jl_egal(elt, decl_i)) { - continue; + type_i = inst_varargp_in_env(decl, sparams); + if (jl_has_free_typevars(type_i)) { + JL_GC_POP(); + return 0; // something went badly wrong? } - else if (jl_is_type_type(elt) && jl_is_type_type(jl_tparam0(elt))) { - return 0; + if (jl_egal(elt, type_i)) + continue; // elt could be chosen by inst_varargp_in_env for these sparams + elt = jl_unwrap_vararg(elt); + if (jl_is_type_type(elt) && jl_is_type_type(jl_tparam0(elt))) { + JL_GC_POP(); + return 0; // elt would be set equal to jl_type_type instead } - // else, it needs to meet the usual rules + // else, elt also needs to meet the usual rules } + jl_value_t *decl_i = jl_nth_slot_type(decl, i); + type_i = jl_rewrap_unionall(decl_i, decl); + if (i_arg > 0 && i_arg <= sizeof(definition->nospecialize) * 8 && (definition->nospecialize & (1 << (i_arg - 1)))) { if (!jl_has_free_typevars(decl_i) && !jl_is_kind(decl_i)) { if (jl_egal(elt, decl_i)) continue; + JL_GC_POP(); return 0; } } if (jl_is_kind(elt)) { // kind slots always get guard entries (checking for subtypes of Type) - if (jl_subtype(elt, decl_i) && !jl_subtype((jl_value_t*)jl_type_type, decl_i)) + if (jl_subtype(elt, type_i) && !jl_subtype((jl_value_t*)jl_type_type, type_i)) continue; - // TODO: other code paths that could reach here + // TODO: other code paths that could reach here? + JL_GC_POP(); return 0; } - else if (jl_is_kind(decl_i)) { + else if (jl_is_kind(type_i)) { + JL_GC_POP(); return 0; } if (jl_is_type_type(jl_unwrap_unionall(elt))) { - int iscalled = i_arg > 0 && i_arg <= 8 && (definition->called & (1 << (i_arg - 1))); + int iscalled = (i_arg > 0 && i_arg <= 8 && (definition->called & (1 << (i_arg - 1)))) || + jl_has_free_typevars(decl_i); if (jl_types_equal(elt, (jl_value_t*)jl_type_type)) { - if (!iscalled && very_general_type(decl_i)) + if (!iscalled && very_general_type(type_i)) continue; if (i >= nargs && definition->isva) continue; + JL_GC_POP(); return 0; } - if (!iscalled && very_general_type(decl_i)) + if (!iscalled && very_general_type(type_i)) { + JL_GC_POP(); return 0; - if (!jl_is_datatype(elt)) + } + if (!jl_is_datatype(elt)) { + JL_GC_POP(); return 0; + } // if the declared type was not Any or Union{Type, ...}, // then the match must been with kind, such as UnionAll or DataType, // and the result of matching the type signature // needs to be corrected to the concrete type 'kind' (and not to Type) jl_value_t *kind = jl_typeof(jl_tparam0(elt)); - if (kind == jl_bottom_type) + if (kind == jl_bottom_type) { + JL_GC_POP(); return 0; // Type{Union{}} gets normalized to typeof(Union{}) - if (jl_subtype(kind, decl_i) && !jl_subtype((jl_value_t*)jl_type_type, decl_i)) + } + if (jl_subtype(kind, type_i) && !jl_subtype((jl_value_t*)jl_type_type, type_i)) { + JL_GC_POP(); return 0; // gets turned into a kind + } else if (jl_is_type_type(jl_tparam0(elt)) && // give up on specializing static parameters for Type{Type{Type{...}}} @@ -972,20 +1011,20 @@ JL_DLLEXPORT int jl_isa_compileable_sig( this can be determined using a type intersection. */ if (i < nargs || !definition->isva) { - jl_value_t *di = jl_type_intersection(decl_i, (jl_value_t*)jl_type_type); - JL_GC_PUSH1(&di); - assert(di != (jl_value_t*)jl_bottom_type); - if (jl_is_kind(di)) { + type_i = jl_type_intersection(type_i, (jl_value_t*)jl_type_type); + assert(type_i != (jl_value_t*)jl_bottom_type); + if (jl_is_kind(type_i)) { JL_GC_POP(); return 0; } - else if (!jl_types_equal(di, elt)) { + else if (!jl_types_equal(type_i, elt)) { JL_GC_POP(); return 0; } - JL_GC_POP(); + continue; } else { + JL_GC_POP(); return 0; } } @@ -993,24 +1032,29 @@ JL_DLLEXPORT int jl_isa_compileable_sig( } int notcalled_func = (i_arg > 0 && i_arg <= 8 && !(definition->called & (1 << (i_arg - 1))) && + !jl_has_free_typevars(decl_i) && jl_subtype(elt, (jl_value_t*)jl_function_type)); - if (notcalled_func && (decl_i == (jl_value_t*)jl_any_type || - decl_i == (jl_value_t*)jl_function_type || - (jl_is_uniontype(decl_i) && // Base.Callable - ((((jl_uniontype_t*)decl_i)->a == (jl_value_t*)jl_function_type && - ((jl_uniontype_t*)decl_i)->b == (jl_value_t*)jl_type_type) || - (((jl_uniontype_t*)decl_i)->b == (jl_value_t*)jl_function_type && - ((jl_uniontype_t*)decl_i)->a == (jl_value_t*)jl_type_type))))) { + if (notcalled_func && (type_i == (jl_value_t*)jl_any_type || + type_i == (jl_value_t*)jl_function_type || + (jl_is_uniontype(type_i) && // Base.Callable + ((((jl_uniontype_t*)type_i)->a == (jl_value_t*)jl_function_type && + ((jl_uniontype_t*)type_i)->b == (jl_value_t*)jl_type_type) || + (((jl_uniontype_t*)type_i)->b == (jl_value_t*)jl_function_type && + ((jl_uniontype_t*)type_i)->a == (jl_value_t*)jl_type_type))))) { // and attempt to despecialize types marked Function, Callable, or Any // when called with a subtype of Function but is not called if (elt == (jl_value_t*)jl_function_type) continue; + JL_GC_POP(); return 0; } - if (!jl_is_concrete_type(elt)) + if (!jl_is_concrete_type(elt)) { + JL_GC_POP(); return 0; + } } + JL_GC_POP(); return 1; } @@ -1078,20 +1122,37 @@ static jl_method_instance_t *cache_method( jl_svec_t *newparams = NULL; JL_GC_PUSH5(&temp, &temp2, &temp3, &newmeth, &newparams); + // Consider if we can cache with the preferred compile signature + // so that we can minimize the number of required cache entries. int cache_with_orig = 1; jl_tupletype_t *compilationsig = tt; jl_methtable_t *kwmt = mt == jl_kwcall_mt ? jl_kwmethod_table_for(definition->sig) : mt; intptr_t nspec = (kwmt == NULL || kwmt == jl_type_type_mt || kwmt == jl_nonfunction_mt || kwmt == jl_kwcall_mt ? definition->nargs + 1 : jl_atomic_load_relaxed(&kwmt->max_args) + 2 + 2 * (mt == jl_kwcall_mt)); jl_compilation_sig(tt, sparams, definition, nspec, &newparams); if (newparams) { - compilationsig = jl_apply_tuple_type(newparams); - temp2 = (jl_value_t*)compilationsig; - // In most cases `!jl_isa_compileable_sig(tt, definition))`, + temp2 = (jl_value_t*)jl_apply_tuple_type(newparams); + // Now there may be a problem: the widened signature is more general + // than just the given arguments, so it might conflict with another + // definition that does not have cache instances yet. To fix this, we + // may insert guard cache entries for all intersections of this + // signature and definitions. Those guard entries will supersede this + // one in conflicted cases, alerting us that there should actually be a + // cache miss. Alternatively, we may use the original signature in the + // cache, but use this return for compilation. + // + // In most cases `!jl_isa_compileable_sig(tt, sparams, definition)`, // although for some cases, (notably Varargs) // we might choose a replacement type that's preferable but not strictly better - cache_with_orig = !jl_subtype((jl_value_t*)compilationsig, definition->sig); + int issubty; + temp = jl_type_intersection_env_s(temp2, (jl_value_t*)definition->sig, &newparams, &issubty); + assert(temp != (jl_value_t*)jl_bottom_type); (void)temp; + if (jl_egal((jl_value_t*)newparams, (jl_value_t*)sparams)) { + cache_with_orig = !issubty; + compilationsig = (jl_datatype_t*)temp2; + } + newparams = NULL; } - // TODO: maybe assert(jl_isa_compileable_sig(compilationsig, definition)); + // TODO: maybe assert(jl_isa_compileable_sig(compilationsig, sparams, definition)); newmeth = jl_specializations_get_linfo(definition, (jl_value_t*)compilationsig, sparams); jl_tupletype_t *cachett = tt; @@ -1110,6 +1171,8 @@ static jl_method_instance_t *cache_method( size_t i, l = jl_array_len(temp); for (i = 0; i < l; i++) { jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(temp, i); + if (matc->method == definition) + continue; jl_svec_t *env = matc->sparams; int k, l; for (k = 0, l = jl_svec_len(env); k < l; k++) { @@ -1128,9 +1191,7 @@ static jl_method_instance_t *cache_method( cache_with_orig = 1; break; } - if (matc->method != definition) { - guards++; - } + guards++; } } if (!cache_with_orig && guards > 0) { @@ -2096,11 +2157,35 @@ static void record_precompile_statement(jl_method_instance_t *mi) JL_UNLOCK(&precomp_statement_out_lock); } +jl_method_instance_t *jl_normalize_to_compilable_mi(jl_method_instance_t *mi JL_PROPAGATES_ROOT); + jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t world) { + // quick check if we already have a compiled result jl_code_instance_t *codeinst = jl_method_compiled(mi, world); if (codeinst) return codeinst; + + // if mi has a better (wider) signature for compilation use that instead + // and just copy it here for caching + jl_method_instance_t *mi2 = jl_normalize_to_compilable_mi(mi); + if (mi2 != mi) { + jl_code_instance_t *codeinst2 = jl_compile_method_internal(mi2, world); + jl_code_instance_t *codeinst = jl_get_method_inferred( + mi, codeinst2->rettype, + codeinst2->min_world, codeinst2->max_world); + if (jl_atomic_load_relaxed(&codeinst->invoke) == NULL) { + // once set, don't change invoke-ptr, as that leads to race conditions + // with the (not) simultaneous updates to invoke and specptr + codeinst->isspecsig = codeinst2->isspecsig; + codeinst->rettype_const = codeinst2->rettype_const; + jl_atomic_store_release(&codeinst->specptr.fptr, jl_atomic_load_relaxed(&codeinst2->specptr.fptr)); + jl_atomic_store_release(&codeinst->invoke, jl_atomic_load_relaxed(&codeinst2->invoke)); + } + // don't call record_precompile_statement here, since we already compiled it as mi2 which is better + return codeinst; + } + int compile_option = jl_options.compile_enabled; jl_method_t *def = mi->def.method; // disabling compilation per-module can override global setting @@ -2135,6 +2220,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t } } } + // if that didn't work and compilation is off, try running in the interpreter if (compile_option == JL_OPTIONS_COMPILE_OFF || compile_option == JL_OPTIONS_COMPILE_MIN) { @@ -2248,13 +2334,45 @@ JL_DLLEXPORT jl_value_t *jl_normalize_to_compilable_sig(jl_methtable_t *mt, jl_t jl_methtable_t *kwmt = mt == jl_kwcall_mt ? jl_kwmethod_table_for(m->sig) : mt; intptr_t nspec = (kwmt == NULL || kwmt == jl_type_type_mt || kwmt == jl_nonfunction_mt || kwmt == jl_kwcall_mt ? m->nargs + 1 : jl_atomic_load_relaxed(&kwmt->max_args) + 2 + 2 * (mt == jl_kwcall_mt)); jl_compilation_sig(ti, env, m, nspec, &newparams); - tt = (newparams ? jl_apply_tuple_type(newparams) : ti); - int is_compileable = ((jl_datatype_t*)ti)->isdispatchtuple || - jl_isa_compileable_sig(tt, m); + int is_compileable = ((jl_datatype_t*)ti)->isdispatchtuple; + if (newparams) { + tt = jl_apply_tuple_type(newparams); + if (!is_compileable) { + // compute new env, if used below + jl_value_t *ti = jl_type_intersection_env((jl_value_t*)tt, (jl_value_t*)m->sig, &newparams); + assert(ti != jl_bottom_type); (void)ti; + env = newparams; + } + } + else { + tt = ti; + } + if (!is_compileable) + is_compileable = jl_isa_compileable_sig(tt, env, m); JL_GC_POP(); return is_compileable ? (jl_value_t*)tt : jl_nothing; } +jl_method_instance_t *jl_normalize_to_compilable_mi(jl_method_instance_t *mi JL_PROPAGATES_ROOT) +{ + jl_method_t *def = mi->def.method; + if (!jl_is_method(def)) + return mi; + jl_methtable_t *mt = jl_method_get_table(def); + if ((jl_value_t*)mt == jl_nothing) + return mi; + jl_value_t *compilationsig = jl_normalize_to_compilable_sig(mt, (jl_datatype_t*)mi->specTypes, mi->sparam_vals, def); + if (compilationsig == jl_nothing || jl_egal(compilationsig, mi->specTypes)) + return mi; + jl_svec_t *env = NULL; + JL_GC_PUSH2(&compilationsig, &env); + jl_value_t *ti = jl_type_intersection_env((jl_value_t*)compilationsig, (jl_value_t*)def->sig, &env); + assert(ti != jl_bottom_type); (void)ti; + mi = jl_specializations_get_linfo(def, (jl_value_t*)compilationsig, env); + JL_GC_POP(); + return mi; +} + // return a MethodInstance for a compileable method_match jl_method_instance_t *jl_method_match_to_mi(jl_method_match_t *match, size_t world, size_t min_valid, size_t max_valid, int mt_cache) { @@ -2265,7 +2383,7 @@ jl_method_instance_t *jl_method_match_to_mi(jl_method_match_t *match, size_t wor if (jl_is_datatype(ti)) { jl_methtable_t *mt = jl_method_get_table(m); if ((jl_value_t*)mt != jl_nothing) { - // get the specialization without caching it + // get the specialization, possibly also caching it if (mt_cache && ((jl_datatype_t*)ti)->isdispatchtuple) { // Since we also use this presence in the cache // to trigger compilation when producing `.ji` files, @@ -2277,11 +2395,15 @@ jl_method_instance_t *jl_method_match_to_mi(jl_method_match_t *match, size_t wor } else { jl_value_t *tt = jl_normalize_to_compilable_sig(mt, ti, env, m); - JL_GC_PUSH1(&tt); if (tt != jl_nothing) { + JL_GC_PUSH2(&tt, &env); + if (!jl_egal(tt, (jl_value_t*)ti)) { + jl_value_t *ti = jl_type_intersection_env((jl_value_t*)tt, (jl_value_t*)m->sig, &env); + assert(ti != jl_bottom_type); (void)ti; + } mi = jl_specializations_get_linfo(m, (jl_value_t*)tt, env); + JL_GC_POP(); } - JL_GC_POP(); } } } @@ -2344,7 +2466,7 @@ jl_method_instance_t *jl_get_compile_hint_specialization(jl_tupletype_t *types J size_t count = 0; for (i = 0; i < n; i++) { jl_method_match_t *match1 = (jl_method_match_t*)jl_array_ptr_ref(matches, i); - if (jl_isa_compileable_sig(types, match1->method)) + if (jl_isa_compileable_sig(types, match1->sparams, match1->method)) jl_array_ptr_set(matches, count++, (jl_value_t*)match1); } jl_array_del_end((jl_array_t*)matches, n - count); @@ -2468,7 +2590,7 @@ static jl_value_t *jl_argtype_with_function(jl_function_t *f, jl_value_t *types0 for(i=0; i < l; i++) jl_svecset(tt, i+1, jl_tparam(types,i)); tt = (jl_value_t*)jl_apply_tuple_type((jl_svec_t*)tt); - tt = jl_rewrap_unionall(tt, types0); + tt = jl_rewrap_unionall_(tt, types0); JL_GC_POP(); return tt; } diff --git a/src/jitlayers.cpp b/src/jitlayers.cpp index f5a0623cd8df6..da5e8c58fdecd 100644 --- a/src/jitlayers.cpp +++ b/src/jitlayers.cpp @@ -267,7 +267,7 @@ static jl_callptr_t _jl_compile_codeinst( // hack to export this pointer value to jl_dump_method_disasm jl_atomic_store_release(&this_code->specptr.fptr, (void*)getAddressForFunction(decls.specFunctionObject)); } - if (this_code== codeinst) + if (this_code == codeinst) fptr = addr; } diff --git a/src/jltypes.c b/src/jltypes.c index 9f93d60fb1acd..b73d5ecab82aa 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -1093,12 +1093,36 @@ jl_value_t *jl_unwrap_unionall(jl_value_t *v) } // wrap `t` in the same unionalls that surround `u` +// where `t` is derived from `u`, so the error checks in jl_type_unionall are unnecessary jl_value_t *jl_rewrap_unionall(jl_value_t *t, jl_value_t *u) { if (!jl_is_unionall(u)) return t; - JL_GC_PUSH1(&t); t = jl_rewrap_unionall(t, ((jl_unionall_t*)u)->body); + jl_tvar_t *v = ((jl_unionall_t*)u)->var; + // normalize `T where T<:S` => S + if (t == (jl_value_t*)v) + return v->ub; + // where var doesn't occur in body just return body + if (!jl_has_typevar(t, v)) + return t; + JL_GC_PUSH1(&t); + //if (v->lb == v->ub) // TODO maybe + // t = jl_substitute_var(body, v, v->ub); + //else + t = jl_new_struct(jl_unionall_type, v, t); + JL_GC_POP(); + return t; +} + +// wrap `t` in the same unionalls that surround `u` +// where `t` is extended from `u`, so the checks in jl_rewrap_unionall are unnecessary +jl_value_t *jl_rewrap_unionall_(jl_value_t *t, jl_value_t *u) +{ + if (!jl_is_unionall(u)) + return t; + t = jl_rewrap_unionall_(t, ((jl_unionall_t*)u)->body); + JL_GC_PUSH1(&t); t = jl_new_struct(jl_unionall_type, ((jl_unionall_t*)u)->var, t); JL_GC_POP(); return t; diff --git a/src/julia.h b/src/julia.h index f7cf69710fa62..2c9be8ae1aa2a 100644 --- a/src/julia.h +++ b/src/julia.h @@ -1433,7 +1433,7 @@ STATIC_INLINE int jl_is_concrete_type(jl_value_t *v) JL_NOTSAFEPOINT return jl_is_datatype(v) && ((jl_datatype_t*)v)->isconcretetype; } -JL_DLLEXPORT int jl_isa_compileable_sig(jl_tupletype_t *type, jl_method_t *definition); +JL_DLLEXPORT int jl_isa_compileable_sig(jl_tupletype_t *type, jl_svec_t *sparams, jl_method_t *definition); // type constructors JL_DLLEXPORT jl_typename_t *jl_new_typename_in(jl_sym_t *name, jl_module_t *inmodule, int abstract, int mutabl); diff --git a/src/julia_internal.h b/src/julia_internal.h index c4ac3e2b35009..1e59cf6f18b5a 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -698,6 +698,7 @@ JL_DLLEXPORT jl_value_t *jl_instantiate_type_in_env(jl_value_t *ty, jl_unionall_ jl_value_t *jl_substitute_var(jl_value_t *t, jl_tvar_t *var, jl_value_t *val); JL_DLLEXPORT jl_value_t *jl_unwrap_unionall(jl_value_t *v JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT; JL_DLLEXPORT jl_value_t *jl_rewrap_unionall(jl_value_t *t, jl_value_t *u); +JL_DLLEXPORT jl_value_t *jl_rewrap_unionall_(jl_value_t *t, jl_value_t *u); int jl_count_union_components(jl_value_t *v); JL_DLLEXPORT jl_value_t *jl_nth_union_component(jl_value_t *v JL_PROPAGATES_ROOT, int i) JL_NOTSAFEPOINT; int jl_find_union_component(jl_value_t *haystack, jl_value_t *needle, unsigned *nth) JL_NOTSAFEPOINT; diff --git a/src/precompile.c b/src/precompile.c index 9c9c79b154a32..ebe7afae69f64 100644 --- a/src/precompile.c +++ b/src/precompile.c @@ -269,7 +269,7 @@ static void jl_compile_all_defs(jl_array_t *mis) size_t i, l = jl_array_len(allmeths); for (i = 0; i < l; i++) { jl_method_t *m = (jl_method_t*)jl_array_ptr_ref(allmeths, i); - if (jl_isa_compileable_sig((jl_tupletype_t*)m->sig, m)) { + if (jl_is_datatype(m->sig) && jl_isa_compileable_sig((jl_tupletype_t*)m->sig, jl_emptysvec, m)) { // method has a single compilable specialization, e.g. its definition // signature is concrete. in this case we can just hint it. jl_compile_hint((jl_tupletype_t*)m->sig); @@ -354,7 +354,7 @@ static void *jl_precompile_(jl_array_t *m) mi = (jl_method_instance_t*)item; size_t min_world = 0; size_t max_world = ~(size_t)0; - if (mi != jl_atomic_load_relaxed(&mi->def.method->unspecialized) && !jl_isa_compileable_sig((jl_tupletype_t*)mi->specTypes, mi->def.method)) + if (mi != jl_atomic_load_relaxed(&mi->def.method->unspecialized) && !jl_isa_compileable_sig((jl_tupletype_t*)mi->specTypes, mi->sparam_vals, mi->def.method)) mi = jl_get_specialization1((jl_tupletype_t*)mi->specTypes, jl_atomic_load_acquire(&jl_world_counter), &min_world, &max_world, 0); if (mi) jl_array_ptr_1d_push(m2, (jl_value_t*)mi); diff --git a/src/subtype.c b/src/subtype.c index cbb11520190cb..1d9d3d875675d 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -2890,8 +2890,8 @@ static jl_value_t *intersect_sub_datatype(jl_datatype_t *xd, jl_datatype_t *yd, jl_value_t *super_pattern=NULL; JL_GC_PUSH2(&isuper, &super_pattern); jl_value_t *wrapper = xd->name->wrapper; - super_pattern = jl_rewrap_unionall((jl_value_t*)((jl_datatype_t*)jl_unwrap_unionall(wrapper))->super, - wrapper); + super_pattern = jl_rewrap_unionall_((jl_value_t*)((jl_datatype_t*)jl_unwrap_unionall(wrapper))->super, + wrapper); int envsz = jl_subtype_env_size(super_pattern); jl_value_t *ii = jl_bottom_type; { @@ -3528,7 +3528,7 @@ jl_value_t *jl_type_intersection_env_s(jl_value_t *a, jl_value_t *b, jl_svec_t * if (jl_is_uniontype(ans_unwrapped)) { ans_unwrapped = switch_union_tuple(((jl_uniontype_t*)ans_unwrapped)->a, ((jl_uniontype_t*)ans_unwrapped)->b); if (ans_unwrapped != NULL) { - *ans = jl_rewrap_unionall(ans_unwrapped, *ans); + *ans = jl_rewrap_unionall_(ans_unwrapped, *ans); } } JL_GC_POP(); diff --git a/stdlib/Random/src/Random.jl b/stdlib/Random/src/Random.jl index 95125422eeee5..bc016fc1cd057 100644 --- a/stdlib/Random/src/Random.jl +++ b/stdlib/Random/src/Random.jl @@ -256,7 +256,7 @@ rand(rng::AbstractRNG, ::UniformT{T}) where {T} = rand(rng, T) rand(rng::AbstractRNG, X) = rand(rng, Sampler(rng, X, Val(1))) # this is needed to disambiguate rand(rng::AbstractRNG, X::Dims) = rand(rng, Sampler(rng, X, Val(1))) -rand(rng::AbstractRNG=default_rng(), ::Type{X}=Float64) where {X} = rand(rng, Sampler(rng, X, Val(1)))::X +rand(rng::AbstractRNG=default_rng(), ::Type{X}=Float64) where {X} = rand(rng, Sampler(rng, X, Val(1)))::X rand(X) = rand(default_rng(), X) rand(::Type{X}) where {X} = rand(default_rng(), X) diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index d440b0097ac53..1cfc3dae125f8 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -406,7 +406,7 @@ f11366(x::Type{Ref{T}}) where {T} = Ref{x} let f(T) = Type{T} - @test Base.return_types(f, Tuple{Type{Int}}) == [Type{Type{Int}}] + @test Base.return_types(f, Tuple{Type{Int}}) == Any[Type{Type{Int}}] end # issue #9222 diff --git a/test/core.jl b/test/core.jl index 116b514b2ed5f..bab6be0de5644 100644 --- a/test/core.jl +++ b/test/core.jl @@ -7902,3 +7902,28 @@ struct ModTparamTestStruct{M}; end end @test ModTparamTestStruct{@__MODULE__}() == 2 @test ModTparamTestStruct{ModTparamTest}() == 1 + +# issue #47476 +f47476(::Union{Int, NTuple{N,Int}}...) where {N} = N +# force it to populate the MethodInstance specializations cache +# with the correct sparams +code_typed(f47476, (Vararg{Union{Int, NTuple{2,Int}}},)); +code_typed(f47476, (Int, Vararg{Union{Int, NTuple{2,Int}}},)); +code_typed(f47476, (Int, Int, Vararg{Union{Int, NTuple{2,Int}}},)) +code_typed(f47476, (Int, Int, Int, Vararg{Union{Int, NTuple{2,Int}}},)) +code_typed(f47476, (Int, Int, Int, Int, Vararg{Union{Int, NTuple{2,Int}}},)) +@test f47476(1, 2, 3, 4, 5, 6, (7, 8)) === 2 +@test_throws UndefVarError(:N) f47476(1, 2, 3, 4, 5, 6, 7) + +vect47476(::Type{T}) where {T} = T +@test vect47476(Type{Type{Type{Int32}}}) === Type{Type{Type{Int32}}} +@test vect47476(Type{Type{Type{Int64}}}) === Type{Type{Type{Int64}}} + +g47476(::Union{Nothing,Int,Val{T}}...) where {T} = T +@test_throws UndefVarError(:T) g47476(nothing, 1, nothing, 2, nothing, 3, nothing, 4, nothing, 5) +@test g47476(nothing, 1, nothing, 2, nothing, 3, nothing, 4, nothing, 5, Val(6)) === 6 +let spec = only(methods(g47476)).specializations + @test !isempty(spec) + @test any(mi -> mi !== nothing && Base.isvatuple(mi.specTypes), spec) + @test all(mi -> mi === nothing || !Base.has_free_typevars(mi.specTypes), spec) +end diff --git a/test/precompile.jl b/test/precompile.jl index 806887646e137..688286a113b55 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -1493,8 +1493,8 @@ end f(x, y) = x + y f(x::Int, y) = 2x + y end - precompile(M.f, (Int, Any)) - precompile(M.f, (AbstractFloat, Any)) + @test precompile(M.f, (Int, Any)) + @test precompile(M.f, (AbstractFloat, Any)) mis = map(methods(M.f)) do m m.specializations[1] end