Skip to content

Commit

Permalink
Improve apply_type_nothrow precision
Browse files Browse the repository at this point in the history
  • Loading branch information
Keno committed Dec 15, 2020
1 parent fa6077e commit 1731ef9
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 35 deletions.
68 changes: 50 additions & 18 deletions base/compiler/tfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
Expand Down Expand Up @@ -1082,6 +1084,20 @@ function _fieldtype_tfunc(@nospecialize(s), exact::Bool, @nospecialize(name))
end
add_tfunc(fieldtype, 2, 3, fieldtype_tfunc, 0)

# Like `valid_tparam`, but in the type domain.
function valid_tparam_type(T::DataType)
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
Expand All @@ -1101,22 +1117,38 @@ 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
if !(u.var.lb <: ai <: u.var.ub)
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_tparam_type(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
Expand Down
53 changes: 36 additions & 17 deletions test/compiler/inline.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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()...)
Expand All @@ -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)
Expand All @@ -221,23 +233,20 @@ 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
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))
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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})

0 comments on commit 1731ef9

Please sign in to comment.