Skip to content

Commit 62084b7

Browse files
authored
inference: fixes and improvements for exct modeling of invoke/OC calls (#55678)
- fix exct for mismatched opaque closure call: bfeaa27 (following up #55672 ) - improve exct modeling for opaque closure calls: 7a65218 - fix `nothrow` modeling for `invoke` calls: 6916bc1 - improve `exct` modeling for `invoke` calls: 7b2d5d9
2 parents 2f0607f + 9482961 commit 62084b7

File tree

2 files changed

+85
-26
lines changed

2 files changed

+85
-26
lines changed

base/compiler/abstractinterpretation.jl

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f),
4242
arginfo::ArgInfo, si::StmtInfo, @nospecialize(atype),
4343
sv::AbsIntState, max_methods::Int)
4444
𝕃ₚ, 𝕃ᵢ = ipo_lattice(interp), typeinf_lattice(interp)
45-
ₚ, ₚ, = partialorder(𝕃ₚ), join(𝕃ₚ), join(𝕃ᵢ)
45+
ₚ, ₚ, ₚ, = partialorder(𝕃ₚ), strictneqpartialorder(𝕃ₚ), join(𝕃ₚ), join(𝕃ᵢ)
4646
argtypes = arginfo.argtypes
4747
matches = find_method_matches(interp, argtypes, atype; max_methods)
4848
if isa(matches, FailedMethodMatch)
@@ -97,9 +97,8 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f),
9797
else
9898
add_remark!(interp, sv, "[constprop] Discarded because the result was wider than inference")
9999
end
100-
if !(exct ₚ const_call_result.exct)
101-
exct = const_call_result.exct
102-
(; const_result, edge) = const_call_result
100+
if const_call_result.exct exct
101+
(; exct, const_result, edge) = const_call_result
103102
else
104103
add_remark!(interp, sv, "[constprop] Discarded exception type because result was wider than inference")
105104
end
@@ -154,7 +153,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f),
154153
end
155154
# Treat the exception type separately. Currently, constprop often cannot determine the exception type
156155
# because consistent-cy does not apply to exceptions.
157-
if !(this_exct const_call_result.exct)
156+
if const_call_result.exct this_exct
158157
this_exct = const_call_result.exct
159158
(; const_result, edge) = const_call_result
160159
else
@@ -2135,12 +2134,13 @@ function abstract_invoke(interp::AbstractInterpreter, arginfo::ArgInfo, si::Stmt
21352134
(types, isexact, isconcrete, istype) = instanceof_tfunc(argtype_by_index(argtypes, 3), false)
21362135
isexact || return CallMeta(Any, Any, Effects(), NoCallInfo())
21372136
unwrapped = unwrap_unionall(types)
2138-
if types === Bottom || !(unwrapped isa DataType) || unwrapped.name !== Tuple.name
2139-
return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo())
2137+
types === Bottom && return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo())
2138+
if !(unwrapped isa DataType && unwrapped.name === Tuple.name)
2139+
return CallMeta(Bottom, TypeError, EFFECTS_THROWS, NoCallInfo())
21402140
end
21412141
argtype = argtypes_to_type(argtype_tail(argtypes, 4))
21422142
nargtype = typeintersect(types, argtype)
2143-
nargtype === Bottom && return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo())
2143+
nargtype === Bottom && return CallMeta(Bottom, TypeError, EFFECTS_THROWS, NoCallInfo())
21442144
nargtype isa DataType || return CallMeta(Any, Any, Effects(), NoCallInfo()) # other cases are not implemented below
21452145
isdispatchelem(ft) || return CallMeta(Any, Any, Effects(), NoCallInfo()) # check that we might not have a subtype of `ft` at runtime, before doing supertype lookup below
21462146
ft = ft::DataType
@@ -2154,7 +2154,7 @@ function abstract_invoke(interp::AbstractInterpreter, arginfo::ArgInfo, si::Stmt
21542154
tienv = ccall(:jl_type_intersection_with_env, Any, (Any, Any), nargtype, method.sig)::SimpleVector
21552155
ti = tienv[1]; env = tienv[2]::SimpleVector
21562156
result = abstract_call_method(interp, method, ti, env, false, si, sv)
2157-
(; rt, edge, effects, volatile_inf_result) = result
2157+
(; rt, exct, edge, effects, volatile_inf_result) = result
21582158
match = MethodMatch(ti, env, method, argtype <: method.sig)
21592159
res = nothing
21602160
sig = match.spec_types
@@ -2168,20 +2168,28 @@ function abstract_invoke(interp::AbstractInterpreter, arginfo::ArgInfo, si::Stmt
21682168
# argtypes′[i] = t ⊑ a ? t : a
21692169
# end
21702170
𝕃ₚ = ipo_lattice(interp)
2171+
, , = partialorder(𝕃ₚ), strictneqpartialorder(𝕃ₚ), join(𝕃ₚ)
21712172
f = singleton_type(ft′)
21722173
invokecall = InvokeCall(types, lookupsig)
21732174
const_call_result = abstract_call_method_with_const_args(interp,
21742175
result, f, arginfo, si, match, sv, invokecall)
21752176
const_result = volatile_inf_result
21762177
if const_call_result !== nothing
2177-
if (𝕃ₚ, const_call_result.rt, rt)
2178+
if const_call_result.rt rt
21782179
(; rt, effects, const_result, edge) = const_call_result
21792180
end
2181+
if const_call_result.exct exct
2182+
(; exct, const_result, edge) = const_call_result
2183+
end
21802184
end
21812185
rt = from_interprocedural!(interp, rt, sv, arginfo, sig)
21822186
info = InvokeCallInfo(match, const_result)
21832187
edge !== nothing && add_invoke_backedge!(sv, lookupsig, edge)
2184-
return CallMeta(rt, Any, effects, info)
2188+
if !match.fully_covers
2189+
effects = Effects(effects; nothrow=false)
2190+
exct = exct TypeError
2191+
end
2192+
return CallMeta(rt, exct, effects, info)
21852193
end
21862194

21872195
function invoke_rewrite(xs::Vector{Any})
@@ -2202,16 +2210,16 @@ end
22022210

22032211
function abstract_throw(interp::AbstractInterpreter, argtypes::Vector{Any}, ::AbsIntState)
22042212
na = length(argtypes)
2205-
𝕃ᵢ = typeinf_lattice(interp)
2213+
= join(typeinf_lattice(interp))
22062214
if na == 2
22072215
argtype2 = argtypes[2]
22082216
if isvarargtype(argtype2)
2209-
exct = tmerge(𝕃ᵢ, unwrapva(argtype2), ArgumentError)
2217+
exct = unwrapva(argtype2) ArgumentError
22102218
else
22112219
exct = argtype2
22122220
end
22132221
elseif na == 3 && isvarargtype(argtypes[3])
2214-
exct = tmerge(𝕃ᵢ, argtypes[2], ArgumentError)
2222+
exct = argtypes[2] ArgumentError
22152223
else
22162224
exct = ArgumentError
22172225
end
@@ -2339,35 +2347,38 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter,
23392347
ocargsig′ = unwrap_unionall(ocargsig)
23402348
ocargsig′ isa DataType || return CallMeta(Any, Any, Effects(), NoCallInfo())
23412349
ocsig = rewrap_unionall(Tuple{Tuple, ocargsig′.parameters...}, ocargsig)
2342-
hasintersect(sig, ocsig) || return CallMeta(Union{}, TypeError, EFFECTS_THROWS, NoCallInfo())
2350+
hasintersect(sig, ocsig) || return CallMeta(Union{}, Union{MethodError,TypeError}, EFFECTS_THROWS, NoCallInfo())
23432351
ocmethod = closure.source::Method
23442352
result = abstract_call_method(interp, ocmethod, sig, Core.svec(), false, si, sv)
2345-
(; rt, edge, effects, volatile_inf_result) = result
2353+
(; rt, exct, edge, effects, volatile_inf_result) = result
23462354
match = MethodMatch(sig, Core.svec(), ocmethod, sig <: ocsig)
23472355
𝕃ₚ = ipo_lattice(interp)
2348-
= (𝕃ₚ)
2356+
, , = partialorder(𝕃ₚ), strictneqpartialorder(𝕃ₚ), join(𝕃ₚ)
23492357
const_result = volatile_inf_result
23502358
if !result.edgecycle
23512359
const_call_result = abstract_call_method_with_const_args(interp, result,
23522360
nothing, arginfo, si, match, sv)
23532361
if const_call_result !== nothing
2354-
if const_call_result.rt rt
2362+
if const_call_result.rt rt
23552363
(; rt, effects, const_result, edge) = const_call_result
23562364
end
2365+
if const_call_result.exct exct
2366+
(; exct, const_result, edge) = const_call_result
2367+
end
23572368
end
23582369
end
23592370
if check # analyze implicit type asserts on argument and return type
2360-
ftt = closure.typ
2361-
(aty, rty) = (unwrap_unionall(ftt)::DataType).parameters
2362-
rty = rewrap_unionall(rty isa TypeVar ? rty.lb : rty, ftt)
2363-
if !(rt ₚ rty && tuple_tfunc(𝕃ₚ, arginfo.argtypes[2:end]) rewrap_unionall(aty, ftt))
2371+
rty = (unwrap_unionall(tt)::DataType).parameters[2]
2372+
rty = rewrap_unionall(rty isa TypeVar ? rty.ub : rty, tt)
2373+
if !(rt rty && sig ocsig)
23642374
effects = Effects(effects; nothrow=false)
2375+
exct = exct TypeError
23652376
end
23662377
end
23672378
rt = from_interprocedural!(interp, rt, sv, arginfo, match.spec_types)
23682379
info = OpaqueClosureCallInfo(match, const_result)
23692380
edge !== nothing && add_backedge!(sv, edge)
2370-
return CallMeta(rt, Any, effects, info)
2381+
return CallMeta(rt, exct, effects, info)
23712382
end
23722383

23732384
function most_general_argtypes(closure::PartialOpaque)

test/compiler/inference.jl

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6078,9 +6078,7 @@ gcondvarargs(a, x...) = return fcondvarargs(a, x...) ? isa(a, Int64) : !isa(a, I
60786078
@test Core.Compiler.return_type(gcondvarargs, Tuple{Vararg{Any}}) === Bool
60796079

60806080
# JuliaLang/julia#55627: argtypes check in `abstract_call_opaque_closure`
6081-
issue55627_some_method(x) = 2x
6082-
issue55627_make_oc() = Base.Experimental.@opaque (x::Int)->issue55627_some_method(x)
6083-
6081+
issue55627_make_oc() = Base.Experimental.@opaque (x::Int) -> 2x
60846082
@test Base.infer_return_type() do
60856083
f = issue55627_make_oc()
60866084
return f(1), f()
@@ -6089,3 +6087,53 @@ end == Union{}
60896087
f = issue55627_make_oc()
60906088
return f(1), f(xs...)
60916089
end == Tuple{Int,Int}
6090+
@test Base.infer_exception_type() do
6091+
f = issue55627_make_oc()
6092+
return f(1), f()
6093+
end >: MethodError
6094+
@test Base.infer_exception_type() do
6095+
f = issue55627_make_oc()
6096+
return f(1), f('1')
6097+
end >: TypeError
6098+
6099+
# `exct` modeling for opaque closure
6100+
oc_exct_1() = Base.Experimental.@opaque (x) -> x < 0 ? throw(x) : x
6101+
@test Base.infer_exception_type((Int,)) do x
6102+
oc_exct_1()(x)
6103+
end == Int
6104+
oc_exct_2() = Base.Experimental.@opaque Tuple{Number}->Number (x) -> '1'
6105+
@test Base.infer_exception_type((Int,)) do x
6106+
oc_exct_2()(x)
6107+
end == TypeError
6108+
6109+
# nothrow modeling for `invoke` calls
6110+
f_invoke_nothrow(::Number) = :number
6111+
f_invoke_nothrow(::Int) = :int
6112+
@test Base.infer_effects((Int,)) do x
6113+
@invoke f_invoke_nothrow(x::Number)
6114+
end |> Core.Compiler.is_nothrow
6115+
@test Base.infer_effects((Char,)) do x
6116+
@invoke f_invoke_nothrow(x::Number)
6117+
end |> !Core.Compiler.is_nothrow
6118+
@test Base.infer_effects((Union{Nothing,Int},)) do x
6119+
@invoke f_invoke_nothrow(x::Number)
6120+
end |> !Core.Compiler.is_nothrow
6121+
6122+
# `exct` modeling for `invoke` calls
6123+
f_invoke_exct(x::Number) = x < 0 ? throw(x) : x
6124+
f_invoke_exct(x::Int) = x
6125+
@test Base.infer_exception_type((Int,)) do x
6126+
@invoke f_invoke_exct(x::Number)
6127+
end == Int
6128+
@test Base.infer_exception_type() do
6129+
@invoke f_invoke_exct(42::Number)
6130+
end == Union{}
6131+
@test Base.infer_exception_type((Union{Nothing,Int},)) do x
6132+
@invoke f_invoke_exct(x::Number)
6133+
end == Union{Int,TypeError}
6134+
@test Base.infer_exception_type((Int,)) do x
6135+
invoke(f_invoke_exct, Number, x)
6136+
end == TypeError
6137+
@test Base.infer_exception_type((Char,)) do x
6138+
invoke(f_invoke_exct, Tuple{Number}, x)
6139+
end == TypeError

0 commit comments

Comments
 (0)