[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