[llvm] [SimplifyCFG] Look at all uses when checking phi incoming for UB (PR #200164)
Igor Kirillov via llvm-commits
llvm-commits at lists.llvm.org
Wed Jun 3 02:37:20 PDT 2026
https://github.com/igogo-x86 updated https://github.com/llvm/llvm-project/pull/200164
>From 91c87b0744d73ee3bffba1ce03a28c243a0de362 Mon Sep 17 00:00:00 2001
From: Igor Kirillov <igor.kirillov at arm.com>
Date: Thu, 28 May 2026 11:55:35 +0000
Subject: [PATCH 1/3] [SimplifyCFG] Look at all uses when checking phi incoming
for UB
passingValueIsAlwaysUndefined only looks at the first use of the phi
that has a UB-candidate opcode. If that use is in a different block,
the function gives up, even when another use in the same block would
prove UB. Use-list order is not guaranteed, so this happens in practice.
Move the same-block check into the find_if lambda so the scan keeps
going past cross-block uses.
---
llvm/lib/Transforms/Utils/SimplifyCFG.cpp | 12 +++---
.../SimplifyCFG/phi-undef-loadstore.ll | 39 +++++++++++++++++++
2 files changed, 44 insertions(+), 7 deletions(-)
diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 9182ba6701834..4843dea4cd6c5 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -8758,8 +8758,12 @@ static bool passingValueIsAlwaysUndefined(Value *V, Instruction *I, bool PtrValu
if (C->isNullValue() || isa<UndefValue>(C)) {
// Only look at the first use we can handle, avoid hurting compile time with
// long uselists
- auto FindUse = llvm::find_if(I->uses(), [](auto &U) {
+ auto FindUse = llvm::find_if(I->uses(), [I](auto &U) {
auto *Use = cast<Instruction>(U.getUser());
+ // Only same-block uses after I can witness UB at I's program point.
+ // Self-uses and before-I uses can occur when I is a PHI node.
+ if (Use->getParent() != I->getParent() || Use == I || Use->comesBefore(I))
+ return false;
// Change this list when we want to add new instructions.
switch (Use->getOpcode()) {
default:
@@ -8786,12 +8790,6 @@ static bool passingValueIsAlwaysUndefined(Value *V, Instruction *I, bool PtrValu
return false;
auto &Use = *FindUse;
auto *User = cast<Instruction>(Use.getUser());
- // Bail out if User is not in the same BB as I or User == I or User comes
- // before I in the block. The latter two can be the case if User is a
- // PHI node.
- if (User->getParent() != I->getParent() || User == I ||
- User->comesBefore(I))
- return false;
// Now make sure that there are no instructions in between that can alter
// control flow (eg. calls)
diff --git a/llvm/test/Transforms/SimplifyCFG/phi-undef-loadstore.ll b/llvm/test/Transforms/SimplifyCFG/phi-undef-loadstore.ll
index 2fe57e429808c..7d43ff4732f69 100644
--- a/llvm/test/Transforms/SimplifyCFG/phi-undef-loadstore.ll
+++ b/llvm/test/Transforms/SimplifyCFG/phi-undef-loadstore.ll
@@ -349,4 +349,43 @@ if.end7: ; preds = %if.else, %if.then4,
ret i32 %tmp9
}
+define i64 @test5_several_uses(i1 %c, i1 %branch, ptr %p) nounwind #0 {
+; CHECK-LABEL: define i64 @test5_several_uses
+; CHECK-SAME: (i1 [[C:%.*]], i1 [[BRANCH:%.*]], ptr [[P:%.*]]) #[[ATTR1]] {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[SPEC_SELECT:%.*]] = select i1 [[C]], ptr [[P]], ptr null
+; CHECK-NEXT: [[G_IN_BLOCK:%.*]] = getelementptr i8, ptr [[SPEC_SELECT]], i64 48
+; CHECK-NEXT: [[V:%.*]] = load i64, ptr [[G_IN_BLOCK]], align 8
+; CHECK-NEXT: br i1 [[BRANCH]], label [[COMMON_RET:%.*]], label [[USE:%.*]]
+; CHECK: common.ret:
+; CHECK-NEXT: [[COMMON_RET_OP:%.*]] = phi i64 [ [[SUM:%.*]], [[USE]] ], [ [[V]], [[ENTRY:%.*]] ]
+; CHECK-NEXT: ret i64 [[COMMON_RET_OP]]
+; CHECK: use:
+; CHECK-NEXT: [[G_CROSS:%.*]] = getelementptr i8, ptr [[SPEC_SELECT]], i64 16
+; CHECK-NEXT: [[X:%.*]] = load i64, ptr [[G_CROSS]], align 8
+; CHECK-NEXT: [[SUM]] = add i64 [[V]], [[X]]
+; CHECK-NEXT: br label [[COMMON_RET]]
+;
+entry:
+ br i1 %c, label %acquire, label %join
+
+acquire:
+ br label %join
+
+join:
+ %obj = phi ptr [ null, %entry ], [ %p, %acquire ]
+ %g_in_block = getelementptr i8, ptr %obj, i64 48
+ %v = load i64, ptr %g_in_block, align 8
+ br i1 %branch, label %ret_direct, label %use
+
+ret_direct:
+ ret i64 %v
+
+use:
+ %g_cross = getelementptr i8, ptr %obj, i64 16
+ %x = load i64, ptr %g_cross, align 8
+ %sum = add i64 %v, %x
+ ret i64 %sum
+}
+
attributes #0 = { null_pointer_is_valid }
>From 2cbed864e367243adc85c5ebdddd7832bdacc71c Mon Sep 17 00:00:00 2001
From: Igor Kirillov <igor.kirillov at arm.com>
Date: Mon, 1 Jun 2026 14:11:20 +0000
Subject: [PATCH 2/3] Udpate comment
---
llvm/lib/Transforms/Utils/SimplifyCFG.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 4843dea4cd6c5..3503b3f0bd056 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -8756,8 +8756,8 @@ static bool passingValueIsAlwaysUndefined(Value *V, Instruction *I, bool PtrValu
return false;
if (C->isNullValue() || isa<UndefValue>(C)) {
- // Only look at the first use we can handle, avoid hurting compile time with
- // long uselists
+ // Find the first same-block use with a UB-triggering opcode, skipping
+ // cross-block or before-I uses.
auto FindUse = llvm::find_if(I->uses(), [I](auto &U) {
auto *Use = cast<Instruction>(U.getUser());
// Only same-block uses after I can witness UB at I's program point.
>From 051fac7cbdca1b480db1838233abfb0633eb6065 Mon Sep 17 00:00:00 2001
From: Igor Kirillov <igor.kirillov at arm.com>
Date: Wed, 3 Jun 2026 08:43:45 +0000
Subject: [PATCH 3/3] Fix test
---
.../test/Transforms/SimplifyCFG/phi-undef-loadstore.ll | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/llvm/test/Transforms/SimplifyCFG/phi-undef-loadstore.ll b/llvm/test/Transforms/SimplifyCFG/phi-undef-loadstore.ll
index 7d43ff4732f69..935a02124e337 100644
--- a/llvm/test/Transforms/SimplifyCFG/phi-undef-loadstore.ll
+++ b/llvm/test/Transforms/SimplifyCFG/phi-undef-loadstore.ll
@@ -349,19 +349,19 @@ if.end7: ; preds = %if.else, %if.then4,
ret i32 %tmp9
}
-define i64 @test5_several_uses(i1 %c, i1 %branch, ptr %p) nounwind #0 {
+define i64 @test5_several_uses(i1 %c, i1 %branch, ptr %p) nounwind {
; CHECK-LABEL: define i64 @test5_several_uses
-; CHECK-SAME: (i1 [[C:%.*]], i1 [[BRANCH:%.*]], ptr [[P:%.*]]) #[[ATTR1]] {
+; CHECK-SAME: (i1 [[C:%.*]], i1 [[BRANCH:%.*]], ptr [[P:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: entry:
-; CHECK-NEXT: [[SPEC_SELECT:%.*]] = select i1 [[C]], ptr [[P]], ptr null
-; CHECK-NEXT: [[G_IN_BLOCK:%.*]] = getelementptr i8, ptr [[SPEC_SELECT]], i64 48
+; CHECK-NEXT: call void @llvm.assume(i1 [[C]])
+; CHECK-NEXT: [[G_IN_BLOCK:%.*]] = getelementptr i8, ptr [[P]], i64 48
; CHECK-NEXT: [[V:%.*]] = load i64, ptr [[G_IN_BLOCK]], align 8
; CHECK-NEXT: br i1 [[BRANCH]], label [[COMMON_RET:%.*]], label [[USE:%.*]]
; CHECK: common.ret:
; CHECK-NEXT: [[COMMON_RET_OP:%.*]] = phi i64 [ [[SUM:%.*]], [[USE]] ], [ [[V]], [[ENTRY:%.*]] ]
; CHECK-NEXT: ret i64 [[COMMON_RET_OP]]
; CHECK: use:
-; CHECK-NEXT: [[G_CROSS:%.*]] = getelementptr i8, ptr [[SPEC_SELECT]], i64 16
+; CHECK-NEXT: [[G_CROSS:%.*]] = getelementptr i8, ptr [[P]], i64 16
; CHECK-NEXT: [[X:%.*]] = load i64, ptr [[G_CROSS]], align 8
; CHECK-NEXT: [[SUM]] = add i64 [[V]], [[X]]
; CHECK-NEXT: br label [[COMMON_RET]]
More information about the llvm-commits
mailing list