-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
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
optimizer: enhance SROA, handle partially-initialized allocations #42834
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -75,7 +75,22 @@ function compute_value_for_block(ir::IRCode, domtree::DomTree, allblocks::Vector | |
end | ||
|
||
function compute_value_for_use(ir::IRCode, domtree::DomTree, allblocks::Vector{Int}, du::SSADefUse, phinodes::IdDict{Int, SSAValue}, fidx::Int, use_idx::Int) | ||
# Find the first dominating def | ||
def, stmtblock, curblock = find_def_for_use(ir, domtree, allblocks, du, use_idx) | ||
if def == 0 | ||
if !haskey(phinodes, curblock) | ||
# If this happens, we need to search the predecessors for defs. Which | ||
# one doesn't matter - if it did, we'd have had a phinode | ||
return compute_value_for_block(ir, domtree, allblocks, du, phinodes, fidx, first(ir.cfg.blocks[stmtblock].preds)) | ||
end | ||
# The use is the phinode | ||
return phinodes[curblock] | ||
else | ||
return val_for_def_expr(ir, def, fidx) | ||
end | ||
end | ||
|
||
# find the first dominating def for the given use | ||
function find_def_for_use(ir::IRCode, domtree::DomTree, allblocks::Vector{Int}, du::SSADefUse, use_idx::Int) | ||
stmtblock = block_for_inst(ir.cfg, use_idx) | ||
curblock = find_curblock(domtree, allblocks, stmtblock) | ||
local def = 0 | ||
|
@@ -90,17 +105,7 @@ function compute_value_for_use(ir::IRCode, domtree::DomTree, allblocks::Vector{I | |
end | ||
end | ||
end | ||
if def == 0 | ||
if !haskey(phinodes, curblock) | ||
# If this happens, we need to search the predecessors for defs. Which | ||
# one doesn't matter - if it did, we'd have had a phinode | ||
return compute_value_for_block(ir, domtree, allblocks, du, phinodes, fidx, first(ir.cfg.blocks[stmtblock].preds)) | ||
end | ||
# The use is the phinode | ||
return phinodes[curblock] | ||
else | ||
return val_for_def_expr(ir, def, fidx) | ||
end | ||
return def, stmtblock, curblock | ||
end | ||
|
||
function simple_walk(compact::IncrementalCompact, @nospecialize(defssa#=::AnySSAValue=#), | ||
|
@@ -538,7 +543,7 @@ function perform_lifting!(compact::IncrementalCompact, | |
end | ||
|
||
""" | ||
getfield_elim_pass!(ir::IRCode) -> newir::IRCode | ||
sroa_pass!(ir::IRCode) -> newir::IRCode | ||
|
||
`getfield` elimination pass, a.k.a. Scalar Replacements of Aggregates optimization. | ||
|
||
|
@@ -555,7 +560,7 @@ its argument). | |
In a case when all usages are fully eliminated, `struct` allocation may also be erased as | ||
a result of dead code elimination. | ||
""" | ||
function getfield_elim_pass!(ir::IRCode) | ||
function sroa_pass!(ir::IRCode) | ||
compact = IncrementalCompact(ir) | ||
defuses = IdDict{Int, Tuple{IdSet{Int}, SSADefUse}}() | ||
lifting_cache = IdDict{Pair{AnySSAValue, Any}, AnySSAValue}() | ||
|
@@ -784,7 +789,6 @@ function getfield_elim_pass!(ir::IRCode) | |
typ = typ::DataType | ||
# Partition defuses by field | ||
fielddefuse = SSADefUse[SSADefUse() for _ = 1:fieldcount(typ)] | ||
ok = true | ||
for use in defuse.uses | ||
stmt = ir[SSAValue(use)] | ||
# We may have discovered above that this use is dead | ||
|
@@ -793,47 +797,59 @@ function getfield_elim_pass!(ir::IRCode) | |
# the use in that case. | ||
stmt === nothing && continue | ||
field = try_compute_fieldidx_stmt(compact, stmt::Expr, typ) | ||
field === nothing && (ok = false; break) | ||
field === nothing && @goto skip | ||
push!(fielddefuse[field].uses, use) | ||
end | ||
ok || continue | ||
for use in defuse.defs | ||
field = try_compute_fieldidx_stmt(compact, ir[SSAValue(use)]::Expr, typ) | ||
field === nothing && (ok = false; break) | ||
field === nothing && @goto skip | ||
push!(fielddefuse[field].defs, use) | ||
end | ||
ok || continue | ||
# Check that the defexpr has defined values for all the fields | ||
# we're accessing. In the future, we may want to relax this, | ||
# but we should come up with semantics for well defined semantics | ||
# for uninitialized fields first. | ||
for (fidx, du) in pairs(fielddefuse) | ||
ndefuse = length(fielddefuse) | ||
blocks = Vector{Tuple{#=phiblocks=# Vector{Int}, #=allblocks=# Vector{Int}}}(undef, ndefuse) | ||
for fidx in 1:ndefuse | ||
du = fielddefuse[fidx] | ||
isempty(du.uses) && continue | ||
push!(du.defs, idx) | ||
ldu = compute_live_ins(ir.cfg, du) | ||
phiblocks = Int[] | ||
if !isempty(ldu.live_in_bbs) | ||
phiblocks = idf(ir.cfg, ldu, domtree) | ||
end | ||
allblocks = sort(vcat(phiblocks, ldu.def_bbs)) | ||
blocks[fidx] = phiblocks, allblocks | ||
if fidx + 1 > length(defexpr.args) | ||
ok = false | ||
break | ||
# even if the allocation contains an uninitialized field, we try an extra effort | ||
# to check if all uses have any "solid" `setfield!` calls that define the field | ||
# although we give up the cases below: | ||
# `def == idx`: this field can only defined at the allocation site (thus this case will throw) | ||
# `def == 0`: this field comes from `PhiNode` | ||
# we may be able to traverse control flows of PhiNode values, but it sounds | ||
# more complicated than beneficial under the current implementation | ||
for use in du.uses | ||
def = find_def_for_use(ir, domtree, allblocks, du, use)[1] | ||
(def == 0 || def == idx) && @goto skip | ||
end | ||
end | ||
end | ||
ok || continue | ||
preserve_uses = IdDict{Int, Vector{Any}}((idx=>Any[] for idx in IdSet{Int}(defuse.ccall_preserve_uses))) | ||
# Everything accounted for. Go field by field and perform idf | ||
for (fidx, du) in pairs(fielddefuse) | ||
for fidx in 1:ndefuse | ||
du = fielddefuse[fidx] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A bit nitpicky, but is there any difference between the original and this change? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, no difference. If |
||
ftyp = fieldtype(typ, fidx) | ||
if !isempty(du.uses) | ||
push!(du.defs, idx) | ||
ldu = compute_live_ins(ir.cfg, du) | ||
phiblocks = Int[] | ||
if !isempty(ldu.live_in_bbs) | ||
phiblocks = idf(ir.cfg, ldu, domtree) | ||
end | ||
phiblocks, allblocks = blocks[fidx] | ||
phinodes = IdDict{Int, SSAValue}() | ||
for b in phiblocks | ||
n = PhiNode() | ||
phinodes[b] = insert_node!(ir, first(ir.cfg.blocks[b].stmts), | ||
NewInstruction(n, ftyp)) | ||
end | ||
# Now go through all uses and rewrite them | ||
allblocks = sort(vcat(phiblocks, ldu.def_bbs)) | ||
for stmt in du.uses | ||
ir[SSAValue(stmt)] = compute_value_for_use(ir, domtree, allblocks, du, phinodes, fidx, stmt) | ||
end | ||
|
@@ -855,7 +871,6 @@ function getfield_elim_pass!(ir::IRCode) | |
stmt == idx && continue | ||
ir[SSAValue(stmt)] = nothing | ||
end | ||
continue | ||
end | ||
isempty(defuse.ccall_preserve_uses) && continue | ||
push!(intermediaries, idx) | ||
|
@@ -870,6 +885,8 @@ function getfield_elim_pass!(ir::IRCode) | |
old_preserves..., new_preserves...) | ||
ir[SSAValue(use)] = new_expr | ||
end | ||
|
||
@label skip | ||
end | ||
|
||
return ir | ||
|
@@ -919,14 +936,14 @@ In addition to a simple DCE for unused values and allocations, | |
this pass also nullifies `typeassert` calls that can be proved to be no-op, | ||
in order to allow LLVM to emit simpler code down the road. | ||
|
||
Note that this pass is more effective after SROA optimization (i.e. `getfield_elim_pass!`), | ||
Note that this pass is more effective after SROA optimization (i.e. `sroa_pass!`), | ||
since SROA often allows this pass to: | ||
- eliminate allocation of object whose field references are all replaced with scalar values, and | ||
- nullify `typeassert` call whose first operand has been replaced with a scalar value | ||
(, which may have introduced new type information that inference did not understand) | ||
|
||
Also note that currently this pass _needs_ to run after `getfield_elim_pass!`, because | ||
the `typeassert` elimination depends on the transformation within `getfield_elim_pass!` | ||
Also note that currently this pass _needs_ to run after `sroa_pass!`, because | ||
the `typeassert` elimination depends on the transformation within `sroa_pass!` | ||
which redirects references of `typeassert`ed value to the corresponding `PiNode`. | ||
""" | ||
function adce_pass!(ir::IRCode) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If
def == 0
here, doesn't that mean that this field is truly unitialized? maybe I am misunderstandingThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
def
is the index where the object field is defined, so it could be:def >= 1
setfield!
call:def >= 1
def == 0
So
def == 0
case here corresponds to something like:Since traversing such PhiNode could be complicated (I think?), I just leave it as TODO.
Maybe this is worth to be left as comment ?