https://github.com/Lancern updated https://github.com/llvm/llvm-project/pull/212284
>From 5b77992c5f1868ed8ab054c8657e3050004c9555 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 | 79 +++++++++- .../CIR/Transforms/constant-load-fold.cir | 140 ++++++++++++++++++ .../CIR/Transforms/constant-load-fold.cpp | 38 +++++ 3 files changed, 256 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..48f3d705521e1 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,81 @@ 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 or if multiple store +/// operations satisfy the criteria. +cir::StoreOp findDominatingInitOp(cir::AllocaOp alloca, cir::LoadOp load, + const DominanceInfo &domInfo) { + cir::StoreOp result; + + // 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)) { + if (result) { + // If we have already found a dominating store, then there are multiple + // dominating stores, we intentionally don't simplify the load. + return nullptr; + } + result = store; + } + } + + return result; +} + +/// 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(); + if (initStoreOp.getIsVolatile() || initStoreOp.getMemOrder()) { + // We intentionally act conservatively here and we don't want to simplify + // the load if the corresponding store is either volatile or atomic. + 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 +372,7 @@ struct CIRSimplifyPass : public impl::CIRSimplifyBase<CIRSimplifyPass> { void populateMergeCleanupPatterns(RewritePatternSet &patterns) { // clang-format off patterns.add< + SimplifyConstantLoad, SimplifyTernary, SimplifySelect, SimplifySwitch, @@ -312,7 +389,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..fdc3b1470f903 --- /dev/null +++ b/clang/test/CIR/Transforms/constant-load-fold.cir @@ -0,0 +1,140 @@ +// 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 +} + +// CHECK-LABEL: cir.func @do_not_fold_multiple_dominating_stores +// CHECK: cir.store +// CHECK: cir.store +// CHECK: %[[VALUE:.*]] = cir.load +// CHECK: cir.return %[[VALUE]] +cir.func @do_not_fold_multiple_dominating_stores() -> !s32i { + %x = cir.alloca "x" align(4) init const : !cir.ptr<!s32i> + %first = cir.const #cir.int<109> : !s32i + cir.store %first, %x : !s32i, !cir.ptr<!s32i> + %second = cir.const #cir.int<110> : !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_volatile_store +// CHECK: cir.store volatile +// CHECK: %[[VALUE:.*]] = cir.load +// CHECK: cir.return %[[VALUE]] +cir.func @do_not_fold_volatile_store() -> !s32i { + %x = cir.alloca "x" align(4) init const : !cir.ptr<!s32i> + %init = cir.const #cir.int<111> : !s32i + cir.store volatile %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_atomic_store +// CHECK: cir.store atomic(seq_cst) +// CHECK: %[[VALUE:.*]] = cir.load +// CHECK: cir.return %[[VALUE]] +cir.func @do_not_fold_atomic_store() -> !s32i { + %x = cir.alloca "x" align(4) init const : !cir.ptr<!s32i> + %init = cir.const #cir.int<112> : !s32i + cir.store atomic(seq_cst) %init, %x : !s32i, !cir.ptr<!s32i> + %value = cir.load %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
