[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