From 7b2d5d966fb9f3081e0ca6b2259c086bf84bfab4 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Tue, 3 Sep 2024 17:06:21 +0900 Subject: [PATCH] improve `exct` modeling for `invoke` calls --- base/compiler/abstractinterpretation.jl | 31 ++++++++++++++----------- test/compiler/inference.jl | 30 +++++++++++++++++++----- 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 4136ce02f6b2e0..6196089c6cffc8 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -98,8 +98,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), add_remark!(interp, sv, "[constprop] Discarded because the result was wider than inference") end if const_call_result.exct β‹€ exct - exct = const_call_result.exct - (; const_result, edge) = const_call_result + (; exct, const_result, edge) = const_call_result else add_remark!(interp, sv, "[constprop] Discarded exception type because result was wider than inference") end @@ -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 @@ -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 @@ -2168,23 +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) if !match.fully_covers effects = Effects(effects; nothrow=false) + exct = exct βŠ” TypeError end - return CallMeta(rt, Any, effects, info) + return CallMeta(rt, exct, effects, info) end function invoke_rewrite(xs::Vector{Any}) @@ -2348,16 +2353,16 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter, (; rt, exct, edge, effects, volatile_inf_result) = result match = MethodMatch(sig, Core.svec(), ocmethod, sig <: ocsig) π•ƒβ‚š = ipo_lattice(interp) - βŠ‘β‚š, β‹€β‚š = partialorder(π•ƒβ‚š), strictneqpartialorder(π•ƒβ‚š) + βŠ‘, β‹€, βŠ” = 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 + if const_call_result.exct β‹€ exct (; exct, const_result, edge) = const_call_result end end @@ -2365,9 +2370,9 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter, if check # analyze implicit type asserts on argument and return type rty = (unwrap_unionall(tt)::DataType).parameters[2] rty = rewrap_unionall(rty isa TypeVar ? rty.ub : rty, tt) - if !(rt βŠ‘β‚š rty && sig βŠ‘β‚š ocsig) + if !(rt βŠ‘ rty && sig βŠ‘ ocsig) effects = Effects(effects; nothrow=false) - exct = tmerge(π•ƒβ‚š, exct, TypeError) + exct = exct βŠ” TypeError end end rt = from_interprocedural!(interp, rt, sv, arginfo, match.spec_types) diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index a282a8911ce233..f15df49d757455 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -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() @@ -6099,9 +6097,7 @@ end >: MethodError end >: TypeError # `exct` modeling for opaque closure -oc_exct_1() = Base.Experimental.@opaque function (x) - return x < 0 ? throw(x) : x - end +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 @@ -6116,6 +6112,28 @@ 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