Skip to content

Commit

Permalink
effects: Allow consistency of :new with slightly imprecise type (#48267)
Browse files Browse the repository at this point in the history
For our consistency check, all we need to prove is that the type
we're constructing is not mutable and does not contain uinitialized
data. This is possible as long as we know what the ultimate DataType
is going to be, but we do not need all of the parameters.
  • Loading branch information
Keno authored Jan 16, 2023
1 parent 687433b commit 388864a
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 60 deletions.
87 changes: 45 additions & 42 deletions base/compiler/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2291,33 +2291,14 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp
t = rt
elseif ehead === :new
t, isexact = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv))
nothrow = true
if isconcretedispatch(t)
ismutable = ismutabletype(t)
fcount = fieldcount(t)
ut = unwrap_unionall(t)
consistent = ALWAYS_FALSE
nothrow = false
if isa(ut, DataType) && !isabstracttype(ut)
ismutable = ismutabletype(ut)
fcount = datatype_fieldcount(ut)
nargs = length(e.args) - 1
@assert fcount nargs "malformed :new expression" # syntactically enforced by the front-end
ats = Vector{Any}(undef, nargs)
local anyrefine = false
local allconst = true
for i = 1:nargs
at = widenslotwrapper(abstract_eval_value(interp, e.args[i+1], vtypes, sv))
ft = fieldtype(t, i)
nothrow && (nothrow = at ᵢ ft)
at = tmeet(𝕃ᵢ, at, ft)
at === Bottom && @goto always_throw
if ismutable && !isconst(t, i)
ats[i] = ft # can't constrain this field (as it may be modified later)
continue
end
allconst &= isa(at, Const)
if !anyrefine
anyrefine = has_nontrivial_extended_info(𝕃ᵢ, at) || # extended lattice information
(𝕃ᵢ, at, ft) # just a type-level information, but more precise than the declared type
end
ats[i] = at
end
if fcount > nargs && any(i::Int -> !is_undefref_fieldtype(fieldtype(t, i)), (nargs+1):fcount)
if fcount === nothing || (fcount > nargs && any(i::Int -> !is_undefref_fieldtype(fieldtype(t, i)), (nargs+1):fcount))
# allocation with undefined field leads to undefined behavior and should taint `:consistent`-cy
consistent = ALWAYS_FALSE
elseif ismutable
Expand All @@ -2327,25 +2308,47 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp
else
consistent = ALWAYS_TRUE
end
# For now, don't allow:
# - Const/PartialStruct of mutables (but still allow PartialStruct of mutables
# with `const` fields if anything refined)
# - partially initialized Const/PartialStruct
if fcount == nargs
if consistent === ALWAYS_TRUE && allconst
argvals = Vector{Any}(undef, nargs)
for j in 1:nargs
argvals[j] = (ats[j]::Const).val
if isconcretedispatch(t)
nothrow = true
@assert fcount !== nothing && fcount nargs "malformed :new expression" # syntactically enforced by the front-end
ats = Vector{Any}(undef, nargs)
local anyrefine = false
local allconst = true
for i = 1:nargs
at = widenslotwrapper(abstract_eval_value(interp, e.args[i+1], vtypes, sv))
ft = fieldtype(t, i)
nothrow && (nothrow = at ᵢ ft)
at = tmeet(𝕃ᵢ, at, ft)
at === Bottom && @goto always_throw
if ismutable && !isconst(t, i)
ats[i] = ft # can't constrain this field (as it may be modified later)
continue
end
allconst &= isa(at, Const)
if !anyrefine
anyrefine = has_nontrivial_extended_info(𝕃ᵢ, at) || # extended lattice information
(𝕃ᵢ, at, ft) # just a type-level information, but more precise than the declared type
end
t = Const(ccall(:jl_new_structv, Any, (Any, Ptr{Cvoid}, UInt32), t, argvals, nargs))
elseif anyrefine
t = PartialStruct(t, ats)
ats[i] = at
end
# For now, don't allow:
# - Const/PartialStruct of mutables (but still allow PartialStruct of mutables
# with `const` fields if anything refined)
# - partially initialized Const/PartialStruct
if fcount == nargs
if consistent === ALWAYS_TRUE && allconst
argvals = Vector{Any}(undef, nargs)
for j in 1:nargs
argvals[j] = (ats[j]::Const).val
end
t = Const(ccall(:jl_new_structv, Any, (Any, Ptr{Cvoid}, UInt32), t, argvals, nargs))
elseif anyrefine
t = PartialStruct(t, ats)
end
end
else
t = refine_partial_type(t)
end
else
consistent = ALWAYS_FALSE
nothrow = false
t = refine_partial_type(t)
end
effects = Effects(EFFECTS_TOTAL; consistent, nothrow)
elseif ehead === :splatnew
Expand Down
4 changes: 3 additions & 1 deletion base/compiler/optimize.jl
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,9 @@ function stmt_effect_flags(𝕃ₒ::AbstractLattice, @nospecialize(stmt), @nospe
isconcretedispatch(typ) || return (false, false, false)
end
typ = typ::DataType
fieldcount(typ) >= length(args) - 1 || return (false, false, false)
fcount = datatype_fieldcount(typ)
fcount === nothing && return (false, false, false)
fcount >= length(args) - 1 || return (false, false, false)
for fld_idx in 1:(length(args) - 1)
eT = argextype(args[fld_idx + 1], src)
fT = fieldtype(typ, fld_idx)
Expand Down
39 changes: 22 additions & 17 deletions base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,25 @@ function argument_datatype(@nospecialize t)
return ccall(:jl_argument_datatype, Any, (Any,), t)::Union{Nothing,DataType}
end

function datatype_fieldcount(t::DataType)
if t.name === _NAMEDTUPLE_NAME
names, types = t.parameters[1], t.parameters[2]
if names isa Tuple
return length(names)
end
if types isa DataType && types <: Tuple
return fieldcount(types)
end
return nothing
elseif isabstracttype(t) || (t.name === Tuple.name && isvatuple(t))
return nothing
end
if isdefined(t, :types)
return length(t.types)
end
return length(t.name.names)
end

"""
fieldcount(t::Type)
Expand All @@ -857,25 +876,11 @@ function fieldcount(@nospecialize t)
if !(t isa DataType)
throw(TypeError(:fieldcount, DataType, t))
end
if t.name === _NAMEDTUPLE_NAME
names, types = t.parameters[1], t.parameters[2]
if names isa Tuple
return length(names)
end
if types isa DataType && types <: Tuple
return fieldcount(types)
end
abstr = true
else
abstr = isabstracttype(t) || (t.name === Tuple.name && isvatuple(t))
end
if abstr
fcount = datatype_fieldcount(t)
if fcount === nothing
throw(ArgumentError("type does not have a definite number of fields"))
end
if isdefined(t, :types)
return length(t.types)
end
return length(t.name.names)
return fcount
end

"""
Expand Down
8 changes: 8 additions & 0 deletions test/compiler/effects.jl
Original file line number Diff line number Diff line change
Expand Up @@ -731,3 +731,11 @@ end |> Core.Compiler.is_total
@test Base.infer_effects(Tuple{Int64}) do i
@inbounds (1,2,3)[i]
end |> !Core.Compiler.is_consistent

# Test that :new of non-concrete, but otherwise known type
# does not taint consistency.
@eval struct ImmutRef{T}
x::T
ImmutRef(x) = $(Expr(:new, :(ImmutRef{typeof(x)}), :x))
end
@test Core.Compiler.is_foldable(Base.infer_effects(ImmutRef, Tuple{Any}))

0 comments on commit 388864a

Please sign in to comment.