This is an archive of the discontinued LLVM Phabricator instance.

[GVN] Simple GVN hoist - scalars
AcceptedPublic

Authored by chill on Sep 30 2021, 5:53 AM.

Details

Summary

RFC/discussion: https://lists.llvm.org/pipermail/llvm-dev/2021-September/152665.html

This patch implements simple hoisting of instructions from two
single-predecessor blocks to their common predecessor, as a subroutine
in the GVN pass.

The patch pairs two instructions (A and B) with the same value number,
moves A to the predecessor block, replaces all uses of B with A, and
deletes B.

Outline of the algorithm follows:

First we scan the then-block to collect hoist candidates ("then-" and "else-"
prefixes are purely naming and have no connection to the condition in the
predecessor block)

Then we scan the else-block for hoist candidates, that match some already
selected instruction from the then-block.

During both scans, instructions, which are not guaranteed to transfer control to
the following instruction act as "hoist barriers" - after we encounter such an
instruction, we select for potential hoisting/merge only instructions, which are
safe to execute speculatively.

Next we try hoist to hoist each candidate pair. We begin by trying to hoist
operands of the then-instruction. Every such operand must already be in a
dominating block or in itself paired with an instruction from the else-block. If
we cannot hoist an operand for whatever reason, the we stop trying to hoist the
pair.

Now that all the operands of the then-instruction are in a dominating block, we
check the operands of the else-instruction. They all must already be in a
dominating block, either initially or as a result of hoisting operands of the
then-instruction. If any of the operands is still in the else-block, we stop
trying to hoist the pair.

As a last step, we move the then-instruction to the predecessor block and delete
the else-instruction.

Diff Detail

Event Timeline

chill created this revision.Sep 30 2021, 5:53 AM
chill requested review of this revision.Sep 30 2021, 5:53 AM
Herald added a project: Restricted Project. · View Herald TranscriptSep 30 2021, 5:53 AM
fhahn added a subscriber: fhahn.Sep 30 2021, 10:12 AM
mkazantsev added inline comments.Oct 4 2021, 2:10 AM
llvm/include/llvm/Transforms/Scalar/GVN.h
246

Not entirely clear from description which pairs does this store. What if we have N equivalent instructions in different paths, none dominating other? How many (and which) pairs is it going to store?

llvm/lib/Transforms/Scalar/GVN.cpp
3073

What worries me is that when we meet a non-guaranteed-to-transfer-execution instruction, we add it to HoistPairs before we actually raise the flag. Does it mean we can sometimes hoist such instructions?

3076

nit: space after "barrier".

3181

Why not RPOT?

chill marked 2 inline comments as done.Oct 4 2021, 2:59 AM
chill added inline comments.
llvm/include/llvm/Transforms/Scalar/GVN.h
246

This map stores pairs of instructions from two blocks having a single common predecessor for the duration of a single top level iteration in performHoist.

I will update the comment in that sense.

llvm/lib/Transforms/Scalar/GVN.cpp
3073

Yes, that's right, we can hoist such instructions. Should be fine, as we only hoist/merge *pairs* from the two sibling blocks.

In principle, we can hoist (some) following instructions as well, if the two "barrier" instructions are themselves hoisted, but that's sort of expensive to check, so that's a limitation of the algorithm.

3076

Printing the instruction adds two spaces.

3181

That is to prevent hoisting an instruction more than once. Alternatively, we could track how many blocks/instructions up an instruction moves, but that entails an auxiliary data structure.

mkazantsev added inline comments.Oct 5 2021, 11:00 PM
llvm/lib/Transforms/Scalar/GVN.cpp
3181

But RPOT is that guarantees you that it doesn't hoist twice. Note that RPOT stands for *reverse* post order traversal, which is topological up-down traversal. It has a property (on DAG) that all block's predecessors are handled before the block.

Here is example:

   A
   |  \
   B   C
   | /  \
   D     E
  /  \
F     G

If you chose DFS traversal, it may take A B D F G C E. You could hoist (F, G) -> D when processing D and then (D, E) -> C when processing C, which is hoisting same instruction twice.

RPOT will protect you from this.

mkazantsev added inline comments.Oct 5 2021, 11:06 PM
llvm/lib/Transforms/Scalar/GVN.cpp
3073

Then you have a problem with ICF (see comment below).

3076

That's tricky :)

3170

Since you move instructions with implicit control flow (i.e. non guaranteed to pass execution), this s incorrect treatment of ICF. You need to remove both EiseI and ThenI from ICF *before* you move it, and then add the moved instruction to ICF in the new block.

3181

Maybe not the best example because current implementation doesn't hoist D->C, but still... RPOT looks safer.

mkazantsev requested changes to this revision.Oct 5 2021, 11:06 PM

Marking "Request changes" because of ICF mistreatment.

This revision now requires changes to proceed.Oct 5 2021, 11:06 PM
chill updated this revision to Diff 378223.Oct 8 2021, 8:16 AM
chill marked 9 inline comments as done.
  • Updated to update the ICF tracking
  • traverse the CFG in RPO-rder
mkazantsev added inline comments.Oct 9 2021, 12:40 AM
llvm/test/Transforms/GVN/gc_relocate.ll
2–3

Why disable it here? It should be interesting to see how it improves.

mkazantsev added inline comments.Oct 10 2021, 9:32 PM
llvm/lib/Transforms/Scalar/GVN.cpp
3062

Re-reading the patch, I noticed that it might be missing aliasing notion. How do we prevent hoisting loads across aliasing store?

chill updated this revision to Diff 378598.Oct 11 2021, 2:51 AM
chill marked 2 inline comments as done.Oct 11 2021, 2:54 AM
chill added inline comments.
llvm/lib/Transforms/Scalar/GVN.cpp
3062

In this part we don't hoist loads and stores at all, since they have unique value numbers.

mkazantsev added inline comments.Oct 15 2021, 1:06 AM
llvm/lib/Transforms/Scalar/GVN.cpp
3062

Could you please add assert that hoisted instruction does not read or write memory? We don't want to miss this once this changes.

chill updated this revision to Diff 380457.Oct 18 2021, 10:11 AM
chill marked an inline comment as done.
chill marked an inline comment as done.
mkazantsev accepted this revision.Oct 18 2021, 10:03 PM

Looks good now, thanks.

llvm/include/llvm/Transforms/Scalar/GVN.h
367

nit: please give them some names.

llvm/lib/Transforms/Scalar/GVN.cpp
129

nit: gvn-hoist

3069

Looks like it also makes sense to skip Phis, just to not bother computing their hashes.

llvm/test/Transforms/GVN/simple-gvnhoist-scalars.ll
1

Unless there is a reason to not do so, please use update_test_checks script & precommit the test.

This revision is now accepted and ready to land.Oct 18 2021, 10:03 PM
chill updated this revision to Diff 381929.Oct 25 2021, 4:14 AM
chill marked 2 inline comments as done.
chill marked 2 inline comments as done.
efriedma added inline comments.
llvm/lib/Transforms/Scalar/GVN.cpp
3205

BasicBlock::size() is almost never something you want to call. Issues:

  1. It's O(n) in the number of instructions in the block.
  2. It's sensitive to debug intrinsic calls, so using it to drive a heuristic is inherently broken.
chill added inline comments.Dec 6 2021, 8:26 AM
llvm/lib/Transforms/Scalar/GVN.cpp
3205

Ack.

i fixed PR46874 last year in case this was the issue https://reviews.llvm.org/D108425. If there are further bugs with GVNHoist, i'm happy to fix them.

Herald added a project: Restricted Project. · View Herald TranscriptMar 29 2022, 2:12 PM