Skip to content

Commit 8924193

Browse files
committed
Allow CodeInstance in Expr(:invoke)
This is a quick experiment to see what it would look like to switch Expr(:invoke) to use CodeInstance rather than MethodInstance. There is some unresolved semantic questions here about whether this is a good idea or if there's some other representation that's better, but discussion might be easier with an implementation. The larger context here is the question of whether and how to do more general method specialization. I had written some thoughts in [1], but as mentioned there remains ongoing discussion of whether this is the correct direction or not. [1] https://hackmd.io/@Og2_pcUySm6R_RPqbZ06JA/S1bqP1D_6
1 parent 02699bb commit 8924193

File tree

9 files changed

+184
-133
lines changed

9 files changed

+184
-133
lines changed

base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl

+2-1
Original file line numberDiff line numberDiff line change
@@ -1060,7 +1060,8 @@ end
10601060

10611061
# escape statically-resolved call, i.e. `Expr(:invoke, ::MethodInstance, ...)`
10621062
function escape_invoke!(astate::AnalysisState, pc::Int, args::Vector{Any})
1063-
mi = first(args)::MethodInstance
1063+
arg1 = first(args)
1064+
mi = isa(arg1, Core.CodeInstance) ? arg1.def : arg1::MethodInstance
10641065
first_idx, last_idx = 2, length(args)
10651066
# TODO inspect `astate.ir.stmts[pc][:info]` and use const-prop'ed `InferenceResult` if available
10661067
cache = astate.get_escape_cache(mi)

base/compiler/ssair/inlining.jl

+40-19
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ struct SomeCase
3333
end
3434

3535
struct InvokeCase
36-
invoke::MethodInstance
36+
invoke::Union{MethodInstance, CodeInstance}
3737
effects::Effects
3838
info::CallInfo
3939
end
@@ -831,29 +831,44 @@ function compileable_specialization(mi::MethodInstance, effects::Effects,
831831
return InvokeCase(mi_invoke, effects, info)
832832
end
833833

834+
function compileable_specialization(ci::CodeInstance, effects::Effects,
835+
et::InliningEdgeTracker, @nospecialize(info::CallInfo); compilesig_invokes::Bool=true)
836+
if compilesig_invokes ? !isa_compileable_sig(ci.def.specTypes, ci.def.sparam_vals, ci.def.def) :
837+
any(@nospecialize(t)->isa(t, TypeVar), ci.def.sparam_vals)
838+
return compileable_specialization(ci.def, effects, et, info; compilesig_invokes)
839+
end
840+
add_inlining_backedge!(et, ci.def) # to the dispatch lookup
841+
push!(et.edges, ci.def.def.sig, ci.def) # add_inlining_backedge to the invoke call
842+
return InvokeCase(ci, effects, info)
843+
end
844+
834845
function compileable_specialization(match::MethodMatch, effects::Effects,
835846
et::InliningEdgeTracker, @nospecialize(info::CallInfo); compilesig_invokes::Bool=true)
836847
mi = specialize_method(match)
837848
return compileable_specialization(mi, effects, et, info; compilesig_invokes)
838849
end
839850

840851
struct InferredResult
852+
ci::Union{Nothing, CodeInstance}
841853
src::Any
842854
effects::Effects
843-
InferredResult(@nospecialize(src), effects::Effects) = new(src, effects)
855+
InferredResult(ci::Union{Nothing, CodeInstance}, @nospecialize(src), effects::Effects) = new(ci, src, effects)
856+
end
857+
@inline function get_cached_result(code::CodeInstance)
858+
if use_const_api(code)
859+
# in this case function can be inlined to a constant
860+
return ConstantCase(quoted(code.rettype_const))
861+
end
862+
src = @atomic :monotonic code.inferred
863+
effects = decode_effects(code.ipo_purity_bits)
864+
return InferredResult(code, src, effects)
844865
end
845866
@inline function get_cached_result(state::InliningState, mi::MethodInstance)
846867
code = get(code_cache(state), mi, nothing)
847868
if code isa CodeInstance
848-
if use_const_api(code)
849-
# in this case function can be inlined to a constant
850-
return ConstantCase(quoted(code.rettype_const))
851-
end
852-
src = @atomic :monotonic code.inferred
853-
effects = decode_effects(code.ipo_purity_bits)
854-
return InferredResult(src, effects)
869+
return get_cached_result(code)
855870
end
856-
return InferredResult(nothing, Effects())
871+
return InferredResult(nothing, nothing, Effects())
857872
end
858873
@inline function get_local_result(inf_result::InferenceResult)
859874
effects = inf_result.ipo_effects
@@ -864,11 +879,11 @@ end
864879
return ConstantCase(quoted(res.val))
865880
end
866881
end
867-
return InferredResult(inf_result.src, effects)
882+
return InferredResult(nothing, inf_result.src, effects)
868883
end
869884

870885
# the general resolver for usual and const-prop'ed calls
871-
function resolve_todo(mi::MethodInstance, result::Union{Nothing,InferenceResult,VolatileInferenceResult},
886+
function resolve_todo(mi::MethodInstance, result::Union{Nothing,InferenceResult,VolatileInferenceResult,CodeInstance},
872887
@nospecialize(info::CallInfo), flag::UInt32, state::InliningState;
873888
invokesig::Union{Nothing,Vector{Any}}=nothing)
874889
et = InliningEdgeTracker(state, invokesig)
@@ -887,17 +902,18 @@ function resolve_todo(mi::MethodInstance, result::Union{Nothing,InferenceResult,
887902
add_inlining_backedge!(et, mi)
888903
return inferred_result
889904
end
890-
(; src, effects) = inferred_result
905+
(; src, ci, effects) = inferred_result
906+
invoke_target = mi
891907

892908
# the duplicated check might have been done already within `analyze_method!`, but still
893909
# we need it here too since we may come here directly using a constant-prop' result
894910
if !OptimizationParams(state.interp).inlining || is_stmt_noinline(flag)
895-
return compileable_specialization(mi, effects, et, info;
911+
return compileable_specialization(invoke_target, effects, et, info;
896912
compilesig_invokes=OptimizationParams(state.interp).compilesig_invokes)
897913
end
898914

899915
src = inlining_policy(state.interp, src, info, flag)
900-
src === nothing && return compileable_specialization(mi, effects, et, info;
916+
src === nothing && return compileable_specialization(invoke_target, effects, et, info;
901917
compilesig_invokes=OptimizationParams(state.interp).compilesig_invokes)
902918

903919
add_inlining_backedge!(et, mi)
@@ -906,15 +922,21 @@ function resolve_todo(mi::MethodInstance, result::Union{Nothing,InferenceResult,
906922
end
907923

908924
# the special resolver for :invoke-d call
909-
function resolve_todo(mi::MethodInstance, @nospecialize(info::CallInfo), flag::UInt32,
925+
function resolve_todo(mici::Union{MethodInstance, CodeInstance}, @nospecialize(info::CallInfo), flag::UInt32,
910926
state::InliningState)
911927
if !OptimizationParams(state.interp).inlining || is_stmt_noinline(flag)
912928
return nothing
913929
end
914930

915931
et = InliningEdgeTracker(state)
916932

917-
cached_result = get_cached_result(state, mi)
933+
if isa(mici, CodeInstance)
934+
cached_result = get_cached_result(mici)
935+
mi = mici.def
936+
else
937+
cached_result = get_cached_result(state, mici)
938+
mi = mici
939+
end
918940
if cached_result isa ConstantCase
919941
add_inlining_backedge!(et, mi)
920942
return cached_result
@@ -1640,8 +1662,7 @@ end
16401662

16411663
function handle_invoke_expr!(todo::Vector{Pair{Int,Any}}, ir::IRCode,
16421664
idx::Int, stmt::Expr, @nospecialize(info::CallInfo), flag::UInt32, sig::Signature, state::InliningState)
1643-
mi = stmt.args[1]::MethodInstance
1644-
case = resolve_todo(mi, info, flag, state)
1665+
case = resolve_todo(stmt.args[1], info, flag, state)
16451666
handle_single_case!(todo, ir, idx, stmt, case, false)
16461667
return nothing
16471668
end

base/compiler/ssair/irinterp.jl

+3-1
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,9 @@ function reprocess_instruction!(interp::AbstractInterpreter, inst::Instruction,
141141
(; rt, effects) = abstract_eval_statement_expr(interp, stmt, nothing, irsv)
142142
add_flag!(inst, flags_for_effects(effects))
143143
elseif head === :invoke
144-
rt, (nothrow, noub) = concrete_eval_invoke(interp, stmt, stmt.args[1]::MethodInstance, irsv)
144+
arg1 = stmt.args[1]
145+
mi = isa(arg1, CodeInstance) ? arg1.def : arg1::MethodInstance
146+
rt, (nothrow, noub) = concrete_eval_invoke(interp, stmt, mi, irsv)
145147
if nothrow
146148
add_flag!(inst, IR_FLAG_NOTHROW)
147149
end

base/compiler/ssair/passes.jl

+9-3
Original file line numberDiff line numberDiff line change
@@ -1480,9 +1480,15 @@ end
14801480
# NOTE we resolve the inlining source here as we don't want to serialize `Core.Compiler`
14811481
# data structure into the global cache (see the comment in `handle_finalizer_call!`)
14821482
function try_inline_finalizer!(ir::IRCode, argexprs::Vector{Any}, idx::Int,
1483-
mi::MethodInstance, @nospecialize(info::CallInfo), inlining::InliningState,
1483+
code_or_mi::Union{MethodInstance, CodeInstance}, @nospecialize(info::CallInfo), inlining::InliningState,
14841484
attach_after::Bool)
1485-
code = get(code_cache(inlining), mi, nothing)
1485+
if isa(code_or_mi, CodeInstance)
1486+
code = code_or_mi
1487+
mi = code.def
1488+
else
1489+
mi = code_or_mi
1490+
code = get(code_cache(inlining), mi, nothing)
1491+
end
14861492
et = InliningEdgeTracker(inlining)
14871493
if code isa CodeInstance
14881494
if use_const_api(code)
@@ -1649,7 +1655,7 @@ function try_resolve_finalizer!(ir::IRCode, idx::Int, finalizer_idx::Int, defuse
16491655
if inline === nothing
16501656
# No code in the function - Nothing to do
16511657
else
1652-
mi = finalizer_stmt.args[5]::MethodInstance
1658+
mi = finalizer_stmt.args[5]::Union{MethodInstance, CodeInstance}
16531659
if inline::Bool && try_inline_finalizer!(ir, argexprs, loc, mi, info, inlining, attach_after)
16541660
# the finalizer body has been inlined
16551661
else

base/compiler/ssair/show.jl

+2-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ function print_stmt(io::IO, idx::Int, @nospecialize(stmt), used::BitSet, maxleng
5252
stmt = stmt::Expr
5353
# TODO: why is this here, and not in Base.show_unquoted
5454
print(io, "invoke ")
55-
linfo = stmt.args[1]::Core.MethodInstance
55+
arg1 = stmt.args[1]
56+
linfo = isa(arg1, Core.CodeInstance) ? arg1.def : arg1::Core.MethodInstance
5657
show_unquoted(io, stmt.args[2], indent)
5758
print(io, "(")
5859
# XXX: this is wrong if `sig` is not a concretetype method

src/codegen.cpp

+90-79
Original file line numberDiff line numberDiff line change
@@ -4977,95 +4977,106 @@ static jl_cgval_t emit_invoke(jl_codectx_t &ctx, const jl_cgval_t &lival, ArrayR
49774977
bool handled = false;
49784978
jl_cgval_t result;
49794979
if (lival.constant) {
4980-
jl_method_instance_t *mi = (jl_method_instance_t*)lival.constant;
4981-
assert(jl_is_method_instance(mi));
4982-
if (mi == ctx.linfo) {
4983-
// handle self-recursion specially
4984-
jl_returninfo_t::CallingConv cc = jl_returninfo_t::CallingConv::Boxed;
4985-
FunctionType *ft = ctx.f->getFunctionType();
4986-
StringRef protoname = ctx.f->getName();
4987-
if (ft == ctx.types().T_jlfunc) {
4988-
result = emit_call_specfun_boxed(ctx, ctx.rettype, protoname, nullptr, argv, nargs, rt);
4989-
handled = true;
4990-
}
4991-
else if (ft != ctx.types().T_jlfuncparams) {
4992-
unsigned return_roots = 0;
4993-
result = emit_call_specfun_other(ctx, mi, ctx.rettype, protoname, nullptr, argv, nargs, &cc, &return_roots, rt);
4994-
handled = true;
4995-
}
4996-
}
4997-
else {
4998-
jl_value_t *ci = ctx.params->lookup(mi, ctx.min_world, ctx.max_world);
4999-
if (ci != jl_nothing) {
5000-
jl_code_instance_t *codeinst = (jl_code_instance_t*)ci;
5001-
auto invoke = jl_atomic_load_acquire(&codeinst->invoke);
5002-
// check if we know how to handle this specptr
5003-
if (invoke == jl_fptr_const_return_addr) {
5004-
result = mark_julia_const(ctx, codeinst->rettype_const);
4980+
jl_code_instance_t *codeinst = NULL;
4981+
jl_method_instance_t *mi = NULL;
4982+
if (jl_is_method_instance(lival.constant)) {
4983+
mi = (jl_method_instance_t*)lival.constant;
4984+
assert(jl_is_method_instance(mi));
4985+
if (mi == ctx.linfo) {
4986+
// handle self-recursion specially
4987+
jl_returninfo_t::CallingConv cc = jl_returninfo_t::CallingConv::Boxed;
4988+
FunctionType *ft = ctx.f->getFunctionType();
4989+
StringRef protoname = ctx.f->getName();
4990+
if (ft == ctx.types().T_jlfunc) {
4991+
result = emit_call_specfun_boxed(ctx, ctx.rettype, protoname, nullptr, argv, nargs, rt);
50054992
handled = true;
50064993
}
5007-
else if (invoke != jl_fptr_sparam_addr) {
5008-
bool specsig, needsparams;
5009-
std::tie(specsig, needsparams) = uses_specsig(mi, codeinst->rettype, ctx.params->prefer_specsig);
5010-
std::string name;
5011-
StringRef protoname;
5012-
bool need_to_emit = true;
5013-
bool cache_valid = ctx.use_cache || ctx.external_linkage;
5014-
bool external = false;
5015-
5016-
// Check if we already queued this up
5017-
auto it = ctx.call_targets.find(codeinst);
5018-
if (need_to_emit && it != ctx.call_targets.end()) {
5019-
protoname = it->second.decl->getName();
5020-
need_to_emit = cache_valid = false;
5021-
}
4994+
else if (ft != ctx.types().T_jlfuncparams) {
4995+
unsigned return_roots = 0;
4996+
result = emit_call_specfun_other(ctx, mi, ctx.rettype, protoname, nullptr, argv, nargs, &cc, &return_roots, rt);
4997+
handled = true;
4998+
}
4999+
goto done;
5000+
} else {
5001+
jl_value_t *ci = ctx.params->lookup(mi, ctx.min_world, ctx.max_world);
5002+
if (ci == jl_nothing)
5003+
goto done;
5004+
codeinst = (jl_code_instance_t*)ci;
5005+
}
5006+
} else {
5007+
assert(jl_is_code_instance(lival.constant));
5008+
codeinst = (jl_code_instance_t*)lival.constant;
5009+
// TODO: Separate copy of the callsig in the CodeInstance
5010+
mi = codeinst->def;
5011+
}
50225012

5023-
// Check if it is already compiled (either JIT or externally)
5024-
if (cache_valid) {
5025-
// optimization: emit the correct name immediately, if we know it
5026-
// TODO: use `emitted` map here too to try to consolidate names?
5027-
// WARNING: isspecsig is protected by the codegen-lock. If that lock is removed, then the isspecsig load needs to be properly atomically sequenced with this.
5028-
auto fptr = jl_atomic_load_relaxed(&codeinst->specptr.fptr);
5029-
if (fptr) {
5030-
while (!(jl_atomic_load_acquire(&codeinst->specsigflags) & 0b10)) {
5031-
jl_cpu_pause();
5032-
}
5033-
invoke = jl_atomic_load_relaxed(&codeinst->invoke);
5034-
if (specsig ? jl_atomic_load_relaxed(&codeinst->specsigflags) & 0b1 : invoke == jl_fptr_args_addr) {
5035-
protoname = jl_ExecutionEngine->getFunctionAtAddress((uintptr_t)fptr, codeinst);
5036-
if (ctx.external_linkage) {
5037-
// TODO: Add !specsig support to aotcompile.cpp
5038-
// Check that the codeinst is containing native code
5039-
if (specsig && jl_atomic_load_relaxed(&codeinst->specsigflags) & 0b100) {
5040-
external = true;
5041-
need_to_emit = false;
5042-
}
5043-
}
5044-
else { // ctx.use_cache
5045-
need_to_emit = false;
5046-
}
5013+
auto invoke = jl_atomic_load_acquire(&codeinst->invoke);
5014+
// check if we know how to handle this specptr
5015+
if (invoke == jl_fptr_const_return_addr) {
5016+
result = mark_julia_const(ctx, codeinst->rettype_const);
5017+
handled = true;
5018+
}
5019+
else if (invoke != jl_fptr_sparam_addr) {
5020+
bool specsig, needsparams;
5021+
std::tie(specsig, needsparams) = uses_specsig(mi, codeinst->rettype, ctx.params->prefer_specsig);
5022+
std::string name;
5023+
StringRef protoname;
5024+
bool need_to_emit = true;
5025+
bool cache_valid = ctx.use_cache || ctx.external_linkage;
5026+
bool external = false;
5027+
5028+
// Check if we already queued this up
5029+
auto it = ctx.call_targets.find(codeinst);
5030+
if (need_to_emit && it != ctx.call_targets.end()) {
5031+
protoname = it->second.decl->getName();
5032+
need_to_emit = cache_valid = false;
5033+
}
5034+
5035+
// Check if it is already compiled (either JIT or externally)
5036+
if (cache_valid) {
5037+
// optimization: emit the correct name immediately, if we know it
5038+
// TODO: use `emitted` map here too to try to consolidate names?
5039+
// WARNING: isspecsig is protected by the codegen-lock. If that lock is removed, then the isspecsig load needs to be properly atomically sequenced with this.
5040+
auto fptr = jl_atomic_load_relaxed(&codeinst->specptr.fptr);
5041+
if (fptr) {
5042+
while (!(jl_atomic_load_acquire(&codeinst->specsigflags) & 0b10)) {
5043+
jl_cpu_pause();
5044+
}
5045+
invoke = jl_atomic_load_relaxed(&codeinst->invoke);
5046+
if (specsig ? jl_atomic_load_relaxed(&codeinst->specsigflags) & 0b1 : invoke == jl_fptr_args_addr) {
5047+
protoname = jl_ExecutionEngine->getFunctionAtAddress((uintptr_t)fptr, codeinst);
5048+
if (ctx.external_linkage) {
5049+
// TODO: Add !specsig support to aotcompile.cpp
5050+
// Check that the codeinst is containing native code
5051+
if (specsig && jl_atomic_load_relaxed(&codeinst->specsigflags) & 0b100) {
5052+
external = true;
5053+
need_to_emit = false;
50475054
}
50485055
}
5049-
}
5050-
if (need_to_emit) {
5051-
raw_string_ostream(name) << (specsig ? "j_" : "j1_") << name_from_method_instance(mi) << "_" << jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1);
5052-
protoname = StringRef(name);
5053-
}
5054-
jl_returninfo_t::CallingConv cc = jl_returninfo_t::CallingConv::Boxed;
5055-
unsigned return_roots = 0;
5056-
if (specsig)
5057-
result = emit_call_specfun_other(ctx, mi, codeinst->rettype, protoname, external ? codeinst : nullptr, argv, nargs, &cc, &return_roots, rt);
5058-
else
5059-
result = emit_call_specfun_boxed(ctx, codeinst->rettype, protoname, external ? codeinst : nullptr, argv, nargs, rt);
5060-
handled = true;
5061-
if (need_to_emit) {
5062-
Function *trampoline_decl = cast<Function>(jl_Module->getNamedValue(protoname));
5063-
ctx.call_targets[codeinst] = {cc, return_roots, trampoline_decl, specsig};
5056+
else { // ctx.use_cache
5057+
need_to_emit = false;
5058+
}
50645059
}
50655060
}
50665061
}
5062+
if (need_to_emit) {
5063+
raw_string_ostream(name) << (specsig ? "j_" : "j1_") << name_from_method_instance(mi) << "_" << jl_atomic_fetch_add(&globalUniqueGeneratedNames, 1);
5064+
protoname = StringRef(name);
5065+
}
5066+
jl_returninfo_t::CallingConv cc = jl_returninfo_t::CallingConv::Boxed;
5067+
unsigned return_roots = 0;
5068+
if (specsig)
5069+
result = emit_call_specfun_other(ctx, mi, codeinst->rettype, protoname, external ? codeinst : nullptr, argv, nargs, &cc, &return_roots, rt);
5070+
else
5071+
result = emit_call_specfun_boxed(ctx, codeinst->rettype, protoname, external ? codeinst : nullptr, argv, nargs, rt);
5072+
handled = true;
5073+
if (need_to_emit) {
5074+
Function *trampoline_decl = cast<Function>(jl_Module->getNamedValue(protoname));
5075+
ctx.call_targets[codeinst] = {cc, return_roots, trampoline_decl, specsig};
5076+
}
50675077
}
50685078
}
5079+
done:
50695080
if (!handled) {
50705081
Value *r = emit_jlcall(ctx, jlinvoke_func, boxed(ctx, lival), argv, nargs, julia_call2);
50715082
result = mark_julia_type(ctx, r, true, rt);

0 commit comments

Comments
 (0)