Skip to content

Commit fed4544

Browse files
authored
inference: follow up #43852 (#44101)
This commit consists of minor follow up tweaks for #43852: - inlining: use `ConstResult` if available - refactor tests - simplify `CodeInstance` constructor signature - tweak `concrete_eval_const_proven_total_or_error` signature for JET integration
1 parent 708873a commit fed4544

File tree

7 files changed

+129
-116
lines changed

7 files changed

+129
-116
lines changed

base/compiler/abstractinterpretation.jl

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,7 @@ struct MethodCallResult
619619
end
620620
end
621621

622-
function is_all_const_arg((; fargs, argtypes)::ArgInfo)
622+
function is_all_const_arg((; argtypes)::ArgInfo)
623623
for a in argtypes
624624
if !isa(a, Const) && !isconstType(a) && !issingletontype(a)
625625
return false
@@ -628,9 +628,8 @@ function is_all_const_arg((; fargs, argtypes)::ArgInfo)
628628
return true
629629
end
630630

631-
function concrete_eval_const_proven_total_or_error(
632-
interp::AbstractInterpreter,
633-
@nospecialize(f), argtypes::Vector{Any})
631+
function concrete_eval_const_proven_total_or_error(interp::AbstractInterpreter,
632+
@nospecialize(f), (; argtypes)::ArgInfo, _::InferenceState)
634633
args = Any[ (a = widenconditional(argtypes[i]);
635634
isa(a, Const) ? a.val :
636635
isconstType(a) ? (a::DataType).parameters[1] :
@@ -673,7 +672,7 @@ function abstract_call_method_with_const_args(interp::AbstractInterpreter, resul
673672
return nothing
674673
end
675674
if f !== nothing && result.edge !== nothing && is_total_or_error(result.edge_effects) && is_all_const_arg(arginfo)
676-
rt = concrete_eval_const_proven_total_or_error(interp, f, arginfo.argtypes)
675+
rt = concrete_eval_const_proven_total_or_error(interp, f, arginfo, sv)
677676
add_backedge!(result.edge, sv)
678677
if rt === nothing
679678
# The evaulation threw. By :consistent-cy, we're guaranteed this would have happened at runtime

base/compiler/ssair/inlining.jl

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,18 +1034,22 @@ function inline_invoke!(
10341034
# TODO: We could union split out the signature check and continue on
10351035
return nothing
10361036
end
1037-
argtypes = invoke_rewrite(sig.argtypes)
10381037
result = info.result
1039-
if isa(result, InferenceResult)
1040-
(; mi) = item = InliningTodo(result, argtypes)
1041-
validate_sparams(mi.sparam_vals) || return nothing
1042-
if argtypes_to_type(argtypes) <: mi.def.sig
1043-
state.mi_cache !== nothing && (item = resolve_todo(item, state, flag))
1044-
handle_single_case!(ir, idx, stmt, item, todo, state.params, true)
1045-
return nothing
1038+
if isa(result, ConstResult)
1039+
item = const_result_item(result, state)
1040+
else
1041+
argtypes = invoke_rewrite(sig.argtypes)
1042+
if isa(result, InferenceResult)
1043+
(; mi) = item = InliningTodo(result, argtypes)
1044+
validate_sparams(mi.sparam_vals) || return nothing
1045+
if argtypes_to_type(argtypes) <: mi.def.sig
1046+
state.mi_cache !== nothing && (item = resolve_todo(item, state, flag))
1047+
handle_single_case!(ir, idx, stmt, item, todo, state.params, true)
1048+
return nothing
1049+
end
10461050
end
1051+
item = analyze_method!(match, argtypes, flag, state)
10471052
end
1048-
item = analyze_method!(match, argtypes, flag, state)
10491053
handle_single_case!(ir, idx, stmt, item, todo, state.params, true)
10501054
return nothing
10511055
end
@@ -1241,28 +1245,18 @@ function handle_const_call!(
12411245
for match in meth
12421246
j += 1
12431247
result = results[j]
1244-
if result === false
1245-
# Inference determined that this call is guaranteed to throw.
1246-
# Do not inline.
1247-
fully_covered = false
1248-
continue
1249-
end
12501248
if isa(result, ConstResult)
1251-
if !isdefined(result, :result) || !is_inlineable_constant(result.result)
1252-
case = compileable_specialization(state.et, result.mi, EFFECTS_TOTAL)
1253-
else
1254-
case = ConstantCase(quoted(result.result))
1255-
end
1249+
case = const_result_item(result, state)
12561250
signature_union = Union{signature_union, result.mi.specTypes}
12571251
push!(cases, InliningCase(result.mi.specTypes, case))
12581252
continue
1259-
end
1260-
if result === nothing
1253+
elseif isa(result, InferenceResult)
1254+
signature_union = Union{signature_union, result.linfo.specTypes}
1255+
fully_covered &= handle_inf_result!(result, argtypes, flag, state, cases)
1256+
else
1257+
@assert result === nothing
12611258
signature_union = Union{signature_union, match.spec_types}
12621259
fully_covered &= handle_match!(match, argtypes, flag, state, cases)
1263-
else
1264-
signature_union = Union{signature_union, result.linfo.specTypes}
1265-
fully_covered &= handle_const_result!(result, argtypes, flag, state, cases)
12661260
end
12671261
end
12681262
end
@@ -1296,7 +1290,7 @@ function handle_match!(
12961290
return true
12971291
end
12981292

1299-
function handle_const_result!(
1293+
function handle_inf_result!(
13001294
result::InferenceResult, argtypes::Vector{Any}, flag::UInt8, state::InliningState,
13011295
cases::Vector{InliningCase})
13021296
(; mi) = item = InliningTodo(result, argtypes)
@@ -1309,6 +1303,14 @@ function handle_const_result!(
13091303
return true
13101304
end
13111305

1306+
function const_result_item(result::ConstResult, state::InliningState)
1307+
if !isdefined(result, :result) || !is_inlineable_constant(result.result)
1308+
return compileable_specialization(state.et, result.mi, EFFECTS_TOTAL)
1309+
else
1310+
return ConstantCase(quoted(result.result))
1311+
end
1312+
end
1313+
13121314
function handle_cases!(ir::IRCode, idx::Int, stmt::Expr, @nospecialize(atype),
13131315
cases::Vector{InliningCase}, fully_covered::Bool, todo::Vector{Pair{Int, Any}},
13141316
params::OptimizationParams)
@@ -1375,7 +1377,11 @@ function assemble_inline_todo!(ir::IRCode, state::InliningState)
13751377
ir, idx, stmt, result, flag,
13761378
sig, state, todo)
13771379
else
1378-
item = analyze_method!(info.match, sig.argtypes, flag, state)
1380+
if isa(result, ConstResult)
1381+
item = const_result_item(result, state)
1382+
else
1383+
item = analyze_method!(info.match, sig.argtypes, flag, state)
1384+
end
13791385
handle_single_case!(ir, idx, stmt, item, todo, state.params)
13801386
end
13811387
continue

base/compiler/typeinfer.jl

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -277,9 +277,8 @@ function _typeinf(interp::AbstractInterpreter, frame::InferenceState)
277277
return true
278278
end
279279

280-
function CodeInstance(result::InferenceResult, @nospecialize(inferred_result),
281-
valid_worlds::WorldRange, effects::Effects, ipo_effects::Effects,
282-
relocatability::UInt8)
280+
function CodeInstance(
281+
result::InferenceResult, @nospecialize(inferred_result), valid_worlds::WorldRange)
283282
local const_flags::Int32
284283
result_type = result.result
285284
@assert !(result_type isa LimitedAccuracy)
@@ -309,10 +308,13 @@ function CodeInstance(result::InferenceResult, @nospecialize(inferred_result),
309308
const_flags = 0x00
310309
end
311310
end
311+
relocatability = isa(inferred_result, Vector{UInt8}) ? inferred_result[end] : UInt8(0)
312312
return CodeInstance(result.linfo,
313313
widenconst(result_type), rettype_const, inferred_result,
314314
const_flags, first(valid_worlds), last(valid_worlds),
315-
encode_effects(effects), encode_effects(ipo_effects), relocatability)
315+
# TODO: Actually do something with non-IPO effects
316+
encode_effects(result.ipo_effects), encode_effects(result.ipo_effects),
317+
relocatability)
316318
end
317319

318320
# For the NativeInterpreter, we don't need to do an actual cache query to know
@@ -386,10 +388,7 @@ function cache_result!(interp::AbstractInterpreter, result::InferenceResult)
386388
# TODO: also don't store inferred code if we've previously decided to interpret this function
387389
if !already_inferred
388390
inferred_result = transform_result_for_cache(interp, linfo, valid_worlds, result.src)
389-
relocatability = isa(inferred_result, Vector{UInt8}) ? inferred_result[end] : UInt8(0)
390-
code_cache(interp)[linfo] = CodeInstance(result, inferred_result, valid_worlds,
391-
# TODO: Actually do something with non-IPO effects
392-
result.ipo_effects, result.ipo_effects, relocatability)
391+
code_cache(interp)[linfo] = CodeInstance(result, inferred_result, valid_worlds)
393392
end
394393
unlock_mi_inference(interp, linfo)
395394
nothing

base/expr.jl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -378,9 +378,10 @@ end
378378
`@assume_effects` overrides the compiler's effect modeling for the given method.
379379
`ex` must be a method definition or `@ccall` expression.
380380
381-
WARNING: Improper use of this macro causes undefined behavior (including crashes,
382-
incorrect answers, or other hard to track bugs). Use with care and only if absolutely
383-
required.
381+
!!! warning
382+
Improper use of this macro causes undefined behavior (including crashes,
383+
incorrect answers, or other hard to track bugs). Use with care and only if
384+
absolutely required.
384385
385386
In general, each `setting` value makes an assertion about the behavior of the
386387
function, without requiring the compiler to prove that this behavior is indeed

test/compiler/inline.jl

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ using Test
44
using Base.Meta
55
using Core: ReturnNode
66

7+
include(normpath(@__DIR__, "irutils.jl"))
8+
79
"""
810
Helper to walk the AST and call a function on every node.
911
"""
@@ -150,19 +152,6 @@ end
150152
@test !any(x -> x isa Expr && x.head === :invoke, src.code)
151153
end
152154

153-
function fully_eliminated(f, args)
154-
@nospecialize f args
155-
let code = code_typed(f, args)[1][1].code
156-
return length(code) == 1 && isa(code[1], ReturnNode)
157-
end
158-
end
159-
function fully_eliminated(f, args, retval)
160-
@nospecialize f args
161-
let code = code_typed(f, args)[1][1].code
162-
return length(code) == 1 && isa(code[1], ReturnNode) && code[1].val == retval
163-
end
164-
end
165-
166155
# check that ismutabletype(type) can be fully eliminated
167156
f_mutable_nothrow(s::String) = Val{typeof(s).name.flags}
168157
@test fully_eliminated(f_mutable_nothrow, (String,))
@@ -246,7 +235,7 @@ function f_subtype()
246235
T = SomeArbitraryStruct
247236
T <: Bool
248237
end
249-
@test fully_eliminated(f_subtype, Tuple{}, false)
238+
@test fully_eliminated(f_subtype, Tuple{}; retval=false)
250239

251240
# check that pointerref gets deleted if unused
252241
f_pointerref(T::Type{S}) where S = Val(length(T.parameters))
@@ -270,7 +259,7 @@ function foo_apply_apply_type_svec()
270259
B = Tuple{Float32, Float32}
271260
Core.apply_type(A..., B.types...)
272261
end
273-
@test fully_eliminated(foo_apply_apply_type_svec, Tuple{}, NTuple{3, Float32})
262+
@test fully_eliminated(foo_apply_apply_type_svec, Tuple{}; retval=NTuple{3, Float32})
274263

275264
# The that inlining doesn't drop ambiguity errors (#30118)
276265
c30118(::Tuple{Ref{<:Type}, Vararg}) = nothing
@@ -284,7 +273,7 @@ b30118(x...) = c30118(x)
284273
f34900(x::Int, y) = x
285274
f34900(x, y::Int) = y
286275
f34900(x::Int, y::Int) = invoke(f34900, Tuple{Int, Any}, x, y)
287-
@test fully_eliminated(f34900, Tuple{Int, Int}, Core.Argument(2))
276+
@test fully_eliminated(f34900, Tuple{Int, Int}; retval=Core.Argument(2))
288277

289278
@testset "check jl_ir_flag_inlineable for inline macro" begin
290279
@test ccall(:jl_ir_flag_inlineable, Bool, (Any,), first(methods(@inline x -> x)).source)
@@ -324,10 +313,7 @@ struct NonIsBitsDims
324313
dims::NTuple{N, Int} where N
325314
end
326315
NonIsBitsDims() = NonIsBitsDims(())
327-
let ci = code_typed(NonIsBitsDims, Tuple{})[1].first
328-
@test length(ci.code) == 1 && isa(ci.code[1], ReturnNode) &&
329-
ci.code[1].val.value == NonIsBitsDims()
330-
end
316+
@test fully_eliminated(NonIsBitsDims, (); retval=QuoteNode(NonIsBitsDims()))
331317

332318
struct NonIsBitsDimsUndef
333319
dims::NTuple{N, Int} where N
@@ -923,7 +909,7 @@ end
923909
g() = Core.get_binding_type($m, :y)
924910
end
925911

926-
@test fully_eliminated(m.f, Tuple{}, Int)
912+
@test fully_eliminated(m.f, Tuple{}; retval=Int)
927913
src = code_typed(m.g, ())[][1]
928914
@test count(iscall((src, Core.get_binding_type)), src.code) == 1
929915
@test m.g() === Any
@@ -962,17 +948,48 @@ end
962948
@test fully_eliminated(f_sin_perf, Tuple{})
963949

964950
# Test that we inline the constructor of something that is not const-inlineable
951+
const THE_REF_NULL = Ref{Int}()
965952
const THE_REF = Ref{Int}(0)
966953
struct FooTheRef
967954
x::Ref
968-
FooTheRef() = new(THE_REF)
955+
FooTheRef(v) = new(v === nothing ? THE_REF_NULL : THE_REF)
956+
end
957+
let src = code_typed1() do
958+
FooTheRef(nothing)
959+
end
960+
@test count(isnew, src.code) == 1
961+
end
962+
let src = code_typed1() do
963+
FooTheRef(0)
964+
end
965+
@test count(isnew, src.code) == 1
969966
end
970-
f_make_the_ref() = FooTheRef()
971-
f_make_the_ref_but_dont_return_it() = (FooTheRef(); nothing)
972-
let src = code_typed1(f_make_the_ref, ())
973-
@test count(x->isexpr(x, :new), src.code) == 1
967+
let src = code_typed1() do
968+
Base.@invoke FooTheRef(nothing::Any)
969+
end
970+
@test count(isnew, src.code) == 1
971+
end
972+
let src = code_typed1() do
973+
Base.@invoke FooTheRef(0::Any)
974+
end
975+
@test count(isnew, src.code) == 1
976+
end
977+
@test fully_eliminated() do
978+
FooTheRef(nothing)
979+
nothing
980+
end
981+
@test fully_eliminated() do
982+
FooTheRef(0)
983+
nothing
984+
end
985+
@test fully_eliminated() do
986+
Base.@invoke FooTheRef(nothing::Any)
987+
nothing
988+
end
989+
@test fully_eliminated() do
990+
Base.@invoke FooTheRef(0::Any)
991+
nothing
974992
end
975-
@test fully_eliminated(f_make_the_ref_but_dont_return_it, Tuple{})
976993

977994
# Test that the Core._apply_iterate bail path taints effects
978995
function f_apply_bail(f)

test/compiler/irpasses.jl

Lines changed: 3 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,7 @@ using Test
44
using Base.Meta
55
using Core: PhiNode, SSAValue, GotoNode, PiNode, QuoteNode, ReturnNode, GotoIfNot
66

7-
# utilities
8-
# =========
9-
10-
import Core.Compiler: argextype, singleton_type
11-
12-
argextype(@nospecialize args...) = argextype(args..., Any[])
13-
code_typed1(args...; kwargs...) = first(only(code_typed(args...; kwargs...)))::Core.CodeInfo
14-
get_code(args...; kwargs...) = code_typed1(args...; kwargs...).code
15-
16-
# check if `x` is a statement with a given `head`
17-
isnew(@nospecialize x) = Meta.isexpr(x, :new)
18-
19-
# check if `x` is a dynamic call of a given function
20-
iscall(y) = @nospecialize(x) -> iscall(y, x)
21-
function iscall((src, f)::Tuple{Core.CodeInfo,Base.Callable}, @nospecialize(x))
22-
return iscall(x) do @nospecialize x
23-
singleton_type(argextype(x, src)) === f
24-
end
25-
end
26-
iscall(pred::Base.Callable, @nospecialize(x)) = Meta.isexpr(x, :call) && pred(x.args[1])
27-
28-
# check if `x` is a statically-resolved call of a function whose name is `sym`
29-
isinvoke(y) = @nospecialize(x) -> isinvoke(y, x)
30-
isinvoke(sym::Symbol, @nospecialize(x)) = isinvoke(mi->mi.def.name===sym, x)
31-
isinvoke(pred::Function, @nospecialize(x)) = Meta.isexpr(x, :invoke) && pred(x.args[1]::Core.MethodInstance)
7+
include(normpath(@__DIR__, "irutils.jl"))
328

339
# domsort
3410
# =======
@@ -473,9 +449,7 @@ struct FooPartial
473449
global f_partial
474450
f_partial(x) = new(x, 2).x
475451
end
476-
let ci = code_typed(f_partial, Tuple{Float64})[1].first
477-
@test length(ci.code) == 1 && isa(ci.code[1], ReturnNode)
478-
end
452+
@test fully_eliminated(f_partial, Tuple{Float64})
479453

480454
# A SSAValue after the compaction line
481455
let m = Meta.@lower 1 + 1
@@ -657,11 +631,7 @@ function no_op_refint(r)
657631
r[]
658632
return
659633
end
660-
let code = code_typed(no_op_refint,Tuple{Base.RefValue{Int}})[1].first.code
661-
@test length(code) == 1
662-
@test isa(code[1], Core.ReturnNode)
663-
@test code[1].val === nothing
664-
end
634+
@test fully_eliminated(no_op_refint,Tuple{Base.RefValue{Int}}; retval=nothing)
665635

666636
# check getfield elim handling of GlobalRef
667637
const _some_coeffs = (1,[2],3,4)
@@ -773,19 +743,6 @@ end
773743
# test `stmt_effect_free` and DCE
774744
# ===============================
775745

776-
function fully_eliminated(f, args)
777-
@nospecialize f args
778-
let code = code_typed(f, args)[1][1].code
779-
return length(code) == 1 && isa(code[1], ReturnNode)
780-
end
781-
end
782-
function fully_eliminated(f, args, retval)
783-
@nospecialize f args
784-
let code = code_typed(f, args)[1][1].code
785-
return length(code) == 1 && isa(code[1], ReturnNode) && code[1].val == retval
786-
end
787-
end
788-
789746
let # effect-freeness computation for array allocation
790747

791748
# should eliminate dead allocations

0 commit comments

Comments
 (0)