Skip to content

Commit

Permalink
[C++20][Coroutines] Lambda-coroutine with operator new in promise_type (
Browse files Browse the repository at this point in the history
#84193)

Fix #84064

According to http://eel.is/c++draft/dcl.fct.def.coroutine#9 the first
parameter for overload resolution of `operator new` is `size_t` followed
by the arguments of the coroutine function.
http://eel.is/c++draft/dcl.fct.def.coroutine#4 states that the first
argument is the lvalue of `*this` if the coroutine is a member function.

Before this patch, Clang handled class types correctly but ignored
lambdas. This patch adds support for lambda coroutines with a
`promise_type` that implements a custom `operator new`.

The patch does consider C++23 `static operator()`, which already worked
as there is no `this` parameter.
  • Loading branch information
andreasfertig authored Mar 8, 2024
1 parent 0456a32 commit 35d3b33
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 9 deletions.
12 changes: 10 additions & 2 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -6752,10 +6752,18 @@ class Sema final {
SourceLocation RParenLoc);

//// ActOnCXXThis - Parse 'this' pointer.
ExprResult ActOnCXXThis(SourceLocation loc);
///
/// \param ThisRefersToClosureObject Whether to skip the 'this' check for a
/// lambda because 'this' refers to the closure object.
ExprResult ActOnCXXThis(SourceLocation loc,
bool ThisRefersToClosureObject = false);

/// Build a CXXThisExpr and mark it referenced in the current context.
Expr *BuildCXXThisExpr(SourceLocation Loc, QualType Type, bool IsImplicit);
///
/// \param ThisRefersToClosureObject Whether to skip the 'this' check for a
/// lambda because 'this' refers to the closure object.
Expr *BuildCXXThisExpr(SourceLocation Loc, QualType Type, bool IsImplicit,
bool ThisRefersToClosureObject = false);
void MarkThisReferenced(CXXThisExpr *This);

/// Try to retrieve the type of the 'this' pointer.
Expand Down
18 changes: 16 additions & 2 deletions clang/lib/Sema/SemaCoroutine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "clang/Sema/Initialization.h"
#include "clang/Sema/Overload.h"
#include "clang/Sema/ScopeInfo.h"
#include "clang/Sema/Sema.h"
#include "clang/Sema/SemaInternal.h"
#include "llvm/ADT/SmallSet.h"

Expand Down Expand Up @@ -1378,8 +1379,21 @@ bool CoroutineStmtBuilder::makeReturnOnAllocFailure() {
static bool collectPlacementArgs(Sema &S, FunctionDecl &FD, SourceLocation Loc,
SmallVectorImpl<Expr *> &PlacementArgs) {
if (auto *MD = dyn_cast<CXXMethodDecl>(&FD)) {
if (MD->isImplicitObjectMemberFunction() && !isLambdaCallOperator(MD)) {
ExprResult ThisExpr = S.ActOnCXXThis(Loc);
if (MD->isImplicitObjectMemberFunction()) {
ExprResult ThisExpr{};

if (isLambdaCallOperator(MD) && !MD->isStatic()) {
Qualifiers ThisQuals = MD->getMethodQualifiers();
CXXRecordDecl *Record = MD->getParent();

Sema::CXXThisScopeRAII ThisScope(S, Record, ThisQuals,
Record != nullptr);

ThisExpr = S.ActOnCXXThis(Loc, /*ThisRefersToClosureObject=*/true);
} else {
ThisExpr = S.ActOnCXXThis(Loc);
}

if (ThisExpr.isInvalid())
return false;
ThisExpr = S.CreateBuiltinUnaryOp(Loc, UO_Deref, ThisExpr.get());
Expand Down
16 changes: 11 additions & 5 deletions clang/lib/Sema/SemaExprCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1414,7 +1414,8 @@ bool Sema::CheckCXXThisCapture(SourceLocation Loc, const bool Explicit,
return false;
}

ExprResult Sema::ActOnCXXThis(SourceLocation Loc) {
ExprResult Sema::ActOnCXXThis(SourceLocation Loc,
bool ThisRefersToClosureObject) {
/// C++ 9.3.2: In the body of a non-static member function, the keyword this
/// is a non-lvalue expression whose value is the address of the object for
/// which the function is called.
Expand All @@ -1434,13 +1435,18 @@ ExprResult Sema::ActOnCXXThis(SourceLocation Loc) {
return Diag(Loc, diag::err_invalid_this_use) << 0;
}

return BuildCXXThisExpr(Loc, ThisTy, /*IsImplicit=*/false);
return BuildCXXThisExpr(Loc, ThisTy, /*IsImplicit=*/false,
ThisRefersToClosureObject);
}

Expr *Sema::BuildCXXThisExpr(SourceLocation Loc, QualType Type,
bool IsImplicit) {
Expr *Sema::BuildCXXThisExpr(SourceLocation Loc, QualType Type, bool IsImplicit,
bool ThisRefersToClosureObject) {
auto *This = CXXThisExpr::Create(Context, Loc, Type, IsImplicit);
MarkThisReferenced(This);

if (!ThisRefersToClosureObject) {
MarkThisReferenced(This);
}

return This;
}

Expand Down
79 changes: 79 additions & 0 deletions clang/test/SemaCXX/gh84064-1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// RUN: %clang_cc1 -fsyntax-only -verify -I%S/Inputs -std=c++20 %s

// expected-no-diagnostics

#include "std-coroutine.h"

using size_t = decltype(sizeof(0));

struct Generator {
struct promise_type {
int _val{};

Generator get_return_object() noexcept
{
return {};
}

std::suspend_never initial_suspend() noexcept
{
return {};
}

std::suspend_always final_suspend() noexcept
{
return {};
}

void return_void() noexcept {}
void unhandled_exception() noexcept {}

template<typename This, typename... TheRest>
static void*
operator new(size_t size,
This&,
TheRest&&...) noexcept
{
return nullptr;
}

static void operator delete(void*, size_t)
{
}
};
};

struct CapturingThisTest
{
int x{};

void AsPointer()
{
auto lamb = [=,this]() -> Generator {
int y = x;
co_return;
};

static_assert(sizeof(decltype(lamb)) == sizeof(void*));
}

void AsStarThis()
{
auto lamb = [*this]() -> Generator {
int y = x;
co_return;
};

static_assert(sizeof(decltype(lamb)) == sizeof(int));
}
};

int main()
{
auto lamb = []() -> Generator {
co_return;
};

static_assert(sizeof(decltype(lamb)) == 1);
}

53 changes: 53 additions & 0 deletions clang/test/SemaCXX/gh84064-2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// RUN: %clang_cc1 -fsyntax-only -verify -I%S/Inputs -std=c++23 %s

// expected-no-diagnostics

#include "std-coroutine.h"

using size_t = decltype(sizeof(0));

struct GeneratorStatic {
struct promise_type {
int _val{};

GeneratorStatic get_return_object() noexcept
{
return {};
}

std::suspend_never initial_suspend() noexcept
{
return {};
}

std::suspend_always final_suspend() noexcept
{
return {};
}

void return_void() noexcept {}
void unhandled_exception() noexcept {}

template<typename... TheRest>
static void*
operator new(size_t size,
TheRest&&...) noexcept
{
return nullptr;
}

static void operator delete(void*, size_t)
{
}
};
};


int main()
{
auto lambCpp23 = []() static -> GeneratorStatic {
co_return;
};

static_assert(sizeof(decltype(lambCpp23)) == 1);
}

0 comments on commit 35d3b33

Please sign in to comment.