[llvm] [EarlyCSE] Forward non-byte-sized integer stores to byte-aligned loads (PR #192604)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Apr 17 00:01:47 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: yiiiz (ZY546)
<details>
<summary>Changes</summary>
Fixed: https://github.com/llvm/llvm-project/issues/189213
Extend store-to-load forwarding in EarlyCSE to handle integer stores whose bit width is not a multiple of 8.
According to the new LangRef (see https://github.com/llvm/llvm-project/pull/180739), storing such values zero-extends them to the next byte-aligned size in memory. This patch allows forwarding in cases like:
store i1 %x, ptr %p
%v = load i8, ptr %p
by producing a zext from i1 to i8.
This enables further simplifications such as eliminating redundant loads.
Tests:
- Added coverage for i1->i8 and other non-byte-sized integer cases
- Added negative tests to ensure no incorrect forwarding occurs
---
Full diff: https://github.com/llvm/llvm-project/pull/192604.diff
2 Files Affected:
- (modified) llvm/lib/Transforms/Scalar/EarlyCSE.cpp (+27-1)
- (added) llvm/test/Transforms/EarlyCSE/store-non-byte-int-forwarding.ll (+220)
``````````diff
diff --git a/llvm/lib/Transforms/Scalar/EarlyCSE.cpp b/llvm/lib/Transforms/Scalar/EarlyCSE.cpp
index ff3d55e5747e5..ea7334236ef57 100644
--- a/llvm/lib/Transforms/Scalar/EarlyCSE.cpp
+++ b/llvm/lib/Transforms/Scalar/EarlyCSE.cpp
@@ -967,7 +967,33 @@ class EarlyCSE {
V = isa<LoadInst>(Inst) ? Inst : cast<StoreInst>(Inst)->getValueOperand();
}
- return V->getType() == ExpectedType ? V : nullptr;
+ if (V->getType() == ExpectedType) {
+ return V;
+ }
+
+ if (CanCreate && isa<StoreInst>(Inst)) {
+ auto *FromTy = dyn_cast<IntegerType>(V->getType());
+ auto *ToTy = dyn_cast<IntegerType>(ExpectedType);
+ if (!FromTy || !ToTy)
+ return nullptr;
+
+ unsigned FromBW = FromTy->getBitWidth();
+ unsigned RoundedBW = alignTo(FromBW, 8);
+ auto *ExtendedType = IntegerType::get(V->getContext(), RoundedBW);
+
+ if (ExpectedType != ExtendedType)
+ return nullptr;
+
+ if (auto *CI = dyn_cast<ConstantInt>(V))
+ return ConstantInt::get(cast<IntegerType>(ExpectedType),
+ CI->getValue().zext(RoundedBW));
+
+ return CastInst::Create(Instruction::ZExt, V,
+ cast<IntegerType>(ExpectedType), "",
+ Inst->getIterator());
+ } else {
+ return nullptr;
+ }
}
/// Return true if the instruction is known to only operate on memory
diff --git a/llvm/test/Transforms/EarlyCSE/store-non-byte-int-forwarding.ll b/llvm/test/Transforms/EarlyCSE/store-non-byte-int-forwarding.ll
new file mode 100644
index 0000000000000..b9745e8dc495c
--- /dev/null
+++ b/llvm/test/Transforms/EarlyCSE/store-non-byte-int-forwarding.ll
@@ -0,0 +1,220 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes=early-cse -S %s | FileCheck %s
+
+; =========================
+; Positive cases
+; =========================
+
+define i1 @store_i1_load_i8_false(ptr %p) {
+; CHECK-LABEL: define i1 @store_i1_load_i8_false(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT: store i1 false, ptr [[P]], align 1
+; CHECK-NEXT: ret i1 false
+;
+ store i1 false, ptr %p, align 1
+ %v = load i8, ptr %p, align 1
+ %t = trunc i8 %v to i1
+ ret i1 %t
+}
+
+define i1 @store_i1_load_i8_true(ptr %p) {
+; CHECK-LABEL: define i1 @store_i1_load_i8_true(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT: store i1 true, ptr [[P]], align 1
+; CHECK-NEXT: ret i1 true
+;
+ store i1 true, ptr %p, align 1
+ %v = load i8, ptr %p, align 1
+ %t = trunc i8 %v to i1
+ ret i1 %t
+}
+
+define i8 @store_i1_load_i8_variable(i1 %x, ptr %p) {
+; CHECK-LABEL: define i8 @store_i1_load_i8_variable(
+; CHECK-SAME: i1 [[X:%.*]], ptr [[P:%.*]]) {
+; CHECK-NEXT: [[EXT:%.*]] = zext i1 [[X]] to i8
+; CHECK-NEXT: store i1 [[X]], ptr [[P]], align 1
+; CHECK-NEXT: ret i8 [[EXT]]
+;
+ store i1 %x, ptr %p, align 1
+ %v = load i8, ptr %p, align 1
+ ret i8 %v
+}
+
+define i1 @store_i7_load_i8_trunc(ptr %p, i7 %x) {
+; CHECK-LABEL: define i1 @store_i7_load_i8_trunc(
+; CHECK-SAME: ptr [[P:%.*]], i7 [[X:%.*]]) {
+; CHECK-NEXT: [[EXT:%.*]] = zext i7 [[X]] to i8
+; CHECK-NEXT: store i7 [[X]], ptr [[P]], align 1
+; CHECK-NEXT: [[TR:%.*]] = trunc i8 [[EXT]] to i1
+; CHECK-NEXT: ret i1 [[TR]]
+;
+ store i7 %x, ptr %p, align 1
+ %v = load i8, ptr %p, align 1
+ %t = trunc i8 %v to i1
+ ret i1 %t
+}
+
+define i8 @store_i7_const_load_i8(ptr %p) {
+; CHECK-LABEL: define i8 @store_i7_const_load_i8(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT: store i7 5, ptr [[P]], align 1
+; CHECK-NEXT: ret i8 5
+;
+ store i7 5, ptr %p, align 1
+ %v = load i8, ptr %p, align 1
+ ret i8 %v
+}
+
+define i16 @store_i9_load_i16_variable(i9 %x, ptr %p) {
+; CHECK-LABEL: define i16 @store_i9_load_i16_variable(
+; CHECK-SAME: i9 [[X:%.*]], ptr [[P:%.*]]) {
+; CHECK-NEXT: [[EXT:%.*]] = zext i9 [[X]] to i16
+; CHECK-NEXT: store i9 [[X]], ptr [[P]], align 2
+; CHECK-NEXT: ret i16 [[EXT]]
+;
+ store i9 %x, ptr %p, align 2
+ %v = load i16, ptr %p, align 2
+ ret i16 %v
+}
+
+define i16 @store_i9_const_load_i16(ptr %p) {
+; CHECK-LABEL: define i16 @store_i9_const_load_i16(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT: store i9 -255, ptr [[P]], align 2
+; CHECK-NEXT: ret i16 257
+;
+ store i9 257, ptr %p, align 2
+ %v = load i16, ptr %p, align 2
+ ret i16 %v
+}
+
+define i24 @store_i20_load_i24_variable(i20 %x, ptr %p) {
+; CHECK-LABEL: define i24 @store_i20_load_i24_variable(
+; CHECK-SAME: i20 [[X:%.*]], ptr [[P:%.*]]) {
+; CHECK-NEXT: [[EXT:%.*]] = zext i20 [[X]] to i24
+; CHECK-NEXT: store i20 [[X]], ptr [[P]], align 4
+; CHECK-NEXT: ret i24 [[EXT]]
+;
+ store i20 %x, ptr %p, align 4
+ %v = load i24, ptr %p, align 4
+ ret i24 %v
+}
+
+define i24 @store_i20_const_load_i24(ptr %p) {
+; CHECK-LABEL: define i24 @store_i20_const_load_i24(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT: store i20 42, ptr [[P]], align 4
+; CHECK-NEXT: ret i24 42
+;
+ store i20 42, ptr %p, align 4
+ %v = load i24, ptr %p, align 4
+ ret i24 %v
+}
+
+; =========================
+; Negative cases
+; =========================
+
+; Byte-sized integer store: should not go through the new widening path.
+define i16 @store_i8_load_i16_no_forward(ptr %p, i8 %x) {
+; CHECK-LABEL: define i16 @store_i8_load_i16_no_forward(
+; CHECK-SAME: ptr [[P:%.*]], i8 [[X:%.*]]) {
+; CHECK-NEXT: store i8 [[X]], ptr [[P]], align 1
+; CHECK-NEXT: [[V:%.*]] = load i16, ptr [[P]], align 2
+; CHECK-NEXT: ret i16 [[V]]
+;
+ store i8 %x, ptr %p, align 1
+ %v = load i16, ptr %p, align 2
+ ret i16 %v
+}
+
+; Rounded type mismatch: i1 rounds to i8, not i16.
+define i16 @store_i1_load_i16_no_forward(ptr %p, i1 %x) {
+; CHECK-LABEL: define i16 @store_i1_load_i16_no_forward(
+; CHECK-SAME: ptr [[P:%.*]], i1 [[X:%.*]]) {
+; CHECK-NEXT: store i1 [[X]], ptr [[P]], align 1
+; CHECK-NEXT: [[V:%.*]] = load i16, ptr [[P]], align 2
+; CHECK-NEXT: ret i16 [[V]]
+;
+ store i1 %x, ptr %p, align 1
+ %v = load i16, ptr %p, align 2
+ ret i16 %v
+}
+
+; Rounded type mismatch: i9 rounds to i16, not i24.
+define i24 @store_i9_load_i24_no_forward(ptr %p, i9 %x) {
+; CHECK-LABEL: define i24 @store_i9_load_i24_no_forward(
+; CHECK-SAME: ptr [[P:%.*]], i9 [[X:%.*]]) {
+; CHECK-NEXT: store i9 [[X]], ptr [[P]], align 2
+; CHECK-NEXT: [[V:%.*]] = load i24, ptr [[P]], align 4
+; CHECK-NEXT: ret i24 [[V]]
+;
+ store i9 %x, ptr %p, align 2
+ %v = load i24, ptr %p, align 4
+ ret i24 %v
+}
+
+; Different address: must not forward.
+define i8 @store_i1_load_i8_different_ptr(ptr %p, ptr %q, i1 %x) {
+; CHECK-LABEL: define i8 @store_i1_load_i8_different_ptr(
+; CHECK-SAME: ptr [[P:%.*]], ptr [[Q:%.*]], i1 [[X:%.*]]) {
+; CHECK-NEXT: store i1 [[X]], ptr [[P]], align 1
+; CHECK-NEXT: [[V:%.*]] = load i8, ptr [[Q]], align 1
+; CHECK-NEXT: ret i8 [[V]]
+;
+ store i1 %x, ptr %p, align 1
+ %v = load i8, ptr %q, align 1
+ ret i8 %v
+}
+
+; Intervening clobber: second store changes memory contents.
+define i8 @store_i1_then_store_i8_load_i8_no_old_forward(ptr %p, i1 %x, i8 %y) {
+; CHECK-LABEL: define i8 @store_i1_then_store_i8_load_i8_no_old_forward(
+; CHECK-SAME: ptr [[P:%.*]], i1 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT: store i1 [[X]], ptr [[P]], align 1
+; CHECK-NEXT: store i8 [[Y]], ptr [[P]], align 1
+; CHECK-NEXT: ret i8 [[Y]]
+;
+ store i1 %x, ptr %p, align 1
+ store i8 %y, ptr %p, align 1
+ %v = load i8, ptr %p, align 1
+ ret i8 %v
+}
+
+; Load before store: should not use future store.
+define i8 @load_before_store_no_forward(ptr %p, i1 %x) {
+; CHECK-LABEL: define i8 @load_before_store_no_forward(
+; CHECK-SAME: ptr [[P:%.*]], i1 [[X:%.*]]) {
+; CHECK-NEXT: [[V:%.*]] = load i8, ptr [[P]], align 1
+; CHECK-NEXT: store i1 [[X]], ptr [[P]], align 1
+; CHECK-NEXT: ret i8 [[V]]
+;
+ %v = load i8, ptr %p, align 1
+ store i1 %x, ptr %p, align 1
+ ret i8 %v
+}
+
+; Float store should not be handled by the integer widening logic.
+define float @store_float_load_float_unchanged(ptr %p, float %x) {
+; CHECK-LABEL: define float @store_float_load_float_unchanged(
+; CHECK-SAME: ptr [[P:%.*]], float [[X:%.*]]) {
+; CHECK-NEXT: store float [[X]], ptr [[P]], align 4
+; CHECK-NEXT: ret float [[X]]
+;
+ store float %x, ptr %p, align 4
+ %v = load float, ptr %p, align 4
+ ret float %v
+}
+
+; Pointer store should not be handled by the integer widening logic.
+define ptr @store_ptr_load_ptr_unchanged(ptr %slot, ptr %x) {
+; CHECK-LABEL: define ptr @store_ptr_load_ptr_unchanged(
+; CHECK-SAME: ptr [[SLOT:%.*]], ptr [[X:%.*]]) {
+; CHECK-NEXT: store ptr [[X]], ptr [[SLOT]], align 8
+; CHECK-NEXT: ret ptr [[X]]
+;
+ store ptr %x, ptr %slot, align 8
+ %v = load ptr, ptr %slot, align 8
+ ret ptr %v
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/192604
More information about the llvm-commits
mailing list