Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

inference: fixes and improvements for exct modeling of invoke/OC calls #55678

Merged
merged 4 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 34 additions & 23 deletions base/compiler/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f),
arginfo::ArgInfo, si::StmtInfo, @nospecialize(atype),
sv::AbsIntState, max_methods::Int)
𝕃ₚ, 𝕃ᵢ = ipo_lattice(interp), typeinf_lattice(interp)
⊑ₚ, ⊔ₚ, ⊔ᵢ = partialorder(𝕃ₚ), join(𝕃ₚ), join(𝕃ᵢ)
⊑ₚ, ⋤ₚ, ⊔ₚ, ⊔ᵢ = partialorder(𝕃ₚ), strictneqpartialorder(𝕃ₚ), join(𝕃ₚ), join(𝕃ᵢ)
argtypes = arginfo.argtypes
matches = find_method_matches(interp, argtypes, atype; max_methods)
if isa(matches, FailedMethodMatch)
Expand Down Expand Up @@ -97,9 +97,8 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f),
else
add_remark!(interp, sv, "[constprop] Discarded because the result was wider than inference")
end
if !(exct ⊑ₚ const_call_result.exct)
exct = const_call_result.exct
(; const_result, edge) = const_call_result
if const_call_result.exct ⋤ exct
(; exct, const_result, edge) = const_call_result
else
add_remark!(interp, sv, "[constprop] Discarded exception type because result was wider than inference")
end
Expand Down Expand Up @@ -154,7 +153,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f),
end
# Treat the exception type separately. Currently, constprop often cannot determine the exception type
# because consistent-cy does not apply to exceptions.
if !(this_exct ⊑ₚ const_call_result.exct)
if const_call_result.exct ⋤ this_exct
this_exct = const_call_result.exct
(; const_result, edge) = const_call_result
else
Expand Down Expand Up @@ -2135,12 +2134,13 @@ function abstract_invoke(interp::AbstractInterpreter, arginfo::ArgInfo, si::Stmt
(types, isexact, isconcrete, istype) = instanceof_tfunc(argtype_by_index(argtypes, 3), false)
isexact || return CallMeta(Any, Any, Effects(), NoCallInfo())
unwrapped = unwrap_unionall(types)
if types === Bottom || !(unwrapped isa DataType) || unwrapped.name !== Tuple.name
return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo())
types === Bottom && return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo())
if !(unwrapped isa DataType && unwrapped.name === Tuple.name)
return CallMeta(Bottom, TypeError, EFFECTS_THROWS, NoCallInfo())
end
argtype = argtypes_to_type(argtype_tail(argtypes, 4))
nargtype = typeintersect(types, argtype)
nargtype === Bottom && return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo())
nargtype === Bottom && return CallMeta(Bottom, TypeError, EFFECTS_THROWS, NoCallInfo())
nargtype isa DataType || return CallMeta(Any, Any, Effects(), NoCallInfo()) # other cases are not implemented below
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
ft = ft::DataType
Expand All @@ -2154,7 +2154,7 @@ function abstract_invoke(interp::AbstractInterpreter, arginfo::ArgInfo, si::Stmt
tienv = ccall(:jl_type_intersection_with_env, Any, (Any, Any), nargtype, method.sig)::SimpleVector
ti = tienv[1]; env = tienv[2]::SimpleVector
result = abstract_call_method(interp, method, ti, env, false, si, sv)
(; rt, edge, effects, volatile_inf_result) = result
(; rt, exct, edge, effects, volatile_inf_result) = result
match = MethodMatch(ti, env, method, argtype <: method.sig)
res = nothing
sig = match.spec_types
Expand All @@ -2168,20 +2168,28 @@ function abstract_invoke(interp::AbstractInterpreter, arginfo::ArgInfo, si::Stmt
# argtypes′[i] = t ⊑ a ? t : a
# end
𝕃ₚ = ipo_lattice(interp)
⊑, ⋤, ⊔ = partialorder(𝕃ₚ), strictneqpartialorder(𝕃ₚ), join(𝕃ₚ)
f = singleton_type(ft′)
invokecall = InvokeCall(types, lookupsig)
const_call_result = abstract_call_method_with_const_args(interp,
result, f, arginfo, si, match, sv, invokecall)
const_result = volatile_inf_result
if const_call_result !== nothing
if ⊑(𝕃ₚ, const_call_result.rt, rt)
if const_call_result.rt ⊑ rt
(; rt, effects, const_result, edge) = const_call_result
end
if const_call_result.exct ⋤ exct
(; exct, const_result, edge) = const_call_result
end
end
rt = from_interprocedural!(interp, rt, sv, arginfo, sig)
info = InvokeCallInfo(match, const_result)
edge !== nothing && add_invoke_backedge!(sv, lookupsig, edge)
return CallMeta(rt, Any, effects, info)
if !match.fully_covers
effects = Effects(effects; nothrow=false)
exct = exct ⊔ TypeError
end
return CallMeta(rt, exct, effects, info)
end

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

function abstract_throw(interp::AbstractInterpreter, argtypes::Vector{Any}, ::AbsIntState)
na = length(argtypes)
𝕃ᵢ = typeinf_lattice(interp)
= join(typeinf_lattice(interp))
if na == 2
argtype2 = argtypes[2]
if isvarargtype(argtype2)
exct = tmerge(𝕃ᵢ, unwrapva(argtype2), ArgumentError)
exct = unwrapva(argtype2)ArgumentError
else
exct = argtype2
end
elseif na == 3 && isvarargtype(argtypes[3])
exct = tmerge(𝕃ᵢ, argtypes[2], ArgumentError)
exct = argtypes[2]ArgumentError
else
exct = ArgumentError
end
Expand Down Expand Up @@ -2339,35 +2347,38 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter,
ocargsig′ = unwrap_unionall(ocargsig)
ocargsig′ isa DataType || return CallMeta(Any, Any, Effects(), NoCallInfo())
ocsig = rewrap_unionall(Tuple{Tuple, ocargsig′.parameters...}, ocargsig)
hasintersect(sig, ocsig) || return CallMeta(Union{}, TypeError, EFFECTS_THROWS, NoCallInfo())
hasintersect(sig, ocsig) || return CallMeta(Union{}, Union{MethodError,TypeError}, EFFECTS_THROWS, NoCallInfo())
ocmethod = closure.source::Method
result = abstract_call_method(interp, ocmethod, sig, Core.svec(), false, si, sv)
(; rt, edge, effects, volatile_inf_result) = result
(; rt, exct, edge, effects, volatile_inf_result) = result
match = MethodMatch(sig, Core.svec(), ocmethod, sig <: ocsig)
𝕃ₚ = ipo_lattice(interp)
ₚ = ⊑(𝕃ₚ)
, ⋤, ⊔ = partialorder(𝕃ₚ), strictneqpartialorder(𝕃ₚ), join(𝕃ₚ)
const_result = volatile_inf_result
if !result.edgecycle
const_call_result = abstract_call_method_with_const_args(interp, result,
nothing, arginfo, si, match, sv)
if const_call_result !== nothing
if const_call_result.rt ⊑ rt
if const_call_result.rt ⊑ rt
(; rt, effects, const_result, edge) = const_call_result
end
if const_call_result.exct ⋤ exct
(; exct, const_result, edge) = const_call_result
end
end
end
if check # analyze implicit type asserts on argument and return type
ftt = closure.typ
(aty, rty) = (unwrap_unionall(ftt)::DataType).parameters
rty = rewrap_unionall(rty isa TypeVar ? rty.lb : rty, ftt)
if !(rt ⊑ₚ rty && tuple_tfunc(𝕃ₚ, arginfo.argtypes[2:end]) ⊑ₚ rewrap_unionall(aty, ftt))
rty = (unwrap_unionall(tt)::DataType).parameters[2]
rty = rewrap_unionall(rty isa TypeVar ? rty.ub : rty, tt)
if !(rt ⊑ rty && sig ⊑ ocsig)
effects = Effects(effects; nothrow=false)
exct = exct ⊔ TypeError
end
end
rt = from_interprocedural!(interp, rt, sv, arginfo, match.spec_types)
info = OpaqueClosureCallInfo(match, const_result)
edge !== nothing && add_backedge!(sv, edge)
return CallMeta(rt, Any, effects, info)
return CallMeta(rt, exct, effects, info)
end

function most_general_argtypes(closure::PartialOpaque)
Expand Down
54 changes: 51 additions & 3 deletions test/compiler/inference.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6078,9 +6078,7 @@ gcondvarargs(a, x...) = return fcondvarargs(a, x...) ? isa(a, Int64) : !isa(a, I
@test Core.Compiler.return_type(gcondvarargs, Tuple{Vararg{Any}}) === Bool

# JuliaLang/julia#55627: argtypes check in `abstract_call_opaque_closure`
issue55627_some_method(x) = 2x
issue55627_make_oc() = Base.Experimental.@opaque (x::Int)->issue55627_some_method(x)

issue55627_make_oc() = Base.Experimental.@opaque (x::Int) -> 2x
@test Base.infer_return_type() do
f = issue55627_make_oc()
return f(1), f()
Expand All @@ -6089,3 +6087,53 @@ end == Union{}
f = issue55627_make_oc()
return f(1), f(xs...)
end == Tuple{Int,Int}
@test Base.infer_exception_type() do
f = issue55627_make_oc()
return f(1), f()
end >: MethodError
@test Base.infer_exception_type() do
f = issue55627_make_oc()
return f(1), f('1')
end >: TypeError

# `exct` modeling for opaque closure
oc_exct_1() = Base.Experimental.@opaque (x) -> x < 0 ? throw(x) : x
@test Base.infer_exception_type((Int,)) do x
oc_exct_1()(x)
end == Int
oc_exct_2() = Base.Experimental.@opaque Tuple{Number}->Number (x) -> '1'
@test Base.infer_exception_type((Int,)) do x
oc_exct_2()(x)
end == TypeError

# nothrow modeling for `invoke` calls
f_invoke_nothrow(::Number) = :number
f_invoke_nothrow(::Int) = :int
@test Base.infer_effects((Int,)) do x
@invoke f_invoke_nothrow(x::Number)
end |> Core.Compiler.is_nothrow
@test Base.infer_effects((Char,)) do x
@invoke f_invoke_nothrow(x::Number)
end |> !Core.Compiler.is_nothrow
@test Base.infer_effects((Union{Nothing,Int},)) do x
@invoke f_invoke_nothrow(x::Number)
end |> !Core.Compiler.is_nothrow

# `exct` modeling for `invoke` calls
f_invoke_exct(x::Number) = x < 0 ? throw(x) : x
f_invoke_exct(x::Int) = x
@test Base.infer_exception_type((Int,)) do x
@invoke f_invoke_exct(x::Number)
end == Int
@test Base.infer_exception_type() do
@invoke f_invoke_exct(42::Number)
end == Union{}
@test Base.infer_exception_type((Union{Nothing,Int},)) do x
@invoke f_invoke_exct(x::Number)
end == Union{Int,TypeError}
@test Base.infer_exception_type((Int,)) do x
invoke(f_invoke_exct, Number, x)
end == TypeError
@test Base.infer_exception_type((Char,)) do x
invoke(f_invoke_exct, Tuple{Number}, x)
end == TypeError