Skip to content

[coroutine] Implement llvm.coro.await.suspend intrinsic #79712

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions clang/include/clang/AST/ExprCXX.h
Original file line number Diff line number Diff line change
Expand Up @@ -5038,6 +5038,9 @@ class CoroutineSuspendExpr : public Expr {
OpaqueValueExpr *OpaqueValue = nullptr;

public:
// These types correspond to the three C++ 'await_suspend' return variants
enum class SuspendReturnType { SuspendVoid, SuspendBool, SuspendHandle };

CoroutineSuspendExpr(StmtClass SC, SourceLocation KeywordLoc, Expr *Operand,
Expr *Common, Expr *Ready, Expr *Suspend, Expr *Resume,
OpaqueValueExpr *OpaqueValue)
Expand Down Expand Up @@ -5097,6 +5100,24 @@ class CoroutineSuspendExpr : public Expr {
return static_cast<Expr *>(SubExprs[SubExpr::Operand]);
}

SuspendReturnType getSuspendReturnType() const {
auto *SuspendExpr = getSuspendExpr();
assert(SuspendExpr);

auto SuspendType = SuspendExpr->getType();

if (SuspendType->isVoidType())
return SuspendReturnType::SuspendVoid;
if (SuspendType->isBooleanType())
return SuspendReturnType::SuspendBool;

// Void pointer is the type of handle.address(), which is returned
// from the await suspend wrapper so that the temporary coroutine handle
// value won't go to the frame by mistake
assert(SuspendType->isVoidPointerType());
return SuspendReturnType::SuspendHandle;
}

SourceLocation getKeywordLoc() const { return KeywordLoc; }

SourceLocation getBeginLoc() const LLVM_READONLY { return KeywordLoc; }
Expand Down
161 changes: 150 additions & 11 deletions clang/lib/CodeGen/CGCoroutine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ static bool FunctionCanThrow(const FunctionDecl *D) {
Proto->canThrow() != CT_Cannot;
}

static bool ResumeStmtCanThrow(const Stmt *S) {
static bool StmtCanThrow(const Stmt *S) {
if (const auto *CE = dyn_cast<CallExpr>(S)) {
const auto *Callee = CE->getDirectCallee();
if (!Callee)
Expand All @@ -167,7 +167,7 @@ static bool ResumeStmtCanThrow(const Stmt *S) {
}

for (const auto *child : S->children())
if (ResumeStmtCanThrow(child))
if (StmtCanThrow(child))
return true;

return false;
Expand All @@ -178,18 +178,31 @@ static bool ResumeStmtCanThrow(const Stmt *S) {
// auto && x = CommonExpr();
// if (!x.await_ready()) {
// llvm_coro_save();
// x.await_suspend(...); (*)
// llvm_coro_suspend(); (**)
// llvm_coro_await_suspend(&x, frame, wrapper) (*) (**)
// llvm_coro_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:
// (*) llvm_coro_await_suspend_{void, bool, handle} is lowered to
// wrapper(&x, frame) when it's certain not to interfere with
// coroutine transform. await_suspend expression is
// asynchronous to the coroutine body and not all analyses
// and transformations can handle it correctly at the moment.
//
// Wrapper function encapsulates x.await_suspend(...) call and looks like:
//
// auto __await_suspend_wrapper(auto& awaiter, void* frame) {
// std::coroutine_handle<> handle(frame);
// return awaiter.await_suspend(handle);
// }
//
// (**) If x.await_suspend return type is bool, it allows to veto a suspend:
// if (x.await_suspend(...))
// llvm_coro_suspend();
//
// (**) llvm_coro_suspend() encodes three possible continuations as
// (***) llvm_coro_suspend() encodes three possible continuations as
// a switch instruction:
//
// %where-to = call i8 @llvm.coro.suspend(...)
Expand All @@ -212,9 +225,10 @@ static LValueOrRValue emitSuspendExpression(CodeGenFunction &CGF, CGCoroData &Co
bool ignoreResult, bool forLValue) {
auto *E = S.getCommonExpr();

auto Binder =
auto CommonBinder =
CodeGenFunction::OpaqueValueMappingData::bind(CGF, S.getOpaqueValue(), E);
auto UnbindOnExit = llvm::make_scope_exit([&] { Binder.unbind(CGF); });
auto UnbindCommonOnExit =
llvm::make_scope_exit([&] { CommonBinder.unbind(CGF); });

auto Prefix = buildSuspendPrefixStr(Coro, Kind);
BasicBlock *ReadyBlock = CGF.createBasicBlock(Prefix + Twine(".ready"));
Expand All @@ -232,16 +246,73 @@ static LValueOrRValue emitSuspendExpression(CodeGenFunction &CGF, CGCoroData &Co
auto *NullPtr = llvm::ConstantPointerNull::get(CGF.CGM.Int8PtrTy);
auto *SaveCall = Builder.CreateCall(CoroSave, {NullPtr});

auto SuspendWrapper = CodeGenFunction(CGF.CGM).generateAwaitSuspendWrapper(
CGF.CurFn->getName(), Prefix, S);

CGF.CurCoro.InSuspendBlock = true;
auto *SuspendRet = CGF.EmitScalarExpr(S.getSuspendExpr());

assert(CGF.CurCoro.Data && CGF.CurCoro.Data->CoroBegin &&
"expected to be called in coroutine context");

SmallVector<llvm::Value *, 3> SuspendIntrinsicCallArgs;
SuspendIntrinsicCallArgs.push_back(
CGF.getOrCreateOpaqueLValueMapping(S.getOpaqueValue()).getPointer(CGF));

SuspendIntrinsicCallArgs.push_back(CGF.CurCoro.Data->CoroBegin);
SuspendIntrinsicCallArgs.push_back(SuspendWrapper);

const auto SuspendReturnType = S.getSuspendReturnType();
llvm::Intrinsic::ID AwaitSuspendIID;

switch (SuspendReturnType) {
case CoroutineSuspendExpr::SuspendReturnType::SuspendVoid:
AwaitSuspendIID = llvm::Intrinsic::coro_await_suspend_void;
break;
case CoroutineSuspendExpr::SuspendReturnType::SuspendBool:
AwaitSuspendIID = llvm::Intrinsic::coro_await_suspend_bool;
break;
case CoroutineSuspendExpr::SuspendReturnType::SuspendHandle:
AwaitSuspendIID = llvm::Intrinsic::coro_await_suspend_handle;
break;
}

llvm::Function *AwaitSuspendIntrinsic = CGF.CGM.getIntrinsic(AwaitSuspendIID);

const auto AwaitSuspendCanThrow = StmtCanThrow(S.getSuspendExpr());

llvm::CallBase *SuspendRet = nullptr;
// FIXME: add call attributes?
if (AwaitSuspendCanThrow)
SuspendRet =
CGF.EmitCallOrInvoke(AwaitSuspendIntrinsic, SuspendIntrinsicCallArgs);
else
SuspendRet = CGF.EmitNounwindRuntimeCall(AwaitSuspendIntrinsic,
SuspendIntrinsicCallArgs);
Comment on lines +284 to +290
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to do this? I feel EmitCallOrInvoke is sufficient. LLVM is able to propagate nounwind in trivial cases.


assert(SuspendRet);
CGF.CurCoro.InSuspendBlock = false;

if (SuspendRet != nullptr && SuspendRet->getType()->isIntegerTy(1)) {
switch (SuspendReturnType) {
case CoroutineSuspendExpr::SuspendReturnType::SuspendVoid:
assert(SuspendRet->getType()->isVoidTy());
break;
case CoroutineSuspendExpr::SuspendReturnType::SuspendBool: {
assert(SuspendRet->getType()->isIntegerTy());

// Veto suspension if requested by bool returning await_suspend.
BasicBlock *RealSuspendBlock =
CGF.createBasicBlock(Prefix + Twine(".suspend.bool"));
CGF.Builder.CreateCondBr(SuspendRet, RealSuspendBlock, ReadyBlock);
CGF.EmitBlock(RealSuspendBlock);
break;
}
case CoroutineSuspendExpr::SuspendReturnType::SuspendHandle: {
assert(SuspendRet->getType()->isPointerTy());

auto ResumeIntrinsic = CGF.CGM.getIntrinsic(llvm::Intrinsic::coro_resume);
Builder.CreateCall(ResumeIntrinsic, SuspendRet);
break;
}
}

// Emit the suspend point.
Expand All @@ -267,7 +338,7 @@ static LValueOrRValue emitSuspendExpression(CodeGenFunction &CGF, CGCoroData &Co
// is marked as 'noexcept', we avoid generating this additional IR.
CXXTryStmt *TryStmt = nullptr;
if (Coro.ExceptionHandler && Kind == AwaitKind::Init &&
ResumeStmtCanThrow(S.getResumeExpr())) {
StmtCanThrow(S.getResumeExpr())) {
Coro.ResumeEHVar =
CGF.CreateTempAlloca(Builder.getInt1Ty(), Prefix + Twine("resume.eh"));
Builder.CreateFlagStore(true, Coro.ResumeEHVar);
Expand Down Expand Up @@ -338,6 +409,69 @@ static QualType getCoroutineSuspendExprReturnType(const ASTContext &Ctx,
}
#endif

llvm::Function *
CodeGenFunction::generateAwaitSuspendWrapper(Twine const &CoroName,
Twine const &SuspendPointName,
CoroutineSuspendExpr const &S) {
std::string FuncName = "__await_suspend_wrapper_";
FuncName += CoroName.str();
FuncName += '_';
FuncName += SuspendPointName.str();
Comment on lines +417 to +419
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel it is better to concat the name of the awaiter instead of the coroutines and suspend number.


ASTContext &C = getContext();

FunctionArgList args;

ImplicitParamDecl AwaiterDecl(C, C.VoidPtrTy, ImplicitParamKind::Other);
ImplicitParamDecl FrameDecl(C, C.VoidPtrTy, ImplicitParamKind::Other);
QualType ReturnTy = S.getSuspendExpr()->getType();

args.push_back(&AwaiterDecl);
args.push_back(&FrameDecl);

const CGFunctionInfo &FI =
CGM.getTypes().arrangeBuiltinFunctionDeclaration(ReturnTy, args);

llvm::FunctionType *LTy = CGM.getTypes().GetFunctionType(FI);

llvm::Function *Fn = llvm::Function::Create(
LTy, llvm::GlobalValue::PrivateLinkage, FuncName, &CGM.getModule());

Fn->addParamAttr(0, llvm::Attribute::AttrKind::NonNull);
Fn->addParamAttr(0, llvm::Attribute::AttrKind::NoUndef);

Fn->addParamAttr(1, llvm::Attribute::AttrKind::NoUndef);

Fn->setMustProgress();
Fn->addFnAttr(llvm::Attribute::AttrKind::AlwaysInline);

StartFunction(GlobalDecl(), ReturnTy, Fn, FI, args);

// FIXME: add TBAA metadata to the loads
llvm::Value *AwaiterPtr = Builder.CreateLoad(GetAddrOfLocalVar(&AwaiterDecl));
auto AwaiterLValue =
MakeNaturalAlignAddrLValue(AwaiterPtr, AwaiterDecl.getType());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder whether the type passed to MakeNaturalAlignAddrLValue is correct. AwaiterDecl.getType() is void *, but shouldn't S.getOpaqueValue() be passed?

I think this was causing a libc++ test to crash when ubsan was enabled (see #86898).

S.getOpaqueValue() and AwaiterLValue are passed to OpaqueValueMappingData::bind. clang mis-compiles when the lvalue has a larger alignment than the type of S.getOpaqueValue().

https://github.com/llvm/llvm-project/pull/79712/files#diff-da3676b91275038b801aabe1e10b6fdedfbe50b679eac152080c2a26fbd8ec7dR458

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah.. it looks like it would be better to pass the type of S.getOpaqueValue(). The void* is simply a placeholder.

Would you like to handle this? I feel you are the better option since you can test it with your own patch. Otherwise I'll try to make it myself.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a patch: #87134


CurAwaitSuspendWrapper.FramePtr =
Builder.CreateLoad(GetAddrOfLocalVar(&FrameDecl));

auto AwaiterBinder = CodeGenFunction::OpaqueValueMappingData::bind(
*this, S.getOpaqueValue(), AwaiterLValue);

auto *SuspendRet = EmitScalarExpr(S.getSuspendExpr());

auto UnbindCommonOnExit =
llvm::make_scope_exit([&] { AwaiterBinder.unbind(*this); });
if (SuspendRet != nullptr) {
Fn->addRetAttr(llvm::Attribute::AttrKind::NoUndef);
Builder.CreateStore(SuspendRet, ReturnValue);
}

CurAwaitSuspendWrapper.FramePtr = nullptr;
FinishFunction();
return Fn;
}

LValue
CodeGenFunction::EmitCoawaitLValue(const CoawaitExpr *E) {
assert(getCoroutineSuspendExprReturnType(getContext(), E)->isReferenceType() &&
Expand Down Expand Up @@ -834,6 +968,11 @@ RValue CodeGenFunction::EmitCoroutineIntrinsic(const CallExpr *E,
if (CurCoro.Data && CurCoro.Data->CoroBegin) {
return RValue::get(CurCoro.Data->CoroBegin);
}

if (CurAwaitSuspendWrapper.FramePtr) {
return RValue::get(CurAwaitSuspendWrapper.FramePtr);
}

CGM.Error(E->getBeginLoc(), "this builtin expect that __builtin_coro_begin "
"has been used earlier in this function");
auto *NullPtr = llvm::ConstantPointerNull::get(Builder.getPtrTy());
Expand Down
19 changes: 19 additions & 0 deletions clang/lib/CodeGen/CodeGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,25 @@ class CodeGenFunction : public CodeGenTypeCache {
return isCoroutine() && CurCoro.InSuspendBlock;
}

// Holds FramePtr for await_suspend wrapper generation,
// so that __builtin_coro_frame call can be lowered
// directly to value of its second argument
struct AwaitSuspendWrapperInfo {
llvm::Value *FramePtr = nullptr;
};
AwaitSuspendWrapperInfo CurAwaitSuspendWrapper;

// Generates wrapper function for `llvm.coro.await.suspend.*` intrinisics.
// It encapsulates SuspendExpr in a function, to separate it's body
// from the main coroutine to avoid miscompilations. Intrinisic
// is lowered to this function call in CoroSplit pass
// Function signature is:
// <type> __await_suspend_wrapper_<name>(ptr %awaiter, ptr %hdl)
// where type is one of (void, i1, ptr)
llvm::Function *generateAwaitSuspendWrapper(Twine const &CoroName,
Twine const &SuspendPointName,
CoroutineSuspendExpr const &S);

/// CurGD - The GlobalDecl for the current function being compiled.
GlobalDecl CurGD;

Expand Down
Loading