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, !cir.bool) -> !cir.bool + // Coroutine + cir.func coroutine @_Z10silly_taskv() -> !CoroTask { + ... + cir.await(...) + ... + } + ``` }]; let arguments = (ins SymbolNameAttr:$sym_name, CIR_VisibilityAttr:$global_visibility, TypeAttrOf:$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 7a6c084f51cd7..1d9f37ea295f4 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -137,6 +137,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(*this, *e); case Builtin::BI__builtin_elementwise_cos: return emitUnaryFPBuiltin(*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(); + 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(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(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 b26b4f2500579..27ee6decbed4d 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(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 c0ed8b4006ec5..aeedd0ce05fe8 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 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 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 2bd2729f0b0fb..f5facfa970253 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -2065,6 +2065,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 2c4c6dd14e2ff..d252bc0379ef7 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -473,6 +473,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(*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 struct remove_reference { typedef T type; }; +template struct remove_reference { typedef T type; }; +template struct remove_reference { typedef T type; }; + +template +typename remove_reference::type &&move(T &&t) noexcept; + +template +struct coroutine_traits { using promise_type = typename Ret::promise_type; }; + +template +struct coroutine_handle { + static coroutine_handle from_address(void *) noexcept; +}; +template <> +struct coroutine_handle { + template + coroutine_handle(coroutine_handle) 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 +struct Task { + struct promise_type { + Task 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) noexcept { return final_suspend(); } + }; + bool await_ready() noexcept { return false; } + void await_suspend(coroutine_handle<>) noexcept {} + T await_resume(); +}; + +template<> +struct Task { + struct promise_type { + Task 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) 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 +// T operator()(Task&& 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 + Task operator()(F&& f, A&&... a) const { + return Task(); + } +}; + +co_invoke_fn co_invoke; + +}} // namespace folly::coro + +// CIR-DAG: ![[VoidTask:.*]] = !cir.record" 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, !cir.ptr, !cir.ptr) -> !u32i + +using VoidTask = folly::coro::Task; + +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 : !cir.ptr +// 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 0e9a92fcf8201..48c8510fd9a87 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() + }