[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