https://github.com/amanmaurya92 created https://github.com/llvm/llvm-project/pull/232280
In `CIRGenFunction::emitLValue`, `Expr::CoawaitExprClass` previously called `errorNYI`. When a coroutine awaits an expression whose `await_resume()` returns an lvalue reference (for example, `int &x = co_await awaiter;` or `co_await awaiter = 42;`), Clang evaluates the `CoawaitExpr` as an lvalue. This patch: - Adds `emitCoawaitLValue` in `CIRGenFunction` and routes `Expr::CoawaitExprClass` to it in `emitLValue`. - In `emitSuspendExpression`, when `forLValue` is true, allocates a temporary alloca in the parent lexical scope to store the resume lvalue pointer before yielding from the `resume` region. - In `emitCoawaitLValue`, reloads the pointer outside `cir.await` to preserve MLIR SSA dominance in the parent block. - Adds FileCheck tests under `clang/test/CIR/CodeGen/coro-await.cpp`. Fixes #232232. >From d0301c8ac529444cbad7898832cbfced91c8d4cb Mon Sep 17 00:00:00 2001 From: amanmaurya92 <[email protected]> Date: Thu, 15 Oct 2026 16:49:01 +0530 Subject: [PATCH] [CIR] Support CoawaitExpr in emitLValue (#232232) Support evaluating CoawaitExpr as an lvalue when await_resume() returns a reference. The resume pointer is spilled and reloaded across cir.await to maintain MLIR SSA dominance in the parent scope. Fixes #232232. --- clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp | 42 ++++++++- clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 3 +- clang/lib/CIR/CodeGen/CIRGenFunction.h | 1 + clang/test/CIR/CodeGen/coro-await.cpp | 101 ++++++++++++++++++++++ 4 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 clang/test/CIR/CodeGen/coro-await.cpp diff --git a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp index b0dbd11795439..a6ad3812a9916 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp @@ -796,9 +796,15 @@ emitSuspendExpression(CIRGenFunction &cgf, CGCoroData &coro, // await_resume(). awaitRes.rv = RValue::getIgnored(); } else if (forLValue) { - // FIXME(cir): the alloca for the resume expr should be placed in the - // enclosing cir.scope instead. awaitRes.lv = cgf.emitLValue(s.getResumeExpr()); + assert(awaitRes.lv.isSimple() && + "expected simple lvalue for coroutine resume"); + mlir::Value ptr = awaitRes.lv.getPointer(); + tmpResumeRValAddr = cgf.emitAlloca( + "__coawait_resume_lval", ptr.getType(), loc, + CharUnits::One(), + builder.getBestAllocaInsertPoint(scopeParentBlock)); + builder.CIRBaseBuilderTy::createStore(loc, ptr, tmpResumeRValAddr); } else { awaitRes.rv = cgf.emitAnyExpr(s.getResumeExpr(), aggSlot, ignoreResult); @@ -883,6 +889,38 @@ RValue CIRGenFunction::emitCoawaitExpr(const CoawaitExpr &e, ignoreResult); } +#ifndef NDEBUG +static QualType +getCoroutineSuspendExprReturnType(const ASTContext &ctx, + const CoroutineSuspendExpr *e) { + const auto *re = e->getResumeExpr(); + assert(isa<CallExpr>(re) && "unexpected suspend expression type"); + return cast<CallExpr>(re)->getCallReturnType(ctx); +} +#endif + +LValue CIRGenFunction::emitCoawaitLValue(const CoawaitExpr *e) { + assert(getCoroutineSuspendExprReturnType(getContext(), e)->isReferenceType() && + "Can't have a scalar return unless the return type is a " + "reference type!"); + mlir::Location scopeLoc = getLoc(e->getSourceRange()); + mlir::Block *currEntryBlock = curLexScope->getEntryBlock(); + mlir::Value tmpResumeLValAddr; + LValue lv = emitSuspendExpression(*this, *curCoro.data, *e, + curCoro.data->currentAwaitKind, + AggValueSlot::ignored(), + /*ignoreResult=*/false, currEntryBlock, + tmpResumeLValAddr, /*forLValue=*/true) + .lv; + if (tmpResumeLValAddr) { + Address addr = lv.getAddress(); + mlir::Value loadedPtr = cir::LoadOp::create( + getBuilder(), scopeLoc, addr.getPointer().getType(), tmpResumeLValAddr); + lv.setAddress(addr.withPointer(loadedPtr)); + } + return lv; +} + RValue CIRGenFunction::emitCoyieldExpr(const CoyieldExpr &e, AggValueSlot aggSlot, bool ignoreResult) { diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index 14fc9cdff3ab6..1b67ea3981ca3 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -1353,8 +1353,7 @@ LValue CIRGenFunction::emitLValue(const Expr *e) { case Expr::MaterializeTemporaryExprClass: return emitMaterializeTemporaryExpr(cast<MaterializeTemporaryExpr>(e)); case Expr::CoawaitExprClass: - getCIRGenModule().errorNYI(e->getSourceRange(), "emitLValue: CoawaitExpr"); - return LValue(); + return emitCoawaitLValue(cast<CoawaitExpr>(e)); case Expr::CoyieldExprClass: getCIRGenModule().errorNYI(e->getSourceRange(), "emitLValue: CoyieldExpr"); return LValue(); diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index 8be794a660224..6293375e2e154 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -2303,6 +2303,7 @@ class CIRGenFunction : public CIRGenTypeCache { RValue emitCoawaitExpr(const CoawaitExpr &e, AggValueSlot aggSlot = AggValueSlot::ignored(), bool ignoreResult = false); + LValue emitCoawaitLValue(const CoawaitExpr *e); RValue emitCoyieldExpr(const CoyieldExpr &e, AggValueSlot aggSlot = AggValueSlot::ignored(), diff --git a/clang/test/CIR/CodeGen/coro-await.cpp b/clang/test/CIR/CodeGen/coro-await.cpp new file mode 100644 index 0000000000000..029ea991e2735 --- /dev/null +++ b/clang/test/CIR/CodeGen/coro-await.cpp @@ -0,0 +1,101 @@ +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -Wno-coroutine-missing-unhandled-exception -emit-cir %s -o %t.cir +// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -emit-llvm -disable-llvm-passes -Wno-coroutine-missing-unhandled-exception %s -o %t.ll +// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG + +namespace std { +template <class Ret, typename... T> +struct coroutine_traits { using promise_type = typename Ret::promise_type; }; + +template <class Promise = void> struct coroutine_handle { + coroutine_handle() = default; + static coroutine_handle from_address(void *) noexcept; + void *address() const noexcept; +}; + +template <> struct coroutine_handle<void> { + coroutine_handle() = default; + template <class PromiseType> + coroutine_handle(coroutine_handle<PromiseType>) noexcept; + static coroutine_handle from_address(void *) noexcept; + void *address() const noexcept; +}; + +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 + +struct Task { + struct promise_type { + Task get_return_object() { return {}; } + std::suspend_never initial_suspend() noexcept { return {}; } + std::suspend_never final_suspend() noexcept { return {}; } + void return_void() {} + void unhandled_exception() {} + }; +}; + +struct Awaiter { + bool await_ready() { return false; } + void await_suspend(std::coroutine_handle<>) {} + int &await_resume() { return value; } + int value; +}; + +// CIR-LABEL: cir.func coroutine {{.*}} @_Z14test_assign_lvv +// OGCG-LABEL: define dso_local void @_Z14test_assign_lvv +Task test_assign_lv() { + // CIR: %[[VAL:.*]] = cir.const #cir.int<42> : !s32i + // CIR: cir.await(user, ready : { + // CIR: %[[READY:.*]] = cir.call @_ZN7Awaiter11await_readyEv(%{{.*}}) : (!cir.ptr<!rec_Awaiter>{{.*}}) -> (!cir.bool{{.*}}) + // CIR: cir.condition(%[[READY]]) + // CIR: }, suspend : { + // CIR: cir.call @_ZN7Awaiter13await_suspendESt16coroutine_handleIvE(%{{.*}}) + // CIR: cir.coro.suspend_point + // CIR: }, resume : { + // CIR: %[[RES:.*]] = cir.call @_ZN7Awaiter12await_resumeEv(%{{.*}}) : (!cir.ptr<!rec_Awaiter>{{.*}}) -> (!cir.ptr<!s32i>{{.*}}) + // CIR: cir.store %[[RES]], %[[TMP_ADDR:.*]] : !cir.ptr<!s32i>, !cir.ptr<!cir.ptr<!s32i>> + // CIR: cir.yield + // CIR: },) + // CIR: %[[LOADED_PTR:.*]] = cir.load %[[TMP_ADDR]] : !cir.ptr<!cir.ptr<!s32i>>, !cir.ptr<!s32i> + // CIR: cir.store align(4) %[[VAL]], %[[LOADED_PTR]] : !s32i, !cir.ptr<!s32i> + + // OGCG: %[[RES_OGCG:.*]] = call noundef nonnull align 4 dereferenceable(4) ptr @_ZN7Awaiter12await_resumeEv(ptr {{.*}}) + // OGCG: store i32 42, ptr %[[RES_OGCG]], align 4 + Awaiter a; + co_await a = 42; +} + +// CIR-LABEL: cir.func coroutine {{.*}} @_Z11test_ref_lvv +// OGCG-LABEL: define dso_local void @_Z11test_ref_lvv +Task test_ref_lv() { + // CIR: %[[R_ADDR:.*]] = cir.alloca "r" align(8) init const : !cir.ptr<!cir.ptr<!s32i>> + // CIR: cir.await(user, ready : { + // CIR: %[[READY:.*]] = cir.call @_ZN7Awaiter11await_readyEv(%{{.*}}) : (!cir.ptr<!rec_Awaiter>{{.*}}) -> (!cir.bool{{.*}}) + // CIR: cir.condition(%[[READY]]) + // CIR: }, suspend : { + // CIR: cir.call @_ZN7Awaiter13await_suspendESt16coroutine_handleIvE(%{{.*}}) + // CIR: cir.coro.suspend_point + // CIR: }, resume : { + // CIR: %[[RES:.*]] = cir.call @_ZN7Awaiter12await_resumeEv(%{{.*}}) : (!cir.ptr<!rec_Awaiter>{{.*}}) -> (!cir.ptr<!s32i>{{.*}}) + // CIR: cir.store %[[RES]], %[[TMP_ADDR:.*]] : !cir.ptr<!s32i>, !cir.ptr<!cir.ptr<!s32i>> + // CIR: cir.yield + // CIR: },) + // CIR: %[[LOADED_PTR:.*]] = cir.load %[[TMP_ADDR]] : !cir.ptr<!cir.ptr<!s32i>>, !cir.ptr<!s32i> + // CIR: cir.store align(8) %[[LOADED_PTR]], %[[R_ADDR]] : !cir.ptr<!s32i>, !cir.ptr<!cir.ptr<!s32i>> + + // OGCG: %[[R_ADDR_OGCG:.*]] = alloca ptr, align 8 + // OGCG: %[[RES_OGCG:.*]] = call noundef nonnull align 4 dereferenceable(4) ptr @_ZN7Awaiter12await_resumeEv(ptr {{.*}}) + // OGCG: store ptr %[[RES_OGCG]], ptr %[[R_ADDR_OGCG]], align 8 + Awaiter a; + int &r = co_await a; +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
