https://github.com/Lancern updated 
https://github.com/llvm/llvm-project/pull/212284

>From fc39a9dd4003434b3bb2b1ac8d5eec53be3bc989 Mon Sep 17 00:00:00 2001
From: Sirui Mu <[email protected]>
Date: Mon, 27 Jul 2026 23:35:30 +0800
Subject: [PATCH] [CIR] Fold load from constant alloca slots

This patch folds `cir.load` operation that loads from a constant alloca slot
into the initial value stored into that slot, if the initialization dominates
the load.

This effectively enables "constant folding" at the C/C++ language level.
Consider the following C/C++ source program:

```cpp
int g();
void use(int);

void h(const int *);   // <-- The body of h is external.
void f() {
  const int x = g();
  h(&x);
  use(x);
}
```

Since `h` is external, the conventional LLVM optimizer cannot eliminate the load
of `x` before the call to the `use` function. This patch enables this
optimization by folding the load of `x` priorly on ClangIR.

PR #175037 has some prior discussions on this topic.

Assisted-by: codex / gpt-5.6 sol
---
 .../CIR/Dialect/Transforms/CIRSimplify.cpp    |  66 +++++++++++-
 .../CIR/Transforms/constant-load-fold.cir     | 101 ++++++++++++++++++
 .../CIR/Transforms/constant-load-fold.cpp     |  38 +++++++
 3 files changed, 204 insertions(+), 1 deletion(-)
 create mode 100644 clang/test/CIR/Transforms/constant-load-fold.cir
 create mode 100644 clang/test/CIR/Transforms/constant-load-fold.cpp

diff --git a/clang/lib/CIR/Dialect/Transforms/CIRSimplify.cpp 
b/clang/lib/CIR/Dialect/Transforms/CIRSimplify.cpp
index 45cf41052089c..b4a0db17e7de9 100644
--- a/clang/lib/CIR/Dialect/Transforms/CIRSimplify.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CIRSimplify.cpp
@@ -9,6 +9,7 @@
 #include "PassDetail.h"
 #include "mlir/Dialect/Func/IR/FuncOps.h"
 #include "mlir/IR/Block.h"
+#include "mlir/IR/Dominance.h"
 #include "mlir/IR/Operation.h"
 #include "mlir/IR/PatternMatch.h"
 #include "mlir/IR/Region.h"
@@ -32,6 +33,68 @@ namespace mlir {
 
 namespace {
 
+/// Find the `cir.store` operation that stores to the given alloca and 
dominates
+/// the given load operation. Dominance calculation is done through the given
+/// DominanceInfo object.
+///
+/// Return nullptr if no such store operation exists. If multiple store
+/// operations satisfy the criteria, any of them may be returned.
+cir::StoreOp findDominatingInitOp(cir::AllocaOp alloca, cir::LoadOp load,
+                                  const DominanceInfo &domInfo) {
+  // Walk through all uses of the alloca and visit the store operations that
+  // store to the alloca
+  for (const mlir::OpOperand &use : alloca->getUses()) {
+    auto store = mlir::dyn_cast<cir::StoreOp>(use.getOwner());
+    if (!store)
+      continue;
+
+    // `cir.store` has two operands, we're only interested if the store is
+    // storing into the alloca, not if the store is storing the address of the
+    // alloca slot into somewhere else
+    if (use.getOperandNumber() != cir::StoreOp::odsIndex_addr)
+      continue;
+
+    if (domInfo.dominates(store, load))
+      return store;
+  }
+
+  return nullptr;
+}
+
+/// Simplify `cir.load` that loads from an alloca marked as "constant".
+///
+/// For example:
+///
+///   %0 = cir.alloca "x" align(4) const : !cir.ptr<!s32i>
+///   cir.store %init, %0 : !s32i, !cir.ptr<!s32i>
+///   %1 = cir.load %0 : !cir.ptr<!s32i>
+///
+/// All uses of the load above could be replaced with the SSA value `%init`.
+struct SimplifyConstantLoad : public OpRewritePattern<LoadOp> {
+  using OpRewritePattern<LoadOp>::OpRewritePattern;
+
+  LogicalResult matchAndRewrite(LoadOp op,
+                                PatternRewriter &rewriter) const override {
+    // Volatile or atomic loads should not be simplified.
+    if (op.getIsVolatile() || op.getMemOrder())
+      return mlir::failure();
+
+    auto allocaOp = op.getAddr().getDefiningOp<cir::AllocaOp>();
+    if (!allocaOp || !allocaOp.getConstant())
+      return mlir::failure();
+
+    cir::StoreOp initStoreOp = findDominatingInitOp(allocaOp, op, domInfo);
+    if (!initStoreOp)
+      return mlir::failure();
+
+    rewriter.replaceOp(op, initStoreOp.getValue());
+    return mlir::success();
+  }
+
+private:
+  mlir::DominanceInfo domInfo;
+};
+
 /// Simplify suitable ternary operations into select operations.
 ///
 /// For now we only simplify those ternary operations whose true and false
@@ -296,6 +359,7 @@ struct CIRSimplifyPass : public 
impl::CIRSimplifyBase<CIRSimplifyPass> {
 void populateMergeCleanupPatterns(RewritePatternSet &patterns) {
   // clang-format off
   patterns.add<
+    SimplifyConstantLoad,
     SimplifyTernary,
     SimplifySelect,
     SimplifySwitch,
@@ -312,7 +376,7 @@ void CIRSimplifyPass::runOnOperation() {
   // Collect operations to apply patterns.
   llvm::SmallVector<Operation *, 16> ops;
   getOperation()->walk([&](Operation *op) {
-    if (isa<TernaryOp, SelectOp, SwitchOp, VecSplatOp>(op))
+    if (isa<TernaryOp, SelectOp, SwitchOp, VecSplatOp, LoadOp>(op))
       ops.push_back(op);
   });
 
diff --git a/clang/test/CIR/Transforms/constant-load-fold.cir 
b/clang/test/CIR/Transforms/constant-load-fold.cir
new file mode 100644
index 0000000000000..72ebdac6313f3
--- /dev/null
+++ b/clang/test/CIR/Transforms/constant-load-fold.cir
@@ -0,0 +1,101 @@
+// RUN: cir-opt %s -cir-simplify | FileCheck %s
+
+!s32i = !cir.int<s, 32>
+
+cir.func private @use_ptr(!cir.ptr<!s32i>)
+
+// CHECK-LABEL: cir.func @fold_constant_load
+// CHECK: %[[ALLOCA:.*]] = cir.alloca "x" {{.*}} const
+// CHECK: %[[INIT:.*]] = cir.const #cir.int<100>
+// CHECK: cir.store %[[INIT]], %[[ALLOCA]]
+// CHECK: cir.call @use_ptr(%[[ALLOCA]])
+// CHECK-NOT: cir.load
+// CHECK: cir.return %[[INIT]]
+cir.func @fold_constant_load() -> !s32i {
+  %x = cir.alloca "x" align(4) init const : !cir.ptr<!s32i>
+  %init = cir.const #cir.int<100> : !s32i
+  cir.store %init, %x : !s32i, !cir.ptr<!s32i>
+  cir.call @use_ptr(%x) : (!cir.ptr<!s32i>) -> ()
+  %value = cir.load %x : !cir.ptr<!s32i>, !s32i
+  cir.return %value : !s32i
+}
+
+// CHECK-LABEL: cir.func @fold_through_dominating_store
+// CHECK: cir.brcond
+// CHECK: %[[A:.+]] = cir.const #cir.int<10> : !s32i
+// CHECK: cir.return %[[A]] : !s32i
+// CHECK: %[[B:.+]] = cir.const #cir.int<20> : !s32i
+// CHECK: cir.return %[[B]] : !s32i
+cir.func @fold_through_dominating_store(%cond: !cir.bool) -> !s32i {
+  %x = cir.alloca "x" align(4) init const : !cir.ptr<!s32i>
+  cir.brcond %cond ^bb1, ^bb2
+^bb1:
+  %a = cir.const #cir.int<10> : !s32i
+  cir.store %a, %x : !s32i, !cir.ptr<!s32i>
+  %0 = cir.load %x : !cir.ptr<!s32i>, !s32i
+  cir.return %0 : !s32i
+^bb2:
+  %b = cir.const #cir.int<20> : !s32i
+  cir.store %b, %x : !s32i, !cir.ptr<!s32i>
+  %1 = cir.load %x : !cir.ptr<!s32i>, !s32i
+  cir.return %1 : !s32i
+}
+
+// CHECK-LABEL: cir.func @do_not_fold_non_constant_alloca
+// CHECK: %[[VALUE:.*]] = cir.load
+// CHECK: cir.return %[[VALUE]]
+cir.func @do_not_fold_non_constant_alloca() -> !s32i {
+  %x = cir.alloca "x" align(4) init : !cir.ptr<!s32i>
+  %init = cir.const #cir.int<103> : !s32i
+  cir.store %init, %x : !s32i, !cir.ptr<!s32i>
+  %value = cir.load %x : !cir.ptr<!s32i>, !s32i
+  cir.return %value : !s32i
+}
+
+// CHECK-LABEL: cir.func @do_not_fold_without_store
+// CHECK: %[[VALUE:.*]] = cir.load
+// CHECK: cir.return %[[VALUE]]
+cir.func @do_not_fold_without_store() -> !s32i {
+  %x = cir.alloca "x" align(4) const : !cir.ptr<!s32i>
+  %value = cir.load %x : !cir.ptr<!s32i>, !s32i
+  cir.return %value : !s32i
+}
+
+// CHECK-LABEL: cir.func @do_not_fold_non_dominating_store
+// CHECK: cir.brcond
+// CHECK: cir.store
+// CHECK: %[[VALUE:.*]] = cir.load
+// CHECK: cir.return %[[VALUE]]
+cir.func @do_not_fold_non_dominating_store(%cond: !cir.bool) -> !s32i {
+  %x = cir.alloca "x" align(4) init const : !cir.ptr<!s32i>
+  %init = cir.const #cir.int<106> : !s32i
+  cir.brcond %cond ^bb1, ^bb2
+^bb1:
+  cir.store %init, %x : !s32i, !cir.ptr<!s32i>
+  cir.br ^bb2
+^bb2:
+  %value = cir.load %x : !cir.ptr<!s32i>, !s32i
+  cir.return %value : !s32i
+}
+
+// CHECK-LABEL: cir.func @do_not_fold_volatile_load
+// CHECK: %[[VALUE:.*]] = cir.load volatile
+// CHECK: cir.return %[[VALUE]]
+cir.func @do_not_fold_volatile_load() -> !s32i {
+  %x = cir.alloca "x" align(4) init const : !cir.ptr<!s32i>
+  %init = cir.const #cir.int<107> : !s32i
+  cir.store %init, %x : !s32i, !cir.ptr<!s32i>
+  %value = cir.load volatile %x : !cir.ptr<!s32i>, !s32i
+  cir.return %value : !s32i
+}
+
+// CHECK-LABEL: cir.func @do_not_fold_atomic_load
+// CHECK: %[[VALUE:.*]] = cir.load atomic(seq_cst)
+// CHECK: cir.return %[[VALUE]]
+cir.func @do_not_fold_atomic_load() -> !s32i {
+  %x = cir.alloca "x" align(4) init const : !cir.ptr<!s32i>
+  %init = cir.const #cir.int<108> : !s32i
+  cir.store %init, %x : !s32i, !cir.ptr<!s32i>
+  %value = cir.load atomic(seq_cst) %x : !cir.ptr<!s32i>, !s32i
+  cir.return %value : !s32i
+}
diff --git a/clang/test/CIR/Transforms/constant-load-fold.cpp 
b/clang/test/CIR/Transforms/constant-load-fold.cpp
new file mode 100644
index 0000000000000..d2faba0e07dc8
--- /dev/null
+++ b/clang/test/CIR/Transforms/constant-load-fold.cpp
@@ -0,0 +1,38 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -Wno-unused-value 
-fclangir -emit-cir -mmlir --mlir-print-ir-before=cir-simplify %s -o %t.cir 2> 
%t-before-simplify.cir
+// RUN: FileCheck --input-file=%t-before-simplify.cir %s 
-check-prefix=CIR-BEFORE-SIMPLIFY
+// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -Wno-unused-value 
-fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM
+
+int g();
+void use(int);
+
+void h(const int *);
+
+// CIR-BEFORE-SIMPLIFY-LABEL: @_Z18fold_constant_loadv
+// CIR-LABEL: @_Z18fold_constant_loadv
+// LLVM-LABEL: @_Z18fold_constant_loadv
+void fold_constant_load() {
+  const int x = g();
+  h(&x);
+  use(x);
+
+  // CIR-BEFORE-SIMPLIFY: %[[ALLOCA:.+]] = cir.alloca "x" align(4) init const 
: !cir.ptr<!s32i>
+  // CIR-BEFORE-SIMPLIFY: %[[INIT:.+]] = cir.call @_Z1gv()
+  // CIR-BEFORE-SIMPLIFY: cir.store align(4) %[[INIT]], %[[ALLOCA]] : !s32i, 
!cir.ptr<!s32i>
+  // CIR-BEFORE-SIMPLIFY: cir.call @_Z1hPKi(%[[ALLOCA]])
+  // CIR-BEFORE-SIMPLIFY: %[[RELOAD:.+]] = cir.load align(4) %[[ALLOCA]] : 
!cir.ptr<!s32i>, !s32i
+  // CIR-BEFORE-SIMPLIFY: cir.call @_Z3usei(%[[RELOAD]])
+
+  // CIR: %[[ALLOCA:.+]] = cir.alloca "x" align(4) init const : !cir.ptr<!s32i>
+  // CIR: %[[INIT:.+]] = cir.call @_Z1gv()
+  // CIR: cir.store align(4) %[[INIT]], %[[ALLOCA]] : !s32i, !cir.ptr<!s32i>
+  // CIR: cir.call @_Z1hPKi(%[[ALLOCA]])
+  // CIR: cir.call @_Z3usei(%[[INIT]])
+
+  // LLVM: %[[ALLOCA:.+]] = alloca i32, align 4
+  // LLVM: %[[INIT:.+]] = tail call noundef i32 @_Z1gv()
+  // LLVM: store i32 %[[INIT]], ptr %[[ALLOCA]], align 4
+  // LLVM: call void @_Z1hPKi(ptr noundef nonnull %[[ALLOCA]])
+  // LLVM: call void @_Z3usei(i32 noundef %[[INIT]])
+}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to