Skip to content

Commit

Permalink
inference: improve :nothrow modeling for :static_parameter (#46820)
Browse files Browse the repository at this point in the history
  • Loading branch information
Keno committed Feb 7, 2023
1 parent 367b76f commit 0da756b
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 65 deletions.
13 changes: 8 additions & 5 deletions base/compiler/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2189,12 +2189,16 @@ function abstract_eval_value_expr(interp::AbstractInterpreter, e::Expr, vtypes::
head = e.head
if head === :static_parameter
n = e.args[1]::Int
nothrow = false
if 1 <= n <= length(sv.sptypes)
rt = sv.sptypes[n]
if is_maybeundefsp(rt)
rt = unwrap_maybeundefsp(rt)
else
nothrow = true
end
end
if !isa(rt, Const)
merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; nothrow=false))
end
merge_effects!(interp, sv, Effects(EFFECTS_TOTAL; nothrow))
return rt
elseif head === :boundscheck
if isa(sv, InferenceState)
Expand Down Expand Up @@ -2456,8 +2460,7 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp
elseif isexpr(sym, :static_parameter)
n = sym.args[1]::Int
if 1 <= n <= length(sv.sptypes)
spty = sv.sptypes[n]
if isa(spty, Const)
if !is_maybeundefsp(sv.sptypes, n)
t = Const(true)
end
end
Expand Down
94 changes: 86 additions & 8 deletions base/compiler/inferencestate.jl
Original file line number Diff line number Diff line change
Expand Up @@ -348,23 +348,100 @@ function InferenceState(result::InferenceResult, cache::Symbol, interp::Abstract
return InferenceState(result, src, cache, interp)
end

"""
constrains_param(var::TypeVar, sig, covariant::Bool)
Check if `var` will be constrained to have a definite value
in any concrete leaftype subtype of `sig`.
It is used as a helper to determine whether type intersection is guaranteed to be able to
find a value for a particular type parameter.
A necessary condition for type intersection to not assign a parameter is that it only
appears in a `Union[All]` and during subtyping some other union component (that does not
constrain the type parameter) is selected.
"""
function constrains_param(var::TypeVar, @nospecialize(typ), covariant::Bool)
typ === var && return true
while typ isa UnionAll
covariant && constrains_param(var, typ.var.ub, covariant) && return true
# typ.var.lb doesn't constrain var
typ = typ.body
end
if typ isa Union
# for unions, verify that both options would constrain var
ba = constrains_param(var, typ.a, covariant)
bb = constrains_param(var, typ.b, covariant)
(ba && bb) && return true
elseif typ isa DataType
# return true if any param constrains var
fc = length(typ.parameters)
if fc > 0
if typ.name === Tuple.name
# vararg tuple needs special handling
for i in 1:(fc - 1)
p = typ.parameters[i]
constrains_param(var, p, covariant) && return true
end
lastp = typ.parameters[fc]
vararg = unwrap_unionall(lastp)
if vararg isa Core.TypeofVararg && isdefined(vararg, :N)
constrains_param(var, vararg.N, covariant) && return true
# T = vararg.parameters[1] doesn't constrain var
else
constrains_param(var, lastp, covariant) && return true
end
else
for i in 1:fc
p = typ.parameters[i]
constrains_param(var, p, false) && return true
end
end
end
end
return false
end

"""
MaybeUndefSP(typ)
is_maybeundefsp(typ) -> Bool
unwrap_maybeundefsp(typ) -> Any
A special wrapper that represents a static parameter that could be undefined at runtime.
This does not participate in the native type system nor the inference lattice,
and it thus should be always unwrapped when performing any type or lattice operations on it.
"""
struct MaybeUndefSP
typ
MaybeUndefSP(@nospecialize typ) = new(typ)
end
is_maybeundefsp(@nospecialize typ) = isa(typ, MaybeUndefSP)
unwrap_maybeundefsp(@nospecialize typ) = isa(typ, MaybeUndefSP) ? typ.typ : typ
is_maybeundefsp(sptypes::Vector{Any}, idx::Int) = is_maybeundefsp(sptypes[idx])
unwrap_maybeundefsp(sptypes::Vector{Any}, idx::Int) = unwrap_maybeundefsp(sptypes[idx])

const EMPTY_SPTYPES = Any[]

function sptypes_from_meth_instance(linfo::MethodInstance)
toplevel = !isa(linfo.def, Method)
if !toplevel && isempty(linfo.sparam_vals) && isa(linfo.def.sig, UnionAll)
def = linfo.def
isa(def, Method) || return EMPTY_SPTYPES # toplevel
sig = def.sig
if isempty(linfo.sparam_vals)
isa(sig, UnionAll) || return EMPTY_SPTYPES
# linfo is unspecialized
sp = Any[]
sig = linfo.def.sig
while isa(sig, UnionAll)
push!(sp, sig.var)
sig = sig.body
sig = sig
while isa(sig, UnionAll)
push!(sp, sig.var)
sig = sig.body
end
else
sp = collect(Any, linfo.sparam_vals)
end
for i = 1:length(sp)
v = sp[i]
if v isa TypeVar
temp = linfo.def.sig
maybe_undef = !constrains_param(v, linfo.specTypes, true)
temp = sig
for j = 1:i-1
temp = temp.body
end
Expand Down Expand Up @@ -402,12 +479,13 @@ function sptypes_from_meth_instance(linfo::MethodInstance)
tv = TypeVar(v.name, lb, ub)
ty = UnionAll(tv, Type{tv})
end
@label ty_computed
maybe_undef && (ty = MaybeUndefSP(ty))
elseif isvarargtype(v)
ty = Int
else
ty = Const(v)
end
@label ty_computed
sp[i] = ty
end
return sp
Expand Down
6 changes: 3 additions & 3 deletions base/compiler/optimize.jl
Original file line number Diff line number Diff line change
Expand Up @@ -267,9 +267,9 @@ function stmt_effect_flags(𝕃ₒ::AbstractLattice, @nospecialize(stmt), @nospe
if isa(stmt, Expr)
(; head, args) = stmt
if head === :static_parameter
etyp = (isa(src, IRCode) ? src.sptypes : src.ir.sptypes)[args[1]::Int]
# if we aren't certain enough about the type, it might be an UndefVarError at runtime
nothrow = isa(etyp, Const)
sptypes = isa(src, IRCode) ? src.sptypes : src.ir.sptypes
nothrow = !is_maybeundefsp(sptypes, args[1]::Int)
return (true, nothrow, nothrow)
end
if head === :call
Expand Down Expand Up @@ -377,7 +377,7 @@ function argextype(
sptypes::Vector{Any}, slottypes::Vector{Any})
if isa(x, Expr)
if x.head === :static_parameter
return sptypes[x.args[1]::Int]
return unwrap_maybeundefsp(sptypes, x.args[1]::Int)
elseif x.head === :boundscheck
return Bool
elseif x.head === :copyast
Expand Down
2 changes: 1 addition & 1 deletion base/compiler/ssair/slot2ssa.jl
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ end
function typ_for_val(@nospecialize(x), ci::CodeInfo, sptypes::Vector{Any}, idx::Int, slottypes::Vector{Any})
if isa(x, Expr)
if x.head === :static_parameter
return sptypes[x.args[1]::Int]
return unwrap_maybeundefsp(sptypes, x.args[1]::Int)
elseif x.head === :boundscheck
return Bool
elseif x.head === :copyast
Expand Down
2 changes: 1 addition & 1 deletion base/compiler/ssair/verify.jl
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ function verify_ir(ir::IRCode, print::Bool=true,
elseif stmt.head === :foreigncall
isforeigncall = true
elseif stmt.head === :isdefined && length(stmt.args) == 1 &&
(stmt.args[1] isa GlobalRef || (stmt.args[1] isa Expr && stmt.args[1].head === :static_parameter))
(stmt.args[1] isa GlobalRef || isexpr(stmt.args[1], :static_parameter))
# a GlobalRef or static_parameter isdefined check does not evaluate its argument
continue
elseif stmt.head === :call
Expand Down
45 changes: 1 addition & 44 deletions stdlib/Test/src/Test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1970,54 +1970,11 @@ function detect_unbound_args(mods...;
return collect(ambs)
end

# find if var will be constrained to have a definite value
# in any concrete leaftype subtype of typ
function constrains_param(var::TypeVar, @nospecialize(typ), covariant::Bool)
typ === var && return true
while typ isa UnionAll
covariant && constrains_param(var, typ.var.ub, covariant) && return true
# typ.var.lb doesn't constrain var
typ = typ.body
end
if typ isa Union
# for unions, verify that both options would constrain var
ba = constrains_param(var, typ.a, covariant)
bb = constrains_param(var, typ.b, covariant)
(ba && bb) && return true
elseif typ isa DataType
# return true if any param constrains var
fc = length(typ.parameters)
if fc > 0
if typ.name === Tuple.name
# vararg tuple needs special handling
for i in 1:(fc - 1)
p = typ.parameters[i]
constrains_param(var, p, covariant) && return true
end
lastp = typ.parameters[fc]
vararg = Base.unwrap_unionall(lastp)
if vararg isa Core.TypeofVararg && isdefined(vararg, :N)
constrains_param(var, vararg.N, covariant) && return true
# T = vararg.parameters[1] doesn't constrain var
else
constrains_param(var, lastp, covariant) && return true
end
else
for i in 1:fc
p = typ.parameters[i]
constrains_param(var, p, false) && return true
end
end
end
end
return false
end

function has_unbound_vars(@nospecialize sig)
while sig isa UnionAll
var = sig.var
sig = sig.body
if !constrains_param(var, sig, true)
if !Core.Compiler.constrains_param(var, sig, true)
return true
end
end
Expand Down
11 changes: 8 additions & 3 deletions test/compiler/effects.jl
Original file line number Diff line number Diff line change
Expand Up @@ -748,8 +748,13 @@ end
# unknown :static_parameter should taint :nothrow
# https://github.com/JuliaLang/julia/issues/46771
unknown_sparam_throw(::Union{Nothing, Type{T}}) where T = (T; nothing)
unknown_sparam_nothrow1(x::Ref{T}) where T = (T; nothing)
unknown_sparam_nothrow2(x::Ref{Ref{T}}) where T = (T; nothing)
@test Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Type{Int},)))
@test Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Type{<:Integer},)))
@test Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Type,)))
@test !Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Nothing,)))

unknown_sparam_nothrow(x::Ref{T}) where {T} = (T; nothing)
@test_broken Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_nothrow, (Ref,)))
@test !Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Union{Type{Int},Nothing},)))
@test !Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_throw, (Any,)))
@test Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_nothrow1, (Ref,)))
@test Core.Compiler.is_nothrow(Base.infer_effects(unknown_sparam_nothrow2, (Ref{Ref{T}} where T,)))
13 changes: 13 additions & 0 deletions test/compiler/inference.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4735,3 +4735,16 @@ g_no_bail_effects_any(x::Any) = f_no_bail_effects_any(x)

# issue #48374
@test (() -> Union{<:Nothing})() == Nothing

# :static_parameter accuracy
unknown_sparam_throw(::Union{Nothing, Type{T}}) where T = @isdefined(T) ? T::Type : nothing
unknown_sparam_nothrow1(x::Ref{T}) where T = @isdefined(T) ? T::Type : nothing
unknown_sparam_nothrow2(x::Ref{Ref{T}}) where T = @isdefined(T) ? T::Type : nothing
@test only(Base.return_types(unknown_sparam_throw, (Type{Int},))) == Type{Int}
@test only(Base.return_types(unknown_sparam_throw, (Type{<:Integer},))) == Type{<:Integer}
@test only(Base.return_types(unknown_sparam_throw, (Type,))) == Type
@test_broken only(Base.return_types(unknown_sparam_throw, (Nothing,))) === Nothing
@test_broken only(Base.return_types(unknown_sparam_throw, (Union{Type{Int},Nothing},))) === Union{Nothing,Type{Int}}
@test only(Base.return_types(unknown_sparam_throw, (Any,))) === Union{Nothing,Type}
@test only(Base.return_types(unknown_sparam_nothrow1, (Ref,))) === Type
@test only(Base.return_types(unknown_sparam_nothrow2, (Ref{Ref{T}} where T,))) === Type

0 comments on commit 0da756b

Please sign in to comment.