Author: Andres-Salamanca Date: 2025-10-03T13:56:53-05:00 New Revision: 16f5a85fb648027b9644b21c8c0fa9188d6c39b9
URL: https://github.com/llvm/llvm-project/commit/16f5a85fb648027b9644b21c8c0fa9188d6c39b9 DIFF: https://github.com/llvm/llvm-project/commit/16f5a85fb648027b9644b21c8c0fa9188d6c39b9.diff LOG: [CIR] Initial support for emitting coroutine body (#161616) 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. Added: clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp clang/test/CIR/CodeGen/coro-task.cpp Modified: clang/include/clang/CIR/Dialect/IR/CIROps.td clang/include/clang/CIR/MissingFeatures.h clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp clang/lib/CIR/CodeGen/CIRGenFunction.cpp clang/lib/CIR/CodeGen/CIRGenFunction.h clang/lib/CIR/CodeGen/CIRGenModule.cpp clang/lib/CIR/CodeGen/CIRGenModule.h clang/lib/CIR/CodeGen/CIRGenStmt.cpp clang/lib/CIR/CodeGen/CMakeLists.txt clang/lib/CIR/Dialect/IR/CIRDialect.cpp clang/test/CIR/IR/func.cir Removed: ################################################################################ diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 0a78492aa9a86..7f2e55d14b51c 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -2341,6 +2341,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 a 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. @@ -2362,11 +2368,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 3dfcafc0399a5..0e7cec4e1a0e0 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -136,6 +136,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..4cfa91e09efb4 100644 --- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp @@ -428,6 +428,32 @@ 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: + cgm.errorNYI(e->getSourceRange(), "BI__builtin_coro_id like NYI"); + return getUndefRValue(e->getType()); + + 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..c25cce4ab33b3 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp @@ -0,0 +1,82 @@ +//===----- 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) { + assert(!curCoro.data && "EmitCoroutineBodyStatement called twice?"); + + curCoro.data = std::make_unique<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); + + auto fn = mlir::cast<cir::FuncOp>(curFn); + 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 3e34f5ca541fe..52fb0d758dff8 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()); + auto fn = dyn_cast<cir::FuncOp>(cgf.curFn); assert(fn && "emitReturn from non-function"); if (!fn.getFunctionType().hasVoidReturn()) { diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index 3eba242550c2c..dfd9d2ccf0313 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(); @@ -1174,6 +1188,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 beeabafaeb964..8485564a4a117 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -2069,6 +2069,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 541432407fea5..c6a6681021d47 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -475,6 +475,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 644c383693e37..0b8f8bfdfb046 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 3ebf460f7d34c..36db4bd9bab25 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 6b5cc808e9a29..fba094f42414f 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -1632,12 +1632,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()) @@ -1747,6 +1754,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]]) diff --git a/clang/test/CIR/IR/func.cir b/clang/test/CIR/IR/func.cir index 9532859587629..d7e81849ab74e 100644 --- a/clang/test/CIR/IR/func.cir +++ b/clang/test/CIR/IR/func.cir @@ -99,4 +99,15 @@ cir.func @ullfunc() -> !u64i { // CHECK: %[[VAL:.*]] = cir.const #cir.int<42> : !u64i // CHECK: cir.return %[[VAL:.*]] : !u64i // CHECK: } + +cir.func coroutine @coro() { + cir.return +} +// CHECK: cir.func{{.*}} coroutine @coro() + +cir.func builtin @builtin() { + cir.return +} +// CHECK: cir.func{{.*}} builtin @builtin() + } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
