Skip to content

Commit

Permalink
inference: fixes and improvements for exct modeling of invoke/OC ca…
Browse files Browse the repository at this point in the history
…lls (#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
  • Loading branch information
aviatesk authored Sep 4, 2024
2 parents 2f0607f + 9482961 commit 62084b7
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 26 deletions.
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

0 comments on commit 62084b7

Please sign in to comment.