Skip to content

Commit

Permalink
[SimplifyCFG] Add support for sinking instructions with multiple uses (
Browse files Browse the repository at this point in the history
…#95521)

Sinking currently only supports instructions that have zero or one uses.
Extend this to handle instructions with any number of uses, as long as
all uses are consistent (i.e. the "same" for all sinking candidates).

After #94462 this is basically just a matter of looping over all uses
instead of checking the first one only.
  • Loading branch information
nikic committed Jun 17, 2024
1 parent 1d4e857 commit ede27d8
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 44 deletions.
25 changes: 11 additions & 14 deletions llvm/lib/Transforms/Utils/SimplifyCFG.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1934,9 +1934,8 @@ static bool canSinkInstructions(
ArrayRef<Instruction *> Insts,
DenseMap<const Use *, SmallVector<Value *, 4>> &PHIOperands) {
// Prune out obviously bad instructions to move. Each instruction must have
// exactly zero or one use, and we check later that use is by a single, common
// PHI instruction in the successor.
bool HasUse = !Insts.front()->user_empty();
// the same number of uses, and we check later that the uses are consistent.
std::optional<unsigned> NumUses;
for (auto *I : Insts) {
// These instructions may change or break semantics if moved.
if (isa<PHINode>(I) || I->isEHPad() || isa<AllocaInst>(I) ||
Expand All @@ -1956,10 +1955,9 @@ static bool canSinkInstructions(
if (C->isInlineAsm() || C->cannotMerge() || C->isConvergent())
return false;

// Each instruction must have zero or one use.
if (HasUse && !I->hasOneUse())
return false;
if (!HasUse && !I->user_empty())
if (!NumUses)
NumUses = I->getNumUses();
else if (NumUses != I->getNumUses())
return false;
}

Expand Down Expand Up @@ -1987,8 +1985,7 @@ static bool canSinkInstructions(
// then the other phi operands must match the instructions from Insts. This
// also has to hold true for any phi nodes that would be created as a result
// of sinking. Both of these cases are represented by PhiOperands.
if (HasUse) {
const Use &U = *I0->use_begin();
for (const Use &U : I0->uses()) {
auto It = PHIOperands.find(&U);
if (It == PHIOperands.end())
// There may be uses in other blocks when sinking into a loop header.
Expand Down Expand Up @@ -2138,11 +2135,11 @@ static void sinkLastInstruction(ArrayRef<BasicBlock*> Blocks) {
I0->andIRFlags(I);
}

if (!I0->user_empty()) {
// canSinkLastInstruction checked that all instructions were used by
// one and only one PHI node. Find that now, RAUW it to our common
// instruction and nuke it.
auto *PN = cast<PHINode>(*I0->user_begin());
for (User *U : make_early_inc_range(I0->users())) {
// canSinkLastInstruction checked that all instructions are only used by
// phi nodes in a way that allows replacing the phi node with the common
// instruction.
auto *PN = cast<PHINode>(U);
PN->replaceAllUsesWith(I0);
PN->eraseFromParent();
}
Expand Down
46 changes: 16 additions & 30 deletions llvm/test/Transforms/SimplifyCFG/X86/sink-common-code.ll
Original file line number Diff line number Diff line change
Expand Up @@ -1704,19 +1704,14 @@ return:

define ptr @multi_use_in_phi(i1 %cond, ptr %p, i64 %a, i64 %b) {
; CHECK-LABEL: @multi_use_in_phi(
; CHECK-NEXT: br i1 [[COND:%.*]], label [[IF:%.*]], label [[ELSE:%.*]]
; CHECK-NEXT: br i1 [[COND:%.*]], label [[IF:%.*]], label [[JOIN:%.*]]
; CHECK: if:
; CHECK-NEXT: call void @dummy()
; CHECK-NEXT: [[GEP1_A:%.*]] = getelementptr i8, ptr [[P:%.*]], i64 [[A:%.*]]
; CHECK-NEXT: br label [[JOIN:%.*]]
; CHECK: else:
; CHECK-NEXT: [[GEP1_B:%.*]] = getelementptr i8, ptr [[P]], i64 [[A]]
; CHECK-NEXT: br label [[JOIN]]
; CHECK: join:
; CHECK-NEXT: [[GEP1_B_SINK:%.*]] = phi ptr [ [[GEP1_B]], [[ELSE]] ], [ [[GEP1_A]], [[IF]] ]
; CHECK-NEXT: [[PHI1:%.*]] = phi ptr [ [[GEP1_A]], [[IF]] ], [ [[GEP1_B]], [[ELSE]] ]
; CHECK-NEXT: [[GEP2_B:%.*]] = getelementptr i8, ptr [[GEP1_B_SINK]], i64 [[B:%.*]]
; CHECK-NEXT: call void @use.ptr(ptr [[PHI1]])
; CHECK-NEXT: [[GEP1_B:%.*]] = getelementptr i8, ptr [[P:%.*]], i64 [[A:%.*]]
; CHECK-NEXT: [[GEP2_B:%.*]] = getelementptr i8, ptr [[GEP1_B]], i64 [[B:%.*]]
; CHECK-NEXT: call void @use.ptr(ptr [[GEP1_B]])
; CHECK-NEXT: ret ptr [[GEP2_B]]
;
br i1 %cond, label %if, label %else
Expand Down Expand Up @@ -1778,23 +1773,16 @@ join:

define i64 @multi_use_in_block(i1 %cond, ptr %p, i64 %a, i64 %b) {
; CHECK-LABEL: @multi_use_in_block(
; CHECK-NEXT: br i1 [[COND:%.*]], label [[IF:%.*]], label [[ELSE:%.*]]
; CHECK-NEXT: br i1 [[COND:%.*]], label [[IF:%.*]], label [[JOIN:%.*]]
; CHECK: if:
; CHECK-NEXT: call void @dummy()
; CHECK-NEXT: [[GEP1_A:%.*]] = getelementptr i8, ptr [[P:%.*]], i64 [[A:%.*]]
; CHECK-NEXT: [[V_A:%.*]] = load i64, ptr [[GEP1_A]], align 8
; CHECK-NEXT: [[GEP2_A:%.*]] = getelementptr i8, ptr [[GEP1_A]], i64 [[V_A]]
; CHECK-NEXT: br label [[JOIN:%.*]]
; CHECK: else:
; CHECK-NEXT: [[GEP1_B:%.*]] = getelementptr i8, ptr [[P]], i64 [[A]]
; CHECK-NEXT: [[V_B:%.*]] = load i64, ptr [[GEP1_B]], align 8
; CHECK-NEXT: [[GEP2_B:%.*]] = getelementptr i8, ptr [[GEP1_B]], i64 [[V_B]]
; CHECK-NEXT: br label [[JOIN]]
; CHECK: join:
; CHECK-NEXT: [[PHI1:%.*]] = phi i64 [ [[V_A]], [[IF]] ], [ [[V_B]], [[ELSE]] ]
; CHECK-NEXT: [[PHI2:%.*]] = phi ptr [ [[GEP2_A]], [[IF]] ], [ [[GEP2_B]], [[ELSE]] ]
; CHECK-NEXT: call void @use.ptr(ptr [[PHI2]])
; CHECK-NEXT: ret i64 [[PHI1]]
; CHECK-NEXT: [[GEP1_B:%.*]] = getelementptr i8, ptr [[P:%.*]], i64 [[A:%.*]]
; CHECK-NEXT: [[V_B:%.*]] = load i64, ptr [[GEP1_B]], align 8
; CHECK-NEXT: [[GEP2_B:%.*]] = getelementptr i8, ptr [[GEP1_B]], i64 [[V_B]]
; CHECK-NEXT: call void @use.ptr(ptr [[GEP2_B]])
; CHECK-NEXT: ret i64 [[V_B]]
;
br i1 %cond, label %if, label %else

Expand Down Expand Up @@ -1824,19 +1812,17 @@ define i64 @multi_use_in_block_inconsistent(i1 %cond, ptr %p, i64 %a, i64 %b) {
; CHECK: if:
; CHECK-NEXT: call void @dummy()
; CHECK-NEXT: [[GEP1_A:%.*]] = getelementptr i8, ptr [[P:%.*]], i64 [[A:%.*]]
; CHECK-NEXT: [[V_A:%.*]] = load i64, ptr [[GEP1_A]], align 8
; CHECK-NEXT: [[GEP2_A:%.*]] = getelementptr i8, ptr [[GEP1_A]], i64 [[V_A]]
; CHECK-NEXT: br label [[JOIN:%.*]]
; CHECK: else:
; CHECK-NEXT: [[GEP1_B:%.*]] = getelementptr i8, ptr [[P]], i64 [[A]]
; CHECK-NEXT: [[V_B:%.*]] = load i64, ptr [[P]], align 8
; CHECK-NEXT: [[GEP2_B:%.*]] = getelementptr i8, ptr [[GEP1_B]], i64 [[V_B]]
; CHECK-NEXT: br label [[JOIN]]
; CHECK: join:
; CHECK-NEXT: [[PHI1:%.*]] = phi i64 [ [[V_A]], [[IF]] ], [ [[V_B]], [[ELSE]] ]
; CHECK-NEXT: [[PHI2:%.*]] = phi ptr [ [[GEP2_A]], [[IF]] ], [ [[GEP2_B]], [[ELSE]] ]
; CHECK-NEXT: call void @use.ptr(ptr [[PHI2]])
; CHECK-NEXT: ret i64 [[PHI1]]
; CHECK-NEXT: [[P_SINK:%.*]] = phi ptr [ [[P]], [[ELSE]] ], [ [[GEP1_A]], [[IF]] ]
; CHECK-NEXT: [[GEP1_B_SINK:%.*]] = phi ptr [ [[GEP1_B]], [[ELSE]] ], [ [[GEP1_A]], [[IF]] ]
; CHECK-NEXT: [[V_B:%.*]] = load i64, ptr [[P_SINK]], align 8
; CHECK-NEXT: [[GEP2_B:%.*]] = getelementptr i8, ptr [[GEP1_B_SINK]], i64 [[V_B]]
; CHECK-NEXT: call void @use.ptr(ptr [[GEP2_B]])
; CHECK-NEXT: ret i64 [[V_B]]
;
br i1 %cond, label %if, label %else

Expand Down

0 comments on commit ede27d8

Please sign in to comment.