-
Notifications
You must be signed in to change notification settings - Fork 13.6k
[SimplifyCFG] Add support for sinking instructions with multiple uses #95521
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
Conversation
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 llvm#94462 this is basically just a matter of looping over all uses instead of checking the first one only.
@llvm/pr-subscribers-llvm-transforms Author: Nikita Popov (nikic) ChangesSinking 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. Full diff: https://github.com/llvm/llvm-project/pull/95521.diff 2 Files Affected:
diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 9c7f90b0613a0..85d774d20daa0 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -1936,7 +1936,7 @@ static bool canSinkInstructions(
// 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();
+ 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) ||
@@ -1956,10 +1956,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;
}
@@ -1987,8 +1986,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.
@@ -2138,11 +2136,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 a way that allows replacing the phi node with the common
+ // instruction.
+ auto *PN = cast<PHINode>(U);
PN->replaceAllUsesWith(I0);
PN->eraseFromParent();
}
diff --git a/llvm/test/Transforms/SimplifyCFG/X86/sink-common-code.ll b/llvm/test/Transforms/SimplifyCFG/X86/sink-common-code.ll
index 7b2161351a794..be2f17dc1c780 100644
--- a/llvm/test/Transforms/SimplifyCFG/X86/sink-common-code.ll
+++ b/llvm/test/Transforms/SimplifyCFG/X86/sink-common-code.ll
@@ -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
@@ -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
@@ -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
|
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.
LGTM.
This patch is causing a 20% performance regression in Details in #96838 |
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.