[llvm] e5f4f31 - [JumpThreading] Freeze undef/poison select condition in unfoldSelectInstr (#199408)

via llvm-commits llvm-commits at lists.llvm.org
Tue May 26 06:45:20 PDT 2026


Author: Justin Lebar
Date: 2026-05-26T06:45:14-07:00
New Revision: e5f4f31b3508dd06598051039268c27498472218

URL: https://github.com/llvm/llvm-project/commit/e5f4f31b3508dd06598051039268c27498472218
DIFF: https://github.com/llvm/llvm-project/commit/e5f4f31b3508dd06598051039268c27498472218.diff

LOG: [JumpThreading] Freeze undef/poison select condition in unfoldSelectInstr (#199408)

When unfoldSelectInstr rewrites a select as a conditional branch, it
used SI->getCondition() directly, without freezing it first. A select
with an undef or poison condition does not immediately cause UB,
but branching on undef or poison does, so we need to freeze it.

This bug was found by a large run of Opus 4.7 looking for bugs in LLVM.

Added: 
    

Modified: 
    llvm/lib/Transforms/Scalar/JumpThreading.cpp
    llvm/test/Transforms/JumpThreading/branch-debug-info2.ll
    llvm/test/Transforms/JumpThreading/select.ll
    llvm/test/Transforms/JumpThreading/stale-loop-info-after-unfold-select.ll

Removed: 
    


################################################################################
diff  --git a/llvm/lib/Transforms/Scalar/JumpThreading.cpp b/llvm/lib/Transforms/Scalar/JumpThreading.cpp
index 415136b612ac2..a9a7a87cb8c46 100644
--- a/llvm/lib/Transforms/Scalar/JumpThreading.cpp
+++ b/llvm/lib/Transforms/Scalar/JumpThreading.cpp
@@ -2788,8 +2788,16 @@ void JumpThreadingPass::unfoldSelectInstr(BasicBlock *Pred, BasicBlock *BB,
   // Move the unconditional branch to NewBB.
   PredTerm->removeFromParent();
   PredTerm->insertInto(NewBB, NewBB->end());
+  // A select with an undef or poison condition does not immediately cause UB,
+  // but branching on undef or poison does. Freeze the condition if needed
+  // before using it as a branch input.
+  Value *Cond = SI->getCondition();
+  if (!isGuaranteedNotToBeUndefOrPoison(Cond, nullptr, SI)) {
+    Cond = new FreezeInst(Cond, "cond.fr", SI->getIterator());
+    cast<FreezeInst>(Cond)->setDebugLoc(DebugLoc::getTemporary());
+  }
   // Create a conditional branch and update PHI nodes.
-  auto *BI = CondBrInst::Create(SI->getCondition(), NewBB, BB, Pred);
+  auto *BI = CondBrInst::Create(Cond, NewBB, BB, Pred);
   BI->applyMergedLocation(PredTerm->getDebugLoc(), SI->getDebugLoc());
   BI->copyMetadata(*SI, {LLVMContext::MD_prof});
   SIUse->setIncomingValue(Idx, SI->getFalseValue());

diff  --git a/llvm/test/Transforms/JumpThreading/branch-debug-info2.ll b/llvm/test/Transforms/JumpThreading/branch-debug-info2.ll
index 59b6ef51d670f..021b3c0290170 100644
--- a/llvm/test/Transforms/JumpThreading/branch-debug-info2.ll
+++ b/llvm/test/Transforms/JumpThreading/branch-debug-info2.ll
@@ -1,5 +1,31 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
 ; RUN: opt -passes=jump-threading -S %s -o - | FileCheck %s
 define void @prepare_next_shadow() !dbg !7 {
+; CHECK-LABEL: define void @prepare_next_shadow(
+; CHECK-SAME: ) !dbg [[DBG7:![0-9]+]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    br label %[[SHADOW_TO_PTR_EXIT:.*]], !dbg [[DBG9:![0-9]+]]
+; CHECK:       [[SHADOW_TO_PTR_EXIT]]:
+; CHECK-NEXT:    [[SHR_I:%.*]] = lshr i64 0, 12, !dbg [[DBG10:![0-9]+]]
+; CHECK-NEXT:    [[CMP_I:%.*]] = icmp ult i64 [[SHR_I]], undef, !dbg [[DBG11:![0-9]+]]
+; CHECK-NEXT:    br i1 [[CMP_I]], label %[[FOR_INC:.*]], label %[[IF_END_I60:.*]], !dbg [[DBG12:![0-9]+]]
+; CHECK:       [[IF_END_I60]]:
+; CHECK-NEXT:    [[SUB_I:%.*]] = sub i64 [[SHR_I]], undef, !dbg [[DBG13:![0-9]+]]
+; CHECK-NEXT:    [[CMP3_I:%.*]] = icmp ugt i64 [[SUB_I]], 32763, !dbg [[DBG14:![0-9]+]]
+; CHECK-NEXT:    [[CONV7_I:%.*]] = trunc i64 [[SUB_I]] to i32, !dbg [[DBG15:![0-9]+]]
+; CHECK-NEXT:    [[COND_FR:%.*]] = freeze i1 [[CMP3_I]]
+; Jump threading folds the select below into this branch. Ensure debug info is
+; not lost, and is merged from the select and the branch.
+; CHECK-NEXT:    br i1 [[COND_FR]], label %[[FOR_INC]], label %[[PTR_TO_SHADOW_EXIT:.*]], !dbg [[DBG16:![0-9]+]]
+; CHECK:       [[PTR_TO_SHADOW_EXIT]]:
+; CHECK-NEXT:    [[CALL1861:%.*]] = phi i32 [ [[CONV7_I]], %[[IF_END_I60]] ], !dbg [[DBG17:![0-9]+]]
+; CHECK-NEXT:    [[CMP19:%.*]] = icmp slt i32 [[CALL1861]], 0, !dbg [[DBG18:![0-9]+]]
+; CHECK-NEXT:    br i1 [[CMP19]], label %[[FOR_INC]], label %[[IF_END22:.*]], !dbg [[DBG19:![0-9]+]]
+; CHECK:       [[IF_END22]]:
+; CHECK-NEXT:    unreachable, !dbg [[DBG20:![0-9]+]]
+; CHECK:       [[FOR_INC]]:
+; CHECK-NEXT:    br label %[[SHADOW_TO_PTR_EXIT]], !dbg [[DBG21:![0-9]+]]
+;
 entry:
   br label %for.cond, !dbg !9
 
@@ -16,11 +42,6 @@ if.end.i60:                                       ; preds = %shadow_to_ptr.exit
   %cmp3.i = icmp ugt i64 %sub.i, 32763, !dbg !15
   %conv7.i = trunc i64 %sub.i to i32, !dbg !16
   %spec.select.i = select i1 %cmp3.i, i32 -1, i32 %conv7.i, !dbg !17
-; Jump threading is going to fold the select in to the branch. Ensure debug
-; info is not lost, and is merged from the select and the branch.
-; CHECK-NOT: br i1 %cmp3.i, label %for.inc, label %if.end22
-; CHECK: br i1 %cmp3.i, label %for.inc, label %if.end22, !dbg [[DBG:![0-9]+]]
-; CHECK: [[DBG]] = !DILocation(line: 9, column: 1, scope: !{{.*}})
 
   br label %ptr_to_shadow.exit, !dbg !17
 
@@ -68,3 +89,23 @@ for.inc:                                          ; preds = %ptr_to_shadow.exit
 !22 = !DILocation(line: 14, column: 1, scope: !7)
 !23 = !DILocation(line: 15, column: 1, scope: !7)
 !24 = !DILocation(line: 16, column: 1, scope: !7)
+;.
+; CHECK: [[META1:![0-9]+]] = distinct !DICompileUnit(language: DW_LANG_C, file: [[META2:![0-9]+]], producer: "debugify", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: [[META3:![0-9]+]])
+; CHECK: [[META2]] = !DIFile(filename: "{{.*}}jump_threading.ll", directory: {{.*}})
+; CHECK: [[META3]] = !{}
+; CHECK: [[DBG7]] = distinct !DISubprogram(name: "prepare_next_shadow", linkageName: "prepare_next_shadow", scope: null, file: [[META2]], line: 1, type: [[META8:![0-9]+]], scopeLine: 1, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: [[META1]], retainedNodes: [[META3]])
+; CHECK: [[META8]] = !DISubroutineType(types: [[META3]])
+; CHECK: [[DBG9]] = !DILocation(line: 1, column: 1, scope: [[DBG7]])
+; CHECK: [[DBG10]] = !DILocation(line: 3, column: 1, scope: [[DBG7]])
+; CHECK: [[DBG11]] = !DILocation(line: 4, column: 1, scope: [[DBG7]])
+; CHECK: [[DBG12]] = !DILocation(line: 5, column: 1, scope: [[DBG7]])
+; CHECK: [[DBG13]] = !DILocation(line: 6, column: 1, scope: [[DBG7]])
+; CHECK: [[DBG14]] = !DILocation(line: 7, column: 1, scope: [[DBG7]])
+; CHECK: [[DBG15]] = !DILocation(line: 8, column: 1, scope: [[DBG7]])
+; CHECK: [[DBG16]] = !DILocation(line: 9, column: 1, scope: [[DBG7]])
+; CHECK: [[DBG17]] = !DILocation(line: 12, column: 1, scope: [[DBG7]])
+; CHECK: [[DBG18]] = !DILocation(line: 13, column: 1, scope: [[DBG7]])
+; CHECK: [[DBG19]] = !DILocation(line: 14, column: 1, scope: [[DBG7]])
+; CHECK: [[DBG20]] = !DILocation(line: 15, column: 1, scope: [[DBG7]])
+; CHECK: [[DBG21]] = !DILocation(line: 16, column: 1, scope: [[DBG7]])
+;.

diff  --git a/llvm/test/Transforms/JumpThreading/select.ll b/llvm/test/Transforms/JumpThreading/select.ll
index 4ec55a66bb8ac..8e6ae8b6d1ee2 100644
--- a/llvm/test/Transforms/JumpThreading/select.ll
+++ b/llvm/test/Transforms/JumpThreading/select.ll
@@ -286,7 +286,8 @@ define void @unfold1(double %x, double %y) nounwind !prof !1 {
 ; CHECK:       cond.false:
 ; CHECK-NEXT:    [[ADD:%.*]] = fadd double [[X]], [[Y]]
 ; CHECK-NEXT:    [[CMP1:%.*]] = fcmp ogt double [[ADD]], 1.000000e+01
-; CHECK-NEXT:    br i1 [[CMP1]], label [[COND_END4]], label [[IF_THEN:%.*]], !prof [[PROF1:![0-9]+]]
+; CHECK-NEXT:    [[COND_FR:%.*]] = freeze i1 [[CMP1]]
+; CHECK-NEXT:    br i1 [[COND_FR]], label [[COND_END4]], label [[IF_THEN:%.*]], !prof [[PROF1:![0-9]+]]
 ; CHECK:       cond.end4:
 ; CHECK-NEXT:    [[COND5:%.*]] = phi double [ [[SUB]], [[ENTRY:%.*]] ], [ [[ADD]], [[COND_FALSE]] ]
 ; CHECK-NEXT:    [[CMP6:%.*]] = fcmp oeq double [[COND5]], 0.000000e+00
@@ -332,7 +333,8 @@ define void @unfold2(i32 %x, i32 %y) nounwind !prof !1 {
 ; CHECK:       cond.false:
 ; CHECK-NEXT:    [[ADD:%.*]] = add nsw i32 [[X]], [[Y]]
 ; CHECK-NEXT:    [[CMP1:%.*]] = icmp sgt i32 [[ADD]], 10
-; CHECK-NEXT:    br i1 [[CMP1]], label [[IF_THEN:%.*]], label [[COND_END4:%.*]], !prof [[PROF1]]
+; CHECK-NEXT:    [[COND_FR:%.*]] = freeze i1 [[CMP1]]
+; CHECK-NEXT:    br i1 [[COND_FR]], label [[IF_THEN:%.*]], label [[COND_END4:%.*]], !prof [[PROF1]]
 ; CHECK:       cond.end4:
 ; CHECK-NEXT:    [[COND5:%.*]] = phi i32 [ [[ADD]], [[COND_FALSE]] ]
 ; CHECK-NEXT:    [[CMP6:%.*]] = icmp eq i32 [[COND5]], 0
@@ -556,7 +558,8 @@ define void @test_func(ptr nocapture readonly %a, ptr nocapture readonly %b, ptr
 ; CHECK-NEXT:    [[ARRAYIDX5:%.*]] = getelementptr inbounds i32, ptr [[C:%.*]], i64 [[TMP0]]
 ; CHECK-NEXT:    [[TMP3:%.*]] = load i32, ptr [[ARRAYIDX5]], align 4
 ; CHECK-NEXT:    [[CMP6:%.*]] = icmp eq i32 [[TMP2]], [[TMP3]]
-; CHECK-NEXT:    br i1 [[CMP6]], label [[SW_BB:%.*]], label [[SW_BB7:%.*]]
+; CHECK-NEXT:    [[COND_FR:%.*]] = freeze i1 [[CMP6]]
+; CHECK-NEXT:    br i1 [[COND_FR]], label [[SW_BB:%.*]], label [[SW_BB7:%.*]]
 ; CHECK:       if.end:
 ; CHECK-NEXT:    [[LOCAL_VAR_0:%.*]] = phi i32 [ [[TMP1]], [[FOR_BODY]] ]
 ; CHECK-NEXT:    switch i32 [[LOCAL_VAR_0]], label [[SW_DEFAULT]] [

diff  --git a/llvm/test/Transforms/JumpThreading/stale-loop-info-after-unfold-select.ll b/llvm/test/Transforms/JumpThreading/stale-loop-info-after-unfold-select.ll
index ba40980c6bf89..086bafe831d5e 100644
--- a/llvm/test/Transforms/JumpThreading/stale-loop-info-after-unfold-select.ll
+++ b/llvm/test/Transforms/JumpThreading/stale-loop-info-after-unfold-select.ll
@@ -13,8 +13,8 @@ define dso_local ptr @func2(ptr %this, ptr) {
 ; CHECK:       while.cond:
 ; CHECK-NEXT:    [[MONTH_0:%.*]] = phi i32 [ undef, [[ENTRY:%.*]] ], [ [[CALL2:%.*]], [[FUNC1_EXIT:%.*]] ], [ [[ADD:%.*]], [[SELECT_UNFOLD:%.*]] ]
 ; CHECK-NEXT:    switch i32 [[MONTH_0]], label [[IF_END_I:%.*]] [
-; CHECK-NEXT:    i32 4, label [[FUNC1_EXIT]]
-; CHECK-NEXT:    i32 1, label [[FUNC1_EXIT]]
+; CHECK-NEXT:      i32 4, label [[FUNC1_EXIT]]
+; CHECK-NEXT:      i32 1, label [[FUNC1_EXIT]]
 ; CHECK-NEXT:    ]
 ; CHECK:       if.end.i:
 ; CHECK-NEXT:    br label [[FUNC1_EXIT]]
@@ -23,7 +23,8 @@ define dso_local ptr @func2(ptr %this, ptr) {
 ; CHECK-NEXT:    [[CALL2]] = tail call signext i32 @func3(i32 signext [[RETVAL_0_I]], i32 signext 1, i32 signext 3)
 ; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[CALL2]], 1
 ; CHECK-NEXT:    [[ADD]] = add nsw i32 [[CALL2]], 2
-; CHECK-NEXT:    br i1 [[CMP]], label [[SELECT_UNFOLD]], label [[WHILE_COND]]
+; CHECK-NEXT:    [[COND_FR:%.*]] = freeze i1 [[CMP]]
+; CHECK-NEXT:    br i1 [[COND_FR]], label [[SELECT_UNFOLD]], label [[WHILE_COND]]
 ;
 entry:
   br label %while.cond
@@ -49,3 +50,47 @@ func1.exit:                  ; preds = %if.end.i, %while.cond, %while.cond
 
 declare i32 @func3(i32, i32, i32)
 
+declare void @sink(i32)
+
+; Check that unfoldSelectInstr freezes a possibly-undef-or-poison condition
+; before using it as a branch input. A select with an undef or poison condition
+; does not immediately cause UB, but branching on undef or poison does.
+define void @unfold_select_poison_cond(i1 %c, i1 %maybe_poison) {
+; CHECK-LABEL: define void @unfold_select_poison_cond(
+; CHECK-SAME: i1 [[C:%.*]], i1 [[MAYBE_POISON:%.*]]) {
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br i1 [[C]], label %[[PRED1:.*]], label %[[IF_ELSE:.*]]
+; CHECK:       [[PRED1]]:
+; CHECK-NEXT:    [[COND_FR:%.*]] = freeze i1 [[MAYBE_POISON]]
+; CHECK-NEXT:    br i1 [[COND_FR]], label %[[IF_THEN:.*]], label %[[IF_ELSE]]
+; CHECK:       [[IF_THEN]]:
+; CHECK-NEXT:    call void @sink(i32 1)
+; CHECK-NEXT:    ret void
+; CHECK:       [[IF_ELSE]]:
+; CHECK-NEXT:    call void @sink(i32 2)
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 %c, label %pred1, label %pred2
+
+pred1:
+  %s = select i1 %maybe_poison, i32 0, i32 10
+  br label %merge
+
+pred2:
+  br label %merge
+
+merge:
+  %p = phi i32 [ %s, %pred1 ], [ 5, %pred2 ]
+  %cmp = icmp eq i32 %p, 0
+  %fcmp = freeze i1 %cmp
+  br i1 %fcmp, label %if_then, label %if_else
+
+if_then:
+  call void @sink(i32 1)
+  ret void
+
+if_else:
+  call void @sink(i32 2)
+  ret void
+}


        


More information about the llvm-commits mailing list