Skip to content

Commit 5e82790

Browse files
authored
Streamline bail path in inlining (#36771)
When inlining declines to inline something, it instead turns them into :invoke statements. These are then turned into direct (non-inlined) calls by codegen or otherwise receive a fast path at runtime. While inlining has evolved quite a bit, this code has stayed much the same since it was introduced four years ago and doesn't seem to make much sense as is. In particular: 1. For the non-`invoke()` case we were doing an extra method look that seems entirely superfluous, because we already had to do the very same method lookup just to reach this point. The only thing this path was doing at that point was creating a "compilable" specialization (which might use a slightly different signature). We might as well do that directly. 2. For the invoke case, we were pro-actively adding the specialization to the `->invokes` dispatch cache. However, this doesn't make much sense a priori either, because the bail path does not go through the runtime `invoke()` code that uses that cache (it did many years ago when this code was introduced, but hasn't in a long time). There does not seem to be a good reason to believe that this signature will be any more likely than any other to be invoked using the runtime mechanism. This cleans up that path by getting rid of both the superfluous method lookup and the superfluous addition to the `->invokes` cache. There should be a slight performance improvement as well from avoiding this superfluous work, but the bail path is less common than one might expect (the vast majority of call sites are inlined) and in measurements the effect seems to be in the noise. Nevertheless, it seems like a nice simplification and is conceptually clearer.
1 parent ea07765 commit 5e82790

File tree

4 files changed

+73
-109
lines changed

4 files changed

+73
-109
lines changed

base/boot.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,8 @@ eval(Core, :(CodeInstance(mi::MethodInstance, @nospecialize(rettype), @nospecial
406406
eval(Core, :(Const(@nospecialize(v)) = $(Expr(:new, :Const, :v, false))))
407407
eval(Core, :(Const(@nospecialize(v), actual::Bool) = $(Expr(:new, :Const, :v, :actual))))
408408
eval(Core, :(PartialStruct(@nospecialize(typ), fields::Array{Any, 1}) = $(Expr(:new, :PartialStruct, :typ, :fields))))
409+
eval(Core, :(MethodMatch(@nospecialize(spec_types), sparams::SimpleVector, method::Method, fully_covers::Bool) =
410+
$(Expr(:new, :MethodMatch, :spec_types, :sparams, :method, :fully_covers))))
409411

410412
Module(name::Symbol=:anonymous, std_imports::Bool=true) = ccall(:jl_f_new_module, Ref{Module}, (Any, Bool), name, std_imports)
411413

base/compiler/ssair/inlining.jl

Lines changed: 34 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -578,23 +578,6 @@ function batch_inline!(todo::Vector{Any}, ir::IRCode, linetable::Vector{LineInfo
578578
return ir
579579
end
580580

581-
function spec_lambda(@nospecialize(atype), sv::OptimizationState, @nospecialize(invoke_data))
582-
min_valid = RefValue{UInt}(typemin(UInt))
583-
max_valid = RefValue{UInt}(typemax(UInt))
584-
if invoke_data === nothing
585-
mi = ccall(:jl_get_spec_lambda, Any, (Any, UInt, Ptr{UInt}, Ptr{UInt}), atype, sv.world, min_valid, max_valid)
586-
else
587-
invoke_data = invoke_data::InvokeData
588-
atype <: invoke_data.types0 || return nothing
589-
mi = ccall(:jl_get_invoke_lambda, Any, (Any, Any), invoke_data.entry, atype)
590-
min_valid[] = invoke_data.min_valid
591-
max_valid[] = invoke_data.max_valid
592-
end
593-
mi !== nothing && add_backedge!(mi::MethodInstance, sv)
594-
update_valid_age!(sv, WorldRange(min_valid[], max_valid[]))
595-
return mi
596-
end
597-
598581
# This assumes the caller has verified that all arguments to the _apply call are Tuples.
599582
function rewrite_apply_exprargs!(ir::IRCode, todo::Vector{Any}, idx::Int, argexprs::Vector{Any}, atypes::Vector{Any}, arginfos::Vector{Any}, arg_start::Int, sv::OptimizationState)
600583
new_argexprs = Any[argexprs[arg_start]]
@@ -688,16 +671,23 @@ function singleton_type(@nospecialize(ft))
688671
return nothing
689672
end
690673

691-
function analyze_method!(idx::Int, sig::Signature, @nospecialize(metharg), methsp::SimpleVector,
692-
method::Method, stmt::Expr, sv::OptimizationState,
693-
isinvoke::Bool, invoke_data::Union{InvokeData,Nothing}, @nospecialize(stmttyp))
674+
function compileable_specialization(match::MethodMatch, sv::OptimizationState)
675+
mi = specialize_method(match, false, true)
676+
mi !== nothing && add_backedge!(mi::MethodInstance, sv)
677+
return mi
678+
end
679+
680+
function analyze_method!(idx::Int, sig::Signature, match::MethodMatch,
681+
stmt::Expr, sv::OptimizationState,
682+
isinvoke::Bool, @nospecialize(stmttyp))
694683
f, ft, atypes, atype_unlimited = sig.f, sig.ft, sig.atypes, sig.atype
684+
method = match.method
695685
methsig = method.sig
696686

697687
# Check whether this call just evaluates to a constant
698688
if isa(f, widenconst(ft)) &&
699689
isa(stmttyp, Const) && stmttyp.actual && is_inlineable_constant(stmttyp.val)
700-
return ConstantCase(quoted(stmttyp.val), method, Any[methsp...], metharg)
690+
return ConstantCase(quoted(stmttyp.val), method, Any[match.sparams...], match.spec_types)
701691
end
702692

703693
# Check that we habe the correct number of arguments
@@ -713,34 +703,34 @@ function analyze_method!(idx::Int, sig::Signature, @nospecialize(metharg), meths
713703

714704
# Bail out if any static parameters are left as TypeVar
715705
ok = true
716-
for i = 1:length(methsp)
717-
isa(methsp[i], TypeVar) && return nothing
706+
for i = 1:length(match.sparams)
707+
isa(match.sparams[i], TypeVar) && return nothing
718708
end
719709

720710
# See if there exists a specialization for this method signature
721-
mi = specialize_method(method, metharg, methsp, true) # Union{Nothing, MethodInstance}
711+
mi = specialize_method(match, true) # Union{Nothing, MethodInstance}
722712
if !isa(mi, MethodInstance)
723-
return spec_lambda(atype_unlimited, sv, invoke_data)
713+
return compileable_specialization(match, sv)
724714
end
725715

726716
isconst, src = find_inferred(mi, atypes, sv, stmttyp)
727717
if isconst
728718
if sv.params.inlining
729719
add_backedge!(mi, sv)
730-
return ConstantCase(src, method, Any[methsp...], metharg)
720+
return ConstantCase(src, method, Any[match.sparams...], match.spec_types)
731721
else
732-
return spec_lambda(atype_unlimited, sv, invoke_data)
722+
return compileable_specialization(match, sv)
733723
end
734724
end
735725
if src === nothing
736-
return spec_lambda(atype_unlimited, sv, invoke_data)
726+
return compileable_specialization(match, sv)
737727
end
738728

739729
src_inferred = ccall(:jl_ir_flag_inferred, Bool, (Any,), src)
740730
src_inlineable = ccall(:jl_ir_flag_inlineable, Bool, (Any,), src)
741731

742732
if !(src_inferred && src_inlineable && sv.params.inlining)
743-
return spec_lambda(atype_unlimited, sv, invoke_data)
733+
return compileable_specialization(match, sv)
744734
end
745735

746736
# At this point we're committed to performing the inlining, add the backedge
@@ -767,7 +757,7 @@ function analyze_method!(idx::Int, sig::Signature, @nospecialize(metharg), meths
767757
return InliningTodo(idx,
768758
na > 0 && method.isva,
769759
isinvoke, na,
770-
method, Any[methsp...], metharg,
760+
method, Any[match.sparams...], match.spec_types,
771761
inline_linetable, ir2, linear_inline_eligible(ir2))
772762
end
773763

@@ -985,7 +975,8 @@ function inline_invoke!(ir::IRCode, idx::Int, sig::Signature, invoke_data::Invok
985975
(metharg, methsp) = ccall(:jl_type_intersection_with_env, Any, (Any, Any),
986976
sig.atype, method.sig)::SimpleVector
987977
methsp = methsp::SimpleVector
988-
result = analyze_method!(idx, sig, metharg, methsp, method, stmt, sv, true, invoke_data, calltype)
978+
match = MethodMatch(metharg, methsp, method, true)
979+
result = analyze_method!(idx, sig, match, stmt, sv, true, calltype)
989980
handle_single_case!(ir, stmt, idx, result, true, todo)
990981
update_valid_age!(sv, WorldRange(invoke_data.min_valid, invoke_data.max_valid))
991982
return nothing
@@ -1087,22 +1078,21 @@ function analyze_single_call!(ir::IRCode, todo::Vector{Any}, idx::Int, @nospecia
10871078
only_method = false
10881079
end
10891080
for match in meth
1090-
(metharg, methsp, method) = (match.spec_types, match.sparams, match.method)
1091-
signature_union = Union{signature_union, metharg}
1092-
if !isdispatchtuple(metharg)
1081+
signature_union = Union{signature_union, match.spec_types}
1082+
if !isdispatchtuple(match.spec_types)
10931083
fully_covered = false
10941084
continue
10951085
end
1096-
case_sig = Signature(sig.f, sig.ft, sig.atypes, metharg)
1097-
case = analyze_method!(idx, case_sig, metharg, methsp, method,
1098-
stmt, sv, false, nothing, calltype)
1086+
case_sig = Signature(sig.f, sig.ft, sig.atypes, match.spec_types)
1087+
case = analyze_method!(idx, case_sig, match,
1088+
stmt, sv, false, calltype)
10991089
if case === nothing
11001090
fully_covered = false
11011091
continue
1102-
elseif _any(p->p[1] === metharg, cases)
1092+
elseif _any(p->p[1] === match.spec_types, cases)
11031093
continue
11041094
end
1105-
push!(cases, Pair{Any,Any}(metharg, case))
1095+
push!(cases, Pair{Any,Any}(match.spec_types, case))
11061096
end
11071097
end
11081098

@@ -1113,18 +1103,17 @@ function analyze_single_call!(ir::IRCode, todo::Vector{Any}, idx::Int, @nospecia
11131103
# we inline, even if the signature is not a dispatch tuple
11141104
if signature_fully_covered && length(cases) == 0 && only_method isa Method
11151105
if length(infos) > 1
1116-
method = only_method
11171106
(metharg, methsp) = ccall(:jl_type_intersection_with_env, Any, (Any, Any),
1118-
sig.atype, method.sig)::SimpleVector
1107+
sig.atype, only_method.sig)::SimpleVector
1108+
match = MethodMatch(metharg, methsp, only_method, true)
11191109
else
11201110
@assert length(meth) == 1
1121-
(metharg, methsp, method) = (meth[1].spec_types, meth[1].sparams, meth[1].method)
1111+
match = meth[1]
11221112
end
11231113
fully_covered = true
1124-
case = analyze_method!(idx, sig, metharg, methsp, method,
1125-
stmt, sv, false, nothing, calltype)
1114+
case = analyze_method!(idx, sig, match, stmt, sv, false, calltype)
11261115
case === nothing && return
1127-
push!(cases, Pair{Any,Any}(metharg, case))
1116+
push!(cases, Pair{Any,Any}(match.spec_types, case))
11281117
end
11291118
if !signature_fully_covered
11301119
fully_covered = false

base/compiler/utilities.jl

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,24 @@ function retrieve_code_info(linfo::MethodInstance)
118118
end
119119
end
120120

121+
# Get at the nonfunction_mt, which happens to be the mt of SimpleVector
122+
const nonfunction_mt = typename(SimpleVector).mt
123+
124+
function get_compileable_sig(method::Method, @nospecialize(atypes), sparams::SimpleVector)
125+
isa(atypes, DataType) || return Nothing
126+
mt = ccall(:jl_method_table_for, Any, (Any,), atypes)
127+
mt === nothing && return nothing
128+
return ccall(:jl_normalize_to_compilable_sig, Any, (Any, Any, Any, Any),
129+
mt, atypes, sparams, method)
130+
end
131+
121132
# get a handle to the unique specialization object representing a particular instantiation of a call
122-
function specialize_method(method::Method, @nospecialize(atypes), sparams::SimpleVector, preexisting::Bool=false)
133+
function specialize_method(method::Method, @nospecialize(atypes), sparams::SimpleVector, preexisting::Bool=false, compilesig::Bool=false)
134+
if compilesig
135+
new_atypes = get_compileable_sig(method, atypes, sparams)
136+
new_atypes === nothing && return nothing
137+
atypes = new_atypes
138+
end
123139
if preexisting
124140
# check cached specializations
125141
# for an existing result stored there
@@ -128,8 +144,8 @@ function specialize_method(method::Method, @nospecialize(atypes), sparams::Simpl
128144
return ccall(:jl_specializations_get_linfo, Ref{MethodInstance}, (Any, Any, Any), method, atypes, sparams)
129145
end
130146

131-
function specialize_method(match::MethodMatch, preexisting::Bool=false)
132-
return specialize_method(match.method, match.spec_types, match.sparams, preexisting)
147+
function specialize_method(match::MethodMatch, preexisting::Bool=false, compilesig::Bool=false)
148+
return specialize_method(match.method, match.spec_types, match.sparams, preexisting, compilesig)
133149
end
134150

135151
# This function is used for computing alternate limit heuristics

src/gf.c

Lines changed: 18 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1945,6 +1945,20 @@ JL_DLLEXPORT int32_t jl_invoke_api(jl_code_instance_t *codeinst)
19451945
return -1;
19461946
}
19471947

1948+
JL_DLLEXPORT jl_value_t *jl_normalize_to_compilable_sig(jl_methtable_t *mt, jl_tupletype_t *ti, jl_svec_t *env, jl_method_t *m)
1949+
{
1950+
jl_tupletype_t *tt = NULL;
1951+
jl_svec_t *newparams = NULL;
1952+
JL_GC_PUSH2(&tt, &newparams);
1953+
intptr_t nspec = (mt == jl_type_type_mt || mt == jl_nonfunction_mt ? m->nargs + 1 : mt->max_args + 2);
1954+
jl_compilation_sig(ti, env, m, nspec, &newparams);
1955+
tt = (newparams ? jl_apply_tuple_type(newparams) : ti);
1956+
int is_compileable = ((jl_datatype_t*)ti)->isdispatchtuple ||
1957+
jl_isa_compileable_sig(tt, m);
1958+
JL_GC_POP();
1959+
return is_compileable ? (jl_value_t*)tt : jl_nothing;
1960+
}
1961+
19481962
// compile-time method lookup
19491963
jl_method_instance_t *jl_get_specialization1(jl_tupletype_t *types JL_PROPAGATES_ROOT, size_t world, size_t *min_valid, size_t *max_valid, int mt_cache)
19501964
{
@@ -1965,9 +1979,8 @@ jl_method_instance_t *jl_get_specialization1(jl_tupletype_t *types JL_PROPAGATES
19651979
*max_valid = max_valid2;
19661980
if (matches == jl_false || jl_array_len(matches) != 1 || ambig)
19671981
return NULL;
1968-
jl_tupletype_t *tt = NULL;
1969-
jl_svec_t *newparams = NULL;
1970-
JL_GC_PUSH3(&matches, &tt, &newparams);
1982+
jl_value_t *tt = NULL;
1983+
JL_GC_PUSH2(&matches, &tt);
19711984
jl_method_match_t *match = (jl_method_match_t*)jl_array_ptr_ref(matches, 0);
19721985
jl_method_t *m = match->method;
19731986
jl_svec_t *env = match->sparams;
@@ -1987,12 +2000,8 @@ jl_method_instance_t *jl_get_specialization1(jl_tupletype_t *types JL_PROPAGATES
19872000
JL_UNLOCK(&mt->writelock);
19882001
}
19892002
else {
1990-
intptr_t nspec = (mt == jl_type_type_mt || mt == jl_nonfunction_mt ? m->nargs + 1 : mt->max_args + 2);
1991-
jl_compilation_sig(ti, env, m, nspec, &newparams);
1992-
tt = (newparams ? jl_apply_tuple_type(newparams) : ti);
1993-
int is_compileable = ((jl_datatype_t*)ti)->isdispatchtuple ||
1994-
jl_isa_compileable_sig(tt, m);
1995-
if (is_compileable) {
2003+
tt = jl_normalize_to_compilable_sig(mt, ti, env, m);
2004+
if (tt != jl_nothing) {
19962005
nf = jl_specializations_get_linfo(m, (jl_value_t*)tt, env);
19972006
}
19982007
}
@@ -2075,14 +2084,6 @@ JL_DLLEXPORT int jl_compile_hint(jl_tupletype_t *types)
20752084
return 1;
20762085
}
20772086

2078-
JL_DLLEXPORT jl_value_t *jl_get_spec_lambda(jl_tupletype_t *types, size_t world, size_t *min_valid, size_t *max_valid)
2079-
{
2080-
jl_method_instance_t *mi = jl_get_specialization1(types, world, min_valid, max_valid, 0);
2081-
if (!mi)
2082-
return jl_nothing;
2083-
return (jl_value_t*)mi;
2084-
}
2085-
20862087
// add type of `f` to front of argument tuple type
20872088
jl_value_t *jl_argtype_with_function(jl_function_t *f, jl_value_t *types0)
20882089
{
@@ -2437,50 +2438,6 @@ static jl_value_t *jl_gf_invoke_by_method(jl_method_t *method, jl_value_t *gf, j
24372438
return _jl_invoke(gf, args, nargs - 1, mfunc, world);
24382439
}
24392440

2440-
JL_DLLEXPORT jl_value_t *jl_get_invoke_lambda(jl_method_t *method, jl_value_t *tt)
2441-
{
2442-
// TODO: refactor this method to be more like `jl_get_specialization1`
2443-
if (!jl_is_datatype(tt) || !((jl_datatype_t*)tt)->isdispatchtuple)
2444-
return jl_nothing;
2445-
2446-
jl_typemap_entry_t *tm = NULL;
2447-
struct jl_typemap_assoc search = {(jl_value_t*)tt, 1, NULL, 0, ~(size_t)0};
2448-
if (method->invokes != NULL) {
2449-
tm = jl_typemap_assoc_by_type(method->invokes, &search, /*offs*/1, /*subtype*/1);
2450-
if (tm) {
2451-
return (jl_value_t*)tm->func.linfo;
2452-
}
2453-
}
2454-
2455-
JL_LOCK(&method->writelock);
2456-
if (method->invokes != NULL) {
2457-
tm = jl_typemap_assoc_by_type(method->invokes, &search, /*offs*/1, /*subtype*/1);
2458-
if (tm) {
2459-
jl_method_instance_t *mfunc = tm->func.linfo;
2460-
JL_UNLOCK(&method->writelock);
2461-
return (jl_value_t*)mfunc;
2462-
}
2463-
}
2464-
2465-
jl_svec_t *tpenv = jl_emptysvec;
2466-
JL_GC_PUSH1(&tpenv);
2467-
if (jl_is_unionall(method->sig)) {
2468-
jl_value_t *ti =
2469-
jl_type_intersection_env(tt, (jl_value_t*)method->sig, &tpenv);
2470-
assert(ti != (jl_value_t*)jl_bottom_type);
2471-
(void)ti;
2472-
}
2473-
2474-
if (method->invokes == NULL)
2475-
method->invokes = jl_nothing;
2476-
2477-
jl_method_instance_t *mfunc = cache_method(NULL, &method->invokes, (jl_value_t*)method,
2478-
(jl_tupletype_t*)tt, method, 1, 1, ~(size_t)0, tpenv);
2479-
JL_GC_POP();
2480-
JL_UNLOCK(&method->writelock);
2481-
return (jl_value_t*)mfunc;
2482-
}
2483-
24842441
// Return value is rooted globally
24852442
jl_function_t *jl_new_generic_function_with_supertype(jl_sym_t *name, jl_module_t *module, jl_datatype_t *st)
24862443
{

0 commit comments

Comments
 (0)