https://github.com/Andres-Salamanca updated https://github.com/llvm/llvm-project/pull/168133
>From baaec5c0649235f280a6c8bc0e760f3d84b5660d Mon Sep 17 00:00:00 2001 From: Andres Salamanca <[email protected]> Date: Fri, 14 Nov 2025 17:12:57 -0500 Subject: [PATCH 1/3] [CIR] Upstream CIR await op --- clang/include/clang/CIR/Dialect/IR/CIROps.td | 100 +++++++++++++++- clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp | 111 ++++++++++++++++++ clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp | 4 + clang/lib/CIR/CodeGen/CIRGenFunction.h | 3 + clang/lib/CIR/CodeGen/CIRGenValue.h | 1 + clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 80 ++++++++++++- .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 6 + clang/lib/CodeGen/CGValue.h | 1 + clang/test/CIR/CodeGen/coro-task.cpp | 39 ++++++ clang/test/CIR/IR/await.cir | 21 ++++ clang/test/CIR/IR/func.cir | 9 ++ clang/test/CIR/IR/invalid-await.cir | 19 +++ 12 files changed, 389 insertions(+), 5 deletions(-) create mode 100644 clang/test/CIR/IR/await.cir create mode 100644 clang/test/CIR/IR/invalid-await.cir diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 16258513239d9..832f79607a101 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -790,8 +790,8 @@ def CIR_ConditionOp : CIR_Op<"condition", [ //===----------------------------------------------------------------------===// defvar CIR_YieldableScopes = [ - "ArrayCtor", "ArrayDtor", "CaseOp", "DoWhileOp", "ForOp", "GlobalOp", "IfOp", - "ScopeOp", "SwitchOp", "TernaryOp", "WhileOp", "TryOp" + "ArrayCtor", "ArrayDtor", "AwaitOp", "CaseOp", "DoWhileOp", "ForOp", + "GlobalOp", "IfOp", "ScopeOp", "SwitchOp", "TernaryOp", "WhileOp", "TryOp" ]; def CIR_YieldOp : CIR_Op<"yield", [ @@ -2707,6 +2707,102 @@ def CIR_CallOp : CIR_CallOpBase<"call", [NoRegionArguments]> { ]; } +//===----------------------------------------------------------------------===// +// AwaitOp +//===----------------------------------------------------------------------===// + +def CIR_AwaitKind : CIR_I32EnumAttr<"AwaitKind", "await kind", [ + I32EnumAttrCase<"Init", 0, "init">, + I32EnumAttrCase<"User", 1, "user">, + I32EnumAttrCase<"Yield", 2, "yield">, + I32EnumAttrCase<"Final", 3, "final"> +]>; + +def CIR_AwaitOp : CIR_Op<"await",[ + DeclareOpInterfaceMethods<RegionBranchOpInterface>, + RecursivelySpeculatable, NoRegionArguments +]> { + let summary = "Wraps C++ co_await implicit logic"; + let description = [{ + The under the hood effect of using C++ `co_await expr` roughly + translates to: + + ```c++ + // co_await expr; + + auto &&x = CommonExpr(); + if (!x.await_ready()) { + ... + x.await_suspend(...); + ... + } + x.await_resume(); + ``` + + `cir.await` represents this logic by using 3 regions: + - ready: covers veto power from x.await_ready() + - suspend: wraps actual x.await_suspend() logic + - resume: handles x.await_resume() + + Breaking this up in regions allow individual scrutiny of conditions + which might lead to folding some of them out. Lowerings coming out + of CIR, e.g. LLVM, should use the `suspend` region to track more + lower level codegen (e.g. intrinsic emission for coro.save/coro.suspend). + + There are also 4 flavors of `cir.await` available: + - `init`: compiler generated initial suspend via implicit `co_await`. + - `user`: also known as normal, representing user written co_await's. + - `yield`: user written `co_yield` expressions. + - `final`: compiler generated final suspend via implicit `co_await`. + + From the C++ snippet we get: + + ```mlir + cir.scope { + ... // auto &&x = CommonExpr(); + cir.await(user, ready : { + ... // x.await_ready() + }, suspend : { + ... // x.await_suspend() + }, resume : { + ... // x.await_resume() + }) + } + ``` + + Note that resulution of the common expression is assumed to happen + as part of the enclosing await scope. + }]; + + let arguments = (ins CIR_AwaitKind:$kind); + let regions = (region SizedRegion<1>:$ready, + SizedRegion<1>:$suspend, + SizedRegion<1>:$resume); + let assemblyFormat = [{ + `(` $kind `,` + `ready` `:` $ready `,` + `suspend` `:` $suspend `,` + `resume` `:` $resume `,` + `)` + attr-dict + }]; + + let skipDefaultBuilders = 1; + let builders = [ + OpBuilder<(ins + "cir::AwaitKind":$kind, + CArg<"BuilderCallbackRef", + "nullptr">:$readyBuilder, + CArg<"BuilderCallbackRef", + "nullptr">:$suspendBuilder, + CArg<"BuilderCallbackRef", + "nullptr">:$resumeBuilder + )> + ]; + + let hasVerifier = 1; +} + //===----------------------------------------------------------------------===// // CopyOp //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp index 05fb1aedcbf4a..bb55991d9366a 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp @@ -22,6 +22,10 @@ using namespace clang; using namespace clang::CIRGen; struct clang::CIRGen::CGCoroData { + // What is the current await expression kind and how many + // await/yield expressions were encountered so far. + // These are used to generate pretty labels for await expressions in LLVM IR. + cir::AwaitKind currentAwaitKind = cir::AwaitKind::Init; // 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; @@ -249,7 +253,114 @@ CIRGenFunction::emitCoroutineBody(const CoroutineBodyStmt &s) { emitAnyExprToMem(s.getReturnValue(), returnValue, s.getReturnValue()->getType().getQualifiers(), /*isInit*/ true); + + assert(!cir::MissingFeatures::ehCleanupScope()); + // FIXME(cir): EHStack.pushCleanup<CallCoroEnd>(EHCleanup); + curCoro.data->currentAwaitKind = cir::AwaitKind::Init; + if (emitStmt(s.getInitSuspendStmt(), /*useCurrentScope=*/true).failed()) + return mlir::failure(); assert(!cir::MissingFeatures::emitBodyAndFallthrough()); } return mlir::success(); } +// Given a suspend expression which roughly looks like: +// +// auto && x = CommonExpr(); +// if (!x.await_ready()) { +// x.await_suspend(...); (*) +// } +// x.await_resume(); +// +// where the result of the entire expression is the result of x.await_resume() +// +// (*) If x.await_suspend return type is bool, it allows to veto a suspend: +// if (x.await_suspend(...)) +// llvm_coro_suspend(); +// +// This is more higher level than LLVM codegen, for that one see llvm's +// docs/Coroutines.rst for more details. +namespace { +struct LValueOrRValue { + LValue lv; + RValue rv; +}; +} // namespace + +static LValueOrRValue +emitSuspendExpression(CIRGenFunction &cgf, CGCoroData &coro, + CoroutineSuspendExpr const &s, cir::AwaitKind kind, + AggValueSlot aggSlot, bool ignoreResult, + mlir::Block *scopeParentBlock, + mlir::Value &tmpResumeRValAddr, bool forLValue) { + mlir::LogicalResult awaitBuild = mlir::success(); + LValueOrRValue awaitRes; + + CIRGenFunction::OpaqueValueMapping binder = + CIRGenFunction::OpaqueValueMapping(cgf, s.getOpaqueValue()); + CIRGenBuilderTy &builder = cgf.getBuilder(); + [[maybe_unused]] cir::AwaitOp awaitOp = cir::AwaitOp::create( + builder, cgf.getLoc(s.getSourceRange()), kind, + /*readyBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + builder.createCondition( + cgf.createDummyValue(loc, cgf.getContext().BoolTy)); + }, + /*suspendBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + cir::YieldOp::create(builder, loc); + }, + /*resumeBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + cir::YieldOp::create(builder, loc); + }); + + assert(awaitBuild.succeeded() && "Should know how to codegen"); + return awaitRes; +} + +static RValue emitSuspendExpr(CIRGenFunction &cgf, + const CoroutineSuspendExpr &e, + cir::AwaitKind kind, AggValueSlot aggSlot, + bool ignoreResult) { + RValue rval; + mlir::Location scopeLoc = cgf.getLoc(e.getSourceRange()); + + // Since we model suspend / resume as an inner region, we must store + // resume scalar results in a tmp alloca, and load it after we build the + // suspend expression. An alternative way to do this would be to make + // every region return a value when promise.return_value() is used, but + // it's a bit awkward given that resume is the only region that actually + // returns a value. + mlir::Block *currEntryBlock = cgf.curLexScope->getEntryBlock(); + [[maybe_unused]] mlir::Value tmpResumeRValAddr; + + // No need to explicitly wrap this into a scope since the AST already uses a + // ExprWithCleanups, which will wrap this into a cir.scope anyways. + rval = emitSuspendExpression(cgf, *cgf.curCoro.data, e, kind, aggSlot, + ignoreResult, currEntryBlock, tmpResumeRValAddr, + /*forLValue*/ false) + .rv; + + if (ignoreResult || rval.isIgnored()) + return rval; + + if (rval.isScalar()) { + rval = RValue::get(cir::LoadOp::create(cgf.getBuilder(), scopeLoc, + rval.getValue().getType(), + tmpResumeRValAddr)); + } else if (rval.isAggregate()) { + // This is probably already handled via AggSlot, remove this assertion + // once we have a testcase and prove all pieces work. + cgf.cgm.errorNYI("emitSuspendExpr Aggregate"); + } else { // complex + cgf.cgm.errorNYI("emitSuspendExpr Complex"); + } + return rval; +} + +RValue CIRGenFunction::emitCoawaitExpr(const CoawaitExpr &e, + AggValueSlot aggSlot, + bool ignoreResult) { + return emitSuspendExpr(*this, e, curCoro.data->currentAwaitKind, aggSlot, + ignoreResult); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp index 4461875fcf678..8925d26bff19d 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp @@ -154,6 +154,10 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> { return cgf.emitLoadOfLValue(lv, e->getExprLoc()).getValue(); } + mlir::Value VisitCoawaitExpr(CoawaitExpr *s) { + return cgf.emitCoawaitExpr(*s).getValue(); + } + mlir::Value emitLoadOfLValue(LValue lv, SourceLocation loc) { return cgf.emitLoadOfLValue(lv, loc).getValue(); } diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index b71a28c54dbef..9a4e413fe8f3a 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -1557,6 +1557,9 @@ class CIRGenFunction : public CIRGenTypeCache { void emitForwardingCallToLambda(const CXXMethodDecl *lambdaCallOperator, CallArgList &callArgs); + RValue emitCoawaitExpr(const CoawaitExpr &e, + AggValueSlot aggSlot = AggValueSlot::ignored(), + bool ignoreResult = false); /// Emit the computation of the specified expression of complex type, /// returning the result. mlir::Value emitComplexExpr(const Expr *e); diff --git a/clang/lib/CIR/CodeGen/CIRGenValue.h b/clang/lib/CIR/CodeGen/CIRGenValue.h index ab245a771d72c..e310910457a42 100644 --- a/clang/lib/CIR/CodeGen/CIRGenValue.h +++ b/clang/lib/CIR/CodeGen/CIRGenValue.h @@ -49,6 +49,7 @@ class RValue { bool isScalar() const { return flavor == Scalar; } bool isComplex() const { return flavor == Complex; } bool isAggregate() const { return flavor == Aggregate; } + bool isIgnored() const { return isScalar() && !getValue(); } bool isVolatileQualified() const { return isVolatile; } diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index 7ba03ce40140c..80b13a4b7fc2e 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -289,7 +289,10 @@ void cir::ConditionOp::getSuccessorRegions( regions.emplace_back(getOperation(), loopOp->getResults()); } - assert(!cir::MissingFeatures::awaitOp()); + // Parent is an await: condition may branch to resume or suspend regions. + auto await = cast<AwaitOp>(getOperation()->getParentOp()); + regions.emplace_back(&await.getResume(), await.getResume().getArguments()); + regions.emplace_back(&await.getSuspend(), await.getSuspend().getArguments()); } MutableOperandRange @@ -299,8 +302,7 @@ cir::ConditionOp::getMutableSuccessorOperands(RegionSuccessor point) { } LogicalResult cir::ConditionOp::verify() { - assert(!cir::MissingFeatures::awaitOp()); - if (!isa<LoopOpInterface>(getOperation()->getParentOp())) + if (!isa<LoopOpInterface, AwaitOp>(getOperation()->getParentOp())) return emitOpError("condition must be within a conditional region"); return success(); } @@ -1900,6 +1902,19 @@ void cir::FuncOp::print(OpAsmPrinter &p) { mlir::LogicalResult cir::FuncOp::verify() { + if (!isDeclaration() && getCoroutine()) { + bool foundAwait = false; + this->walk([&](Operation *op) { + if (auto await = dyn_cast<AwaitOp>(op)) { + foundAwait = true; + return; + } + }); + if (!foundAwait) + return emitOpError() + << "coroutine body must use at least one cir.await op"; + } + llvm::SmallSet<llvm::StringRef, 16> labels; llvm::SmallSet<llvm::StringRef, 16> gotos; @@ -2116,6 +2131,65 @@ OpFoldResult cir::UnaryOp::fold(FoldAdaptor adaptor) { return {}; } +//===----------------------------------------------------------------------===// +// AwaitOp +//===----------------------------------------------------------------------===// + +void cir::AwaitOp::build(OpBuilder &builder, OperationState &result, + cir::AwaitKind kind, BuilderCallbackRef readyBuilder, + BuilderCallbackRef suspendBuilder, + BuilderCallbackRef resumeBuilder) { + result.addAttribute(getKindAttrName(result.name), + cir::AwaitKindAttr::get(builder.getContext(), kind)); + { + OpBuilder::InsertionGuard guard(builder); + Region *readyRegion = result.addRegion(); + builder.createBlock(readyRegion); + readyBuilder(builder, result.location); + } + + { + OpBuilder::InsertionGuard guard(builder); + Region *suspendRegion = result.addRegion(); + builder.createBlock(suspendRegion); + suspendBuilder(builder, result.location); + } + + { + OpBuilder::InsertionGuard guard(builder); + Region *resumeRegion = result.addRegion(); + builder.createBlock(resumeRegion); + resumeBuilder(builder, result.location); + } +} + +/// Given the region at `index`, or the parent operation if `index` is None, +/// return the successor regions. These are the regions that may be selected +/// during the flow of control. `operands` is a set of optional attributes +/// that correspond to a constant value for each operand, or null if that +/// operand is not a constant. +void cir::AwaitOp::getSuccessorRegions( + mlir::RegionBranchPoint point, SmallVectorImpl<RegionSuccessor> ®ions) { + // If any index all the underlying regions branch back to the parent + // operation. + if (!point.isParent()) { + regions.push_back( + RegionSuccessor(getOperation(), getOperation()->getResults())); + return; + } + + // FIXME: we want to look at cond region for getting more accurate results + // if the other regions will get a chance to execute. + regions.push_back(RegionSuccessor(&this->getReady())); + regions.push_back(RegionSuccessor(&this->getSuspend())); + regions.push_back(RegionSuccessor(&this->getResume())); +} + +LogicalResult cir::AwaitOp::verify() { + if (!isa<ConditionOp>(this->getReady().back().getTerminator())) + return emitOpError("ready region must end with cir.condition"); + return success(); +} //===----------------------------------------------------------------------===// // CopyOp Definitions diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index b4afed7019417..6848876c88fea 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -3778,6 +3778,12 @@ mlir::LogicalResult CIRToLLVMVAArgOpLowering::matchAndRewrite( return mlir::success(); } +mlir::LogicalResult CIRToLLVMAwaitOpLowering::matchAndRewrite( + cir::AwaitOp op, OpAdaptor adaptor, + mlir::ConversionPatternRewriter &rewriter) const { + return mlir::failure(); +} + std::unique_ptr<mlir::Pass> createConvertCIRToLLVMPass() { return std::make_unique<ConvertCIRToLLVMPass>(); } diff --git a/clang/lib/CodeGen/CGValue.h b/clang/lib/CodeGen/CGValue.h index c4ec8d207d2e3..6b381b59e71cd 100644 --- a/clang/lib/CodeGen/CGValue.h +++ b/clang/lib/CodeGen/CGValue.h @@ -64,6 +64,7 @@ class RValue { bool isScalar() const { return Flavor == Scalar; } bool isComplex() const { return Flavor == Complex; } bool isAggregate() const { return Flavor == Aggregate; } + bool isIgnored() const { return isScalar() && !getScalarVal(); } bool isVolatileQualified() const { return IsVolatile; } diff --git a/clang/test/CIR/CodeGen/coro-task.cpp b/clang/test/CIR/CodeGen/coro-task.cpp index 5738c815909ea..01e0786fbda71 100644 --- a/clang/test/CIR/CodeGen/coro-task.cpp +++ b/clang/test/CIR/CodeGen/coro-task.cpp @@ -111,6 +111,8 @@ co_invoke_fn co_invoke; // 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-DAG: ![[SuspendAlways:.*]] = !cir.record<struct "std::suspend_always" padded {!u8i}> + // CIR: module {{.*}} { // CIR-NEXT: cir.global external @_ZN5folly4coro9co_invokeE = #cir.zero : !rec_folly3A3Acoro3A3Aco_invoke_fn @@ -153,6 +155,33 @@ VoidTask silly_task() { // CIR: %[[RetObj:.*]] = cir.call @_ZN5folly4coro4TaskIvE12promise_type17get_return_objectEv(%[[VoidPromisseAddr]]) nothrow : {{.*}} -> ![[VoidTask]] // CIR: cir.store{{.*}} %[[RetObj]], %[[VoidTaskAddr]] : ![[VoidTask]] +// Start a new scope for the actual codegen for co_await, create temporary allocas for +// holding coroutine handle and the suspend_always struct. + +// CIR: cir.scope { +// CIR: %[[SuspendAlwaysAddr:.*]] = cir.alloca ![[SuspendAlways]], {{.*}} ["ref.tmp0"] {alignment = 1 : i64} + +// Effectively execute `coawait promise_type::initial_suspend()` by calling initial_suspend() and getting +// the suspend_always struct to use for cir.await. Note that we return by-value since we defer ABI lowering +// to later passes, same is done elsewhere. + +// CIR: %[[Tmp0:.*]] = cir.call @_ZN5folly4coro4TaskIvE12promise_type15initial_suspendEv(%[[VoidPromisseAddr]]) +// CIR: cir.store{{.*}} %[[Tmp0:.*]], %[[SuspendAlwaysAddr]] + +// +// Here we start mapping co_await to cir.await. +// + +// First regions `ready` has a special cir.yield code to veto suspension. + +// CIR: cir.await(init, ready : { +// CIR: cir.condition({{.*}}) +// CIR: }, suspend : { +// CIR: cir.yield +// CIR: }, resume : { +// CIR: cir.yield +// CIR: },) +// CIR: } folly::coro::Task<int> byRef(const std::string& s) { co_return s.size(); @@ -172,3 +201,13 @@ folly::coro::Task<int> byRef(const std::string& s) { // 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]] +// CIR: cir.scope { +// CIR: %[[SuspendAlwaysAddr:.*]] = cir.alloca ![[SuspendAlways]], {{.*}} ["ref.tmp0"] {alignment = 1 : i64} +// CIR: %[[Tmp0:.*]] = cir.call @_ZN5folly4coro4TaskIiE12promise_type15initial_suspendEv(%[[IntPromisseAddr]]) +// CIR: cir.await(init, ready : { +// CIR: cir.condition({{.*}}) +// CIR: }, suspend : { +// CIR: cir.yield +// CIR: }, resume : { +// CIR: cir.yield +// CIR: },) diff --git a/clang/test/CIR/IR/await.cir b/clang/test/CIR/IR/await.cir new file mode 100644 index 0000000000000..c1fb0d6d7c57c --- /dev/null +++ b/clang/test/CIR/IR/await.cir @@ -0,0 +1,21 @@ +// RUN: cir-opt %s --verify-roundtrip | FileCheck %s + +cir.func coroutine @checkPrintParse(%arg0 : !cir.bool) { + cir.await(user, ready : { + cir.condition(%arg0) + }, suspend : { + cir.yield + }, resume : { + cir.yield + },) + cir.return +} + +// CHECK: cir.func coroutine @checkPrintParse +// CHECK: cir.await(user, ready : { +// CHECK: cir.condition(%arg0) +// CHECK: }, suspend : { +// CHECK: cir.yield +// CHECK: }, resume : { +// CHECK: cir.yield +// CHECK: },) diff --git a/clang/test/CIR/IR/func.cir b/clang/test/CIR/IR/func.cir index 6e91898a3b452..4e79149315a9d 100644 --- a/clang/test/CIR/IR/func.cir +++ b/clang/test/CIR/IR/func.cir @@ -101,6 +101,15 @@ cir.func @ullfunc() -> !u64i { // CHECK: } cir.func coroutine @coro() { + cir.await(init, ready : { + %0 = cir.alloca !cir.bool, !cir.ptr<!cir.bool>, [""] {alignment = 1 : i64} + %1 = cir.load align(1) %0 : !cir.ptr<!cir.bool>, !cir.bool + cir.condition(%1) + }, suspend : { + cir.yield + }, resume : { + cir.yield + },) cir.return } // CHECK: cir.func{{.*}} coroutine @coro() diff --git a/clang/test/CIR/IR/invalid-await.cir b/clang/test/CIR/IR/invalid-await.cir new file mode 100644 index 0000000000000..5f422ca7e954e --- /dev/null +++ b/clang/test/CIR/IR/invalid-await.cir @@ -0,0 +1,19 @@ +// RUN: cir-opt %s -verify-diagnostics -split-input-file +cir.func coroutine @bad_task() { // expected-error {{coroutine body must use at least one cir.await op}} + cir.return +} + +// ----- + +cir.func coroutine @missing_condition() { + cir.scope { + cir.await(user, ready : { // expected-error {{ready region must end with cir.condition}} + cir.yield + }, suspend : { + cir.yield + }, resume : { + cir.yield + },) + } + cir.return +} >From 2a6bce974944ee67c05a2fa1acc4c297e533475f Mon Sep 17 00:00:00 2001 From: Andres Salamanca <[email protected]> Date: Fri, 14 Nov 2025 19:32:31 -0500 Subject: [PATCH 2/3] Apply review --- clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index 80b13a4b7fc2e..e5ad14c5bf7f2 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -2163,11 +2163,6 @@ void cir::AwaitOp::build(OpBuilder &builder, OperationState &result, } } -/// Given the region at `index`, or the parent operation if `index` is None, -/// return the successor regions. These are the regions that may be selected -/// during the flow of control. `operands` is a set of optional attributes -/// that correspond to a constant value for each operand, or null if that -/// operand is not a constant. void cir::AwaitOp::getSuccessorRegions( mlir::RegionBranchPoint point, SmallVectorImpl<RegionSuccessor> ®ions) { // If any index all the underlying regions branch back to the parent @@ -2178,8 +2173,9 @@ void cir::AwaitOp::getSuccessorRegions( return; } - // FIXME: we want to look at cond region for getting more accurate results - // if the other regions will get a chance to execute. + // TODO: retrieve information from the promise and only push the + // necessary ones. Example: `std::suspend_never` on initial or final + // await's might allow suspend region to be skipped. regions.push_back(RegionSuccessor(&this->getReady())); regions.push_back(RegionSuccessor(&this->getSuspend())); regions.push_back(RegionSuccessor(&this->getResume())); >From 5ddc04f7807a0638b5750d155472f908c7ceda41 Mon Sep 17 00:00:00 2001 From: Andres Salamanca <[email protected]> Date: Sun, 16 Nov 2025 13:57:14 -0500 Subject: [PATCH 3/3] Apply review --- clang/include/clang/CIR/Dialect/IR/CIROps.td | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 832f79607a101..2c36f733e8a95 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -2744,19 +2744,17 @@ def CIR_AwaitOp : CIR_Op<"await",[ - suspend: wraps actual x.await_suspend() logic - resume: handles x.await_resume() - Breaking this up in regions allow individual scrutiny of conditions + Breaking this up in regions allows individual scrutiny of conditions which might lead to folding some of them out. Lowerings coming out of CIR, e.g. LLVM, should use the `suspend` region to track more lower level codegen (e.g. intrinsic emission for coro.save/coro.suspend). There are also 4 flavors of `cir.await` available: - `init`: compiler generated initial suspend via implicit `co_await`. - - `user`: also known as normal, representing user written co_await's. + - `user`: also known as normal, representing a user written `co_await`. - `yield`: user written `co_yield` expressions. - `final`: compiler generated final suspend via implicit `co_await`. - From the C++ snippet we get: - ```mlir cir.scope { ... // auto &&x = CommonExpr(); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
