Author: Sirui Mu Date: 2026-08-16T21:53:26+08:00 New Revision: 3d3203a2284bd7c439e5de8fe1bd0ab8135e7b31
URL: https://github.com/llvm/llvm-project/commit/3d3203a2284bd7c439e5de8fe1bd0ab8135e7b31 DIFF: https://github.com/llvm/llvm-project/commit/3d3203a2284bd7c439e5de8fe1bd0ab8135e7b31.diff LOG: [CIR] Fold load from constant alloca slots (#212284) 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 Added: clang/test/CIR/Transforms/constant-load-fold.cir clang/test/CIR/Transforms/constant-load-fold.cpp Modified: clang/lib/CIR/Dialect/Transforms/CIRSimplify.cpp Removed: ################################################################################ diff --git a/clang/lib/CIR/Dialect/Transforms/CIRSimplify.cpp b/clang/lib/CIR/Dialect/Transforms/CIRSimplify.cpp index 45cf41052089c..eeb6a5ea10c0f 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 @@ -291,6 +367,9 @@ struct CIRSimplifyPass : public impl::CIRSimplifyBase<CIRSimplifyPass> { using CIRSimplifyBase::CIRSimplifyBase; void runOnOperation() override; + +private: + void runSimplifyConstantLoad(); }; void populateMergeCleanupPatterns(RewritePatternSet &patterns) { @@ -319,6 +398,24 @@ void CIRSimplifyPass::runOnOperation() { // Apply patterns. if (applyOpPatternsGreedily(ops, std::move(patterns)).failed()) signalPassFailure(); + + // SimplifyConstantLoad needs to query dominance information, which could be + // invalidated by other rewrite patterns. Thus we run it separately after + // other patterns have been applied. + runSimplifyConstantLoad(); +} + +void CIRSimplifyPass::runSimplifyConstantLoad() { + RewritePatternSet patterns(&getContext()); + patterns.add<SimplifyConstantLoad>(patterns.getContext()); + + llvm::SmallVector<Operation *, 16> ops; + getOperation()->walk([&](Operation *op) { + if (isa<LoadOp>(op)) + ops.push_back(op); + }); + if (applyOpPatternsGreedily(ops, std::move(patterns)).failed()) + signalPassFailure(); } } // namespace 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
