From 058af8f694f0bf55836b8ba7158cb60c5bb36937 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Tue, 18 Apr 2023 22:34:14 +0000 Subject: [PATCH 1/2] Fix inference of one-arg `return_type` method `Core.Compiler.return_type` has two methods: - return_type(f, args::Type{<:Tuple}) - return_type(args::Type{<:Tuple}) Our inference code was only catching the first one. Expand it to support both. --- base/compiler/tfuncs.jl | 114 ++++++++++++++++++++----------------- test/compiler/inference.jl | 6 ++ 2 files changed, 69 insertions(+), 51 deletions(-) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index cb75a8e769712..cba74a3e658ca 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -2608,60 +2608,72 @@ end # since abstract_call_gf_by_type is a very inaccurate model of _method and of typeinf_type, # while this assumes that it is an absolutely precise and accurate and exact model of both function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, si::StmtInfo, sv::AbsIntState) + UNKNOWN = CallMeta(Type, EFFECTS_THROWS, NoCallInfo()) + if !(2 <= length(argtypes) <= 3) + return UNKNOWN + end + + tt = widenslotwrapper(argtypes[end]) + if !isa(tt, Const) && !(isType(tt) && !has_free_typevars(tt)) + return UNKNOWN + end + + af_argtype = isa(tt, Const) ? tt.val : (tt::DataType).parameters[1] + if !isa(af_argtype, DataType) || !(af_argtype <: Tuple) + return UNKNOWN + end + if length(argtypes) == 3 - tt = widenslotwrapper(argtypes[3]) - if isa(tt, Const) || (isType(tt) && !has_free_typevars(tt)) - aft = widenslotwrapper(argtypes[2]) - if isa(aft, Const) || (isType(aft) && !has_free_typevars(aft)) || - (isconcretetype(aft) && !(aft <: Builtin)) - af_argtype = isa(tt, Const) ? tt.val : (tt::DataType).parameters[1] - if isa(af_argtype, DataType) && af_argtype <: Tuple - argtypes_vec = Any[aft, af_argtype.parameters...] - if contains_is(argtypes_vec, Union{}) - return CallMeta(Const(Union{}), EFFECTS_TOTAL, NoCallInfo()) - end - # - # Run the abstract_call without restricting abstract call - # sites. Otherwise, our behavior model of abstract_call - # below will be wrong. - if isa(sv, InferenceState) - old_restrict = sv.restrict_abstract_call_sites - sv.restrict_abstract_call_sites = false - call = abstract_call(interp, ArgInfo(nothing, argtypes_vec), si, sv, -1) - sv.restrict_abstract_call_sites = old_restrict - else - call = abstract_call(interp, ArgInfo(nothing, argtypes_vec), si, sv, -1) - end - info = verbose_stmt_info(interp) ? MethodResultPure(ReturnTypeCallInfo(call.info)) : MethodResultPure() - rt = widenslotwrapper(call.rt) - if isa(rt, Const) - # output was computed to be constant - return CallMeta(Const(typeof(rt.val)), EFFECTS_TOTAL, info) - end - rt = widenconst(rt) - if rt === Bottom || (isconcretetype(rt) && !iskindtype(rt)) - # output cannot be improved so it is known for certain - return CallMeta(Const(rt), EFFECTS_TOTAL, info) - elseif isa(sv, InferenceState) && !isempty(sv.pclimitations) - # conservatively express uncertainty of this result - # in two ways: both as being a subtype of this, and - # because of LimitedAccuracy causes - return CallMeta(Type{<:rt}, EFFECTS_TOTAL, info) - elseif (isa(tt, Const) || isconstType(tt)) && - (isa(aft, Const) || isconstType(aft)) - # input arguments were known for certain - # XXX: this doesn't imply we know anything about rt - return CallMeta(Const(rt), EFFECTS_TOTAL, info) - elseif isType(rt) - return CallMeta(Type{rt}, EFFECTS_TOTAL, info) - else - return CallMeta(Type{<:rt}, EFFECTS_TOTAL, info) - end - end - end + aft = widenslotwrapper(argtypes[2]) + if !isa(aft, Const) && !(isType(aft) && !has_free_typevars(aft)) && + !(isconcretetype(aft) && !(aft <: Builtin)) + return UNKNOWN end + argtypes_vec = Any[aft, af_argtype.parameters...] + else + argtypes_vec = Any[af_argtype.parameters...] + end + + if contains_is(argtypes_vec, Union{}) + return CallMeta(Const(Union{}), EFFECTS_TOTAL, NoCallInfo()) + end + + # Run the abstract_call without restricting abstract call + # sites. Otherwise, our behavior model of abstract_call + # below will be wrong. + if isa(sv, InferenceState) + old_restrict = sv.restrict_abstract_call_sites + sv.restrict_abstract_call_sites = false + call = abstract_call(interp, ArgInfo(nothing, argtypes_vec), si, sv, -1) + sv.restrict_abstract_call_sites = old_restrict + else + call = abstract_call(interp, ArgInfo(nothing, argtypes_vec), si, sv, -1) + end + info = verbose_stmt_info(interp) ? MethodResultPure(ReturnTypeCallInfo(call.info)) : MethodResultPure() + rt = widenslotwrapper(call.rt) + if isa(rt, Const) + # output was computed to be constant + return CallMeta(Const(typeof(rt.val)), EFFECTS_TOTAL, info) + end + rt = widenconst(rt) + if rt === Bottom || (isconcretetype(rt) && !iskindtype(rt)) + # output cannot be improved so it is known for certain + return CallMeta(Const(rt), EFFECTS_TOTAL, info) + elseif isa(sv, InferenceState) && !isempty(sv.pclimitations) + # conservatively express uncertainty of this result + # in two ways: both as being a subtype of this, and + # because of LimitedAccuracy causes + return CallMeta(Type{<:rt}, EFFECTS_TOTAL, info) + elseif (isa(tt, Const) || isconstType(tt)) && + (isa(aft, Const) || isconstType(aft)) + # input arguments were known for certain + # XXX: this doesn't imply we know anything about rt + return CallMeta(Const(rt), EFFECTS_TOTAL, info) + elseif isType(rt) + return CallMeta(Type{rt}, EFFECTS_TOTAL, info) + else + return CallMeta(Type{<:rt}, EFFECTS_TOTAL, info) end - return CallMeta(Type, EFFECTS_THROWS, NoCallInfo()) end # a simplified model of abstract_call_gf_by_type for applicable diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 1ee077f6ca3ef..22d76e6977344 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -4854,3 +4854,9 @@ end |> only === Tuple{Int,Symbol} return T end end) == Type{Nothing} + +# Test that Core.Compiler.return_type inference works for the 1-arg version +function cc_rt_one_arg() + Core.Compiler.return_type(Tuple{typeof(+), Int, Int}) +end +@test Core.Compiler.return_type(Tuple{typeof(cc_rt_one_arg)}) == Type{Int} From c196de52c9688e18000112bec8cc96a38b42d2d3 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Thu, 20 Apr 2023 11:26:22 +0900 Subject: [PATCH 2/2] Update test/compiler/inference.jl --- test/compiler/inference.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 22d76e6977344..8e23ca2760241 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -4856,7 +4856,6 @@ end |> only === Tuple{Int,Symbol} end) == Type{Nothing} # Test that Core.Compiler.return_type inference works for the 1-arg version -function cc_rt_one_arg() +@test Base.return_types() do Core.Compiler.return_type(Tuple{typeof(+), Int, Int}) -end -@test Core.Compiler.return_type(Tuple{typeof(cc_rt_one_arg)}) == Type{Int} +end |> only == Type{Int}