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

adce_pass: Drop phinode edges that can be proved unused #43922

Merged
merged 6 commits into from
Jan 26, 2022
Merged
Show file tree
Hide file tree
Changes from 2 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
135 changes: 110 additions & 25 deletions base/compiler/ssair/passes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -471,19 +471,21 @@ end
make_MaybeUndef(@nospecialize(typ)) = isa(typ, MaybeUndef) ? typ : MaybeUndef(typ)

"""
lift_comparison!(compact::IncrementalCompact, idx::Int, stmt::Expr)
lift_comparison!(cmp, compact::IncrementalCompact, idx::Int, stmt::Expr)

Replaces `φ(x, y)::Union{X,Y} === constant` by `φ(x === constant, y === constant)`,
where `x === constant` and `y === constant` can be replaced with constant `Bool`eans.
It helps codegen avoid generating expensive code for `===` with `Union` types.
Replaces `cmp(φ(x, y)::Union{X,Y}, constant)` by `φ(cmp(x, constant), cmp(y, constant))`,
where `cmp(x, constant)` and `cmp(y, constant)` can be replaced with constant `Bool`eans.
It helps codegen avoid generating expensive code for `cmp` with `Union` types.
In particular, this is supposed to improve the performance of the iteration protocol:
```julia
while x !== nothing
x = iterate(...)::Union{Nothing,Tuple{Any,Any}}
end
```
"""
function lift_comparison!(compact::IncrementalCompact,
function lift_comparison! end

function lift_comparison!(::typeof(===), compact::IncrementalCompact,
idx::Int, stmt::Expr, lifting_cache::IdDict{Pair{AnySSAValue, Any}, AnySSAValue})
args = stmt.args
length(args) == 3 || return
Expand All @@ -493,37 +495,59 @@ function lift_comparison!(compact::IncrementalCompact,
vr = argextype(rhs, compact)
if isa(vl, Const)
isa(vr, Const) && return
cmp = vl
typeconstraint = widenconst(vr)
val = rhs
target = lhs
elseif isa(vr, Const)
cmp = vr
typeconstraint = widenconst(vl)
val = lhs
target = rhs
else
return
end

valtyp = widenconst(argextype(val, compact))
isa(valtyp, Union) || return # bail out if there won't be a good chance for lifting
lift_comparison_leaves!(egal_tfunc, compact, val, target, lifting_cache, idx)
end

function lift_comparison!(::typeof(isa), compact::IncrementalCompact,
idx::Int, stmt::Expr, lifting_cache::IdDict{Pair{AnySSAValue, Any}, AnySSAValue})
args = stmt.args
length(args) == 3 || return
lift_comparison_leaves!(isa_tfunc, compact, args[2], args[3], lifting_cache, idx)
end

function lift_comparison!(::typeof(isdefined), compact::IncrementalCompact,
idx::Int, stmt::Expr, lifting_cache::IdDict{Pair{AnySSAValue, Any}, AnySSAValue})
args = stmt.args
length(args) == 3 || return
lift_comparison_leaves!(isdefined_tfunc, compact, args[2], args[3], lifting_cache, idx)
end

leaves, visited_phinodes = collect_leaves(compact, val, valtyp)
function lift_comparison_leaves!(@specialize(tfunc),
compact::IncrementalCompact, @nospecialize(val), @nospecialize(target),
lifting_cache::IdDict{Pair{AnySSAValue, Any}, AnySSAValue}, idx::Int)
typeconstraint = widenconst(argextype(val, compact))
if isa(val, Union{OldSSAValue, SSAValue})
val, typeconstraint = simple_walk_constraint(compact, val, typeconstraint)
end
isa(typeconstraint, Union) || return # bail out if there won't be a good chance for lifting
leaves, visited_phinodes = collect_leaves(compact, val, typeconstraint)
length(leaves) ≤ 1 && return # bail out if we don't have multiple leaves

# Let's check if we evaluate the comparison for each one of the leaves
# check if we can evaluate the comparison for each one of the leaves
cmp = argextype(target, compact)
lifted_leaves = nothing
for leaf in leaves
r = egal_tfunc(argextype(leaf, compact), cmp)
if isa(r, Const)
result = tfunc(argextype(leaf, compact), cmp)
if isa(result, Const)
if lifted_leaves === nothing
lifted_leaves = LiftedLeaves()
end
lifted_leaves[leaf] = LiftedValue(r.val)
lifted_leaves[leaf] = LiftedValue(result.val)
else
return # TODO In some cases it might be profitable to hoist the === here
return # TODO In some cases it might be profitable to hoist the comparison here
end
end

# perform lifting
lifted_val = perform_lifting!(compact,
visited_phinodes, cmp, lifting_cache, Bool,
lifted_leaves::LiftedLeaves, val)::LiftedValue
Expand Down Expand Up @@ -715,10 +739,14 @@ function sroa_pass!(ir::IRCode)
canonicalize_typeassert!(compact, idx, stmt)
continue
elseif is_known_call(stmt, (===), compact)
lift_comparison!(compact, idx, stmt, lifting_cache)
lift_comparison!(===, compact, idx, stmt, lifting_cache)
continue
elseif is_known_call(stmt, isa, compact)
lift_comparison!(isa, compact, idx, stmt, lifting_cache)
continue
elseif is_known_call(stmt, isdefined, compact)
lift_comparison!(isdefined, compact, idx, stmt, lifting_cache)
continue
# elseif is_known_call(stmt, isa, compact)
# TODO do a similar optimization as `lift_comparison!` for `===`
else
continue
end
Expand Down Expand Up @@ -1032,6 +1060,11 @@ function mark_phi_cycles!(compact::IncrementalCompact, safe_phis::SPCSet, phi::I
end
end

function is_union_phi(compact::IncrementalCompact, idx::Int)
inst = compact.result[idx]
return isa(inst[:inst], PhiNode) && isa(inst[:type], Union)
end

"""
adce_pass!(ir::IRCode) -> newir::IRCode

Expand All @@ -1054,22 +1087,74 @@ within `sroa_pass!` which redirects references of `typeassert`ed value to the co
function adce_pass!(ir::IRCode)
phi_uses = fill(0, length(ir.stmts) + length(ir.new_nodes))
all_phis = Int[]
unionphis = Int[] # sorted
unionphi_types = Any[]
compact = IncrementalCompact(ir)
for ((_, idx), stmt) in compact
if isa(stmt, PhiNode)
push!(all_phis, idx)
elseif is_known_call(stmt, typeassert, compact) && length(stmt.args) == 3
# nullify safe `typeassert` calls
ty, isexact = instanceof_tfunc(argextype(stmt.args[3], compact))
if isexact && argextype(stmt.args[2], compact) ⊑ ty
compact[idx] = nothing
if isa(compact.result[idx][:type], Union)
push!(unionphis, idx)
push!(unionphi_types, Union{})
end
elseif isa(stmt, PiNode)
val = stmt.val
if isa(val, SSAValue) && is_union_phi(compact, val.id)
r = searchsorted(unionphis, val.id)
if !isempty(r)
unionphi_types[first(r)] = Union{unionphi_types[first(r)], widenconst(stmt.typ)}
end
end
else
if is_known_call(stmt, typeassert, compact) && length(stmt.args) == 3
# nullify safe `typeassert` calls
ty, isexact = instanceof_tfunc(argextype(stmt.args[3], compact))
if isexact && argextype(stmt.args[2], compact) ⊑ ty
compact[idx] = nothing
continue
end
end
for ur in userefs(stmt)
use = ur[]
if isa(use, SSAValue) && is_union_phi(compact, use.id)
r = searchsorted(unionphis, use.id)
if !isempty(r)
deleteat!(unionphis, first(r))
deleteat!(unionphi_types, first(r))
Comment on lines +1122 to +1123
Copy link
Sponsor Member

Choose a reason for hiding this comment

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

Seems like you should be using a Set or small (unsorted) array, if you need deletion. Otherwise this object is costing O(n)

end
end
end
end
end
non_dce_finish!(compact)
for phi in all_phis
count_uses(compact.result[phi][:inst]::PhiNode, phi_uses)
end
# Narrow any union phi nodes that have unused branches
for (phi, t) in zip(unionphis, unionphi_types)
Copy link
Sponsor Member

Choose a reason for hiding this comment

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

I remember @vtjnash said that this (phi, t) may allocate arbitrary tuple types depending on user types fed into unionphi_types:

Suggested change
for (phi, t) in zip(unionphis, unionphi_types)
@assert length(unionphis) == length(unionphi_types)
for i = length(unionphis)
phi = unionphis[i]
t = unionphi_types[i]

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, right. Good catch.

if phi_uses[phi] != 0
continue
end
if t === Union{}
Copy link
Sponsor Member

Choose a reason for hiding this comment

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

When this condition can be hit? Do we sometimes have π(...)::Union{}?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, or during compact all the uses turned out dead or something. We could do this folding in compact, but since this is the place that changes it, might as well do it here.

compact.result[phi][:inst] = nothing
continue
end
to_drop = Int[]
stmt = compact[phi]
stmt === nothing && continue
for i = 1:length(stmt.values)
if !isassigned(stmt.values, i)
# Should be impossible to have something used only by PiNodes that's undef
push!(to_drop, i)
elseif typeintersect(widenconst(argextype(stmt.values[i], compact)), t) === Union{}
Keno marked this conversation as resolved.
Show resolved Hide resolved
push!(to_drop, i)
end
end
isempty(to_drop) && continue
deleteat!(stmt.values, to_drop)
deleteat!(stmt.edges, to_drop)
compact.result[phi][:type] = t
end
# Perform simple DCE for unused values
extra_worklist = Int[]
for (idx, nused) in Iterators.enumerate(compact.used_ssas)
Expand Down
Loading