Skip to content

Commit c31710a

Browse files
authored
Make Expr(:invoke) target be a CodeInstance, not MethodInstance (#54899)
This changes our IR representation to use a CodeInstance directly as the invoke function target to specify the ABI in its entirety, instead of just the MethodInstance (specifically for the rettype). That allows removing the lookup call at that point to decide upon the ABI. It is based around the idea that eventually we now keep track of these anyways to form a graph of the inferred edge data, for use later in validation anyways (instead of attempting to invert the backedges graph in staticdata_utils.c), so we might as well use the same target type for the :invoke call representation also now.
1 parent 859c25a commit c31710a

23 files changed

+151
-144
lines changed

Compiler/src/abstractinterpretation.jl

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -323,11 +323,11 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(fun
323323
if mi === nothing || !const_prop_methodinstance_heuristic(interp, mi, arginfo, sv)
324324
csig = get_compileable_sig(method, sig, match.sparams)
325325
if csig !== nothing && (!seenall || csig !== sig) # corresponds to whether the first look already looked at this, so repeating abstract_call_method is not useful
326+
#println(sig, " changed to ", csig, " for ", method)
326327
sp_ = ccall(:jl_type_intersection_with_env, Any, (Any, Any), csig, method.sig)::SimpleVector
327-
if match.sparams === sp_[2]
328-
mresult = abstract_call_method(interp, method, csig, match.sparams, multiple_matches, StmtInfo(false, false), sv)::Future
329-
isready(mresult) || return false # wait for mresult Future to resolve off the callstack before continuing
330-
end
328+
sparams = sp_[2]::SimpleVector
329+
mresult = abstract_call_method(interp, method, csig, sparams, multiple_matches, StmtInfo(false, false), sv)::Future
330+
isready(mresult) || return false # wait for mresult Future to resolve off the callstack before continuing
331331
end
332332
end
333333
end
@@ -1365,7 +1365,8 @@ function const_prop_call(interp::AbstractInterpreter,
13651365
pop!(callstack)
13661366
return nothing
13671367
end
1368-
inf_result.ci_as_edge = codeinst_as_edge(interp, frame)
1368+
existing_edge = result.edge
1369+
inf_result.ci_as_edge = codeinst_as_edge(interp, frame, existing_edge)
13691370
@assert frame.frameid != 0 && frame.cycleid == frame.frameid
13701371
@assert frame.parentid == sv.frameid
13711372
@assert inf_result.result !== nothing

Compiler/src/ssair/EscapeAnalysis.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1068,7 +1068,10 @@ end
10681068

10691069
# escape statically-resolved call, i.e. `Expr(:invoke, ::MethodInstance, ...)`
10701070
function escape_invoke!(astate::AnalysisState, pc::Int, args::Vector{Any})
1071-
mi = first(args)::MethodInstance
1071+
mi = first(args)
1072+
if !(mi isa MethodInstance)
1073+
mi = (mi::CodeInstance).def # COMBAK get escape info directly from CI instead?
1074+
end
10721075
first_idx, last_idx = 2, length(args)
10731076
add_liveness_changes!(astate, pc, args, first_idx, last_idx)
10741077
# TODO inspect `astate.ir.stmts[pc][:info]` and use const-prop'ed `InferenceResult` if available

Compiler/src/ssair/inlining.jl

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ struct SomeCase
3838
end
3939

4040
struct InvokeCase
41-
invoke::MethodInstance
41+
invoke::Union{CodeInstance,MethodInstance}
4242
effects::Effects
4343
info::CallInfo
4444
end
@@ -764,19 +764,20 @@ function rewrite_apply_exprargs!(todo::Vector{Pair{Int,Any}},
764764
return new_argtypes
765765
end
766766

767-
function compileable_specialization(mi::MethodInstance, effects::Effects,
767+
function compileable_specialization(code::Union{MethodInstance,CodeInstance}, effects::Effects,
768768
et::InliningEdgeTracker, @nospecialize(info::CallInfo), state::InliningState)
769+
mi = code isa CodeInstance ? code.def : code
769770
mi_invoke = mi
770771
method, atype, sparams = mi.def::Method, mi.specTypes, mi.sparam_vals
771772
if OptimizationParams(state.interp).compilesig_invokes
772773
new_atype = get_compileable_sig(method, atype, sparams)
773774
new_atype === nothing && return nothing
774775
if atype !== new_atype
775776
sp_ = ccall(:jl_type_intersection_with_env, Any, (Any, Any), new_atype, method.sig)::SimpleVector
776-
if sparams === sp_[2]::SimpleVector
777-
mi_invoke = specialize_method(method, new_atype, sparams)
778-
mi_invoke === nothing && return nothing
779-
end
777+
sparams = sp_[2]::SimpleVector
778+
mi_invoke = specialize_method(method, new_atype, sparams)
779+
mi_invoke === nothing && return nothing
780+
code = mi_invoke
780781
end
781782
else
782783
# If this caller does not want us to optimize calls to use their
@@ -786,8 +787,15 @@ function compileable_specialization(mi::MethodInstance, effects::Effects,
786787
return nothing
787788
end
788789
end
789-
add_inlining_edge!(et, mi_invoke) # to the dispatch lookup
790-
return InvokeCase(mi_invoke, effects, info)
790+
# prefer using a CodeInstance gotten from the cache, since that is where the invoke target should get compiled to normally
791+
# TODO: can this code be gotten directly from inference sometimes?
792+
code = get(code_cache(state), mi_invoke, nothing)
793+
if !isa(code, CodeInstance)
794+
#println("missing code for ", mi_invoke, " for ", mi)
795+
code = mi_invoke
796+
end
797+
add_inlining_edge!(et, code) # to the code and edges
798+
return InvokeCase(code, effects, info)
791799
end
792800

793801
struct InferredResult
@@ -844,18 +852,18 @@ function resolve_todo(mi::MethodInstance, result::Union{Nothing,InferenceResult,
844852
src = @atomic :monotonic inferred_result.inferred
845853
effects = decode_effects(inferred_result.ipo_purity_bits)
846854
edge = inferred_result
847-
else # there is no cached source available, bail out
855+
else # there is no cached source available for this, but there might be code for the compilation sig
848856
return compileable_specialization(mi, Effects(), et, info, state)
849857
end
850858

851859
# the duplicated check might have been done already within `analyze_method!`, but still
852860
# we need it here too since we may come here directly using a constant-prop' result
853861
if !OptimizationParams(state.interp).inlining || is_stmt_noinline(flag)
854-
return compileable_specialization(edge.def, effects, et, info, state)
862+
return compileable_specialization(edge, effects, et, info, state)
855863
end
856864

857865
src_inlining_policy(state.interp, src, info, flag) ||
858-
return compileable_specialization(edge.def, effects, et, info, state)
866+
return compileable_specialization(edge, effects, et, info, state)
859867

860868
add_inlining_edge!(et, edge)
861869
if inferred_result isa CodeInstance
@@ -1423,18 +1431,19 @@ end
14231431

14241432
function semiconcrete_result_item(result::SemiConcreteResult,
14251433
@nospecialize(info::CallInfo), flag::UInt32, state::InliningState)
1426-
mi = result.edge.def
1434+
code = result.edge
1435+
mi = code.def
14271436
et = InliningEdgeTracker(state)
14281437

14291438
if (!OptimizationParams(state.interp).inlining || is_stmt_noinline(flag) ||
14301439
# For `NativeInterpreter`, `SemiConcreteResult` may be produced for
14311440
# a `@noinline`-declared method when it's marked as `@constprop :aggressive`.
14321441
# Suppress the inlining here (unless inlining is requested at the callsite).
14331442
(is_declared_noinline(mi.def::Method) && !is_stmt_inline(flag)))
1434-
return compileable_specialization(mi, result.effects, et, info, state)
1443+
return compileable_specialization(code, result.effects, et, info, state)
14351444
end
14361445
src_inlining_policy(state.interp, result.ir, info, flag) ||
1437-
return compileable_specialization(mi, result.effects, et, info, state)
1446+
return compileable_specialization(code, result.effects, et, info, state)
14381447

14391448
add_inlining_edge!(et, result.edge)
14401449
preserve_local_sources = OptimizationParams(state.interp).preserve_local_sources
@@ -1466,7 +1475,7 @@ may_inline_concrete_result(result::ConcreteResult) =
14661475
function concrete_result_item(result::ConcreteResult, @nospecialize(info::CallInfo), state::InliningState)
14671476
if !may_inline_concrete_result(result)
14681477
et = InliningEdgeTracker(state)
1469-
return compileable_specialization(result.edge.def, result.effects, et, info, state)
1478+
return compileable_specialization(result.edge, result.effects, et, info, state)
14701479
end
14711480
@assert result.effects === EFFECTS_TOTAL
14721481
return ConstantCase(quoted(result.result), result.edge)
@@ -1522,11 +1531,7 @@ function handle_modifyop!_call!(ir::IRCode, idx::Int, stmt::Expr, info::ModifyOp
15221531
match = info.results[1]::MethodMatch
15231532
match.fully_covers || return nothing
15241533
edge = info.edges[1]
1525-
if edge === nothing
1526-
edge = specialize_method(match)
1527-
else
1528-
edge = edge.def
1529-
end
1534+
edge === nothing && return nothing
15301535
case = compileable_specialization(edge, Effects(), InliningEdgeTracker(state), info, state)
15311536
case === nothing && return nothing
15321537
stmt.head = :invoke_modify
@@ -1564,8 +1569,11 @@ function handle_finalizer_call!(ir::IRCode, idx::Int, stmt::Expr, info::Finalize
15641569
# `Core.Compiler` data structure into the global cache
15651570
item1 = cases[1].item
15661571
if isa(item1, InliningTodo)
1567-
push!(stmt.args, true)
1568-
push!(stmt.args, item1.mi)
1572+
code = get(code_cache(state), item1.mi, nothing) # COMBAK: this seems like a bad design, can we use stmt_info instead to store the correct info?
1573+
if code isa CodeInstance
1574+
push!(stmt.args, true)
1575+
push!(stmt.args, code)
1576+
end
15691577
elseif isa(item1, InvokeCase)
15701578
push!(stmt.args, false)
15711579
push!(stmt.args, item1.invoke)
@@ -1578,7 +1586,10 @@ end
15781586

15791587
function handle_invoke_expr!(todo::Vector{Pair{Int,Any}}, ir::IRCode,
15801588
idx::Int, stmt::Expr, @nospecialize(info::CallInfo), flag::UInt32, sig::Signature, state::InliningState)
1581-
mi = stmt.args[1]::MethodInstance
1589+
mi = stmt.args[1]
1590+
if !(mi isa MethodInstance)
1591+
mi = (mi::CodeInstance).def
1592+
end
15821593
case = resolve_todo(mi, info, flag, state)
15831594
handle_single_case!(todo, ir, idx, stmt, case, false)
15841595
return nothing

Compiler/src/ssair/irinterp.jl

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,15 @@ end
3333

3434
function abstract_eval_invoke_inst(interp::AbstractInterpreter, inst::Instruction, irsv::IRInterpretationState)
3535
stmt = inst[:stmt]
36-
mi = stmt.args[1]::MethodInstance
37-
world = frame_world(irsv)
38-
mi_cache = WorldView(code_cache(interp), world)
39-
code = get(mi_cache, mi, nothing)
40-
code === nothing && return Pair{Any,Tuple{Bool,Bool}}(nothing, (false, false))
36+
ci = stmt.args[1]
37+
if ci isa MethodInstance
38+
world = frame_world(irsv)
39+
mi_cache = WorldView(code_cache(interp), world)
40+
code = get(mi_cache, ci, nothing)
41+
code === nothing && return Pair{Any,Tuple{Bool,Bool}}(nothing, (false, false))
42+
else
43+
code = ci::CodeInstance
44+
end
4145
argtypes = collect_argtypes(interp, stmt.args[2:end], StatementState(nothing, false), irsv)
4246
argtypes === nothing && return Pair{Any,Tuple{Bool,Bool}}(Bottom, (false, false))
4347
return concrete_eval_invoke(interp, code, argtypes, irsv)
@@ -160,7 +164,7 @@ function reprocess_instruction!(interp::AbstractInterpreter, inst::Instruction,
160164
result isa Future && (result = result[])
161165
(; rt, effects) = result
162166
add_flag!(inst, flags_for_effects(effects))
163-
elseif head === :invoke
167+
elseif head === :invoke # COMBAK: || head === :invoke_modifyfield (similar to call, but for args[2:end])
164168
rt, (nothrow, noub) = abstract_eval_invoke_inst(interp, inst, irsv)
165169
if nothrow
166170
add_flag!(inst, IR_FLAG_NOTHROW)

Compiler/src/ssair/passes.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,7 +1302,7 @@ function sroa_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing)
13021302
# at the end of the intrinsic. Detect that here.
13031303
if length(stmt.args) == 4 && stmt.args[4] === nothing
13041304
# constant case
1305-
elseif length(stmt.args) == 5 && stmt.args[4] isa Bool && stmt.args[5] isa MethodInstance
1305+
elseif length(stmt.args) == 5 && stmt.args[4] isa Bool && stmt.args[5] isa Core.CodeInstance
13061306
# inlining case
13071307
else
13081308
continue
@@ -1522,9 +1522,9 @@ end
15221522
# NOTE we resolve the inlining source here as we don't want to serialize `Core.Compiler`
15231523
# data structure into the global cache (see the comment in `handle_finalizer_call!`)
15241524
function try_inline_finalizer!(ir::IRCode, argexprs::Vector{Any}, idx::Int,
1525-
mi::MethodInstance, @nospecialize(info::CallInfo), inlining::InliningState,
1525+
code::CodeInstance, @nospecialize(info::CallInfo), inlining::InliningState,
15261526
attach_after::Bool)
1527-
code = get(code_cache(inlining), mi, nothing)
1527+
mi = code.def
15281528
et = InliningEdgeTracker(inlining)
15291529
if code isa CodeInstance
15301530
if use_const_api(code)
@@ -1671,11 +1671,11 @@ function try_resolve_finalizer!(ir::IRCode, alloc_idx::Int, finalizer_idx::Int,
16711671
if inline === nothing
16721672
# No code in the function - Nothing to do
16731673
else
1674-
mi = finalizer_stmt.args[5]::MethodInstance
1675-
if inline::Bool && try_inline_finalizer!(ir, argexprs, loc, mi, info, inlining, attach_after)
1674+
ci = finalizer_stmt.args[5]::CodeInstance
1675+
if inline::Bool && try_inline_finalizer!(ir, argexprs, loc, ci, info, inlining, attach_after)
16761676
# the finalizer body has been inlined
16771677
else
1678-
newinst = add_flag(NewInstruction(Expr(:invoke, mi, argexprs...), Nothing), flag)
1678+
newinst = add_flag(NewInstruction(Expr(:invoke, ci, argexprs...), Nothing), flag)
16791679
insert_node!(ir, loc, newinst, attach_after)
16801680
end
16811681
end

Compiler/src/ssair/show.jl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,14 @@ function print_stmt(io::IO, idx::Int, @nospecialize(stmt), code::Union{IRCode,Co
9292
print(io, ", ")
9393
print(io, stmt.typ)
9494
print(io, ")")
95-
elseif isexpr(stmt, :invoke) && length(stmt.args) >= 2 && isa(stmt.args[1], MethodInstance)
95+
elseif isexpr(stmt, :invoke) && length(stmt.args) >= 2 && isa(stmt.args[1], Union{MethodInstance,CodeInstance})
9696
stmt = stmt::Expr
9797
# TODO: why is this here, and not in Base.show_unquoted
9898
printstyled(io, " invoke "; color = :light_black)
99-
mi = stmt.args[1]::Core.MethodInstance
99+
mi = stmt.args[1]
100+
if !(mi isa Core.MethodInstance)
101+
mi = (mi::Core.CodeInstance).def
102+
end
100103
show_unquoted(io, stmt.args[2], indent)
101104
print(io, "(")
102105
# XXX: this is wrong if `sig` is not a concretetype method
@@ -110,6 +113,7 @@ function print_stmt(io::IO, idx::Int, @nospecialize(stmt), code::Union{IRCode,Co
110113
end
111114
join(io, (print_arg(i) for i = 3:length(stmt.args)), ", ")
112115
print(io, ")")
116+
# TODO: if we have a CodeInstance, should we print that rettype info here, which may differ (wider or narrower than the ssavaluetypes)
113117
elseif isexpr(stmt, :call) && length(stmt.args) >= 1 && label_dynamic_calls
114118
ft = maybe_argextype(stmt.args[1], code, sptypes)
115119
f = singleton_type(ft)

Compiler/src/typeinfer.jl

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -449,9 +449,10 @@ function finishinfer!(me::InferenceState, interp::AbstractInterpreter)
449449
maybe_validate_code(me.linfo, me.src, "inferred")
450450

451451
# finish populating inference results into the CodeInstance if possible, and maybe cache that globally for use elsewhere
452-
if isdefined(result, :ci) && !limited_ret
452+
if isdefined(result, :ci)
453453
result_type = result.result
454-
@assert !(result_type === nothing || result_type isa LimitedAccuracy)
454+
result_type isa LimitedAccuracy && (result_type = result_type.typ)
455+
@assert !(result_type === nothing)
455456
if isa(result_type, Const)
456457
rettype_const = result_type.val
457458
const_flags = is_result_constabi_eligible(result) ? 0x3 : 0x2
@@ -760,16 +761,24 @@ function MethodCallResult(::AbstractInterpreter, sv::AbsIntState, method::Method
760761
return MethodCallResult(rt, exct, effects, edge, edgecycle, edgelimited, volatile_inf_result)
761762
end
762763

763-
# allocate a dummy `edge::CodeInstance` to be added by `add_edges!`
764-
function codeinst_as_edge(interp::AbstractInterpreter, sv::InferenceState)
764+
# allocate a dummy `edge::CodeInstance` to be added by `add_edges!`, reusing an existing_edge if possible
765+
# TODO: fill this in fully correctly (currently IPO info such as effects and return types are lost)
766+
function codeinst_as_edge(interp::AbstractInterpreter, sv::InferenceState, @nospecialize existing_edge)
765767
mi = sv.linfo
766-
owner = cache_owner(interp)
767768
min_world, max_world = first(sv.world.valid_worlds), last(sv.world.valid_worlds)
768769
if max_world >= get_world_counter()
769770
max_world = typemax(UInt)
770771
end
771772
edges = Core.svec(sv.edges...)
772-
ci = CodeInstance(mi, owner, Any, Any, nothing, nothing, zero(Int32),
773+
if existing_edge isa CodeInstance
774+
# return an existing_edge, if the existing edge has more restrictions already (more edges and narrower worlds)
775+
if existing_edge.min_world >= min_world &&
776+
existing_edge.max_world <= max_world &&
777+
existing_edge.edges == edges
778+
return existing_edge
779+
end
780+
end
781+
ci = CodeInstance(mi, cache_owner(interp), Any, Any, nothing, nothing, zero(Int32),
773782
min_world, max_world, zero(UInt32), nothing, zero(UInt8), nothing, edges)
774783
if max_world == typemax(UInt)
775784
# if we can record all of the backedges in the global reverse-cache,

Compiler/test/inline.jl

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ f29083(;μ,σ) = μ + σ*randn()
121121
g29083() = f29083=2.0=0.1)
122122
let c = code_typed(g29083, ())[1][1].code
123123
# make sure no call to kwfunc remains
124-
@test !any(e->(isa(e,Expr) && (e.head === :invoke && e.args[1].def.name === :kwfunc)), c)
124+
@test !any(e->(isa(e,Expr) && (e.head === :invoke && e.args[1].def.def.name === :kwfunc)), c)
125125
end
126126

127127
@testset "issue #19122: [no]inline of short func. def. with return type annotation" begin
@@ -723,7 +723,7 @@ mktempdir() do dir
723723
ci, rt = only(code_typed(issue42246))
724724
if any(ci.code) do stmt
725725
Meta.isexpr(stmt, :invoke) &&
726-
stmt.args[1].def.name === nameof(IOBuffer)
726+
stmt.args[1].def.def.name === nameof(IOBuffer)
727727
end
728728
exit(0)
729729
else
@@ -1797,7 +1797,7 @@ end
17971797

17981798
isinvokemodify(y) = @nospecialize(x) -> isinvokemodify(y, x)
17991799
isinvokemodify(sym::Symbol, @nospecialize(x)) = isinvokemodify(mi->mi.def.name===sym, x)
1800-
isinvokemodify(pred::Function, @nospecialize(x)) = isexpr(x, :invoke_modify) && pred(x.args[1]::MethodInstance)
1800+
isinvokemodify(pred::Function, @nospecialize(x)) = isexpr(x, :invoke_modify) && pred((x.args[1]::CodeInstance).def)
18011801

18021802
mutable struct Atomic{T}
18031803
@atomic x::T
@@ -2131,15 +2131,15 @@ let src = code_typed1((Type,)) do x
21312131
end
21322132
@test count(src.code) do @nospecialize x
21332133
isinvoke(:no_compile_sig_invokes, x) &&
2134-
(x.args[1]::MethodInstance).specTypes == Tuple{typeof(no_compile_sig_invokes),Any}
2134+
(x.args[1]::Core.CodeInstance).def.specTypes == Tuple{typeof(no_compile_sig_invokes),Any}
21352135
end == 1
21362136
end
21372137
let src = code_typed1((Type,); interp=NoCompileSigInvokes()) do x
21382138
no_compile_sig_invokes(x)
21392139
end
21402140
@test count(src.code) do @nospecialize x
21412141
isinvoke(:no_compile_sig_invokes, x) &&
2142-
(x.args[1]::MethodInstance).specTypes == Tuple{typeof(no_compile_sig_invokes),Type}
2142+
(x.args[1]::Core.CodeInstance).def.specTypes == Tuple{typeof(no_compile_sig_invokes),Type}
21432143
end == 1
21442144
end
21452145
# test the union split case
@@ -2148,19 +2148,19 @@ let src = code_typed1((Union{DataType,UnionAll},)) do x
21482148
end
21492149
@test count(src.code) do @nospecialize x
21502150
isinvoke(:no_compile_sig_invokes, x) &&
2151-
(x.args[1]::MethodInstance).specTypes == Tuple{typeof(no_compile_sig_invokes),Any}
2151+
(x.args[1]::Core.CodeInstance).def.specTypes == Tuple{typeof(no_compile_sig_invokes),Any}
21522152
end == 2
21532153
end
21542154
let src = code_typed1((Union{DataType,UnionAll},); interp=NoCompileSigInvokes()) do x
21552155
no_compile_sig_invokes(x)
21562156
end
21572157
@test count(src.code) do @nospecialize x
21582158
isinvoke(:no_compile_sig_invokes, x) &&
2159-
(x.args[1]::MethodInstance).specTypes == Tuple{typeof(no_compile_sig_invokes),DataType}
2159+
(x.args[1]::Core.CodeInstance).def.specTypes == Tuple{typeof(no_compile_sig_invokes),DataType}
21602160
end == 1
21612161
@test count(src.code) do @nospecialize x
21622162
isinvoke(:no_compile_sig_invokes, x) &&
2163-
(x.args[1]::MethodInstance).specTypes == Tuple{typeof(no_compile_sig_invokes),UnionAll}
2163+
(x.args[1]::Core.CodeInstance).def.specTypes == Tuple{typeof(no_compile_sig_invokes),UnionAll}
21642164
end == 1
21652165
end
21662166

0 commit comments

Comments
 (0)