llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Sirui Mu (Lancern)
<details>
<summary>Changes</summary>
This patch folds non-volatile non-atomic `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 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` early on CIR.
PR #<!-- -->175037 has some prior discussions on this topic.
Assisted-by: codex / gpt-5.6 sol
---
Full diff: https://github.com/llvm/llvm-project/pull/212284.diff
3 Files Affected:
- (modified) clang/lib/CIR/Dialect/Transforms/CIRSimplify.cpp (+47-1)
- (added) clang/test/CIR/Transforms/constant-load-fold.cir (+95)
- (added) clang/test/CIR/Transforms/constant-load-fold.cpp (+29)
``````````diff
diff --git a/clang/lib/CIR/Dialect/Transforms/CIRSimplify.cpp
b/clang/lib/CIR/Dialect/Transforms/CIRSimplify.cpp
index 45cf41052089c..b1e0d278724e3 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,50 @@ namespace mlir {
namespace {
+/// 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;
+ for (const mlir::OpOperand &use : allocaOp->getUses()) {
+ auto store = mlir::dyn_cast<cir::StoreOp>(use.getOwner());
+ if (!store || use.getOperandNumber() != cir::StoreOp::odsIndex_addr)
+ continue;
+ if (initStoreOp) {
+ // We have multiple stores to the alloca, give up
+ return mlir::failure();
+ }
+ initStoreOp = store;
+ }
+ if (!initStoreOp)
+ return mlir::failure();
+
+ mlir::DominanceInfo dominance;
+ if (!dominance.dominates(initStoreOp, op))
+ return mlir::failure();
+
+ rewriter.replaceOp(op, initStoreOp.getValue());
+ return mlir::success();
+ }
+};
+
/// Simplify suitable ternary operations into select operations.
///
/// For now we only simplify those ternary operations whose true and false
@@ -296,6 +341,7 @@ struct CIRSimplifyPass : public
impl::CIRSimplifyBase<CIRSimplifyPass> {
void populateMergeCleanupPatterns(RewritePatternSet &patterns) {
// clang-format off
patterns.add<
+ SimplifyConstantLoad,
SimplifyTernary,
SimplifySelect,
SimplifySwitch,
@@ -312,7 +358,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..28a5c76df1b42
--- /dev/null
+++ b/clang/test/CIR/Transforms/constant-load-fold.cir
@@ -0,0 +1,95 @@
+// 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 @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_multiple_stores
+// CHECK: cir.store
+// CHECK: cir.store
+// CHECK: %[[VALUE:.*]] = cir.load
+// CHECK: cir.return %[[VALUE]]
+cir.func @do_not_fold_multiple_stores() -> !s32i {
+ %x = cir.alloca "x" align(4) init const : !cir.ptr<!s32i>
+ %first = cir.const #cir.int<104> : !s32i
+ %second = cir.const #cir.int<105> : !s32i
+ cir.store %first, %x : !s32i, !cir.ptr<!s32i>
+ cir.store %second, %x : !s32i, !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..0dfee9f035ab7
--- /dev/null
+++ b/clang/test/CIR/Transforms/constant-load-fold.cpp
@@ -0,0 +1,29 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -Wno-unused-value
-fclangir -emit-cir %s -o %t.cir
+// 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-LABEL: @_Z18fold_constant_loadv
+// LLVM-LABEL: @_Z18fold_constant_loadv
+void fold_constant_load() {
+ const int x = g();
+ h(&x);
+ use(x);
+
+ // 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]])
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/212284
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits