[clang] [CIR] Emit promise declaration in coroutine (PR #166683)
via cfe-commits
cfe-commits at lists.llvm.org
Sat Nov 8 14:20:23 PST 2025
https://github.com/Andres-Salamanca updated https://github.com/llvm/llvm-project/pull/166683
>From 79970c689a18c2c0a0a6f24c1ac0ab084faa0155 Mon Sep 17 00:00:00 2001
From: Andres Salamanca <andrealebarbaritos at gmail.com>
Date: Wed, 5 Nov 2025 20:16:54 -0500
Subject: [PATCH 1/2] [CIR] Emit promise declaration in coroutine
---
clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp | 103 +++++++++++++++++++++-
clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 4 +
clang/lib/CIR/CodeGen/CIRGenFunction.h | 3 +
clang/test/CIR/CodeGen/coro-task.cpp | 36 +++++++-
4 files changed, 143 insertions(+), 3 deletions(-)
diff --git a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
index 930ae55405756..133628045c8fb 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
@@ -13,6 +13,7 @@
#include "CIRGenFunction.h"
#include "mlir/Support/LLVM.h"
#include "clang/AST/StmtCXX.h"
+#include "clang/AST/StmtVisitor.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/CIR/Dialect/IR/CIRTypes.h"
#include "clang/CIR/MissingFeatures.h"
@@ -33,6 +34,65 @@ struct clang::CIRGen::CGCoroData {
CIRGenFunction::CGCoroInfo::CGCoroInfo() {}
CIRGenFunction::CGCoroInfo::~CGCoroInfo() {}
+namespace {
+// FIXME: both GetParamRef and ParamReferenceReplacerRAII are good template
+// candidates to be shared among LLVM / CIR codegen.
+
+// Hunts for the parameter reference in the parameter copy/move declaration.
+struct GetParamRef : public StmtVisitor<GetParamRef> {
+public:
+ DeclRefExpr *expr = nullptr;
+ GetParamRef() {}
+ void VisitDeclRefExpr(DeclRefExpr *e) {
+ assert(expr == nullptr && "multilple declref in param move");
+ expr = e;
+ }
+ void VisitStmt(Stmt *s) {
+ for (auto *c : s->children()) {
+ if (c)
+ Visit(c);
+ }
+ }
+};
+
+// This class replaces references to parameters to their copies by changing
+// the addresses in CGF.LocalDeclMap and restoring back the original values in
+// its destructor.
+struct ParamReferenceReplacerRAII {
+ CIRGenFunction::DeclMapTy savedLocals;
+ CIRGenFunction::DeclMapTy &localDeclMap;
+
+ ParamReferenceReplacerRAII(CIRGenFunction::DeclMapTy &localDeclMap)
+ : localDeclMap(localDeclMap) {}
+
+ void addCopy(DeclStmt const *pm) {
+ // Figure out what param it refers to.
+
+ assert(pm->isSingleDecl());
+ VarDecl const *vd = static_cast<VarDecl const *>(pm->getSingleDecl());
+ Expr const *initExpr = vd->getInit();
+ GetParamRef visitor;
+ visitor.Visit(const_cast<Expr *>(initExpr));
+ assert(visitor.expr);
+ DeclRefExpr *dreOrig = visitor.expr;
+ auto *pd = dreOrig->getDecl();
+
+ auto it = localDeclMap.find(pd);
+ assert(it != localDeclMap.end() && "parameter is not found");
+ savedLocals.insert({pd, it->second});
+
+ auto copyIt = localDeclMap.find(vd);
+ assert(copyIt != localDeclMap.end() && "parameter copy is not found");
+ it->second = copyIt->getSecond();
+ }
+
+ ~ParamReferenceReplacerRAII() {
+ for (auto &&savedLocal : savedLocals) {
+ localDeclMap.insert({savedLocal.first, savedLocal.second});
+ }
+ }
+};
+} // namespace
static void createCoroData(CIRGenFunction &cgf,
CIRGenFunction::CGCoroInfo &curCoro,
cir::CallOp coroId) {
@@ -149,7 +209,46 @@ CIRGenFunction::emitCoroutineBody(const CoroutineBodyStmt &s) {
if (s.getReturnStmtOnAllocFailure())
cgm.errorNYI("handle coroutine return alloc failure");
- assert(!cir::MissingFeatures::generateDebugInfo());
- assert(!cir::MissingFeatures::emitBodyAndFallthrough());
+ {
+ assert(!cir::MissingFeatures::generateDebugInfo());
+ ParamReferenceReplacerRAII paramReplacer(localDeclMap);
+ // Create mapping between parameters and copy-params for coroutine
+ // function.
+ llvm::ArrayRef<const Stmt *> paramMoves = s.getParamMoves();
+ assert((paramMoves.size() == 0 || (paramMoves.size() == fnArgs.size())) &&
+ "ParamMoves and FnArgs should be the same size for coroutine "
+ "function");
+ // For zipping the arg map into debug info.
+ assert(!cir::MissingFeatures::generateDebugInfo());
+
+ // Create parameter copies. We do it before creating a promise, since an
+ // evolution of coroutine TS may allow promise constructor to observe
+ // parameter copies.
+ for (auto *pm : paramMoves) {
+ if (emitStmt(pm, /*useCurrentScope=*/true).failed())
+ return mlir::failure();
+ paramReplacer.addCopy(cast<DeclStmt>(pm));
+ }
+
+ if (emitStmt(s.getPromiseDeclStmt(), /*useCurrentScope=*/true).failed())
+ return mlir::failure();
+ // returnValue should be valid as long as the coroutine's return type
+ // is not void. The assertion could help us to reduce the check later.
+ assert(returnValue.isValid() == (bool)s.getReturnStmt());
+ // Now we have the promise, initialize the GRO.
+ // We need to emit `get_return_object` first. According to:
+ // [dcl.fct.def.coroutine]p7
+ // The call to get_return_object is sequenced before the call to
+ // initial_suspend and is invoked at most once.
+ //
+ // So we couldn't emit return value when we emit return statment,
+ // otherwise the call to get_return_object wouldn't be in front
+ // of initial_suspend.
+ if (returnValue.isValid())
+ emitAnyExprToMem(s.getReturnValue(), returnValue,
+ s.getReturnValue()->getType().getQualifiers(),
+ /*isInit*/ true);
+ assert(!cir::MissingFeatures::emitBodyAndFallthrough());
+ }
return mlir::success();
}
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
index 71ff20a3b0e43..9925ad0a9f6d9 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -620,6 +620,10 @@ cir::FuncOp CIRGenFunction::generateCode(clang::GlobalDecl gd, cir::FuncOp fn,
startFunction(gd, retTy, fn, funcType, args, loc, bodyRange.getBegin());
+ // Save parameters for coroutine function.
+ if (body && isa_and_nonnull<CoroutineBodyStmt>(body))
+ llvm::append_range(fnArgs, funcDecl->parameters());
+
if (isa<CXXDestructorDecl>(funcDecl)) {
emitDestructorBody(args);
} else if (isa<CXXConstructorDecl>(funcDecl)) {
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index c3fcd1a69a88e..cb3c91bf2f0e5 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -152,6 +152,9 @@ class CIRGenFunction : public CIRGenTypeCache {
/// global initializers.
mlir::Operation *curFn = nullptr;
+ /// Save Parameter Decl for coroutine.
+ llvm::SmallVector<const ParmVarDecl *, 4> fnArgs;
+
using DeclMapTy = llvm::DenseMap<const clang::Decl *, Address>;
/// This keeps track of the CIR allocas or globals for local C
/// declarations.
diff --git a/clang/test/CIR/CodeGen/coro-task.cpp b/clang/test/CIR/CodeGen/coro-task.cpp
index 265325f82d7f7..5738c815909ea 100644
--- a/clang/test/CIR/CodeGen/coro-task.cpp
+++ b/clang/test/CIR/CodeGen/coro-task.cpp
@@ -36,6 +36,12 @@ struct suspend_never {
void await_resume() noexcept {}
};
+struct string {
+ int size() const;
+ string();
+ string(char const *s);
+};
+
} // namespace std
namespace folly {
@@ -101,7 +107,10 @@ co_invoke_fn co_invoke;
}} // namespace folly::coro
// CIR-DAG: ![[VoidTask:.*]] = !cir.record<struct "folly::coro::Task<void>" padded {!u8i}>
-
+// CIR-DAG: ![[IntTask:.*]] = !cir.record<struct "folly::coro::Task<int>" padded {!u8i}>
+// CIR-DAG: ![[VoidPromisse:.*]] = !cir.record<struct "folly::coro::Task<void>::promise_type" padded {!u8i}>
+// CIR-DAG: ![[IntPromisse:.*]] = !cir.record<struct "folly::coro::Task<int>::promise_type" padded {!u8i}>
+// CIR-DAG: ![[StdString:.*]] = !cir.record<struct "std::string" padded {!u8i}>
// CIR: module {{.*}} {
// CIR-NEXT: cir.global external @_ZN5folly4coro9co_invokeE = #cir.zero : !rec_folly3A3Acoro3A3Aco_invoke_fn
@@ -119,6 +128,7 @@ VoidTask silly_task() {
// CIR: cir.func coroutine dso_local @_Z10silly_taskv() -> ![[VoidTask]]
// CIR: %[[VoidTaskAddr:.*]] = cir.alloca ![[VoidTask]], {{.*}}, ["__retval"]
// CIR: %[[SavedFrameAddr:.*]] = cir.alloca !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>, ["__coro_frame_addr"]
+// CIR: %[[VoidPromisseAddr:.*]] = cir.alloca ![[VoidPromisse]], {{.*}}, ["__promise"]
// Get coroutine id with __builtin_coro_id.
@@ -138,3 +148,27 @@ VoidTask silly_task() {
// CIR: }
// CIR: %[[Load0:.*]] = cir.load{{.*}} %[[SavedFrameAddr]] : !cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void>
// CIR: %[[CoroFrameAddr:.*]] = cir.call @__builtin_coro_begin(%[[CoroId]], %[[Load0]])
+
+// Call promise.get_return_object() to retrieve the task object.
+
+// CIR: %[[RetObj:.*]] = cir.call @_ZN5folly4coro4TaskIvE12promise_type17get_return_objectEv(%[[VoidPromisseAddr]]) nothrow : {{.*}} -> ![[VoidTask]]
+// CIR: cir.store{{.*}} %[[RetObj]], %[[VoidTaskAddr]] : ![[VoidTask]]
+
+folly::coro::Task<int> byRef(const std::string& s) {
+ co_return s.size();
+}
+
+// CIR: cir.func coroutine dso_local @_Z5byRefRKSt6string(%[[ARG:.*]]: !cir.ptr<![[StdString]]> {{.*}}) -> ![[IntTask]]
+// CIR: %[[AllocaParam:.*]] = cir.alloca !cir.ptr<![[StdString]]>, {{.*}}, ["s", init, const]
+// CIR: %[[IntTaskAddr:.*]] = cir.alloca ![[IntTask]], {{.*}}, ["__retval"]
+// CIR: %[[SavedFrameAddr:.*]] = cir.alloca !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>, ["__coro_frame_addr"]
+// CIR: %[[AllocaFnUse:.*]] = cir.alloca !cir.ptr<![[StdString]]>, {{.*}}, ["s", init, const]
+// CIR: %[[IntPromisseAddr:.*]] = cir.alloca ![[IntPromisse]], {{.*}}, ["__promise"]
+// CIR: cir.store %[[ARG]], %[[AllocaParam]] : !cir.ptr<![[StdString]]>, {{.*}}
+
+// Call promise.get_return_object() to retrieve the task object.
+
+// CIR: %[[LOAD:.*]] = cir.load %[[AllocaParam]] : !cir.ptr<!cir.ptr<![[StdString]]>>, !cir.ptr<![[StdString]]>
+// CIR: cir.store {{.*}} %[[LOAD]], %[[AllocaFnUse]] : !cir.ptr<![[StdString]]>, !cir.ptr<!cir.ptr<![[StdString]]>>
+// CIR: %[[RetObj:.*]] = cir.call @_ZN5folly4coro4TaskIiE12promise_type17get_return_objectEv(%4) nothrow : {{.*}} -> ![[IntTask]]
+// CIR: cir.store {{.*}} %[[RetObj]], %[[IntTaskAddr]] : ![[IntTask]]
>From 2732abe24175252084330b1d39d6b36f7cc701c4 Mon Sep 17 00:00:00 2001
From: Andres Salamanca <andrealebarbaritos at gmail.com>
Date: Sat, 8 Nov 2025 17:20:06 -0500
Subject: [PATCH 2/2] Apply Reviews
---
clang/include/clang/CIR/MissingFeatures.h | 1 +
clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp | 9 +++++----
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 48ef8be9fb782..afc4ead8a8085 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -153,6 +153,7 @@ struct MissingFeatures {
static bool coroEndBuiltinCall() { return false; }
static bool coroutineFrame() { return false; }
static bool emitBodyAndFallthrough() { return false; }
+ static bool coroOutsideFrameMD() { return false; }
// Various handling of deferred processing in CIRGenModule.
static bool cgmRelease() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
index 133628045c8fb..05fb1aedcbf4a 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
@@ -48,7 +48,7 @@ struct GetParamRef : public StmtVisitor<GetParamRef> {
expr = e;
}
void VisitStmt(Stmt *s) {
- for (auto *c : s->children()) {
+ for (Stmt *c : s->children()) {
if (c)
Visit(c);
}
@@ -65,12 +65,12 @@ struct ParamReferenceReplacerRAII {
ParamReferenceReplacerRAII(CIRGenFunction::DeclMapTy &localDeclMap)
: localDeclMap(localDeclMap) {}
- void addCopy(DeclStmt const *pm) {
+ void addCopy(const DeclStmt *pm) {
// Figure out what param it refers to.
assert(pm->isSingleDecl());
- VarDecl const *vd = static_cast<VarDecl const *>(pm->getSingleDecl());
- Expr const *initExpr = vd->getInit();
+ const VarDecl *vd = static_cast<const VarDecl *>(pm->getSingleDecl());
+ const Expr *initExpr = vd->getInit();
GetParamRef visitor;
visitor.Visit(const_cast<Expr *>(initExpr));
assert(visitor.expr);
@@ -224,6 +224,7 @@ CIRGenFunction::emitCoroutineBody(const CoroutineBodyStmt &s) {
// Create parameter copies. We do it before creating a promise, since an
// evolution of coroutine TS may allow promise constructor to observe
// parameter copies.
+ assert(!cir::MissingFeatures::coroOutsideFrameMD());
for (auto *pm : paramMoves) {
if (emitStmt(pm, /*useCurrentScope=*/true).failed())
return mlir::failure();
More information about the cfe-commits
mailing list