Repository navigation
Expose loop-invariant inputs to GVN and LICM - #345
Merged
Merged
Conversation
A value that enters a loop and is never reassigned there reached the
loop header as a block parameter that the back edge passes to itself.
SSA sealing removes such parameters, but it seals each block once in
layout order: the header is sealed while its back edge still carries
the loop body's own, not yet sealed, parameters, and only once the body
is sealed do those turn out to be the header's parameter. Nothing looks
at the header again. The one pass that dropped what was left ran inside
strength reduction, after GVN and LICM had treated every use of the
parameter as defined inside the loop, so LICM hoisted nothing from
for (i = 0; i < n; i++)
a[i] = i + x * y;
and GVN could not match x * y in the loop with the same product
computed before it.
Parameter simplification is now a pass of its own, run after the first
CFG simplification so that local CSE, GVN and LICM see the input
itself. It leaves the entry block's parameters alone: they are the
function's arguments, and a back edge into the entry block that passes
one value does not make an argument redundant. Dominance is built only
once some parameter has a single incoming value.
A loop using x * y, y ^ z and x << 3 runs 26.5 percent fewer
instructions on x86-64. Under QEMU its host instructions drop by 8
percent for Armv7, 12 percent for AArch64 and 74 percent for RV32. The
native throughput fixtures compile to the same bytes. A stage 0
self-compile runs about 1 percent more instructions, and the stage 1
compiler it builds runs 0.2 percent fewer.
Close #283
When CFG simplification bypasses the empty arms of a diamond such as
if (c)
y = x;
else
y = x;
both edges of the branch end up on the join with the same arguments.
The branch was kept, so its condition stayed live and x86-64 emitted
"test edi, edi; jne +0" in front of the join.
CFG simplification now turns such a branch into a jump, and DCE drops
the condition. Edges that pass different arguments still select a
value and stay a branch. Folding only deletes an edge, so the scan
continues in place instead of restarting from the first block.
Close #290
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Loop-invariant inputs reached loop headers as block parameters that the back edge passes to itself. SSA sealing seals each block once in layout order, so a header is sealed while its back edge still carries the loop body's unsealed parameters, and nothing revisits it once those resolve to the header's own parameter. The only pass that removed what was left ran inside strength reduction, after GVN and LICM, so LICM hoisted nothing from
for (i = 0; i < n; i++) a[i] = i + x * y;and GVN could not match the in-loopx * ywith the same product computed before the loop. The first commit makes parameter simplification its own pass after the first CFG simplification. It leaves the entry block's parameters (the function's arguments) alone, and builds dominance only when some parameter has a single incoming value. The second commit folds a branch whose two arms reach the same block with the same arguments into a jump; x86-64 used to emittest edi, edi; jne +0forif (c) y = x; else y = x;.Verification:
make bootstrap(stage 1 and stage 2 byte-identical) andmake checkpass on x64, arm, riscv and arm64 for the final tree, with bootstrap on all four plus x64make checkfor the first commit alone.make check-virpasses on x64,tests/vir-core.cpasses under ASan and UBSan, and.ci/check-commentflow.sh,.ci/check-format.shand.ci/check-newline.share clean. New unit tests cover parameter simplification feeding GVN and LICM, entry parameters surviving a back edge into the entry block, and the fold with equal and with different arguments; each fails when its fix is reverted.Measurements on a Ryzen Threadripper 2990WX, in user-space instructions (
perf stat -e instructions:u; under QEMU this counts QEMU's host instructions). For a loop usingx * y,y ^ zandx << 3:The seven native throughput fixtures compile to the same bytes. A stage 0 self-compile runs about 1% more instructions for the extra pass, and the stage 1 compiler it builds runs 0.2% fewer.
Closes #283
Closes #290
Summary by cubic
Exposes loop-invariant inputs to GVN and LICM by simplifying block parameters before those passes run. A loop-invariant value reached a loop header as a block parameter that the back edge passed to itself, with the only cleanup running inside strength reduction, after GVN and LICM, so LICM hoisted nothing from
for (i = 0; i < n; i++) a[i] = i + x * y;and GVN could not match the in-loopx * ywith the same product computed before the loop.Adds
vir_simplify_paramsas its own pass after the first CFG simplification. It replaces each parameter receiving one value on every edge (or itself on a back edge) with that value, and leaves the entry block's parameters alone. Also folds a branch whose two arms reach the same block with the same arguments into a jump; x86-64 used to emittest edi, edi; jne +0forif (c) y = x; else y = x;.A loop using
x * y,y ^ z, andx << 3runs 26.5% fewer user-space instructions natively on x86-64; under QEMU it drops 8.3% on Armv7, 11.7% on AArch64, and 73.6% on RV32.make bootstrapproduces byte-identical stage 1 and stage 2, andmake checkpasses on x86-64, Arm, RISC-V, and arm64. New unit tests cover parameter simplification feeding GVN and LICM, an entry parameter surviving a back edge, and the branch fold with equal and with different arguments.Written for commit 1742111. Summary will update on new commits.