diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 532798bbd9df8a..1418d953c835b5 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -58,27 +58,28 @@ end add_tfunc(throw, 1, 1, (@nospecialize(x)) -> Bottom, 0) # the inverse of typeof_tfunc -# returns (type, isexact, isconcrete) +# returns (type, isexact, isconcrete, istype) # if isexact is false, the actual runtime type may (will) be a subtype of t # if isconcrete is true, the actual runtime type is definitely concrete (unreachable if not valid as a typeof) +# if istype is true, the actual runtime value will definitely be a type (e.g. this is false for Union{Type{Int}, Int}) function instanceof_tfunc(@nospecialize(t)) if isa(t, Const) if isa(t.val, Type) - return t.val, true, isconcretetype(t.val) + return t.val, true, isconcretetype(t.val), true end - return Bottom, true, false # runtime throws on non-Type + return Bottom, true, false, false # runtime throws on non-Type end t = widenconst(t) if t === Bottom - return Bottom, true, true # runtime unreachable + return Bottom, true, true, false # runtime unreachable elseif t === typeof(Bottom) || typeintersect(t, Type) === Bottom - return Bottom, true, false # literal Bottom or non-Type + return Bottom, true, false, false # literal Bottom or non-Type elseif isType(t) tp = t.parameters[1] - return tp, !has_free_typevars(tp), isconcretetype(tp) + return tp, !has_free_typevars(tp), isconcretetype(tp), true elseif isa(t, UnionAll) t′ = unwrap_unionall(t) - t′′, isexact, isconcrete = instanceof_tfunc(t′) + t′′, isexact, isconcrete, istype = instanceof_tfunc(t′) tr = rewrap_unionall(t′′, t) if t′′ isa DataType && t′′.name !== Tuple.name && !has_free_typevars(tr) # a real instance must be within the declared bounds of the type, @@ -91,19 +92,20 @@ function instanceof_tfunc(@nospecialize(t)) isexact = true end end - return tr, isexact, isconcrete + return tr, isexact, isconcrete, istype elseif isa(t, Union) - ta, isexact_a, isconcrete_a = instanceof_tfunc(t.a) - tb, isexact_b, isconcrete_b = instanceof_tfunc(t.b) + ta, isexact_a, isconcrete_a, istype_a = instanceof_tfunc(t.a) + tb, isexact_b, isconcrete_b, istype_b = instanceof_tfunc(t.b) isconcrete = isconcrete_a && isconcrete_b + istype = istype_a && istype_b # most users already handle the Union case, so here we assume that # `isexact` only cares about the answers where there's actually a Type # (and assuming other cases causing runtime errors) - ta === Union{} && return tb, isexact_b, isconcrete - tb === Union{} && return ta, isexact_a, isconcrete - return Union{ta, tb}, false, isconcrete # at runtime, will be exactly one of these + ta === Union{} && return tb, isexact_b, isconcrete, istype + tb === Union{} && return ta, isexact_a, isconcrete, istype + return Union{ta, tb}, false, isconcrete, istype # at runtime, will be exactly one of these end - return Any, false, false + return Any, false, false, false end bitcast_tfunc(@nospecialize(t), @nospecialize(x)) = instanceof_tfunc(t)[1] math_tfunc(@nospecialize(x)) = widenconst(x) @@ -1082,6 +1084,20 @@ function _fieldtype_tfunc(@nospecialize(s), exact::Bool, @nospecialize(name)) end add_tfunc(fieldtype, 2, 3, fieldtype_tfunc, 0) +function valid_type_param(T) + isvarargtype(T) && return false + T === Symbol && return true + isbitstype(T) && return true + if T <: Tuple + isconcretetype(T) || return false + for P in T.parameters + (P === Symbol || isbitstype(P)) || return false + end + return true + end + return false +end + function apply_type_nothrow(argtypes::Array{Any, 1}, @nospecialize(rt)) rt === Type && return false length(argtypes) >= 1 || return false @@ -1101,14 +1117,14 @@ function apply_type_nothrow(argtypes::Array{Any, 1}, @nospecialize(rt)) for i = 2:length(argtypes) isa(u, UnionAll) || return false ai = widenconditional(argtypes[i]) - if ai ⊑ TypeVar + if ai ⊑ TypeVar || ai === DataType # We don't know anything about the bounds of this typevar, but as # long as the UnionAll is not constrained, that's ok. if !(u.var.lb === Union{} && u.var.ub === Any) return false end - elseif isa(ai, Const) && isa(ai.val, Type) - ai = ai.val + elseif (isa(ai, Const) && isa(ai.val, Type)) || isconstType(ai) + ai = isa(ai, Const) ? ai.val : ai.parameters[1] if has_free_typevars(u.var.lb) || has_free_typevars(u.var.ub) return false end @@ -1116,7 +1132,23 @@ function apply_type_nothrow(argtypes::Array{Any, 1}, @nospecialize(rt)) return false end else - return false + T, exact, _, istype = instanceof_tfunc(ai) + if T === Bottom + if !(u.var.lb === Union{} && u.var.ub === Any) + return false + end + if !valid_type_param(widenconst(ai)) + return false + end + else + istype || return false + if !(T <: u.var.ub) + return false + end + if exact ? !(u.var.lb <: T) : !(u.var.lb === Bottom) + return false + end + end end u = u.body end diff --git a/test/compiler/inline.jl b/test/compiler/inline.jl index e8b4c4f4cc77e4..b52d97e4d098d6 100644 --- a/test/compiler/inline.jl +++ b/test/compiler/inline.jl @@ -150,9 +150,21 @@ end @test !any(x -> x isa Expr && x.head === :invoke, src.code) end +function fully_eliminated(f, args) + let code = code_typed(f, args)[1][1].code + return length(code) == 1 && isa(code[1], ReturnNode) + end +end + +function fully_eliminated(f, args, retval) + let code = code_typed(f, args)[1][1].code + return length(code) == 1 && isa(code[1], ReturnNode) && code[1].val == retval + end +end + # check that type.mutable can be fully eliminated f_mutable_nothrow(s::String) = Val{typeof(s).mutable} -@test length(code_typed(f_mutable_nothrow, (String,))[1][1].code) == 1 +@test fully_eliminated(f_mutable_nothrow, (String,)) # check that ifelse can be fully eliminated function f_ifelse(x) @@ -193,7 +205,7 @@ end function cprop_inline_baz1() return cprop_inline_bar(cprop_inline_foo1()..., cprop_inline_foo1()...) end -@test length(code_typed(cprop_inline_baz1, ())[1][1].code) == 1 +@test fully_eliminated(cprop_inline_baz1, ()) function cprop_inline_baz2() return cprop_inline_bar(cprop_inline_foo2()..., cprop_inline_foo2()...) @@ -205,14 +217,14 @@ function f_apply_typevar(T) NTuple{N, T} where N return T end -@test length(code_typed(f_apply_typevar, (Type{Any},))[1][1].code) == 1 +@test fully_eliminated(f_apply_typevar, (Type{Any},)) # check that div can be fully eliminated function f_div(x) div(x, 1) return x end -@test length(code_typed(f_div, (Int,))[1][1].code) == 1 +@test fully_eliminated(f_div, (Int,)) == 1 # ...unless we div by an unknown amount function f_div(x, y) div(x, y) @@ -221,12 +233,12 @@ end @test length(code_typed(f_div, (Int, Int))[1][1].code) > 1 f_identity_splat(t) = (t...,) -@test length(code_typed(f_identity_splat, (Tuple{Int,Int},))[1][1].code) == 1 +@test fully_eliminated(f_identity_splat, (Tuple{Int,Int},)) # splatting one tuple into (,) plus zero or more empties should reduce # this pattern appears for example in `fill_to_length` f_splat_with_empties(t) = (()..., t..., ()..., ()...) -@test length(code_typed(f_splat_with_empties, (NTuple{200,UInt8},))[1][1].code) == 1 +@test fully_eliminated(f_splat_with_empties, (NTuple{200,UInt8},)) # check that <: can be fully eliminated struct SomeArbitraryStruct; end @@ -234,10 +246,7 @@ function f_subtype() T = SomeArbitraryStruct T <: Bool end -let code = code_typed(f_subtype, Tuple{})[1][1].code - @test length(code) == 1 - @test code[1] == ReturnNode(false) -end +@test fully_eliminated(f_subtype, Tuple{}, false) # check that pointerref gets deleted if unused f_pointerref(T::Type{S}) where S = Val(length(T.parameters)) @@ -261,9 +270,7 @@ function foo_apply_apply_type_svec() B = Tuple{Float32, Float32} Core.apply_type(A..., B.types...) end -let ci = code_typed(foo_apply_apply_type_svec, Tuple{})[1].first - @test length(ci.code) == 1 && ci.code[1] == ReturnNode(NTuple{3, Float32}) -end +@test fully_eliminated(foo_apply_apply_type_svec, Tuple{}, NTuple{3, Float32}) # The that inlining doesn't drop ambiguity errors (#30118) c30118(::Tuple{Ref{<:Type}, Vararg}) = nothing @@ -277,10 +284,7 @@ b30118(x...) = c30118(x) f34900(x::Int, y) = x f34900(x, y::Int) = y f34900(x::Int, y::Int) = invoke(f34900, Tuple{Int, Any}, x, y) -let ci = code_typed(f34900, Tuple{Int, Int})[1].first - @test length(ci.code) == 1 && isa(ci.code[1], ReturnNode) && - ci.code[1].val.n == 2 -end +@test fully_eliminated(f34900, Tuple{Int, Int}, Core.Argument(2)) @testset "check jl_ir_flag_inlineable for inline macro" begin @test ccall(:jl_ir_flag_inlineable, Bool, (Any,), first(methods(@inline x -> x)).source) @@ -331,3 +335,18 @@ struct NonIsBitsDimsUndef end @test Core.Compiler.is_inlineable_constant(NonIsBitsDimsUndef()) @test !Core.Compiler.is_inlineable_constant((("a"^1000, "b"^1000), nothing)) + +# More nothrow modeling for apply_type +f_apply_type_typeof(x) = (Ref{typeof(x)}; nothing) +@test fully_eliminated(f_apply_type_typeof, Tuple{Any}) +@test fully_eliminated(f_apply_type_typeof, Tuple{Vector}) +@test fully_eliminated(x->(Val{x}; nothing), Tuple{Int}) +@test fully_eliminated(x->(Val{x}; nothing), Tuple{Symbol}) +@test fully_eliminated(x->(Val{x}; nothing), Tuple{Tuple{Int, Int}}) +@test !fully_eliminated(x->(Val{x}; nothing), Tuple{String}) +@test !fully_eliminated(x->(Val{x}; nothing), Tuple{Any}) +@test !fully_eliminated(x->(Val{x}; nothing), Tuple{Tuple{Int, String}}) + +struct RealConstrained{T <: Real}; end +@test !fully_eliminated(x->(RealConstrained{x}; nothing), Tuple{Int}) +@test !fully_eliminated(x->(RealConstrained{x}; nothing), Tuple{Type{Vector{T}} where T})