Skip to content

Commit

Permalink
[NewOptimizer] Make stmt_effect_free less aggressive
Browse files Browse the repository at this point in the history
Previously, the new optimizer used the pre-existing `effect_free` function
to determine whether it is safe to remove an unreferenced statement. However,
this function ignores many builtin's error cases, causing them to be
removed when that is not legal to do (because that would potentially remove
an exception that would otherwise be thrown). Start fixing this, by
introducing a version of the function that is correct for a subset of
intresting functions. We will likely need to expand this when we look
at the benchmarks, but this should be correct for now.
  • Loading branch information
Keno committed May 1, 2018
1 parent 23388ae commit fa02d34
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 13 deletions.
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)
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

0 comments on commit fa02d34

Please sign in to comment.