[llvm] [CVP] Fold store operands to constants using LazyValueInfo (PR #201195)

Aayush Shrivastava via llvm-commits llvm-commits at lists.llvm.org
Tue Jun 2 13:50:46 PDT 2026


https://github.com/iamaayushrivastava created https://github.com/llvm/llvm-project/pull/201195

Fixes #138636

Add a `processStore` function to CVP that calls `LVI->getConstant(StoredVal, SI)` at each store instruction. If LVI can prove the stored value is a constant at that program point, the operand is replaced with the constant directly.

>From 6023939ee3c7a96776c934e25daa1886fa3151a3 Mon Sep 17 00:00:00 2001
From: iamaayushrivastava <iamaayushrivastava at gmail.com>
Date: Wed, 3 Jun 2026 02:17:08 +0530
Subject: [PATCH] [CVP] Fold store operands to constants using LazyValueInfo

---
 .../Scalar/CorrelatedValuePropagation.cpp     |  23 +++
 .../store-const-fold.ll                       | 159 ++++++++++++++++++
 2 files changed, 182 insertions(+)
 create mode 100644 llvm/test/Transforms/CorrelatedValuePropagation/store-const-fold.ll

diff --git a/llvm/lib/Transforms/Scalar/CorrelatedValuePropagation.cpp b/llvm/lib/Transforms/Scalar/CorrelatedValuePropagation.cpp
index ff0b70b51e5f7..39b6a29fa4bc5 100644
--- a/llvm/lib/Transforms/Scalar/CorrelatedValuePropagation.cpp
+++ b/llvm/lib/Transforms/Scalar/CorrelatedValuePropagation.cpp
@@ -94,6 +94,8 @@ STATISTIC(NumSMinMax,
 STATISTIC(NumUDivURemsNarrowedExpanded,
           "Number of bound udiv's/urem's expanded");
 STATISTIC(NumNNeg, "Number of zext/uitofp non-negative deductions");
+STATISTIC(NumStoreVals,
+          "Number of stores with a constant value operand folded");
 
 static Constant *getConstantAt(Value *V, Instruction *At, LazyValueInfo *LVI) {
   if (Constant *C = LVI->getConstant(V, At))
@@ -1261,6 +1263,24 @@ static bool processTrunc(TruncInst *TI, LazyValueInfo *LVI) {
   return Changed;
 }
 
+/// Try to replace the stored value with a constant if LVI can determine it is
+/// constant at this point (e.g. because the value is derived from a branch
+/// condition known to be true/false on this path).
+static bool processStore(StoreInst *SI, LazyValueInfo *LVI) {
+  Value *StoredVal = SI->getValueOperand();
+  if (isa<Constant>(StoredVal))
+    return false;
+  // LVI does not handle vector types.
+  if (StoredVal->getType()->isVectorTy())
+    return false;
+  Constant *C = LVI->getConstant(StoredVal, SI);
+  if (!C)
+    return false;
+  ++NumStoreVals;
+  SI->setOperand(0, C);
+  return true;
+}
+
 static bool runImpl(Function &F, LazyValueInfo *LVI, DominatorTree *DT,
                     const SimplifyQuery &SQ) {
   bool FnChanged = false;
@@ -1327,6 +1347,9 @@ static bool runImpl(Function &F, LazyValueInfo *LVI, DominatorTree *DT,
       case Instruction::Trunc:
         BBChanged |= processTrunc(cast<TruncInst>(&II), LVI);
         break;
+      case Instruction::Store:
+        BBChanged |= processStore(cast<StoreInst>(&II), LVI);
+        break;
       }
     }
 
diff --git a/llvm/test/Transforms/CorrelatedValuePropagation/store-const-fold.ll b/llvm/test/Transforms/CorrelatedValuePropagation/store-const-fold.ll
new file mode 100644
index 0000000000000..1d8a3e7a36de4
--- /dev/null
+++ b/llvm/test/Transforms/CorrelatedValuePropagation/store-const-fold.ll
@@ -0,0 +1,159 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt -S -passes=correlated-propagation < %s | FileCheck %s
+
+; Basic case from issue #138636: zext(i1) stored in the false-branch successor
+; should be folded to 0 because the branch condition is known false there.
+define void @zext_i1_false_branch(ptr writeonly %p0, i1 %cond, ptr writeonly %p1) {
+; CHECK-LABEL: @zext_i1_false_branch(
+; CHECK-NEXT:    [[EXT:%.*]] = zext i1 [[COND:%.*]] to i8
+; CHECK-NEXT:    store i8 [[EXT]], ptr [[P0:%.*]], align 1
+; CHECK-NEXT:    br i1 [[COND]], label [[TRUE:%.*]], label [[FALSE:%.*]]
+; CHECK:       false:
+; CHECK-NEXT:    store i8 0, ptr [[P1:%.*]], align 1
+; CHECK-NEXT:    br label [[END:%.*]]
+; CHECK:       true:
+; CHECK-NEXT:    br label [[END]]
+; CHECK:       end:
+; CHECK-NEXT:    ret void
+;
+  %ext = zext i1 %cond to i8
+  store i8 %ext, ptr %p0, align 1
+  br i1 %cond, label %true, label %false
+
+false:
+  store i8 %ext, ptr %p1, align 1
+  br label %end
+
+true:
+  br label %end
+
+end:
+  ret void
+}
+
+; In the true-branch, zext(i1 true) == 1.
+define void @zext_i1_true_branch(ptr writeonly %p0, i1 %cond, ptr writeonly %p1) {
+; CHECK-LABEL: @zext_i1_true_branch(
+; CHECK-NEXT:    [[EXT:%.*]] = zext i1 [[COND:%.*]] to i8
+; CHECK-NEXT:    store i8 [[EXT]], ptr [[P0:%.*]], align 1
+; CHECK-NEXT:    br i1 [[COND]], label [[TRUE:%.*]], label [[FALSE:%.*]]
+; CHECK:       false:
+; CHECK-NEXT:    br label [[END:%.*]]
+; CHECK:       true:
+; CHECK-NEXT:    store i8 1, ptr [[P1:%.*]], align 1
+; CHECK-NEXT:    br label [[END]]
+; CHECK:       end:
+; CHECK-NEXT:    ret void
+;
+  %ext = zext i1 %cond to i8
+  store i8 %ext, ptr %p0, align 1
+  br i1 %cond, label %true, label %false
+
+false:
+  br label %end
+
+true:
+  store i8 %ext, ptr %p1, align 1
+  br label %end
+
+end:
+  ret void
+}
+
+; The entry block store cannot be folded — %cond is not yet known there.
+define void @no_fold_in_entry(ptr writeonly %p0, i1 %cond) {
+; CHECK-LABEL: @no_fold_in_entry(
+; CHECK-NEXT:    [[EXT:%.*]] = zext i1 [[COND:%.*]] to i8
+; CHECK-NEXT:    store i8 [[EXT]], ptr [[P0:%.*]], align 1
+; CHECK-NEXT:    br i1 [[COND]], label [[TRUE:%.*]], label [[FALSE:%.*]]
+; CHECK:       false:
+; CHECK-NEXT:    br label [[END:%.*]]
+; CHECK:       true:
+; CHECK-NEXT:    br label [[END]]
+; CHECK:       end:
+; CHECK-NEXT:    ret void
+;
+  %ext = zext i1 %cond to i8
+  store i8 %ext, ptr %p0, align 1
+  br i1 %cond, label %true, label %false
+
+false:
+  br label %end
+
+true:
+  br label %end
+
+end:
+  ret void
+}
+
+; zext i1 to i32 variant — same optimization applies.
+define void @zext_i1_to_i32_false_branch(ptr writeonly %p0, i1 %cond, ptr writeonly %p1) {
+; CHECK-LABEL: @zext_i1_to_i32_false_branch(
+; CHECK-NEXT:    [[EXT:%.*]] = zext i1 [[COND:%.*]] to i32
+; CHECK-NEXT:    store i32 [[EXT]], ptr [[P0:%.*]], align 4
+; CHECK-NEXT:    br i1 [[COND]], label [[TRUE:%.*]], label [[FALSE:%.*]]
+; CHECK:       false:
+; CHECK-NEXT:    store i32 0, ptr [[P1:%.*]], align 4
+; CHECK-NEXT:    br label [[END:%.*]]
+; CHECK:       true:
+; CHECK-NEXT:    br label [[END]]
+; CHECK:       end:
+; CHECK-NEXT:    ret void
+;
+  %ext = zext i1 %cond to i32
+  store i32 %ext, ptr %p0, align 4
+  br i1 %cond, label %true, label %false
+
+false:
+  store i32 %ext, ptr %p1, align 4
+  br label %end
+
+true:
+  br label %end
+
+end:
+  ret void
+}
+
+; Value derived from icmp: %cmp == 0 is known false in the true branch,
+; so %ext == 1.
+define void @zext_icmp_true_branch(ptr writeonly %p0, i32 %x, ptr writeonly %p1) {
+; CHECK-LABEL: @zext_icmp_true_branch(
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[X:%.*]], 0
+; CHECK-NEXT:    [[EXT:%.*]] = zext i1 [[CMP]] to i8
+; CHECK-NEXT:    store i8 [[EXT]], ptr [[P0:%.*]], align 1
+; CHECK-NEXT:    br i1 [[CMP]], label [[TRUE:%.*]], label [[FALSE:%.*]]
+; CHECK:       false:
+; CHECK-NEXT:    store i8 0, ptr [[P1:%.*]], align 1
+; CHECK-NEXT:    br label [[END:%.*]]
+; CHECK:       true:
+; CHECK-NEXT:    br label [[END]]
+; CHECK:       end:
+; CHECK-NEXT:    ret void
+;
+  %cmp = icmp eq i32 %x, 0
+  %ext = zext i1 %cmp to i8
+  store i8 %ext, ptr %p0, align 1
+  br i1 %cmp, label %true, label %false
+
+false:
+  store i8 %ext, ptr %p1, align 1
+  br label %end
+
+true:
+  br label %end
+
+end:
+  ret void
+}
+
+; Already a constant — nothing to do.
+define void @already_constant(ptr writeonly %p) {
+; CHECK-LABEL: @already_constant(
+; CHECK-NEXT:    store i8 0, ptr [[P:%.*]], align 1
+; CHECK-NEXT:    ret void
+;
+  store i8 0, ptr %p, align 1
+  ret void
+}



More information about the llvm-commits mailing list