From 7ce90a399669976899bf4a940864a54bc9ec8ac9 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Tue, 3 Sep 2024 16:25:33 +0900 Subject: [PATCH 1/4] fix `exct` for mismatched opaque closure call --- base/compiler/abstractinterpretation.jl | 2 +- test/compiler/inference.jl | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index f2d4327668137..5141a0e3c19b6 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2339,7 +2339,7 @@ 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 diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 7e1fea54830c9..d96e6351aa437 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -6089,3 +6089,11 @@ 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 From 8f6a3ef4cb65692b0e6e0d07c9c909d1428187da Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Tue, 3 Sep 2024 16:32:25 +0900 Subject: [PATCH 2/4] improve `exct` modeling for opaque closure calls --- base/compiler/abstractinterpretation.jl | 23 +++++++++++++---------- test/compiler/inference.jl | 12 ++++++++++++ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 5141a0e3c19b6..5d3208d40ece3 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -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) @@ -97,7 +97,7 @@ 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) + if const_call_result.exct β‹€ exct exct = const_call_result.exct (; const_result, edge) = const_call_result else @@ -154,7 +154,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 @@ -2342,10 +2342,10 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter, 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(π•ƒβ‚š) const_result = volatile_inf_result if !result.edgecycle const_call_result = abstract_call_method_with_const_args(interp, result, @@ -2354,20 +2354,23 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter, 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 = tmerge(π•ƒβ‚š, 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) diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index d96e6351aa437..abd9b7b4e4d1b 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -6097,3 +6097,15 @@ end >: MethodError f = issue55627_make_oc() return f(1), f('1') end >: TypeError + +# `exct` modeling for opaque closure +oc_exct_1() = Base.Experimental.@opaque function (x) + return x < 0 ? throw(x) : x + end +@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 From f87d16414dc5cdfd14224322ccd354bb05d41954 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Tue, 3 Sep 2024 17:00:21 +0900 Subject: [PATCH 3/4] fix `nothrow` modeling for `invoke` calls --- base/compiler/abstractinterpretation.jl | 3 +++ test/compiler/inference.jl | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 5d3208d40ece3..4136ce02f6b2e 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2181,6 +2181,9 @@ function abstract_invoke(interp::AbstractInterpreter, arginfo::ArgInfo, si::Stmt 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) + end return CallMeta(rt, Any, effects, info) end diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index abd9b7b4e4d1b..a282a8911ce23 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -6109,3 +6109,13 @@ 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((Union{Nothing,Int},)) do x + @invoke f_invoke_nothrow(x::Number) +end |> !Core.Compiler.is_nothrow From 94829611fe2fa6779aff90c54ef06f13def8bc14 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Tue, 3 Sep 2024 17:06:21 +0900 Subject: [PATCH 4/4] improve `exct` modeling for `invoke` calls --- base/compiler/abstractinterpretation.jl | 37 ++++++++++++++----------- test/compiler/inference.jl | 30 ++++++++++++++++---- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 4136ce02f6b2e..8623a32ddbb2b 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}) @@ -2205,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 @@ -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 a282a8911ce23..f15df49d75745 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