Skip to content
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
18 changes: 6 additions & 12 deletions base/compiler/ssair/passes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,11 @@ struct SSADefUse
end
SSADefUse() = SSADefUse(Int[], Int[], Int[])

function try_compute_fieldidx(@nospecialize(typ), @nospecialize(use_expr))
function try_compute_fieldidx_expr(@nospecialize(typ), @nospecialize(use_expr))
field = use_expr.args[3]
isa(field, QuoteNode) && (field = field.value)
isa(field, Union{Int, Symbol}) || return nothing
if isa(field, Symbol)
field = fieldindex(typ, field, false)
field == 0 && return nothing
elseif isa(field, Integer)
(1 <= field <= fieldcount(typ)) || return nothing
end
return field
return try_compute_fieldidx(typ, field)
end

function lift_defuse(cfg::CFG, ssa::SSADefUse)
Expand Down Expand Up @@ -280,15 +274,15 @@ function getfield_elim_pass!(ir::IRCode, domtree::DomTree)
union!(mid, intermediaries)
continue
end
field = try_compute_fieldidx(typ, stmt)
field = try_compute_fieldidx_expr(typ, stmt)
field === nothing && continue
forwarded = def.args[1+field]
else
obj = compact_exprtype(compact, def)
isa(obj, Const) || continue
obj = obj.val
isimmutable(obj) || continue
field = try_compute_fieldidx(typeof(obj), stmt)
field = try_compute_fieldidx_expr(typeof(obj), stmt)
field === nothing && continue
isdefined(obj, field) || continue
val = getfield(obj, field)
Expand Down Expand Up @@ -330,13 +324,13 @@ function getfield_elim_pass!(ir::IRCode, domtree::DomTree)
fielddefuse = SSADefUse[SSADefUse() for _ = 1:fieldcount(typ)]
ok = true
for use in defuse.uses
field = try_compute_fieldidx(typ, ir[SSAValue(use)])
field = try_compute_fieldidx_expr(typ, ir[SSAValue(use)])
field === nothing && (ok = false; break)
push!(fielddefuse[field].uses, use)
end
ok || continue
for use in defuse.defs
field = try_compute_fieldidx(typ, ir[SSAValue(use)])
field = try_compute_fieldidx_expr(typ, ir[SSAValue(use)])
field === nothing && (ok = false; break)
push!(fielddefuse[field].defs, use)
end
Expand Down
52 changes: 51 additions & 1 deletion base/compiler/ssair/queries.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,57 @@
"""
Determine whether a statement is side-effect-free, i.e. may be removed if it has no uses.
"""
function stmt_effect_free(@nospecialize(stmt), src, mod::Module)
isa(stmt, Union{PiNode, PhiNode}) && return true
isa(stmt, Union{ReturnNode, GotoNode, GotoIfNot}) && return false
return effect_free(stmt, src, mod, true)
isa(stmt, GlobalRef) && return isdefined(stmt.mod, stmt.name)
(isa(stmt, Symbol) || isa(stmt, SSAValue) || isa(stmt, Argument)) && return true
isa(stmt, Slot) && return false # Slots shouldn't occur in the IR at this point, but let's be defensive here
if isa(stmt, Expr)
e = stmt::Expr
head = e.head
is_meta_expr_head(head) && return true
if head === :static_parameter
# if we aren't certain enough about the type, it might be an UndefVarError at runtime
return isa(e.typ, Const) || issingletontype(widenconst(e.typ))
end
(e.typ === Bottom) && return false
ea = e.args
if head === :call
f = exprtype(ea[1], src, mod)
if isa(f, Const)
f = f.val
elseif isType(f)
f = f.parameters[1]
else
return false
end
f === return_type && return true
# TODO: This needs significant refinement
contains_is(_PURE_BUILTINS, f) || return false
return builtin_nothrow(f, Any[exprtype(ea[i], src, mod) for i = 2:length(ea)])
elseif head === :new
a = ea[1]
typ = exprtype(a, src, mod)
# `Expr(:new)` of unknown type could raise arbitrary TypeError.
typ, isexact = instanceof_tfunc(typ)
isexact || return false
isconcretedispatch(typ) || return false
typ = typ::DataType
fieldcount(typ) >= length(ea) - 1 || return false
for fld_idx in 1:(length(ea) - 1)
eT = exprtype(ea[fld_idx + 1], src, mod)
fT = fieldtype(typ, fld_idx)
eT ⊑ fT || return false
end
return true
elseif head === :isdefined || head === :the_exception || head === :copyast
return true
else
return false
end
end
return true
end

function abstract_eval_ssavalue(s::SSAValue, src::IRCode)
Expand Down
107 changes: 107 additions & 0 deletions base/compiler/tfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,75 @@ function const_datatype_getfield_tfunc(sv, fld)
return nothing
end

function try_compute_fieldidx(@nospecialize(typ), @nospecialize(field))
if isa(field, Symbol)
field = fieldindex(typ, field, false)
field == 0 && return nothing
elseif isa(field, Integer)
(1 <= field <= fieldcount(typ)) || return nothing
else
return nothing
end
return field
end

function getfield_nothrow(argtypes::Vector{Any})
2 <= length(argtypes) <= 3 || return false
length(argtypes) == 2 && return getfield_nothrow(argtypes[1], argtypes[2], Const(true))
return getfield_nothrow(argtypes[1], argtypes[2], argtypes[3])
end
function getfield_nothrow(@nospecialize(s00), @nospecialize(name), @nospecialize(inbounds))
bounds_check_disabled = isa(inbounds, Const) && inbounds.val === false
# If we don't have invounds and don't know the field, don't even bother
if !bounds_check_disabled
isa(name, Const) || return false
end

# If we have s00 being a const, we can potentially refine our type-based analysis above
if isa(s00, Const) || isconstType(s00)
if !isa(s00, Const)
sv = s00.parameters[1]
else
sv = s00.val
end
if isa(name, Const)
(isa(sv, Module) && isa(name.val, Symbol)) || return false
(isa(name.val, Symbol) || isa(name.val, Int)) || return false
return isdefined(sv, name.val)
Copy link
Member

Choose a reason for hiding this comment

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

only if immutable (or isdefined returns true)

Copy link
Member Author

Choose a reason for hiding this comment

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

Is that true? Can you un-initialize a field of a mutable?

Copy link
Member

Choose a reason for hiding this comment

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

Oh, right. I was thinking this was returning the value – not whether it's an error

end
if bounds_check_disabled && !isa(sv, Module)
# If bounds checking is disabled and all fields are assigned,
# we may assume that we don't throw
for i = 1:fieldcount(typeof(sv))
isdefined(sv, i) || return false
end
return true
end
return false
end

s = unwrap_unionall(widenconst(s00))
if isa(s, Union)
return getfield_nothrow(rewrap(s.a, s00), name, inbounds) &&
getfield_nothrow(rewrap(s.b, s00), name, inbounds)
elseif isa(s, DataType)
# Can't say anything about abstract types
s.abstract && return false
# If all fields are always initialized, and bounds check is disabled, we can assume
# we don't throw
if bounds_check_disabled && !isvatuple(s) && s.name !== NamedTuple.body.body.name && fieldcount(s) == s.ninitialized
return true
end
# Else we need to know what the field is
isa(name, Const) || return false
field = try_compute_fieldidx(s, name.val)
field === nothing && return false
field <= s.ninitialized && return true
end

return false
end

getfield_tfunc(@nospecialize(s00), @nospecialize(name), @nospecialize(inbounds)) =
getfield_tfunc(s00, name)
function getfield_tfunc(@nospecialize(s00), @nospecialize(name))
Expand Down Expand Up @@ -770,6 +839,44 @@ function tuple_tfunc(@nospecialize(argtype))
return argtype
end

function array_builtin_common_nothrow(argtypes, first_idx_idx)
length(argtypes) >= 4 || return false
(argtypes[0] ⊑ Bool && argtypes[1] ⊑ Array) || return false
for i = first_idx_idx:length(argtypes)
argtypes[i] ⊑ Int || return false
end
# If we have @inbounds (first argument is false), we're allowed to assume we don't throw
(isa(argtypes[0], Const) && !argtypes[0].val) && return true
# Else we can't really say anything here
# TODO: In the future we may be able to track the shapes of arrays though
# inference.
return false
end

# Query whether the given builtin is guaranteed not to throw given the argtypes
function builtin_nothrow(@nospecialize(f), argtypes::Array{Any,1})
(f === tuple || f === svec) && return true
if f === arrayset
array_builtin_common_nothrow(argtypes, 4) || return true
# Additionally check element type compatibility
a = widenconst(argtypes[2])
# Check that we can determine the element type
(isa(a, DataType) && isa(a.parameters[1], Type)) || return false
# Check that the element type is compatible with the element we're assigning
(argtypes[3] ⊑ a.parameters[1]::Type) || return false
return true
elseif f === arrayref
return array_builtin_common_nothrow(argtypes, 3)
elseif f === Core._expr
return length(argtypes) >= 1 && argtypes[1] ⊑ Symbol
elseif f === invoke
return false
elseif f === getfield
return getfield_nothrow(argtypes)
end
return false
end

function builtin_tfunction(@nospecialize(f), argtypes::Array{Any,1},
sv::Union{InferenceState,Nothing}, params::Params = sv.params)
isva = !isempty(argtypes) && isvarargtype(argtypes[end])
Expand Down