Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve apply_type_nothrow precision #38071

Merged
merged 1 commit into from
Dec 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not true since Vararg isa DataType.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:(

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JeffBezanson and I talked about fixing this by making Vararg not a DataType, so we'll wait with this PR until @JeffBezanson gets to that change.

# 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})