[clang] [CIR] Initial support for emitting coroutine body (PR #161616)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Oct 1 19:20:04 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: None (Andres-Salamanca)
<details>
<summary>Changes</summary>
This PR adds new `FuncOp` attributes (`coroutine` and `builtin`) and begins the implementation of the `emitCoroutineBody` function. Feature markers were also added for guidance in future PRs.
---
Full diff: https://github.com/llvm/llvm-project/pull/161616.diff
12 Files Affected:
- (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+17)
- (modified) clang/include/clang/CIR/MissingFeatures.h (+7)
- (modified) clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp (+25)
- (added) clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp (+85)
- (modified) clang/lib/CIR/CodeGen/CIRGenFunction.cpp (+3)
- (modified) clang/lib/CIR/CodeGen/CIRGenFunction.h (+18)
- (modified) clang/lib/CIR/CodeGen/CIRGenModule.cpp (+9)
- (modified) clang/lib/CIR/CodeGen/CIRGenModule.h (+7)
- (modified) clang/lib/CIR/CodeGen/CIRGenStmt.cpp (+1)
- (modified) clang/lib/CIR/CodeGen/CMakeLists.txt (+1)
- (modified) clang/lib/CIR/Dialect/IR/CIRDialect.cpp (+13)
- (added) clang/test/CIR/CodeGen/coro-task.cpp (+123)
``````````diff
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index bb394440bf8d8..c19b90d30bf94 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -2326,6 +2326,12 @@ def CIR_FuncOp : CIR_Op<"func", [
The function linkage information is specified by `linkage`, as defined by
`GlobalLinkageKind` attribute.
+ A compiler builtin function must be marked as `builtin` for further
+ processing when lowering from CIR.
+
+ The `coroutine` keyword is used to mark coroutine function, which requires
+ at least one `cir.await` instruction to be used in its body.
+
The `lambda` translates to a C++ `operator()` that implements a lambda, this
allow callsites to make certain assumptions about the real function nature
when writing analysis.
@@ -2347,11 +2353,22 @@ def CIR_FuncOp : CIR_Op<"func", [
// Linkage information
cir.func linkonce_odr @some_method(...)
```
+ // Builtin function
+ cir.func builtin @__builtin_coro_end(!cir.ptr<i8>, !cir.bool) -> !cir.bool
+ // Coroutine
+ cir.func coroutine @_Z10silly_taskv() -> !CoroTask {
+ ...
+ cir.await(...)
+ ...
+ }
+ ```
}];
let arguments = (ins SymbolNameAttr:$sym_name,
CIR_VisibilityAttr:$global_visibility,
TypeAttrOf<CIR_FuncType>:$function_type,
+ UnitAttr:$builtin,
+ UnitAttr:$coroutine,
UnitAttr:$lambda,
UnitAttr:$no_proto,
UnitAttr:$dso_local,
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 0fac1b211239a..73beeacce5de2 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -135,6 +135,13 @@ struct MissingFeatures {
static bool recordZeroInitPadding() { return false; }
static bool zeroSizeRecordMembers() { return false; }
+ // Coroutines
+ static bool coroAllocBuiltinCall() { return false; }
+ static bool coroBeginBuiltinCall() { return false; }
+ static bool coroEndBuiltinCall() { return false; }
+ static bool coroSizeBuiltinCall() { return false; }
+ static bool coroutineFrame() { return false; }
+
// Various handling of deferred processing in CIRGenModule.
static bool cgmRelease() { return false; }
static bool deferredVtables() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
index cf17de144f4d9..512e1b2aa0e86 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -428,6 +428,31 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
return emitUnaryFPBuiltin<cir::ATanOp>(*this, *e);
case Builtin::BI__builtin_elementwise_cos:
return emitUnaryFPBuiltin<cir::CosOp>(*this, *e);
+ case Builtin::BI__builtin_coro_id:
+ case Builtin::BI__builtin_coro_promise:
+ case Builtin::BI__builtin_coro_resume:
+ case Builtin::BI__builtin_coro_noop:
+ case Builtin::BI__builtin_coro_destroy:
+ case Builtin::BI__builtin_coro_done:
+ case Builtin::BI__builtin_coro_alloc:
+ case Builtin::BI__builtin_coro_begin:
+ case Builtin::BI__builtin_coro_end:
+ case Builtin::BI__builtin_coro_suspend:
+ case Builtin::BI__builtin_coro_align:
+ llvm_unreachable("BI__builtin_coro_id like NYI");
+
+ case Builtin::BI__builtin_coro_frame: {
+ cgm.errorNYI(e->getSourceRange(), "BI__builtin_coro_frame NYI");
+ assert(!cir::MissingFeatures::coroutineFrame());
+ return getUndefRValue(e->getType());
+ }
+ case Builtin::BI__builtin_coro_free:
+ case Builtin::BI__builtin_coro_size: {
+ cgm.errorNYI(e->getSourceRange(),
+ "BI__builtin_coro_free, BI__builtin_coro_size NYI");
+ assert(!cir::MissingFeatures::coroSizeBuiltinCall());
+ return getUndefRValue(e->getType());
+ }
}
// If this is an alias for a lib function (e.g. __builtin_sin), emit
diff --git a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
new file mode 100644
index 0000000000000..ee43c221c3cd6
--- /dev/null
+++ b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
@@ -0,0 +1,85 @@
+//===----- CGCoroutine.cpp - Emit CIR Code for C++ coroutines -------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This contains code dealing with C++ code generation of coroutines.
+//
+//===----------------------------------------------------------------------===//
+
+#include "CIRGenFunction.h"
+#include "mlir/Support/LLVM.h"
+#include "clang/AST/StmtCXX.h"
+#include "clang/Basic/TargetInfo.h"
+#include "clang/CIR/Dialect/IR/CIRTypes.h"
+
+using namespace clang;
+using namespace clang::CIRGen;
+
+struct clang::CIRGen::CGCoroData {
+ // Stores the __builtin_coro_id emitted in the function so that we can supply
+ // it as the first argument to other builtins.
+ cir::CallOp coroId = nullptr;
+};
+
+// Defining these here allows to keep CGCoroData private to this file.
+CIRGenFunction::CGCoroInfo::CGCoroInfo() {}
+CIRGenFunction::CGCoroInfo::~CGCoroInfo() {}
+
+static void createCoroData(CIRGenFunction &cgf,
+ CIRGenFunction::CGCoroInfo &curCoro,
+ cir::CallOp coroId) {
+ if (curCoro.data) {
+ llvm_unreachable("EmitCoroutineBodyStatement called twice?");
+ return;
+ }
+
+ curCoro.data = std::unique_ptr<CGCoroData>(new CGCoroData);
+ curCoro.data->coroId = coroId;
+}
+
+cir::CallOp CIRGenFunction::emitCoroIDBuiltinCall(mlir::Location loc,
+ mlir::Value nullPtr) {
+ cir::IntType int32Ty = builder.getUInt32Ty();
+
+ const TargetInfo &ti = cgm.getASTContext().getTargetInfo();
+ unsigned newAlign = ti.getNewAlign() / ti.getCharWidth();
+
+ mlir::Operation *builtin = cgm.getGlobalValue(cgm.builtinCoroId);
+
+ cir::FuncOp fnOp;
+ if (!builtin) {
+ fnOp = cgm.createCIRBuiltinFunction(
+ loc, cgm.builtinCoroId,
+ cir::FuncType::get({int32Ty, VoidPtrTy, VoidPtrTy, VoidPtrTy}, int32Ty),
+ /*FD=*/nullptr);
+ assert(fnOp && "should always succeed");
+ } else
+ fnOp = cast<cir::FuncOp>(builtin);
+
+ return builder.createCallOp(loc, fnOp,
+ mlir::ValueRange{builder.getUInt32(newAlign, loc),
+ nullPtr, nullPtr, nullPtr});
+}
+
+mlir::LogicalResult
+CIRGenFunction::emitCoroutineBody(const CoroutineBodyStmt &s) {
+ mlir::Location openCurlyLoc = getLoc(s.getBeginLoc());
+ cir::ConstantOp nullPtrCst = builder.getNullPtr(VoidPtrTy, openCurlyLoc);
+
+ cir::FuncOp fn = curFn;
+ assert(fn && "other callables are NYI");
+ fn.setCoroutine(true);
+ cir::CallOp coroId = emitCoroIDBuiltinCall(openCurlyLoc, nullPtrCst);
+ createCoroData(*this, curCoro, coroId);
+
+ assert(!cir::MissingFeatures::coroAllocBuiltinCall());
+
+ assert(!cir::MissingFeatures::coroBeginBuiltinCall());
+
+ assert(!cir::MissingFeatures::generateDebugInfo());
+ return mlir::success();
+}
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
index 0abb21a670719..f435979801af9 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -342,6 +342,9 @@ void CIRGenFunction::LexicalScope::cleanup() {
cir::ReturnOp CIRGenFunction::LexicalScope::emitReturn(mlir::Location loc) {
CIRGenBuilderTy &builder = cgf.getBuilder();
+ // If we are on a coroutine, add the coro_end builtin call.
+ assert(!cir::MissingFeatures::coroEndBuiltinCall());
+
if (!cgf.curFn.getFunctionType().hasVoidReturn()) {
// Load the value from `__retval` and return it via the `cir.return` op.
auto value = builder.create<cir::LoadOp>(
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 166435f9e7e9e..ae8d14b1e98e5 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -47,6 +47,8 @@ class LoopOp;
namespace clang::CIRGen {
+struct CGCoroData;
+
class CIRGenFunction : public CIRGenTypeCache {
public:
CIRGenModule &cgm;
@@ -66,6 +68,18 @@ class CIRGenFunction : public CIRGenTypeCache {
/// The compiler-generated variable that holds the return value.
std::optional<mlir::Value> fnRetAlloca;
+ // Holds coroutine data if the current function is a coroutine. We use a
+ // wrapper to manage its lifetime, so that we don't have to define CGCoroData
+ // in this header.
+ struct CGCoroInfo {
+ std::unique_ptr<CGCoroData> data;
+ CGCoroInfo();
+ ~CGCoroInfo();
+ };
+ CGCoroInfo curCoro;
+
+ bool isCoroutine() const { return curCoro.data != nullptr; }
+
/// The temporary alloca to hold the return value. This is
/// invalid iff the function has no return value.
Address returnValue = Address::invalid();
@@ -1168,6 +1182,10 @@ class CIRGenFunction : public CIRGenTypeCache {
void emitConstructorBody(FunctionArgList &args);
+ mlir::LogicalResult emitCoroutineBody(const CoroutineBodyStmt &s);
+ cir::CallOp emitCoroEndBuiltinCall(mlir::Location loc, mlir::Value nullPtr);
+ cir::CallOp emitCoroIDBuiltinCall(mlir::Location loc, mlir::Value nullPtr);
+
void emitDestroy(Address addr, QualType type, Destroyer *destroyer);
void emitDestructorBody(FunctionArgList &args);
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index eef23a0ebda7f..e36df8da1d6c0 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -2043,6 +2043,15 @@ CIRGenModule::createCIRFunction(mlir::Location loc, StringRef name,
return func;
}
+cir::FuncOp
+CIRGenModule::createCIRBuiltinFunction(mlir::Location loc, StringRef name,
+ cir::FuncType ty,
+ const clang::FunctionDecl *fd) {
+ cir::FuncOp fnOp = createCIRFunction(loc, name, ty, fd);
+ fnOp.setBuiltin(true);
+ return fnOp;
+}
+
mlir::SymbolTable::Visibility
CIRGenModule::getMLIRVisibility(cir::GlobalOp op) {
// MLIR doesn't accept public symbols declarations (only
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h
index 073e8d96b773b..6cf40874e0997 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -448,6 +448,13 @@ class CIRGenModule : public CIRGenTypeCache {
cir::FuncType funcType,
const clang::FunctionDecl *funcDecl);
+ /// Create a CIR function with builtin attribute set.
+ cir::FuncOp createCIRBuiltinFunction(mlir::Location loc, llvm::StringRef name,
+ cir::FuncType ty,
+ const clang::FunctionDecl *fd);
+
+ static constexpr const char *builtinCoroId = "__builtin_coro_id";
+
/// Given a builtin id for a function like "__builtin_fabsf", return a
/// Function* for "fabsf".
cir::FuncOp getBuiltinLibFunction(const FunctionDecl *fd, unsigned builtinID);
diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
index e842892d085d2..22a1c54015e63 100644
--- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -197,6 +197,7 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *s,
case Stmt::SEHLeaveStmtClass:
case Stmt::SYCLKernelCallStmtClass:
case Stmt::CoroutineBodyStmtClass:
+ return emitCoroutineBody(cast<CoroutineBodyStmt>(*s));
case Stmt::CoreturnStmtClass:
case Stmt::CXXTryStmtClass:
case Stmt::IndirectGotoStmtClass:
diff --git a/clang/lib/CIR/CodeGen/CMakeLists.txt b/clang/lib/CIR/CodeGen/CMakeLists.txt
index c1f27ec8ba858..73fddb184a792 100644
--- a/clang/lib/CIR/CodeGen/CMakeLists.txt
+++ b/clang/lib/CIR/CodeGen/CMakeLists.txt
@@ -14,6 +14,7 @@ add_clang_library(clangCIR
CIRGenCall.cpp
CIRGenClass.cpp
CIRGenCleanup.cpp
+ CIRGenCoroutine.cpp
CIRGenCXX.cpp
CIRGenCXXABI.cpp
CIRGenBuiltin.cpp
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 58ef500446aa7..dae9162214e6c 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -1546,12 +1546,19 @@ ParseResult cir::FuncOp::parse(OpAsmParser &parser, OperationState &state) {
llvm::SMLoc loc = parser.getCurrentLocation();
mlir::Builder &builder = parser.getBuilder();
+ mlir::StringAttr builtinNameAttr = getBuiltinAttrName(state.name);
+ mlir::StringAttr coroutineNameAttr = getCoroutineAttrName(state.name);
mlir::StringAttr lambdaNameAttr = getLambdaAttrName(state.name);
mlir::StringAttr noProtoNameAttr = getNoProtoAttrName(state.name);
mlir::StringAttr visNameAttr = getSymVisibilityAttrName(state.name);
mlir::StringAttr visibilityNameAttr = getGlobalVisibilityAttrName(state.name);
mlir::StringAttr dsoLocalNameAttr = getDsoLocalAttrName(state.name);
+ if (::mlir::succeeded(parser.parseOptionalKeyword(builtinNameAttr.strref())))
+ state.addAttribute(builtinNameAttr, parser.getBuilder().getUnitAttr());
+ if (::mlir::succeeded(
+ parser.parseOptionalKeyword(coroutineNameAttr.strref())))
+ state.addAttribute(coroutineNameAttr, parser.getBuilder().getUnitAttr());
if (::mlir::succeeded(parser.parseOptionalKeyword(lambdaNameAttr.strref())))
state.addAttribute(lambdaNameAttr, parser.getBuilder().getUnitAttr());
if (parser.parseOptionalKeyword(noProtoNameAttr).succeeded())
@@ -1661,6 +1668,12 @@ mlir::Region *cir::FuncOp::getCallableRegion() {
}
void cir::FuncOp::print(OpAsmPrinter &p) {
+ if (getBuiltin())
+ p << " builtin";
+
+ if (getCoroutine())
+ p << " coroutine";
+
if (getLambda())
p << " lambda";
diff --git a/clang/test/CIR/CodeGen/coro-task.cpp b/clang/test/CIR/CodeGen/coro-task.cpp
new file mode 100644
index 0000000000000..1fc7d77be2bce
--- /dev/null
+++ b/clang/test/CIR/CodeGen/coro-task.cpp
@@ -0,0 +1,123 @@
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
+
+namespace std {
+
+template<typename T> struct remove_reference { typedef T type; };
+template<typename T> struct remove_reference<T &> { typedef T type; };
+template<typename T> struct remove_reference<T &&> { typedef T type; };
+
+template<typename T>
+typename remove_reference<T>::type &&move(T &&t) noexcept;
+
+template <class Ret, typename... T>
+struct coroutine_traits { using promise_type = typename Ret::promise_type; };
+
+template <class Promise = void>
+struct coroutine_handle {
+ static coroutine_handle from_address(void *) noexcept;
+};
+template <>
+struct coroutine_handle<void> {
+ template <class PromiseType>
+ coroutine_handle(coroutine_handle<PromiseType>) noexcept;
+ static coroutine_handle from_address(void *);
+};
+
+struct suspend_always {
+ bool await_ready() noexcept { return false; }
+ void await_suspend(coroutine_handle<>) noexcept {}
+ void await_resume() noexcept {}
+};
+
+struct suspend_never {
+ bool await_ready() noexcept { return true; }
+ void await_suspend(coroutine_handle<>) noexcept {}
+ void await_resume() noexcept {}
+};
+
+} // namespace std
+
+namespace folly {
+namespace coro {
+
+using std::suspend_always;
+using std::suspend_never;
+using std::coroutine_handle;
+
+using SemiFuture = int;
+
+template<class T>
+struct Task {
+ struct promise_type {
+ Task<T> get_return_object() noexcept;
+ suspend_always initial_suspend() noexcept;
+ suspend_always final_suspend() noexcept;
+ void return_value(T);
+ void unhandled_exception();
+ auto yield_value(Task<T>) noexcept { return final_suspend(); }
+ };
+ bool await_ready() noexcept { return false; }
+ void await_suspend(coroutine_handle<>) noexcept {}
+ T await_resume();
+};
+
+template<>
+struct Task<void> {
+ struct promise_type {
+ Task<void> get_return_object() noexcept;
+ suspend_always initial_suspend() noexcept;
+ suspend_always final_suspend() noexcept;
+ void return_void() noexcept;
+ void unhandled_exception() noexcept;
+ auto yield_value(Task<void>) noexcept { return final_suspend(); }
+ };
+ bool await_ready() noexcept { return false; }
+ void await_suspend(coroutine_handle<>) noexcept {}
+ void await_resume() noexcept {}
+ SemiFuture semi();
+};
+
+// FIXME: add CIRGen support here.
+// struct blocking_wait_fn {
+// template <typename T>
+// T operator()(Task<T>&& awaitable) const {
+// return T();
+// }
+// };
+
+// inline constexpr blocking_wait_fn blocking_wait{};
+// static constexpr blocking_wait_fn const& blockingWait = blocking_wait;
+
+struct co_invoke_fn {
+ template <typename F, typename... A>
+ Task<void> operator()(F&& f, A&&... a) const {
+ return Task<void>();
+ }
+};
+
+co_invoke_fn co_invoke;
+
+}} // namespace folly::coro
+
+// CIR-DAG: ![[VoidTask:.*]] = !cir.record<struct "folly::coro::Task<void>" padded {!u8i}>
+
+// CIR: module {{.*}} {
+// CIR-NEXT: cir.global external @_ZN5folly4coro9co_invokeE = #cir.zero : !rec_folly3A3Acoro3A3Aco_invoke_fn
+
+// CIR: cir.func builtin private @__builtin_coro_id(!u32i, !cir.ptr<!void>, !cir.ptr<!void>, !cir.ptr<!void>) -> !u32i
+
+using VoidTask = folly::coro::Task<void>;
+
+VoidTask silly_task() {
+ co_await std::suspend_always();
+}
+
+// CIR: cir.func coroutine dso_local @_Z10silly_taskv() -> ![[VoidTask]]
+// CHECK: %[[#VoidTaskAddr:]] = cir.alloca ![[VoidTask]], {{.*}}, ["__retval"]
+
+// Get coroutine id with __builtin_coro_id.
+
+// CIR: %[[NullPtr:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!void>
+// CIR: %[[Align:.*]] = cir.const #cir.int<16> : !u32i
+// CIR: %[[CoroId:.*]] = cir.call @__builtin_coro_id(%[[Align]], %[[NullPtr]], %[[NullPtr]], %[[NullPtr]])
``````````
</details>
https://github.com/llvm/llvm-project/pull/161616
More information about the cfe-commits
mailing list