[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
Mon Jun 1 07:11:49 PDT 2026


https://github.com/igogo-x86 updated https://github.com/llvm/llvm-project/pull/200164

>From 927f6e1451a95a76c73d061bfc9e65ac609f656c 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/2] [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 a8fd9f4a10064..116ca6b4a5392 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -8754,8 +8754,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:
@@ -8782,12 +8786,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 beb92843fbb259248e75365f3c576b5bc7556d9b 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/2] 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 116ca6b4a5392..d810c3171256d 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -8752,8 +8752,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.



More information about the llvm-commits mailing list