[clang] [CIR][CodeGen] Eliminate unnecessary __retval alloca for simple returns (PR #186320)
Jie Zhang via cfe-commits
cfe-commits at lists.llvm.org
Tue Mar 31 02:49:08 PDT 2026
https://github.com/zj040045 updated https://github.com/llvm/llvm-project/pull/186320
>From 4aa6484a5eddcf5ef966b7251dbea2e05638d00d Mon Sep 17 00:00:00 2001
From: Jie Zhang <zhangjie.9307 at gmail.com>
Date: Thu, 12 Mar 2026 09:43:55 +0800
Subject: [PATCH 1/3] [CIR][CodeGen] Eliminate unnecessary __retval alloca for
simple returns
---
clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 4 +++
clang/lib/CIR/CodeGen/CIRGenStmt.cpp | 45 ++++++++++++++++++++----
clang/test/CIR/CodeGen/retval.c | 40 +++++++++++++++++++++
3 files changed, 82 insertions(+), 7 deletions(-)
create mode 100644 clang/test/CIR/CodeGen/retval.c
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
index 71a647e37ea52..5d3aedd8ecccb 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -815,6 +815,10 @@ cir::FuncOp CIRGenFunction::generateCode(clang::GlobalDecl gd, cir::FuncOp fn,
}
eraseEmptyAndUnusedBlocks(fn);
+
+ if (fnRetAlloca && fnRetAlloca->use_empty())
+ fnRetAlloca->getDefiningOp()->erase();
+
return fn;
}
diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
index a33a0b8bdd50c..871a1ebed0b2b 100644
--- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -585,6 +585,31 @@ mlir::LogicalResult CIRGenFunction::emitDeclStmt(const DeclStmt &s) {
return mlir::success();
}
+static cir::StoreOp findDominatingStoreToReturnValue(CIRGenFunction &cgf) {
+ mlir::Block *currentBlock = cgf.getBuilder().getInsertionBlock();
+ if (!currentBlock || currentBlock->empty())
+ return nullptr;
+
+ if (!cgf.fnRetAlloca)
+ return nullptr;
+
+ mlir::Value retAlloca = *cgf.fnRetAlloca;
+
+ for (auto &op : llvm::reverse(*currentBlock)) {
+ if (auto storeOp = dyn_cast<cir::StoreOp>(op)) {
+ if (storeOp.getAddr() == retAlloca) {
+ return storeOp;
+ }
+ return nullptr;
+ }
+
+ if (op.hasTrait<mlir::OpTrait::IsTerminator>() || isa<cir::CallOp>(op)) {
+ return nullptr;
+ }
+ }
+ return nullptr;
+}
+
mlir::LogicalResult CIRGenFunction::emitReturnStmt(const ReturnStmt &s) {
mlir::Location loc = getLoc(s.getSourceRange());
const Expr *rv = s.getRetValue();
@@ -677,15 +702,21 @@ mlir::LogicalResult CIRGenFunction::emitReturnStmt(const ReturnStmt &s) {
// a shared return block. Because CIR handles branching through cleanups
// during the CFG flattening phase, we can just emit the return statement
// directly.
- // TODO(cir): Eliminate this redundant load and the store above when we can.
if (fnRetAlloca) {
- // Load the value from `__retval` and return it via the `cir.return` op.
- cir::AllocaOp retAlloca =
- mlir::cast<cir::AllocaOp>(fnRetAlloca->getDefiningOp());
- auto value = cir::LoadOp::create(builder, loc, retAlloca.getAllocaType(),
- *fnRetAlloca);
+ mlir::Value returnValue;
+ if (cir::StoreOp storeOp = findDominatingStoreToReturnValue(*this)) {
+ returnValue = storeOp.getValue();
+ storeOp.erase();
+ } else {
+ // Load the value from `__retval` and return it via the `cir.return` op.
+ cir::AllocaOp retAlloca =
+ mlir::cast<cir::AllocaOp>(fnRetAlloca->getDefiningOp());
+ auto loadOp = cir::LoadOp::create(builder, loc, retAlloca.getAllocaType(),
+ *fnRetAlloca);
+ returnValue = loadOp.getResult();
+ }
- cir::ReturnOp::create(builder, loc, {value});
+ cir::ReturnOp::create(builder, loc, {returnValue});
} else {
cir::ReturnOp::create(builder, loc);
}
diff --git a/clang/test/CIR/CodeGen/retval.c b/clang/test/CIR/CodeGen/retval.c
new file mode 100644
index 0000000000000..454082338fc8f
--- /dev/null
+++ b/clang/test/CIR/CodeGen/retval.c
@@ -0,0 +1,40 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s
+
+// Simple scalar return: __retval should be eliminated.
+int add(int a, int b) { return a + b; }
+
+// CHECK-LABEL: cir.func{{.*}} @add(
+// CHECK-NOT: ["__retval"]
+// CHECK: cir.return
+// CHECK: }
+
+// Void return: no __retval at all.
+void noop(void) {}
+
+// CHECK-LABEL: cir.func{{.*}} @noop(
+// CHECK-NOT: ["__retval"]
+// CHECK: cir.return
+// CHECK: }
+
+int select_val(int a, int b, int c) {
+ if (c) return a;
+ return b;
+}
+
+// CHECK-LABEL: cir.func{{.*}} @select_val(
+// CHECK-NOT: ["__retval"]
+// CHECK: cir.return
+// CHECK: }
+
+typedef struct { int x; int y; } Pair;
+Pair make_pair(int a, int b) {
+ Pair p = {a, b};
+ return p;
+}
+
+// CHECK-LABEL: cir.func{{.*}} @make_pair(
+// CHECK: cir.alloca !rec_Pair, !cir.ptr<!rec_Pair>, ["__retval", init]
+// CHECK: cir.load %{{.+}} : !cir.ptr<!rec_Pair>, !rec_Pair
+// CHECK: cir.return
+// CHECK: }
>From a6ce4046e2c608d37ef20dc31a0da810f0d32b95 Mon Sep 17 00:00:00 2001
From: Jie Zhang <zhangjie.9307 at gmail.com>
Date: Sun, 29 Mar 2026 16:44:13 +0800
Subject: [PATCH 2/3] [CIR][CodeGen] Align findDominatingStoreToReturnValue
with CGCall.cpp (LLVM codegen)
---
clang/lib/CIR/CodeGen/CIRGenStmt.cpp | 95 +++++++++++++++++++++-------
1 file changed, 73 insertions(+), 22 deletions(-)
diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
index 871a1ebed0b2b..8c9b32a1b5506 100644
--- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -13,15 +13,18 @@
#include "CIRGenBuilder.h"
#include "CIRGenFunction.h"
-#include "mlir/IR/Builders.h"
-#include "mlir/IR/Location.h"
-#include "mlir/Support/LLVM.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/Stmt.h"
#include "clang/AST/StmtOpenACC.h"
#include "clang/AST/StmtOpenMP.h"
#include "clang/CIR/MissingFeatures.h"
+#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
+#include "mlir/IR/Builders.h"
+#include "mlir/IR/Location.h"
+#include "mlir/Support/LLVM.h"
+#include "llvm/ADT/SmallPtrSet.h"
+
using namespace clang;
using namespace clang::CIRGen;
using namespace cir;
@@ -585,29 +588,77 @@ mlir::LogicalResult CIRGenFunction::emitDeclStmt(const DeclStmt &s) {
return mlir::success();
}
+/// Heuristically search for a dominating store to the return-value slot.
+/// Mirrors \c findDominatingStoreToReturnValue in CGCall.cpp (LLVM codegen).
static cir::StoreOp findDominatingStoreToReturnValue(CIRGenFunction &cgf) {
- mlir::Block *currentBlock = cgf.getBuilder().getInsertionBlock();
- if (!currentBlock || currentBlock->empty())
- return nullptr;
-
- if (!cgf.fnRetAlloca)
- return nullptr;
-
- mlir::Value retAlloca = *cgf.fnRetAlloca;
-
- for (auto &op : llvm::reverse(*currentBlock)) {
- if (auto storeOp = dyn_cast<cir::StoreOp>(op)) {
- if (storeOp.getAddr() == retAlloca) {
- return storeOp;
- }
- return nullptr;
+ mlir::Value returnValuePtr = *cgf.fnRetAlloca;
+
+ // Check if a Operation is a store which address operand is the return-value
+ // slot. We are looking for stores to the ReturnValue, not for stores of the
+ // ReturnValue to some other location.
+ auto getStoreIfValid = [&cgf,
+ returnValuePtr](mlir::Operation *u) -> cir::StoreOp {
+ auto storeOp = mlir::dyn_cast<cir::StoreOp>(u);
+ if (!storeOp || storeOp.getAddr() != returnValuePtr ||
+ storeOp.getValue().getType() != cgf.returnValue.getElementType())
+ return {};
+ // These aren't actually possible for non-coerced returns, and we
+ // only care about non-coerced returns on this code path.
+ // All memory instructions inside __try block are volatile.
+ assert(!storeOp.getMemOrder() && "atomic store to return-value slot");
+ assert(!storeOp.getIsVolatile() && "volatile __retval store (SEH) NYI");
+ return storeOp;
+ };
+ // If there are multiple uses of the return-value slot, just check
+ // for something immediately preceding the IP. Sometimes this can
+ // happen with how we generate implicit-returns; it can also happen
+ // with noreturn cleanups.
+ if (!returnValuePtr.hasOneUse()) {
+ mlir::Block *ip = cgf.getBuilder().getInsertionBlock();
+ if (!ip || ip->empty())
+ return {};
+
+ // Look at directly preceding instruction, skipping bitcasts, lifetime
+ // markers, and fake uses and their operands.
+ const mlir::Operation *loadIntoFakeUse = nullptr;
+ for (mlir::Operation &op : llvm::reverse(*ip)) {
+ // Ignore instructions that are just loads for fake uses; the load should
+ // immediately precede the fake use, so we only need to remember the
+ // operand for the last fake use seen.
+ if (loadIntoFakeUse == &op)
+ continue;
+ if (mlir::isa<cir::CastOp>(op))
+ continue;
+ if (mlir::isa<mlir::LLVM::BitcastOp>(op))
+ continue;
+ if (mlir::isa<mlir::LLVM::LifetimeEndOp>(op))
+ continue;
+ // TODO(cir): skip llvm.fake_use and the defining load of its operand when
+ // those appear in CIR regions.
+
+ return getStoreIfValid(&op);
}
+ return {};
+ }
- if (op.hasTrait<mlir::OpTrait::IsTerminator>() || isa<cir::CallOp>(op)) {
- return nullptr;
- }
+ mlir::OpOperand &use = *returnValuePtr.use_begin();
+ cir::StoreOp store = getStoreIfValid(use.getOwner());
+ if (!store)
+ return {};
+
+ // Now do a first-and-dirty dominance check: just walk up the
+ // single-predecessors chain from the current insertion point.
+ mlir::Block *storeBB = store->getBlock();
+ mlir::Block *ip = cgf.getBuilder().getInsertionBlock();
+ llvm::SmallPtrSet<mlir::Block *, 4> seenBBs;
+ while (ip != storeBB) {
+ if (!seenBBs.insert(ip).second || !(ip = ip->getSinglePredecessor()))
+ return {};
}
- return nullptr;
+
+ // Okay, the store's basic block dominates the insertion point; we
+ // can do our thing.
+ return store;
}
mlir::LogicalResult CIRGenFunction::emitReturnStmt(const ReturnStmt &s) {
>From 45301b32591941e6c916d9cc28bb28b06a53fb75 Mon Sep 17 00:00:00 2001
From: Jie Zhang <zhangjie.9307 at gmail.com>
Date: Tue, 31 Mar 2026 17:48:16 +0800
Subject: [PATCH 3/3] [CIR][CodeGen] Add TODO comment to handle SEH when it is
implemented
---
clang/lib/CIR/CodeGen/CIRGenStmt.cpp | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
index 8c9b32a1b5506..1e63dccb70e8b 100644
--- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -605,8 +605,9 @@ static cir::StoreOp findDominatingStoreToReturnValue(CIRGenFunction &cgf) {
// These aren't actually possible for non-coerced returns, and we
// only care about non-coerced returns on this code path.
// All memory instructions inside __try block are volatile.
- assert(!storeOp.getMemOrder() && "atomic store to return-value slot");
- assert(!storeOp.getIsVolatile() && "volatile __retval store (SEH) NYI");
+ // TODO: SEH is not implemented in CIR yet. Once it is, allow volatile stores
+ // to the return-value slot when the enclosing function uses __try
+ assert(!storeOp.getMemOrder() && !storeOp.getIsVolatile());
return storeOp;
};
// If there are multiple uses of the return-value slot, just check
More information about the cfe-commits
mailing list