From c87265637892e9577100b57e09228e30b5f4365a Mon Sep 17 00:00:00 2001 From: Teodoro Freund Date: Tue, 6 Feb 2024 10:35:32 +0000 Subject: [PATCH 01/11] [stackful] initial test and keywords Now we can tokenize and ignore suspend: suspend int f(); --- clang/include/clang/Basic/TokenKinds.def | 1 + clang/lib/Parse/ParseDecl.cpp | 4 +++- .../suspend-specifier/suspend-specifier.cbs | 20 +++++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/suspend-specifier/suspend-specifier.cbs diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index 09dfe7a5ba71..ac92c8147ac2 100644 --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -463,6 +463,7 @@ KEYWORD(owned , KEYBSC) KEYWORD(trait , KEYBSC) KEYWORD(safe , KEYBSC) KEYWORD(unsafe , KEYBSC) +KEYWORD(suspend , KEYBSC) #endif // GNU Extensions (outside impl-reserved namespace) diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index f57b1f93343c..9fa271baffa1 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -4012,6 +4012,7 @@ void Parser::ParseDeclarationSpecifiers(DeclSpec &DS, #if ENABLE_BSC case tok::kw_async: isInvalid = DS.setFunctionSpecAsync(Loc, PrevSpec, DiagID); + case tok::kw_suspend: break; case tok::kw_safe: case tok::kw_unsafe: { @@ -5655,7 +5656,8 @@ bool Parser::isDeclarationSpecifier(bool DisambiguatingWithExpression) { case tok::kw_async: case tok::kw_safe: case tok::kw_unsafe: -#endif + case tok::kw_suspend: + #endif case tok::kw_virtual: case tok::kw_explicit: case tok::kw__Noreturn: diff --git a/clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/suspend-specifier/suspend-specifier.cbs b/clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/suspend-specifier/suspend-specifier.cbs new file mode 100644 index 000000000000..160eefd203c1 --- /dev/null +++ b/clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/suspend-specifier/suspend-specifier.cbs @@ -0,0 +1,20 @@ +// RUN: %clang %s -o %test.output +// RUN: %test.output +// RUN: %clang -rewrite-bsc %s -o %t-rw.c +// RUN: %clang %t-rw.c -o %t-rw.output +// RUN: %t-rw.output +// expected-no-diagnostics + +suspend int read(int a) { + return 0; +} + +// suspend bool write(int a) { +// return 0; +// } + +// typedef suspend void (*CORO)(int signum); + +int main() { + return 0; +} \ No newline at end of file -- Gitee From 5f09f57be4272a18e2c507afe0245558f72be89a Mon Sep 17 00:00:00 2001 From: Teodoro Freund Date: Mon, 12 Feb 2024 16:29:38 +0000 Subject: [PATCH 02/11] [stackful] First parsing and tests --- clang/include/clang/AST/Decl.h | 11 + clang/include/clang/AST/DeclBase.h | 5 +- clang/include/clang/Basic/TokenKinds.def | 3 + clang/include/clang/Sema/DeclSpec.h | 11 +- clang/lib/AST/Decl.cpp | 10 +- clang/lib/Parse/ParseDecl.cpp | 4 + clang/lib/Parse/ParseExpr.cpp | 41 ++++ clang/lib/Sema/BSC/DeclSpecBSC.cpp | 14 ++ clang/lib/Sema/DeclSpec.cpp | 2 +- clang/lib/Sema/SemaDecl.cpp | 11 +- .../await-expression/await-expression.cbs | 26 +++ .../suspend-expression/suspend-expression.cbs | 27 +++ .../suspend-specifier/suspend-specifier.cbs | 1 + .../StackfulCoroutine/AwaitExpr/tcp/tcp.cbs | 217 ++++++++++++++++++ .../StackfulCoroutine/AwaitExpr/tcp/tcp.hbs | 40 ++++ 15 files changed, 413 insertions(+), 10 deletions(-) create mode 100644 clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/await-expression/await-expression.cbs create mode 100644 clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/suspend-expression/suspend-expression.cbs create mode 100644 clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/tcp/tcp.cbs create mode 100644 clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/tcp/tcp.hbs diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h index 65860f499565..1cb99df08149 100644 --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -2024,6 +2024,7 @@ protected: Expr *TrailingRequiresClause = nullptr #if ENABLE_BSC , bool isAsyncSpecified = false + , bool isSuspendSpecified = false #endif ); @@ -2064,6 +2065,7 @@ public: Expr *TrailingRequiresClause = nullptr #if ENABLE_BSC , bool isAsyncSpecified = false + , bool isSuspendSpecified = false #endif ) { DeclarationNameInfo NameInfo(N, NLoc); @@ -2073,6 +2075,7 @@ public: TrailingRequiresClause #if ENABLE_BSC , isAsyncSpecified + , isSuspendSpecified #endif ); } @@ -2085,6 +2088,7 @@ public: Expr *TrailingRequiresClause #if ENABLE_BSC , bool isAsyncSpecified = false + , bool isSuspendSpecified = false #endif ); @@ -2662,6 +2666,13 @@ public: /// Set whether the "async" keyword was specified for this function. void setAsyncSpecified(bool I) { FunctionDeclBits.IsAsyncSpecified = I; } + + /// Determine whether the "suspend" keyword was specified for this + /// function. + bool isSuspendSpecified() const { return FunctionDeclBits.IsSuspendSpecified; } + + /// Set whether the "suspend" keyword was specified for this function. + void setSuspendSpecified(bool I) { FunctionDeclBits.IsSuspendSpecified = I; } #endif /// Determine whether the "inline" keyword was specified for this diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h index c8cff012e000..bc64500f134b 100644 --- a/clang/include/clang/AST/DeclBase.h +++ b/clang/include/clang/AST/DeclBase.h @@ -1603,6 +1603,7 @@ class DeclContext { uint64_t IsInlineSpecified : 1; #if ENABLE_BSC uint64_t IsAsyncSpecified : 1; + uint64_t IsSuspendSpecified : 1; #endif uint64_t IsVirtualAsWritten : 1; uint64_t IsPure : 1; @@ -1666,7 +1667,7 @@ class DeclContext { /// Number of non-inherited bits in FunctionDeclBitfields. #if ENABLE_BSC - enum { NumFunctionDeclBits = 31 }; + enum { NumFunctionDeclBits = 32 }; #else enum { NumFunctionDeclBits = 28}; #endif @@ -1687,7 +1688,7 @@ class DeclContext { /// will need to be shrunk if some bit is added to NumDeclContextBitfields, /// NumFunctionDeclBitfields or CXXConstructorDeclBitfields. #if ENABLE_BSC - uint64_t NumCtorInitializers : 17; + uint64_t NumCtorInitializers : 16; #else uint64_t NumCtorInitializers : 20; #endif diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index ac92c8147ac2..18ec5ec4b4a1 100644 --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -463,7 +463,10 @@ KEYWORD(owned , KEYBSC) KEYWORD(trait , KEYBSC) KEYWORD(safe , KEYBSC) KEYWORD(unsafe , KEYBSC) +// Stackful Coroutines keywords KEYWORD(suspend , KEYBSC) +KEYWORD(sus_await , KEYBSC) +KEYWORD(yield , KEYBSC) #endif // GNU Extensions (outside impl-reserved namespace) diff --git a/clang/include/clang/Sema/DeclSpec.h b/clang/include/clang/Sema/DeclSpec.h index f390fcda37e8..c0408102605f 100644 --- a/clang/include/clang/Sema/DeclSpec.h +++ b/clang/include/clang/Sema/DeclSpec.h @@ -237,7 +237,7 @@ private: public: /// Used to mark parameters that include 'this'. bool HasThisParam = false; - + QualType getExtendedType() const { return ExtendedType; } void setExtendedType(QualType ExtendedType) { this->ExtendedType = ExtendedType; @@ -396,6 +396,7 @@ private: #if ENABLE_BSC unsigned FS_async_specified : 1; unsigned FS_safe_zone_specified : 2; + unsigned FS_suspend_specified : 1; #endif // friend-specifier @@ -444,6 +445,7 @@ private: #if ENABLE_BSC SourceLocation TQ_ownedLoc; SourceLocation FS_asyncLoc, FS_safe_zone_loc; + SourceLocation FS_suspendLoc; #endif SourceLocation FS_inlineLoc, FS_virtualLoc, FS_explicitLoc, FS_noreturnLoc; SourceLocation FS_explicitCloseParenLoc; @@ -494,6 +496,7 @@ public: FS_noreturn_specified(false), #if ENABLE_BSC FS_async_specified(false), FS_safe_zone_specified(SZ_None), + FS_suspend_specified(false), #endif Friend_specified(false), ConstexprSpecifier(static_cast( ConstexprSpecKind::Unspecified)), @@ -652,6 +655,8 @@ public: return (SafeZoneSpecifier)FS_safe_zone_specified; } SourceLocation getSafeZoneSpecifierLoc() const { return FS_safe_zone_loc; } + bool isSuspendSpecified() const { return FS_suspend_specified; } + SourceLocation getSuspendSpecLoc() const { return FS_suspendLoc; } #endif ExplicitSpecifier getExplicitSpecifier() const { @@ -691,11 +696,13 @@ public: FS_forceinlineLoc = SourceLocation(); #if ENABLE_BSC FS_async_specified = false; + FS_suspend_specified = false; FS_asyncLoc = SourceLocation(); FS_safe_specified = SS_None; FS_safe_loc = SourceLocation(); FS_safe_zone_specified = SZ_None; FS_safe_zone_loc = SourceLocation(); + FS_suspendLoc = SourceLocation(); #endif FS_virtual_specified = false; FS_virtualLoc = SourceLocation(); @@ -837,6 +844,8 @@ public: bool setFunctionSafeZoneSpecifier(SourceLocation Loc, const char *&PrevSpec, unsigned &DiagID, SafeZoneSpecifier SafeSpec); + bool setFunctionSpecSuspend(SourceLocation Loc, const char *&PrevSpec, + unsigned &DiagID); #endif bool setFunctionSpecVirtual(SourceLocation Loc, const char *&PrevSpec, unsigned &DiagID); diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index 9bcd519b4d2a..9a549f68c5e1 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2401,8 +2401,8 @@ bool VarDecl::mightBeUsableInConstantExpressions(const ASTContext &C) const { // OpenCL permits const integral variables to be used in constant // expressions, like in C++98. - if (!Lang.CPlusPlus && !Lang.OpenCL - #if ENABLE_BSC + if (!Lang.CPlusPlus && !Lang.OpenCL + #if ENABLE_BSC && !Lang.BSC #endif ) @@ -2575,7 +2575,7 @@ bool VarDecl::checkForConstantInitialization( assert(!Eval->WasEvaluated && "already evaluated var value before checking for constant init"); #if ENABLE_BSC - assert((getASTContext().getLangOpts().CPlusPlus || getASTContext().getLangOpts().BSC) + assert((getASTContext().getLangOpts().CPlusPlus || getASTContext().getLangOpts().BSC) && "only meaningful in C++ and BSC"); #else assert(getASTContext().getLangOpts().CPlusPlus && "only meaningful in C++"); @@ -2978,6 +2978,7 @@ FunctionDecl::FunctionDecl(Kind DK, ASTContext &C, DeclContext *DC, Expr *TrailingRequiresClause #if ENABLE_BSC , bool isAsyncSpecified + , bool isSuspendSpecified #endif ) : DeclaratorDecl(DK, DC, NameInfo.getLoc(), NameInfo.getName(), T, TInfo, @@ -2990,6 +2991,7 @@ FunctionDecl::FunctionDecl(Kind DK, ASTContext &C, DeclContext *DC, FunctionDeclBits.IsInlineSpecified = isInlineSpecified; #if ENABLE_BSC FunctionDeclBits.IsAsyncSpecified = isAsyncSpecified; + FunctionDeclBits.IsSuspendSpecified = isSuspendSpecified; #endif FunctionDeclBits.IsVirtualAsWritten = false; FunctionDeclBits.IsPure = false; @@ -5167,6 +5169,7 @@ FunctionDecl::Create(ASTContext &C, DeclContext *DC, SourceLocation StartLoc, Expr *TrailingRequiresClause #if ENABLE_BSC , bool isAsyncSpecified + , bool isSuspendSpecified #endif ) { FunctionDecl *New = new (C, DC) @@ -5175,6 +5178,7 @@ FunctionDecl::Create(ASTContext &C, DeclContext *DC, SourceLocation StartLoc, TrailingRequiresClause #if ENABLE_BSC , isAsyncSpecified + , isSuspendSpecified #endif ); New->setHasWrittenPrototype(hasWrittenPrototype); diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index 9fa271baffa1..d009ad4d4e23 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -2636,6 +2636,8 @@ void Parser::ParseSpecifierQualifierList(DeclSpec &DS, AccessSpecifier AS, #if ENABLE_BSC if (DS.isAsyncSpecified()) Diag(DS.getAsyncSpecLoc(), diag::err_typename_invalid_functionspec); + if (DS.isSuspendSpecified()) + Diag(DS.getSuspendSpecLoc(), diag::err_typename_invalid_functionspec); #endif if (DS.isVirtualSpecified()) Diag(DS.getVirtualSpecLoc(), diag::err_typename_invalid_functionspec); @@ -4012,7 +4014,9 @@ void Parser::ParseDeclarationSpecifiers(DeclSpec &DS, #if ENABLE_BSC case tok::kw_async: isInvalid = DS.setFunctionSpecAsync(Loc, PrevSpec, DiagID); + break; case tok::kw_suspend: + isInvalid = DS.setFunctionSpecSuspend(Loc, PrevSpec, DiagID); break; case tok::kw_safe: case tok::kw_unsafe: { diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp index 54d2cc0d5b08..a3576806b138 100644 --- a/clang/lib/Parse/ParseExpr.cpp +++ b/clang/lib/Parse/ParseExpr.cpp @@ -1452,6 +1452,47 @@ ExprResult Parser::ParseCastExpression( Res = Actions.ActOnAwaitExpr(AwaitLoc, Res.get()); return Res; } + + case tok::kw_sus_await: { // unary-expression: 'await' cast-expression + if (NotPrimaryExpression) + *NotPrimaryExpression = true; + SourceLocation SusAwaitLoc = ConsumeToken(); + Res = ParseCastExpression(AnyCastExpr); + // TODO this cast expression must actually be a function call + // if (!Res.isInvalid()) + // Res = Actions.ActOnAwaitExpr(AwaitLoc, Res.get()); + return Res; + } + + case tok::kw_yield: { // unary-expression: 'suspend' cast-expression + if (NotPrimaryExpression) + *NotPrimaryExpression = true; + SourceLocation SuspendLoc = ConsumeToken(); + if (Tok.is(tok::semi)){ + // Not yielding any value, just return void + QualType VoidPT = Actions.Context.getPointerType(Actions.Context.VoidTy); + + CStyleCastExpr* E = CStyleCastExpr::Create( + Actions.Context, + VoidPT, + VK_PRValue, + CK_NullToPointer, + IntegerLiteral::Create(Actions.Context, llvm::APInt(Actions.Context.getTypeSize(Actions.Context.IntTy), 0, false), Actions.Context.IntTy, SourceLocation()), + nullptr, + FPOptionsOverride(), + Actions.Context.getTrivialTypeSourceInfo(VoidPT, SourceLocation()), + SourceLocation(), + SourceLocation()); + + Res = ActionResult(E); + + } else { + Res = ParseCastExpression(AnyCastExpr); + } + // if (!Res.isInvalid()) + // Res = Actions.ActOnAwaitExpr(AwaitLoc, Res.get()); + return Res; + } #endif case tok::kw___extension__:{//unary-expression:'__extension__' cast-expr [GNU] diff --git a/clang/lib/Sema/BSC/DeclSpecBSC.cpp b/clang/lib/Sema/BSC/DeclSpecBSC.cpp index 73b48fcaa9b3..91b30bc11487 100644 --- a/clang/lib/Sema/BSC/DeclSpecBSC.cpp +++ b/clang/lib/Sema/BSC/DeclSpecBSC.cpp @@ -31,6 +31,20 @@ bool DeclSpec::setFunctionSpecAsync(SourceLocation Loc, const char *&PrevSpec, return false; } +bool DeclSpec::setFunctionSpecSuspend(SourceLocation Loc, const char *&PrevSpec, + unsigned &DiagID) { + if (FS_suspend_specified) { + DiagID = diag::warn_duplicate_declspec; + PrevSpec = "suspend"; + // Should this be invalid? + // TODO check that both `suspend` match + return true; + } + FS_suspend_specified = true; + FS_suspendLoc = Loc; + return false; +} + bool DeclSpec::setFunctionSafeSpecifier(SourceLocation Loc, const char *&PrevSpec, unsigned &DiagID, SafeScopeSpecifier SafeSpec) { diff --git a/clang/lib/Sema/DeclSpec.cpp b/clang/lib/Sema/DeclSpec.cpp index 22423cec487c..9949e117987a 100644 --- a/clang/lib/Sema/DeclSpec.cpp +++ b/clang/lib/Sema/DeclSpec.cpp @@ -474,7 +474,7 @@ unsigned DeclSpec::getParsedSpecifiers() const { if (FS_inline_specified || FS_virtual_specified || hasExplicitSpecifier() || FS_noreturn_specified || FS_forceinline_specified #if ENABLE_BSC - || FS_safe_specified || FS_async_specified + || FS_safe_specified || FS_async_specified || FS_suspend_specified #endif ) Res |= PQ_FunctionSpecifier; diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 703a782a8887..84adfd831929 100755 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -6661,6 +6661,8 @@ void Sema::DiagnoseFunctionSpecifiers(const DeclSpec &DS) { #if ENABLE_BSC if (DS.isAsyncSpecified()) Diag(DS.getAsyncSpecLoc(), diag::err_async_non_function); + if (DS.isSuspendSpecified()) + Diag(DS.getSuspendSpecLoc(), diag::err_async_non_function); #endif } @@ -9114,8 +9116,8 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D, "Strict prototypes are required"); #if ENABLE_BSC - if (SemaRef.getLangOpts().BSC && D.getDeclSpec().getConstexprSpecifier() == - ConstexprSpecKind::Constexpr) { + if (SemaRef.getLangOpts().BSC + && D.getDeclSpec().getConstexprSpecifier() == ConstexprSpecKind::Constexpr) { NewFD = FunctionDecl::Create( SemaRef.Context, DC, D.getBeginLoc(), NameInfo, R, TInfo, SC, SemaRef.getCurFPFeatures().isFPConstrained(), isInline, HasPrototype, @@ -11893,6 +11895,9 @@ void Sema::CheckMain(FunctionDecl* FD, const DeclSpec& DS) { if (FD->isAsyncSpecified()) Diag(DS.getAsyncSpecLoc(), diag::err_async_main) << FixItHint::CreateRemoval(DS.getAsyncSpecLoc()); + if (FD->isSuspendSpecified()) + Diag(DS.getSuspendSpecLoc(), diag::err_async_main) + << FixItHint::CreateRemoval(DS.getSuspendSpecLoc()); #endif if (DS.isNoreturnSpecified()) { SourceLocation NoreturnLoc = DS.getNoreturnSpecLoc(); @@ -13940,7 +13945,7 @@ void Sema::CheckCompleteVariableDeclaration(VarDecl *var) { #if ENABLE_BSC ( #endif - getLangOpts().CPlusPlus + getLangOpts().CPlusPlus #if ENABLE_BSC || getLangOpts().BSC) #endif diff --git a/clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/await-expression/await-expression.cbs b/clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/await-expression/await-expression.cbs new file mode 100644 index 000000000000..8cf57a982f8d --- /dev/null +++ b/clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/await-expression/await-expression.cbs @@ -0,0 +1,26 @@ +// RUN: %clang %s -o %test.output +// RUN: %test.output +// RUN: %clang -rewrite-bsc %s -o %t-rw.c +// RUN: %clang %t-rw.c -o %t-rw.output +// RUN: %t-rw.output +// expected-no-diagnostics + +suspend int foo() { + return 1; +} + +suspend int read(int a) { + sus_await foo(); + return 0; +} + +// Next step, enable +// suspend bool write(int a) { +// return 0; +// } + +// typedef suspend void (*CORO)(int signum); + +int main() { + return 0; +} \ No newline at end of file diff --git a/clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/suspend-expression/suspend-expression.cbs b/clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/suspend-expression/suspend-expression.cbs new file mode 100644 index 000000000000..713f5f971482 --- /dev/null +++ b/clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/suspend-expression/suspend-expression.cbs @@ -0,0 +1,27 @@ +// RUN: %clang %s -o %test.output +// RUN: %test.output +// RUN: %clang -rewrite-bsc %s -o %t-rw.c +// RUN: %clang %t-rw.c -o %t-rw.output +// RUN: %t-rw.output +// expected-no-diagnostics + +suspend int foo(int a) { + yield; + return 0; +} + +suspend int read(int a) { + yield 34; + return 0; +} + +// Next step, enable +// suspend bool write(int a) { +// return 0; +// } + +// typedef suspend void (*CORO)(int signum); + +int main() { + return 0; +} \ No newline at end of file diff --git a/clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/suspend-specifier/suspend-specifier.cbs b/clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/suspend-specifier/suspend-specifier.cbs index 160eefd203c1..33daac06c766 100644 --- a/clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/suspend-specifier/suspend-specifier.cbs +++ b/clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/suspend-specifier/suspend-specifier.cbs @@ -9,6 +9,7 @@ suspend int read(int a) { return 0; } +// Next step, enable // suspend bool write(int a) { // return 0; // } diff --git a/clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/tcp/tcp.cbs b/clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/tcp/tcp.cbs new file mode 100644 index 000000000000..563cc500ae20 --- /dev/null +++ b/clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/tcp/tcp.cbs @@ -0,0 +1,217 @@ +// RUN: %clang -rewrite-bsc %S/tcp.hbs -o %t-rw.h +// RUN: %clang -rewrite-bsc %s -o %t-rw.c +// expected-no-diagnostics + +#include "tcp.hbs" + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Scheduler functions +suspend short poll_await(int fd, uint32_t awaiting_events); + + +struct Addr struct Addr::new(char* addr) { + struct Addr a; + a.addr = addr; + return a; +} + + +TcpListener TcpListener::new(const char *ip, const char *port) { + bool non_blocking = true; + bool reuse_address = false; + bool reuse_port = false; + int listen_queue_size = 128; + + TcpListener res; + res.socket = -1; + + // deb_log("Getting listening TCP socket on ip: %s, port: %s\n" + // " non blocking: %d, reuse address: %d, reuse port: %d\n", + // ip, port, non_blocking, reuse_address, reuse_port); + + int listener = -1; // Listening socket descriptor + int ret_val; + + // getAddrInfo, this is compatible with IPv6 and should resolve DNS lookups + struct addrinfo hints; + struct addrinfo *ai; + struct addrinfo *p; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + if ((ret_val = getaddrinfo(ip, port, &hints, &ai)) != 0) { + // deb_log("getaddrinf failed with: %s\n", gai_strerror(ret_val)); + return res; + } + + // Try to bind to all of them + for (p = ai; p != NULL; p = p->ai_next) { + listener = socket( + p->ai_family, p->ai_socktype | (non_blocking ? SOCK_NONBLOCK : 0), p->ai_protocol); + if (listener < 0) { + // deb_log("Failure to create socket with family: %d, type: %d, protocol: %d\n", + // p->ai_family, p->ai_socktype | (non_blocking ? SOCK_NONBLOCK : 0), p->ai_protocol); + continue; + } + + if (reuse_address) { + // deb_log("Reusing address\n"); + int yes = 1; + setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + } + + if (reuse_port) { + // deb_log("Reusing port\n"); + int yes = 1; + setsockopt(listener, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes)); + } + + if (bind(listener, p->ai_addr, p->ai_addrlen) < 0) { + // deb_log("Failure to bind socket\n"); + close(listener); + continue; + } + + break; + } + + // Cleanup + freeaddrinfo(ai); + if (p == NULL) { + // deb_log("None of the addrinfo succeded, exiting\n"); + return res; + } + + // Listen + // deb_log("Setting up listener with %d backlog\n", listen_queue_size); + if (listen(listener, listen_queue_size) == -1) { + close(listener); + return res; + } + + res.socket = listener; + + return res; +} + +suspend TcpStream TcpListener::accept(TcpListener* this){ + // it is assumed that the socket is non blocking + // TODO assert that the socket is non blocking + + TcpStream res; + res.socket = -1; + + // This is a non-await call, switch the stack + // int n_read = accept4(this->socket, NULL, NULL, SOCK_NONBLOCK); + int n_read = accept(this->socket, NULL, NULL); + if (n_read >= 0) { + // accept succesful (fast path) + res.socket = n_read; + return res; + } else { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + // No point in waiting, something else made it fail + // deb_log("Call to accept4 failed! returning error\n"); + res.socket = n_read; + return res; + } + } + + // This should be EPOLLET, but we're using poll now + short revents = sus_await poll_await(this->socket, POLLIN); + if (POLLHUP & revents) { + res.socket = 0; + return res; + } + if (!(POLLIN & revents)) { + res.socket = -1; + return res; + } + + // There must be something here + // res.socket = accept4(this->socket, NULL, NULL, SOCK_NONBLOCK); + res.socket = accept(this->socket, NULL, NULL); + return res; +} + + +void TcpListener::close(TcpListener* this) { + close(this->socket); +} + + +TcpStream TcpStream::new() { + TcpStream res; + return res; +} + +// suspend long long TcpStream::connect(TcpStream* this, struct Addr addr){ + +// } + +suspend int TcpStream::send(TcpStream* this, char* buffer, int bufsz){ + int n_read = send(this->socket, buffer, bufsz, MSG_DONTWAIT); + if (n_read >= 0) { + // send succesful + return n_read; + } else { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + // No point in waiting + // deb_log("Call to send failed! returning error\n"); + return n_read; + } + } + short revents = sus_await poll_await(this->socket, POLLOUT); + if (POLLHUP & revents) + return 0; + if (!(POLLOUT & revents)) + return -1; + + // Assume there's space to write + return send(this->socket, buffer, bufsz, MSG_DONTWAIT); +} + +suspend int TcpStream::recv(TcpStream* this, char* buffer, int bufsz) { + int n_read = recv(this->socket, buffer, bufsz, MSG_DONTWAIT); + if (n_read >= 0) { + // recv succesful + // TODO remove this, I don't like it + buffer[n_read] = 0; + return n_read; + } else { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + // No point in waiting + // deb_log("Call to recv failed! returning error\n"); + return n_read; + } + } + // This should be EPOLLET, but we're using poll now + short revents = sus_await poll_await(this->socket, POLLIN); + if (POLLHUP & revents) + return 0; + if (!(POLLIN & revents)) + return -1; + + // Assume there's data to read + return recv(this->socket, buffer, bufsz, MSG_DONTWAIT); +} + +void TcpStream::close(TcpStream* this){ + close(this->socket); +} diff --git a/clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/tcp/tcp.hbs b/clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/tcp/tcp.hbs new file mode 100644 index 000000000000..082ecfbb3435 --- /dev/null +++ b/clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/tcp/tcp.hbs @@ -0,0 +1,40 @@ +// RUN: %clang -rewrite-bsc %s -o %t-rw.h +// expected-no-diagnostics + +#ifndef TCP_HBS +#define TCP_HBS + + +// Note: we only support ipv4 for now +struct Addr { + char* addr; +}; + +struct Addr struct Addr::new(char* addr); + +typedef struct _TcpListener { + long long socket; + // long long bind_sock; +} TcpListener; + +typedef struct _TcpStream { + // long long create_sock; + long long socket; +} TcpStream; + +TcpListener TcpListener::new(const char *ip, const char *port); + +// int TcpListener::bind(TcpListener* this, struct Addr addr); +suspend TcpStream TcpListener::accept(TcpListener* this); +void TcpListener::close(TcpListener* this); + + +TcpStream TcpStream::new(); + +suspend long long TcpStream::connect(TcpStream* this, struct Addr addr); +suspend int TcpStream::send(TcpStream* this, char* buf, int size); +suspend int TcpStream::recv(TcpStream* this, char* buf, int size); +void TcpStream::close(TcpStream* this); + + +#endif // TCP_HBS \ No newline at end of file -- Gitee From b53d2f0a78f863b7184fadb68860aa3035ba1264 Mon Sep 17 00:00:00 2001 From: Teodoro Freund Date: Tue, 13 Feb 2024 14:58:34 +0000 Subject: [PATCH 03/11] [stackful] Added checks on uses of `yield` --- .../clang/Basic/BSC/DiagnosticBSCSemaKinds.td | 4 +++ clang/include/clang/Sema/Sema.h | 4 +++ clang/lib/Parse/ParseExpr.cpp | 31 ++++++----------- clang/lib/Sema/BSC/SemaStmtBSC.cpp | 34 +++++++++++++++++++ clang/lib/Sema/SemaDecl.cpp | 5 ++- .../suspend-expression/suspend-expression.cbs | 10 ++++++ 6 files changed, 67 insertions(+), 21 deletions(-) create mode 100644 clang/test/BSC/Negative/StackfulCoroutine/AwaitExpr/suspend-expression/suspend-expression.cbs diff --git a/clang/include/clang/Basic/BSC/DiagnosticBSCSemaKinds.td b/clang/include/clang/Basic/BSC/DiagnosticBSCSemaKinds.td index 623e78105b47..261f8d0277bc 100644 --- a/clang/include/clang/Basic/BSC/DiagnosticBSCSemaKinds.td +++ b/clang/include/clang/Basic/BSC/DiagnosticBSCSemaKinds.td @@ -35,6 +35,10 @@ def err_await_expression_not_found: Error < def err_invalid_async_function : Error < "function which returns 'Future' type should not be modified by 'async'">; +// BSC suspend/yield warnings and errors +def err_yield_on_non_suspendable_function : Error< + "'yield' can only appear in 'suspend' functions">; + // BSC ownership warnings and errors. def err_owned_inderictOwned_type_check : Error< "%select{" diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 680fadad6984..d32a5bbaa20b 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -3209,6 +3209,10 @@ public: SmallVector ActOnAsyncFunctionDefinition(FunctionDecl *FD); bool IsBSCCompatibleFutureType(QualType Ty); + + /// Act on a yield expr, perform any semantic analysis and + /// necessary desugar + ExprResult ActOnYieldExpression(SourceLocation SuspendLoc, Expr* E); #endif /// We've found a use of a templated declaration that would trigger an diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp index a3576806b138..cb8c0ddeb27d 100644 --- a/clang/lib/Parse/ParseExpr.cpp +++ b/clang/lib/Parse/ParseExpr.cpp @@ -1464,33 +1464,24 @@ ExprResult Parser::ParseCastExpression( return Res; } - case tok::kw_yield: { // unary-expression: 'suspend' cast-expression + case tok::kw_yield: { // unary-expression: 'yield' cast-expression if (NotPrimaryExpression) *NotPrimaryExpression = true; SourceLocation SuspendLoc = ConsumeToken(); + if (Tok.is(tok::semi)){ // Not yielding any value, just return void - QualType VoidPT = Actions.Context.getPointerType(Actions.Context.VoidTy); - - CStyleCastExpr* E = CStyleCastExpr::Create( - Actions.Context, - VoidPT, - VK_PRValue, - CK_NullToPointer, - IntegerLiteral::Create(Actions.Context, llvm::APInt(Actions.Context.getTypeSize(Actions.Context.IntTy), 0, false), Actions.Context.IntTy, SourceLocation()), - nullptr, - FPOptionsOverride(), - Actions.Context.getTrivialTypeSourceInfo(VoidPT, SourceLocation()), - SourceLocation(), - SourceLocation()); - - Res = ActionResult(E); - + Res = Actions.ActOnYieldExpression(SuspendLoc, nullptr); } else { - Res = ParseCastExpression(AnyCastExpr); + // Parse the optional cast expression, and act on it + auto E = ParseCastExpression(AnyCastExpr); + if (E.isInvalid()) { + return E; + } else { + Res = Actions.ActOnYieldExpression(SuspendLoc, E.get()); + } } - // if (!Res.isInvalid()) - // Res = Actions.ActOnAwaitExpr(AwaitLoc, Res.get()); + return Res; } #endif diff --git a/clang/lib/Sema/BSC/SemaStmtBSC.cpp b/clang/lib/Sema/BSC/SemaStmtBSC.cpp index 589a2974c844..a8412e29120b 100644 --- a/clang/lib/Sema/BSC/SemaStmtBSC.cpp +++ b/clang/lib/Sema/BSC/SemaStmtBSC.cpp @@ -14,6 +14,7 @@ #if ENABLE_BSC #include "clang/Sema/Sema.h" +#include "clang/Sema/SemaDiagnostic.h" using namespace clang; using namespace sema; @@ -39,4 +40,37 @@ void Sema::ActOnPragmaIcallHint(std::string funcInfo) { } } +ExprResult Sema::ActOnYieldExpression(SourceLocation SuspendLoc, Expr* E){ + + const FunctionDecl *FD = getCurFunctionDecl(); + + if (!FD->isSuspendSpecified()){ + Diag(SuspendLoc, diag::err_yield_on_non_suspendable_function) << FD; + } + + // TODO: Check that type of cast expression mathces with function suspend + + // TODO: Generate a type instantiated expr depending on the types of suspend + + if (E){ + return ExprResult(E); + } else { + QualType VoidPT = Context.getPointerType(Context.VoidTy); + + CStyleCastExpr* E = CStyleCastExpr::Create( + Context, + VoidPT, + VK_PRValue, + CK_NullToPointer, + IntegerLiteral::Create(Context, llvm::APInt(Context.getTypeSize(Context.IntTy), 0, false), Context.IntTy, SourceLocation()), + nullptr, + FPOptionsOverride(), + Context.getTrivialTypeSourceInfo(VoidPT, SourceLocation()), + SourceLocation(), + SourceLocation()); + + return ExprResult(E); + } +} + #endif \ No newline at end of file diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 84adfd831929..71f8d6f18d67 100755 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -9067,6 +9067,7 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D, bool isInline = D.getDeclSpec().isInlineSpecified(); #if ENABLE_BSC bool isAsync = D.getDeclSpec().isAsyncSpecified(); + bool isSuspend = D.getDeclSpec().isSuspendSpecified(); #endif if (!SemaRef.getLangOpts().CPlusPlus) { @@ -9123,7 +9124,8 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D, SemaRef.getCurFPFeatures().isFPConstrained(), isInline, HasPrototype, ConstexprSpecKind::Constexpr, /*TrailingRequiresClause=*/nullptr, - isAsync); + isAsync, + isSuspend); } else { #endif NewFD = FunctionDecl::Create( @@ -9133,6 +9135,7 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D, /*TrailingRequiresClause=*/nullptr #if ENABLE_BSC , isAsync + , isSuspend #endif ); #if ENABLE_BSC diff --git a/clang/test/BSC/Negative/StackfulCoroutine/AwaitExpr/suspend-expression/suspend-expression.cbs b/clang/test/BSC/Negative/StackfulCoroutine/AwaitExpr/suspend-expression/suspend-expression.cbs new file mode 100644 index 000000000000..04faeb409674 --- /dev/null +++ b/clang/test/BSC/Negative/StackfulCoroutine/AwaitExpr/suspend-expression/suspend-expression.cbs @@ -0,0 +1,10 @@ +// RUN: %clang_cc1 -verify %s + +int foo(int a) { + (void) yield; // expected-error {{'yield' can only appear in 'suspend' functions}} + return 0; +} + +int main() { + return 0; +} \ No newline at end of file -- Gitee From 35903df39a8a43cf1e573885b795fbafe9f4471c Mon Sep 17 00:00:00 2001 From: Teodoro Freund Date: Tue, 13 Feb 2024 16:49:28 +0000 Subject: [PATCH 04/11] [stackful] Added check for sus_await calls --- .../clang/Basic/BSC/DiagnosticBSCSemaKinds.td | 3 ++ clang/include/clang/Sema/Sema.h | 1 + clang/lib/Parse/ParseExpr.cpp | 11 ++++--- clang/lib/Sema/BSC/SemaBSCCoroutine.cpp | 32 +++++++++++++++++-- .../await-expression/await-expression.cbs | 14 ++++++++ 5 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 clang/test/BSC/Negative/StackfulCoroutine/AwaitExpr/await-expression/await-expression.cbs diff --git a/clang/include/clang/Basic/BSC/DiagnosticBSCSemaKinds.td b/clang/include/clang/Basic/BSC/DiagnosticBSCSemaKinds.td index 261f8d0277bc..84833154d858 100644 --- a/clang/include/clang/Basic/BSC/DiagnosticBSCSemaKinds.td +++ b/clang/include/clang/Basic/BSC/DiagnosticBSCSemaKinds.td @@ -38,6 +38,9 @@ def err_invalid_async_function : Error < // BSC suspend/yield warnings and errors def err_yield_on_non_suspendable_function : Error< "'yield' can only appear in 'suspend' functions">; +def err_not_a_suspend_call : Error< + "the 'sus_await' expression's function must be modified by 'suspend'">; + // BSC ownership warnings and errors. def err_owned_inderictOwned_type_check : Error< diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index d32a5bbaa20b..6380f9bfec92 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -3213,6 +3213,7 @@ public: /// Act on a yield expr, perform any semantic analysis and /// necessary desugar ExprResult ActOnYieldExpression(SourceLocation SuspendLoc, Expr* E); + ExprResult ActOnSusAwaitExpr(SourceLocation AwaitLoc, Expr *E); #endif /// We've found a use of a templated declaration that would trigger an diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp index cb8c0ddeb27d..00a82bd25c9f 100644 --- a/clang/lib/Parse/ParseExpr.cpp +++ b/clang/lib/Parse/ParseExpr.cpp @@ -1457,11 +1457,12 @@ ExprResult Parser::ParseCastExpression( if (NotPrimaryExpression) *NotPrimaryExpression = true; SourceLocation SusAwaitLoc = ConsumeToken(); - Res = ParseCastExpression(AnyCastExpr); - // TODO this cast expression must actually be a function call - // if (!Res.isInvalid()) - // Res = Actions.ActOnAwaitExpr(AwaitLoc, Res.get()); - return Res; + auto E = ParseCastExpression(AnyCastExpr); + if (E.isInvalid()) { + return E; + } else { + return Actions.ActOnSusAwaitExpr(SusAwaitLoc, E.get()); + } } case tok::kw_yield: { // unary-expression: 'yield' cast-expression diff --git a/clang/lib/Sema/BSC/SemaBSCCoroutine.cpp b/clang/lib/Sema/BSC/SemaBSCCoroutine.cpp index f67a5b3ba50d..7cfee17147ff 100644 --- a/clang/lib/Sema/BSC/SemaBSCCoroutine.cpp +++ b/clang/lib/Sema/BSC/SemaBSCCoroutine.cpp @@ -541,7 +541,7 @@ static RecordDecl *buildFutureRecordDecl( : S.Context.getQualifiedType( AE->getType(), AwaitFD->getReturnType().getQualifiers()); } else AEType = AE->getType(); - + if (!S.IsBSCCompatibleFutureType(AEType)) { QualType FatPointerType = lookupGenericType( S, FD->getBeginLoc(), AEType, "__Trait_Future"); @@ -2185,7 +2185,7 @@ class AEFinder : public StmtVisitor { : SemaRef.Context.getQualifiedType( AE->getType(), AwaitFD->getReturnType().getQualifiers()); } else AEType = AE->getType(); - + const RecordType *FutureType = dyn_cast( AEType.getDesugaredType(SemaRef.Context)); RecordDecl *FutureStructRD = FutureType->getDecl(); @@ -2933,6 +2933,34 @@ ExprResult Sema::ActOnAwaitExpr(SourceLocation AwaitLoc, Expr *E) { return BuildAwaitExpr(AwaitLoc, E); } +ExprResult Sema::ActOnSusAwaitExpr(SourceLocation AwaitLoc, Expr *E) { + assert(E && "null expression"); + + if (auto *Call = dyn_cast(E)){ + if (auto *FDecl = dyn_cast_or_null(Call->getCalleeDecl())){ + if (!FDecl->isSuspendSpecified()) { + Diag(E->getExprLoc(), PDiag(diag::err_not_a_suspend_call) + << getExprRange(E)); + return ExprError(); + } + } else { + // Error, it should have a function declaration + Diag(E->getExprLoc(), PDiag(diag::err_not_a_suspend_call) + << getExprRange(E)); + return ExprError(); + + } + } else { + // Error, it should be a call expression + Diag(E->getExprLoc(), PDiag(diag::err_not_a_suspend_call) + << getExprRange(E)); + return ExprError(); + + } + + return ExprResult(E); +} + SmallVector Sema::ActOnAsyncFunctionDeclaration(FunctionDecl *FD) { SmallVector Decls; if (!IsBSCCompatibleFutureType(FD->getReturnType())) { diff --git a/clang/test/BSC/Negative/StackfulCoroutine/AwaitExpr/await-expression/await-expression.cbs b/clang/test/BSC/Negative/StackfulCoroutine/AwaitExpr/await-expression/await-expression.cbs new file mode 100644 index 000000000000..8e628f0cbfad --- /dev/null +++ b/clang/test/BSC/Negative/StackfulCoroutine/AwaitExpr/await-expression/await-expression.cbs @@ -0,0 +1,14 @@ +// RUN: %clang_cc1 -verify %s + +int foo() { + return 1; +} + +suspend int read(int a) { + sus_await foo(); // expected-error {{the 'sus_await' expression's function must be modified by 'suspend'}} + return 0; +} + +int main() { + return 0; +} \ No newline at end of file -- Gitee From ad0e1660757ae88bc7ec1ba8d49d3f9d0df52327 Mon Sep 17 00:00:00 2001 From: Teodoro Freund Date: Wed, 14 Feb 2024 11:52:36 +0000 Subject: [PATCH 05/11] [stackful] further checks for sus_awai --- clang/include/clang/AST/BSC/DeclBSC.h | 12 +++++++----- .../clang/Basic/BSC/DiagnosticBSCSemaKinds.td | 2 ++ clang/lib/AST/BSC/DeclBSC.cpp | 5 +++-- clang/lib/Sema/BSC/SemaBSCCoroutine.cpp | 8 +++++++- clang/lib/Sema/SemaDecl.cpp | 2 +- .../await-expression-from-non-suspend.cbs | 14 ++++++++++++++ .../await-expression-of-non-suspend.cbs} | 0 7 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 clang/test/BSC/Negative/StackfulCoroutine/AwaitExpr/await-expression-from-non-suspend/await-expression-from-non-suspend.cbs rename clang/test/BSC/Negative/StackfulCoroutine/AwaitExpr/{await-expression/await-expression.cbs => await-expression-of-non-suspend/await-expression-of-non-suspend.cbs} (100%) diff --git a/clang/include/clang/AST/BSC/DeclBSC.h b/clang/include/clang/AST/BSC/DeclBSC.h index 286c685c9343..89de32ee09d6 100644 --- a/clang/include/clang/AST/BSC/DeclBSC.h +++ b/clang/include/clang/AST/BSC/DeclBSC.h @@ -37,9 +37,10 @@ protected: QualType T, TypeSourceInfo *TInfo, StorageClass SC, bool UsesFPIntrin, bool isInline, ConstexprSpecKind ConstexprKind, SourceLocation EndLocation, - Expr *TrailingRequiresClause = nullptr, bool isAsync = false) + Expr *TrailingRequiresClause = nullptr, bool isAsync = false, + bool isSuspend = false) : FunctionDecl(DK, C, RD, StartLoc, NameInfo, T, TInfo, SC, UsesFPIntrin, - isInline, ConstexprKind, TrailingRequiresClause, isAsync) { + isInline, ConstexprKind, TrailingRequiresClause, isAsync, isSuspend) { if (EndLocation.isValid()) setRangeEnd(EndLocation); } @@ -50,7 +51,8 @@ public: const DeclarationNameInfo &NameInfo, QualType T, TypeSourceInfo *TInfo, StorageClass SC, bool UsesFPIntrin, bool isInline, ConstexprSpecKind ConstexprKind, SourceLocation EndLocation, - Expr *TrailingRequiresClause = nullptr, bool isAsync = false); + Expr *TrailingRequiresClause = nullptr, bool isAsync = false, + bool isSuspend = false); static BSCMethodDecl *CreateDeserialized(ASTContext &C, unsigned ID); bool getHasThisParam() const { return HasThisParam; } @@ -228,9 +230,9 @@ public: TraitDecl *getTraitDecl(); ImplTraitDecl *getCanonicalDecl() override; - + static bool classof(const Decl *D) { return classofKind(D->getKind()); } - + static bool classofKind(Kind K) { return K == ImplTrait; } }; diff --git a/clang/include/clang/Basic/BSC/DiagnosticBSCSemaKinds.td b/clang/include/clang/Basic/BSC/DiagnosticBSCSemaKinds.td index 84833154d858..de8783675a8b 100644 --- a/clang/include/clang/Basic/BSC/DiagnosticBSCSemaKinds.td +++ b/clang/include/clang/Basic/BSC/DiagnosticBSCSemaKinds.td @@ -38,6 +38,8 @@ def err_invalid_async_function : Error < // BSC suspend/yield warnings and errors def err_yield_on_non_suspendable_function : Error< "'yield' can only appear in 'suspend' functions">; +def err_sus_await_on_non_suspendable_function : Error< + "'sus_await' can only appear in 'suspend' functions">; def err_not_a_suspend_call : Error< "the 'sus_await' expression's function must be modified by 'suspend'">; diff --git a/clang/lib/AST/BSC/DeclBSC.cpp b/clang/lib/AST/BSC/DeclBSC.cpp index 36c59c5e9fb1..91e4fd2d95f5 100644 --- a/clang/lib/AST/BSC/DeclBSC.cpp +++ b/clang/lib/AST/BSC/DeclBSC.cpp @@ -27,10 +27,11 @@ BSCMethodDecl *BSCMethodDecl::Create( const DeclarationNameInfo &NameInfo, QualType T, TypeSourceInfo *TInfo, StorageClass SC, bool UsesFPIntrinbool, bool isInline, ConstexprSpecKind ConstexprKind, SourceLocation EndLocation, - Expr *TrailingRequiresClause, bool isAsync) { + Expr *TrailingRequiresClause, bool isAsync, bool isSuspend) { return new (C, RD) BSCMethodDecl( BSCMethod, C, RD, StartLoc, NameInfo, T, TInfo, SC, UsesFPIntrinbool, - isInline, ConstexprKind, EndLocation, TrailingRequiresClause, isAsync); + isInline, ConstexprKind, EndLocation, TrailingRequiresClause, isAsync, + isSuspend); } BSCMethodDecl *BSCMethodDecl::CreateDeserialized(ASTContext &C, unsigned ID) { diff --git a/clang/lib/Sema/BSC/SemaBSCCoroutine.cpp b/clang/lib/Sema/BSC/SemaBSCCoroutine.cpp index 7cfee17147ff..4a6675a6df4d 100644 --- a/clang/lib/Sema/BSC/SemaBSCCoroutine.cpp +++ b/clang/lib/Sema/BSC/SemaBSCCoroutine.cpp @@ -2934,7 +2934,13 @@ ExprResult Sema::ActOnAwaitExpr(SourceLocation AwaitLoc, Expr *E) { } ExprResult Sema::ActOnSusAwaitExpr(SourceLocation AwaitLoc, Expr *E) { - assert(E && "null expression"); + assert(E && "null expression"); + + const FunctionDecl *FD = getCurFunctionDecl(); + + if (!FD->isSuspendSpecified()){ + Diag(AwaitLoc, diag::err_sus_await_on_non_suspendable_function) << FD; + } if (auto *Call = dyn_cast(E)){ if (auto *FDecl = dyn_cast_or_null(Call->getCalleeDecl())){ diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 71f8d6f18d67..e4a442c06542 100755 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -9099,7 +9099,7 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D, BSCMethodDecl *Ret = BSCMethodDecl::Create( SemaRef.Context, DC, D.getBeginLoc(), NameInfo, R, TInfo, SC, SemaRef.getCurFPFeatures().isFPConstrained(), isInline, ConstexprKind, - SourceLocation(), TrailingRequiresClause, isAsync); + SourceLocation(), TrailingRequiresClause, isAsync, isSuspend); Ret->setHasThisParam(D.getBSCScopeSpec().HasThisParam); Ret->setExtendedType(D.getBSCScopeSpec().getExtendedType()); Ret->setExtentedTypeBeginLoc(D.getBSCScopeSpec().getBeginLoc()); diff --git a/clang/test/BSC/Negative/StackfulCoroutine/AwaitExpr/await-expression-from-non-suspend/await-expression-from-non-suspend.cbs b/clang/test/BSC/Negative/StackfulCoroutine/AwaitExpr/await-expression-from-non-suspend/await-expression-from-non-suspend.cbs new file mode 100644 index 000000000000..103ecb2e4287 --- /dev/null +++ b/clang/test/BSC/Negative/StackfulCoroutine/AwaitExpr/await-expression-from-non-suspend/await-expression-from-non-suspend.cbs @@ -0,0 +1,14 @@ +// RUN: %clang_cc1 -verify %s + +suspend int foo() { + return 1; +} + +int read(int a) { + sus_await foo(); // expected-error {{'sus_await' can only appear in 'suspend' functions}} + return 0; +} + +int main() { + return 0; +} \ No newline at end of file diff --git a/clang/test/BSC/Negative/StackfulCoroutine/AwaitExpr/await-expression/await-expression.cbs b/clang/test/BSC/Negative/StackfulCoroutine/AwaitExpr/await-expression-of-non-suspend/await-expression-of-non-suspend.cbs similarity index 100% rename from clang/test/BSC/Negative/StackfulCoroutine/AwaitExpr/await-expression/await-expression.cbs rename to clang/test/BSC/Negative/StackfulCoroutine/AwaitExpr/await-expression-of-non-suspend/await-expression-of-non-suspend.cbs -- Gitee From 82af44e2c4a69e5cd5deeff2e6d1f458d9b919e6 Mon Sep 17 00:00:00 2001 From: Teodoro Freund Date: Tue, 20 Feb 2024 15:55:52 +0000 Subject: [PATCH 06/11] [stackful] Polish code before PR --- .../clang/Basic/BSC/DiagnosticBSCSemaKinds.td | 2 ++ clang/include/clang/Sema/Sema.h | 2 ++ clang/lib/Parse/ParseExpr.cpp | 10 +++--- clang/lib/Sema/BSC/SemaBSCCoroutine.cpp | 36 +++++++++++++++++-- clang/lib/Sema/BSC/SemaStmtBSC.cpp | 34 ------------------ .../await-expression-of-non-function.cbs | 10 ++++++ 6 files changed, 52 insertions(+), 42 deletions(-) create mode 100644 clang/test/BSC/Negative/StackfulCoroutine/AwaitExpr/await-expression-of-non-function/await-expression-of-non-function.cbs diff --git a/clang/include/clang/Basic/BSC/DiagnosticBSCSemaKinds.td b/clang/include/clang/Basic/BSC/DiagnosticBSCSemaKinds.td index de8783675a8b..f6b554305636 100644 --- a/clang/include/clang/Basic/BSC/DiagnosticBSCSemaKinds.td +++ b/clang/include/clang/Basic/BSC/DiagnosticBSCSemaKinds.td @@ -42,6 +42,8 @@ def err_sus_await_on_non_suspendable_function : Error< "'sus_await' can only appear in 'suspend' functions">; def err_not_a_suspend_call : Error< "the 'sus_await' expression's function must be modified by 'suspend'">; +def err_not_a_function_call : Error< + "the 'sus_await' expression must be a function call">; // BSC ownership warnings and errors. diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 6380f9bfec92..b5387119a027 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -3213,6 +3213,8 @@ public: /// Act on a yield expr, perform any semantic analysis and /// necessary desugar ExprResult ActOnYieldExpression(SourceLocation SuspendLoc, Expr* E); + + /// Act on sus_await expressions ExprResult ActOnSusAwaitExpr(SourceLocation AwaitLoc, Expr *E); #endif diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp index 00a82bd25c9f..00943a45e044 100644 --- a/clang/lib/Parse/ParseExpr.cpp +++ b/clang/lib/Parse/ParseExpr.cpp @@ -1453,7 +1453,7 @@ ExprResult Parser::ParseCastExpression( return Res; } - case tok::kw_sus_await: { // unary-expression: 'await' cast-expression + case tok::kw_sus_await: { // unary-expression: 'sus_await' cast-expression if (NotPrimaryExpression) *NotPrimaryExpression = true; SourceLocation SusAwaitLoc = ConsumeToken(); @@ -1465,25 +1465,23 @@ ExprResult Parser::ParseCastExpression( } } - case tok::kw_yield: { // unary-expression: 'yield' cast-expression + case tok::kw_yield: { // unary-expression: 'yield' cast-expression[opt] if (NotPrimaryExpression) *NotPrimaryExpression = true; SourceLocation SuspendLoc = ConsumeToken(); if (Tok.is(tok::semi)){ // Not yielding any value, just return void - Res = Actions.ActOnYieldExpression(SuspendLoc, nullptr); + return Actions.ActOnYieldExpression(SuspendLoc, nullptr); } else { // Parse the optional cast expression, and act on it auto E = ParseCastExpression(AnyCastExpr); if (E.isInvalid()) { return E; } else { - Res = Actions.ActOnYieldExpression(SuspendLoc, E.get()); + return Actions.ActOnYieldExpression(SuspendLoc, E.get()); } } - - return Res; } #endif diff --git a/clang/lib/Sema/BSC/SemaBSCCoroutine.cpp b/clang/lib/Sema/BSC/SemaBSCCoroutine.cpp index 4a6675a6df4d..aa13a1a3bb8a 100644 --- a/clang/lib/Sema/BSC/SemaBSCCoroutine.cpp +++ b/clang/lib/Sema/BSC/SemaBSCCoroutine.cpp @@ -2933,6 +2933,38 @@ ExprResult Sema::ActOnAwaitExpr(SourceLocation AwaitLoc, Expr *E) { return BuildAwaitExpr(AwaitLoc, E); } +ExprResult Sema::ActOnYieldExpression(SourceLocation SuspendLoc, Expr* E){ + const FunctionDecl *FD = getCurFunctionDecl(); + + if (!FD->isSuspendSpecified()){ + Diag(SuspendLoc, diag::err_yield_on_non_suspendable_function) << FD; + } + + // TODO: Check that type of cast expression mathces with function suspend + + // TODO: desugar and generate a type instantiated expr depending on the types of suspend + + if (E){ + return ExprResult(E); + } else { + QualType VoidPT = Context.getPointerType(Context.VoidTy); + + CStyleCastExpr* E = CStyleCastExpr::Create( + Context, + VoidPT, + VK_PRValue, + CK_NullToPointer, + IntegerLiteral::Create(Context, llvm::APInt(Context.getTypeSize(Context.IntTy), 0, false), Context.IntTy, SourceLocation()), + nullptr, + FPOptionsOverride(), + Context.getTrivialTypeSourceInfo(VoidPT, SourceLocation()), + SourceLocation(), + SourceLocation()); + + return ExprResult(E); + } +} + ExprResult Sema::ActOnSusAwaitExpr(SourceLocation AwaitLoc, Expr *E) { assert(E && "null expression"); @@ -2951,14 +2983,14 @@ ExprResult Sema::ActOnSusAwaitExpr(SourceLocation AwaitLoc, Expr *E) { } } else { // Error, it should have a function declaration - Diag(E->getExprLoc(), PDiag(diag::err_not_a_suspend_call) + Diag(E->getExprLoc(), PDiag(diag::err_not_a_function_call) << getExprRange(E)); return ExprError(); } } else { // Error, it should be a call expression - Diag(E->getExprLoc(), PDiag(diag::err_not_a_suspend_call) + Diag(E->getExprLoc(), PDiag(diag::err_not_a_function_call) << getExprRange(E)); return ExprError(); diff --git a/clang/lib/Sema/BSC/SemaStmtBSC.cpp b/clang/lib/Sema/BSC/SemaStmtBSC.cpp index a8412e29120b..589a2974c844 100644 --- a/clang/lib/Sema/BSC/SemaStmtBSC.cpp +++ b/clang/lib/Sema/BSC/SemaStmtBSC.cpp @@ -14,7 +14,6 @@ #if ENABLE_BSC #include "clang/Sema/Sema.h" -#include "clang/Sema/SemaDiagnostic.h" using namespace clang; using namespace sema; @@ -40,37 +39,4 @@ void Sema::ActOnPragmaIcallHint(std::string funcInfo) { } } -ExprResult Sema::ActOnYieldExpression(SourceLocation SuspendLoc, Expr* E){ - - const FunctionDecl *FD = getCurFunctionDecl(); - - if (!FD->isSuspendSpecified()){ - Diag(SuspendLoc, diag::err_yield_on_non_suspendable_function) << FD; - } - - // TODO: Check that type of cast expression mathces with function suspend - - // TODO: Generate a type instantiated expr depending on the types of suspend - - if (E){ - return ExprResult(E); - } else { - QualType VoidPT = Context.getPointerType(Context.VoidTy); - - CStyleCastExpr* E = CStyleCastExpr::Create( - Context, - VoidPT, - VK_PRValue, - CK_NullToPointer, - IntegerLiteral::Create(Context, llvm::APInt(Context.getTypeSize(Context.IntTy), 0, false), Context.IntTy, SourceLocation()), - nullptr, - FPOptionsOverride(), - Context.getTrivialTypeSourceInfo(VoidPT, SourceLocation()), - SourceLocation(), - SourceLocation()); - - return ExprResult(E); - } -} - #endif \ No newline at end of file diff --git a/clang/test/BSC/Negative/StackfulCoroutine/AwaitExpr/await-expression-of-non-function/await-expression-of-non-function.cbs b/clang/test/BSC/Negative/StackfulCoroutine/AwaitExpr/await-expression-of-non-function/await-expression-of-non-function.cbs new file mode 100644 index 000000000000..761bd284d1a7 --- /dev/null +++ b/clang/test/BSC/Negative/StackfulCoroutine/AwaitExpr/await-expression-of-non-function/await-expression-of-non-function.cbs @@ -0,0 +1,10 @@ +// RUN: %clang_cc1 -verify %s + +suspend int read(int a) { + sus_await 45; // expected-error {{the 'sus_await' expression must be a function call}} + return 0; +} + +int main() { + return 0; +} \ No newline at end of file -- Gitee From 53543499e64695380b40f42d57dec1eddb99e767 Mon Sep 17 00:00:00 2001 From: Teodoro Freund Date: Wed, 21 Feb 2024 10:38:24 +0000 Subject: [PATCH 07/11] [stackful] reorganised tests --- .../yield-expression/yield-expression.cbs} | 0 .../suspend-specifier/suspend-specifier.cbs | 0 .../{AwaitExpr/tcp => TCPServer/Inputs}/tcp.cbs | 10 +++++----- .../{AwaitExpr/tcp => TCPServer/Inputs}/tcp.hbs | 0 .../StackfulCoroutine/TCPServer/main.cbs | 16 ++++++++++++++++ .../yield-expression/yield-expression.cbs} | 0 6 files changed, 21 insertions(+), 5 deletions(-) rename clang/test/BSC/Negative/StackfulCoroutine/{AwaitExpr/suspend-expression/suspend-expression.cbs => YieldExpr/yield-expression/yield-expression.cbs} (100%) rename clang/test/BSC/Positive/StackfulCoroutine/{AwaitExpr => SuspendSpecifier}/suspend-specifier/suspend-specifier.cbs (100%) rename clang/test/BSC/Positive/StackfulCoroutine/{AwaitExpr/tcp => TCPServer/Inputs}/tcp.cbs (97%) rename clang/test/BSC/Positive/StackfulCoroutine/{AwaitExpr/tcp => TCPServer/Inputs}/tcp.hbs (100%) create mode 100644 clang/test/BSC/Positive/StackfulCoroutine/TCPServer/main.cbs rename clang/test/BSC/Positive/StackfulCoroutine/{AwaitExpr/suspend-expression/suspend-expression.cbs => YieldExpr/yield-expression/yield-expression.cbs} (100%) diff --git a/clang/test/BSC/Negative/StackfulCoroutine/AwaitExpr/suspend-expression/suspend-expression.cbs b/clang/test/BSC/Negative/StackfulCoroutine/YieldExpr/yield-expression/yield-expression.cbs similarity index 100% rename from clang/test/BSC/Negative/StackfulCoroutine/AwaitExpr/suspend-expression/suspend-expression.cbs rename to clang/test/BSC/Negative/StackfulCoroutine/YieldExpr/yield-expression/yield-expression.cbs diff --git a/clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/suspend-specifier/suspend-specifier.cbs b/clang/test/BSC/Positive/StackfulCoroutine/SuspendSpecifier/suspend-specifier/suspend-specifier.cbs similarity index 100% rename from clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/suspend-specifier/suspend-specifier.cbs rename to clang/test/BSC/Positive/StackfulCoroutine/SuspendSpecifier/suspend-specifier/suspend-specifier.cbs diff --git a/clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/tcp/tcp.cbs b/clang/test/BSC/Positive/StackfulCoroutine/TCPServer/Inputs/tcp.cbs similarity index 97% rename from clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/tcp/tcp.cbs rename to clang/test/BSC/Positive/StackfulCoroutine/TCPServer/Inputs/tcp.cbs index 563cc500ae20..02b5d1eef689 100644 --- a/clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/tcp/tcp.cbs +++ b/clang/test/BSC/Positive/StackfulCoroutine/TCPServer/Inputs/tcp.cbs @@ -1,9 +1,6 @@ -// RUN: %clang -rewrite-bsc %S/tcp.hbs -o %t-rw.h -// RUN: %clang -rewrite-bsc %s -o %t-rw.c -// expected-no-diagnostics -#include "tcp.hbs" +#include "tcp.hbs" #include #include @@ -21,7 +18,10 @@ #include // Scheduler functions -suspend short poll_await(int fd, uint32_t awaiting_events); +suspend short poll_await(int fd, uint32_t awaiting_events){ + // Dummy implementation + return 0; +} struct Addr struct Addr::new(char* addr) { diff --git a/clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/tcp/tcp.hbs b/clang/test/BSC/Positive/StackfulCoroutine/TCPServer/Inputs/tcp.hbs similarity index 100% rename from clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/tcp/tcp.hbs rename to clang/test/BSC/Positive/StackfulCoroutine/TCPServer/Inputs/tcp.hbs diff --git a/clang/test/BSC/Positive/StackfulCoroutine/TCPServer/main.cbs b/clang/test/BSC/Positive/StackfulCoroutine/TCPServer/main.cbs new file mode 100644 index 000000000000..a7cf305f1c08 --- /dev/null +++ b/clang/test/BSC/Positive/StackfulCoroutine/TCPServer/main.cbs @@ -0,0 +1,16 @@ +// RUN: %clang -g %s %S/Inputs/tcp.cbs -o %t.output +// RUN: %t.output +// RUN: mkdir -p %T/Inputs +// RUN: %clang -rewrite-bsc %S/Inputs/tcp.hbs -o %T/Inputs/tcp.h +// RUN: %clang -rewrite-bsc %S/Inputs/tcp.cbs -o %T/Inputs/tcp.c +// RUN: %clang -rewrite-bsc %s -o %t-rw.c +// RUN: %clang %t-rw.c %T/Inputs/tcp.c -o %t-rw.output +// RUN: %t-rw.output +// expected-no-diagnostics + +#include "Inputs/tcp.hbs" + +int main() { + // TCP will be + return 0; +} \ No newline at end of file diff --git a/clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/suspend-expression/suspend-expression.cbs b/clang/test/BSC/Positive/StackfulCoroutine/YieldExpr/yield-expression/yield-expression.cbs similarity index 100% rename from clang/test/BSC/Positive/StackfulCoroutine/AwaitExpr/suspend-expression/suspend-expression.cbs rename to clang/test/BSC/Positive/StackfulCoroutine/YieldExpr/yield-expression/yield-expression.cbs -- Gitee From ab9734e6edd9ce04bdf56aae08774abaaed1b6c1 Mon Sep 17 00:00:00 2001 From: Teodoro Freund Date: Thu, 29 Feb 2024 13:11:19 +0000 Subject: [PATCH 08/11] [stackful] Code in its own file --- clang/lib/Sema/BSC/SemaBSCCoroutine.cpp | 66 -------------- .../lib/Sema/BSC/SemaBSCSuspendCoroutine.cpp | 90 +++++++++++++++++++ clang/lib/Sema/CMakeLists.txt | 3 +- 3 files changed, 92 insertions(+), 67 deletions(-) create mode 100644 clang/lib/Sema/BSC/SemaBSCSuspendCoroutine.cpp diff --git a/clang/lib/Sema/BSC/SemaBSCCoroutine.cpp b/clang/lib/Sema/BSC/SemaBSCCoroutine.cpp index aa13a1a3bb8a..7fe2a3a38fc2 100644 --- a/clang/lib/Sema/BSC/SemaBSCCoroutine.cpp +++ b/clang/lib/Sema/BSC/SemaBSCCoroutine.cpp @@ -2933,72 +2933,6 @@ ExprResult Sema::ActOnAwaitExpr(SourceLocation AwaitLoc, Expr *E) { return BuildAwaitExpr(AwaitLoc, E); } -ExprResult Sema::ActOnYieldExpression(SourceLocation SuspendLoc, Expr* E){ - const FunctionDecl *FD = getCurFunctionDecl(); - - if (!FD->isSuspendSpecified()){ - Diag(SuspendLoc, diag::err_yield_on_non_suspendable_function) << FD; - } - - // TODO: Check that type of cast expression mathces with function suspend - - // TODO: desugar and generate a type instantiated expr depending on the types of suspend - - if (E){ - return ExprResult(E); - } else { - QualType VoidPT = Context.getPointerType(Context.VoidTy); - - CStyleCastExpr* E = CStyleCastExpr::Create( - Context, - VoidPT, - VK_PRValue, - CK_NullToPointer, - IntegerLiteral::Create(Context, llvm::APInt(Context.getTypeSize(Context.IntTy), 0, false), Context.IntTy, SourceLocation()), - nullptr, - FPOptionsOverride(), - Context.getTrivialTypeSourceInfo(VoidPT, SourceLocation()), - SourceLocation(), - SourceLocation()); - - return ExprResult(E); - } -} - -ExprResult Sema::ActOnSusAwaitExpr(SourceLocation AwaitLoc, Expr *E) { - assert(E && "null expression"); - - const FunctionDecl *FD = getCurFunctionDecl(); - - if (!FD->isSuspendSpecified()){ - Diag(AwaitLoc, diag::err_sus_await_on_non_suspendable_function) << FD; - } - - if (auto *Call = dyn_cast(E)){ - if (auto *FDecl = dyn_cast_or_null(Call->getCalleeDecl())){ - if (!FDecl->isSuspendSpecified()) { - Diag(E->getExprLoc(), PDiag(diag::err_not_a_suspend_call) - << getExprRange(E)); - return ExprError(); - } - } else { - // Error, it should have a function declaration - Diag(E->getExprLoc(), PDiag(diag::err_not_a_function_call) - << getExprRange(E)); - return ExprError(); - - } - } else { - // Error, it should be a call expression - Diag(E->getExprLoc(), PDiag(diag::err_not_a_function_call) - << getExprRange(E)); - return ExprError(); - - } - - return ExprResult(E); -} - SmallVector Sema::ActOnAsyncFunctionDeclaration(FunctionDecl *FD) { SmallVector Decls; if (!IsBSCCompatibleFutureType(FD->getReturnType())) { diff --git a/clang/lib/Sema/BSC/SemaBSCSuspendCoroutine.cpp b/clang/lib/Sema/BSC/SemaBSCSuspendCoroutine.cpp new file mode 100644 index 000000000000..1400ef4f6078 --- /dev/null +++ b/clang/lib/Sema/BSC/SemaBSCSuspendCoroutine.cpp @@ -0,0 +1,90 @@ +//===--- SemaBSCSuspendCoroutine.cpp - Semantic Analysis for BSC Suspend 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 file implements semantic analysis for BSC Coroutines. +// +//===----------------------------------------------------------------------===// + +#if ENABLE_BSC + +#include "clang/AST/Expr.h" +#include "clang/Sema/Sema.h" +#include "clang/Sema/SemaDiagnostic.h" +#include "clang/Sema/SemaInternal.h" + +using namespace clang; +using namespace sema; + +ExprResult Sema::ActOnYieldExpression(SourceLocation SuspendLoc, Expr* E){ + const FunctionDecl *FD = getCurFunctionDecl(); + + if (!FD->isSuspendSpecified()){ + Diag(SuspendLoc, diag::err_yield_on_non_suspendable_function) << FD; + } + + // TODO: Check that type of cast expression mathces with function suspend + + // TODO: desugar and generate a type instantiated expr depending on the types of suspend + + if (E){ + return ExprResult(E); + } else { + QualType VoidPT = Context.getPointerType(Context.VoidTy); + + CStyleCastExpr* E = CStyleCastExpr::Create( + Context, + VoidPT, + VK_PRValue, + CK_NullToPointer, + IntegerLiteral::Create(Context, llvm::APInt(Context.getTypeSize(Context.IntTy), 0, false), Context.IntTy, SourceLocation()), + nullptr, + FPOptionsOverride(), + Context.getTrivialTypeSourceInfo(VoidPT, SourceLocation()), + SourceLocation(), + SourceLocation()); + + return ExprResult(E); + } +} + +ExprResult Sema::ActOnSusAwaitExpr(SourceLocation AwaitLoc, Expr *E) { + assert(E && "null expression"); + + const FunctionDecl *FD = getCurFunctionDecl(); + + if (!FD->isSuspendSpecified()){ + Diag(AwaitLoc, diag::err_sus_await_on_non_suspendable_function) << FD; + } + + if (auto *Call = dyn_cast(E)){ + if (auto *FDecl = dyn_cast_or_null(Call->getCalleeDecl())){ + if (!FDecl->isSuspendSpecified()) { + Diag(E->getExprLoc(), PDiag(diag::err_not_a_suspend_call) + << getExprRange(E)); + return ExprError(); + } + } else { + // Error, it should have a function declaration + Diag(E->getExprLoc(), PDiag(diag::err_not_a_function_call) + << getExprRange(E)); + return ExprError(); + + } + } else { + // Error, it should be a call expression + Diag(E->getExprLoc(), PDiag(diag::err_not_a_function_call) + << getExprRange(E)); + return ExprError(); + + } + + return ExprResult(E); +} + +#endif // ENABLE_BSC \ No newline at end of file diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt index f788bdc6da20..4ec6104d0836 100644 --- a/clang/lib/Sema/CMakeLists.txt +++ b/clang/lib/Sema/CMakeLists.txt @@ -13,11 +13,12 @@ clang_tablegen(OpenCLBuiltins.inc -gen-clang-opencl-builtins add_clang_library(clangSema AnalysisBasedWarnings.cpp BSC/SemaBSCCoroutine.cpp + BSC/SemaBSCSuspendCoroutine.cpp BSC/SemaBSCOwnership.cpp BSC/SemaBSCSafeZone.cpp BSC/SemaBSCTrait.cpp BSC/SemaStmtBSC.cpp - BSC/SemaDeclBSC.cpp + BSC/SemaDeclBSC.cpp BSC/SemaTemplateInstantiateDeclBSC.cpp BSC/DeclSpecBSC.cpp CodeCompleteConsumer.cpp -- Gitee From 7c8674c19e49d9089bfe8b827f44dc69757cba36 Mon Sep 17 00:00:00 2001 From: Teodoro Freund Date: Wed, 21 Feb 2024 15:15:45 +0000 Subject: [PATCH 09/11] [stackful] initial coroutine runtime --- .../Runtime/Inputs/coroutine.cbs | 30 +++ .../Runtime/Inputs/coroutine.hbs | 193 +++++++++++++++ .../Runtime/Inputs/swap_contexts.S | 227 ++++++++++++++++++ .../coroutine_dont_pause.cbs | 35 +++ .../coroutine_suspend/coroutine_suspend.cbs | 48 ++++ .../create_coroutine/create_coroutine.cbs | 29 +++ 6 files changed, 562 insertions(+) create mode 100644 clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/coroutine.cbs create mode 100644 clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/coroutine.hbs create mode 100644 clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/swap_contexts.S create mode 100644 clang/test/BSC/Positive/StackfulCoroutine/Runtime/coroutine_dont_pause/coroutine_dont_pause.cbs create mode 100644 clang/test/BSC/Positive/StackfulCoroutine/Runtime/coroutine_suspend/coroutine_suspend.cbs create mode 100644 clang/test/BSC/Positive/StackfulCoroutine/Runtime/create_coroutine/create_coroutine.cbs diff --git a/clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/coroutine.cbs b/clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/coroutine.cbs new file mode 100644 index 000000000000..cea3e436a2f1 --- /dev/null +++ b/clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/coroutine.cbs @@ -0,0 +1,30 @@ +#include +#include +#include +#include +#include + +#include "coroutine.hbs" + +Segment* new_segment(size_t frame_size) { + Segment* frame = malloc(frame_size + sizeof(Segment)); + + if (!frame) { + exit(-1); + } + +#ifndef NDEBUG + char *ptr = (char *)frame; + for (int i = 0; i < frame_size; i++) { + ptr[i] = 0x13; + } +#endif + + frame->prev = NULL; + frame->next = NULL; + frame->size = frame_size; + frame->canary = (void *)0x999999999999; + + // *rsp = (char *)frame + frame_size; + return frame; +} diff --git a/clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/coroutine.hbs b/clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/coroutine.hbs new file mode 100644 index 000000000000..2525a1580242 --- /dev/null +++ b/clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/coroutine.hbs @@ -0,0 +1,193 @@ +#include +#include +#include +#include +#include + +struct Unit {}; + +typedef enum _State { + NEW, + PAUSED, + RUNNING, + FINISHED +} State; + +// This structure is architecture dependent (calling convention) +typedef struct _SavedContext { + void *stack_top; // 0 + void *current_coroutine; // 8 + + void *rsp; // 16 + void *rbp; // 24 + void *ip; // 32 + void *rbx; // 40 + void *r12; // 48 + void *r13; // 56 + void *r14; // 64 + void *r15; // 72 +} SavedContext; + + +typedef struct _Segment { + struct _Segment *prev; + struct _Segment *next; + size_t size; + void *canary; + uint8_t padding[128]; + // void* frame; +} Segment; + +// Note that the attributes are not really parametric, only its methods are +// TODO these fields should be private, does BiShengC allow that? +struct Coroutine { + // All the registers and flags stored between resumption points + SavedContext context; + + // coroutine state + State state; + + // The linked list of segments + Segment* head; + + struct Coroutine* parent; +}; + + + +Segment* new_segment(size_t frame_size); + +extern __thread void *_current_coroutine; + +/** + * _into and _out work in very similar ways. + * _into gives execution to the given coroutine, setting it as a + * child of the currently running coroutine (if any) + * _out pauses execution of the given coroutine, returning to + * the parent + * + * - `coro` is the coroutine + * - `arg` is the argument passed to the next execution context + * - the return is whatever the other context provides + * + * _into and _out always come in pairs, and _out always links to + * a previously executed _into. + * The return value of each of these should always be the argument of the other. +*/ +void* _into(void* coro, void* arg); +void* _out(void* coro, void* arg); + +// Recover the current coroutine with some type +// Note that this function is a workaround to be able to manually call __suspend, and it's unsafe +struct Coroutine *current_coroutine(){ + return (struct Coroutine*) _current_coroutine; +} + +void* coroutine_prelude(void *fn, void *arg); + +Y typed_coroutine_prelude(Y (*fn)(T), T *arg) { + struct Coroutine* coro = current_coroutine(); + + assert(coro->state == RUNNING); + Y res = fn(*arg); + + coro->state = FINISHED; + _out(coro, (void*) &res); + assert(false); // this should never happen +} + +// Initialize a coroutine on function `fn` with argument `arg` +// It only accepts pointers as it's main argument +struct Coroutine* create_coro (Y (*fn)(T), T arg) { + // TODO fn should be suspend, we can't check it for now + // TODO allow polymorphic parameter types + // TODO allow variable number of arguments + + struct Coroutine* c = malloc(sizeof(struct Coroutine)); + c->state = NEW; + + const size_t frame_size = 16 * 1024; // TODO Do it parametric or smth + + c->head = new_segment(frame_size); + if (!c->head) { + exit(-1); + } + + c->context.stack_top = c->head + sizeof(Segment); + c->context.current_coroutine = c; + + c->context.rsp = ((char *)c->head) + frame_size + sizeof(Segment); + c->context.rbp = NULL; + c->context.ip = (void*) coroutine_prelude; // TODO coroutine_prelude + c->context.rbx = (void *)0xcacabbbb; + c->context.r12 = (void *)0xcaca1212; + c->context.r13 = (void *)typed_coroutine_prelude; + c->context.r14 = (void *)&arg; + c->context.r15 = (void *)fn; + + + /* + * Padding to ensure the stack address is 16-byte aligned + * This is necessary depending on the stack layout because the + * x86-64 ABI mandates a 16-byte aligned stack + */ + // size_t pad = ((uintptr_t)k->resume_point.rsp) % 16; + // k->resume_point.rsp = ((char *)k->resume_point.rsp) - pad; + + // assert(((uintptr_t)k->resume_point.rsp) % 16 == 0); + + return c; +} + +// Free the space used by the coroutine and its segments +void struct Coroutine::delete_coro(struct Coroutine* this){ + // TODO (leaking for now) +} + + + +// Start the coroutine +Y struct Coroutine::start(struct Coroutine* this){ + assert((this->state) == NEW); + // assert(1); + this->state = RUNNING; + + void* ret = _into((void*)this, NULL); + + // At this point, the coroutine either yielded or returned + // which means we need to recover the returned value and return it + return *(Y*)ret; +} +// Resume a suspended coroutine +Y struct Coroutine::resume(struct Coroutine* this, R arg){ + assert(this->state == PAUSED); + this->state = RUNNING; + + void* ret = _into(this, (void*) &arg); + + return *(Y*)ret; +} + +bool struct Coroutine::started(struct Coroutine* this){ + return this->state != NEW; +} + +bool struct Coroutine::running(struct Coroutine* this){ + return this->state == RUNNING; +} + +bool struct Coroutine::finished(struct Coroutine* this){ + return this->state == FINISHED; +} + +// Note that this function is not public and its calls are desugared by +// the compiler from the `suspend` keyword +// We could choose to make it public during a first experimental phase together with the one below +suspend R struct Coroutine::__suspend(struct Coroutine* this, Y arg) { + assert(this->state == RUNNING); + this->state = PAUSED; + + void* ret = _out(this, (void*) &arg); + + return *(R*)ret; +} diff --git a/clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/swap_contexts.S b/clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/swap_contexts.S new file mode 100644 index 000000000000..22a44a269a2e --- /dev/null +++ b/clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/swap_contexts.S @@ -0,0 +1,227 @@ +#define SEFF_STACK_TOP %fs:0x70 + +#define SavedContext_stack_top_offset 0 +#define SavedContext_current_coroutine_offset 8 + +#define SavedContext_rsp_offset 16 +#define SavedContext_rbp_offset 24 +#define SavedContext_rip_offset 32 +#define SavedContext_rbx_offset 40 +#define SavedContext_r12_offset 48 +#define SavedContext_r13_offset 56 +#define SavedContext_r14_offset 64 +#define SavedContext_r15_offset 72 + +#define Coroutine_context_offset 0 +#define Coroutine_state_offset 80 +#define Coroutine_head_offset 88 +#define Coroutine_parent_offset 96 + +.global _into +.global _out +.global coroutine_prelude + +.global _current_coroutine + +.section .tbss + +_current_coroutine: + .zero 8 + +.text + + +# This function should be implemented in ASM and it must: +# - Exhange the current context information with that stored on this +# - Exchange the current_coroutine, stack_top, and other state variables +# - the saved rip should be the return point of this function +# - perform a jmp to whichever rip this had, setting arg as rax (the return) +# void* swap_contexts(void* this, void* arg); + # - rdi has the coroutine pointer + # - rsi has the arg + # Free registers: %rax, %rcx, %r9, %r10, %r11 +_into: + # Swap contexts + movq ( Coroutine_context_offset + SavedContext_rbx_offset )(%rdi), %rax + movq ( Coroutine_context_offset + SavedContext_r12_offset )(%rdi), %rcx + movq ( Coroutine_context_offset + SavedContext_r13_offset )(%rdi), %r9 + movq ( Coroutine_context_offset + SavedContext_r14_offset )(%rdi), %r10 + movq ( Coroutine_context_offset + SavedContext_r15_offset )(%rdi), %r11 + + movq %rbx, ( Coroutine_context_offset + SavedContext_rbx_offset )(%rdi) + movq %r12, ( Coroutine_context_offset + SavedContext_r12_offset )(%rdi) + movq %r13, ( Coroutine_context_offset + SavedContext_r13_offset )(%rdi) + movq %r14, ( Coroutine_context_offset + SavedContext_r14_offset )(%rdi) + movq %r15, ( Coroutine_context_offset + SavedContext_r15_offset )(%rdi) + + movq %rax, %rbx + movq %rcx, %r12 + movq %r9, %r13 + movq %r10, %r14 + movq %r11, %r15 + + movq SEFF_STACK_TOP , %rax + movq %fs:_current_coroutine@tpoff, %rcx + + movq (Coroutine_context_offset + SavedContext_stack_top_offset)(%rdi), %r9 + movq (Coroutine_context_offset + SavedContext_current_coroutine_offset)(%rdi), %r10 + + movq %r9, SEFF_STACK_TOP + movq %r10, %fs:_current_coroutine@tpoff + + movq %rax, (Coroutine_context_offset + SavedContext_stack_top_offset)(%rdi) + movq %rcx, (Coroutine_parent_offset)(%rdi) + + # Save return pointer + popq %r11 + movq (Coroutine_context_offset + SavedContext_rip_offset)(%rdi), %r10 + movq %r11, (Coroutine_context_offset + SavedContext_rip_offset)(%rdi) + +# # At this point, the stack is aligned on a 16-byte boundary, so we use +# # this as the system stack if possible. Note that we need to do this +# # before clobbering RCX +# test %rcx, %rcx +# jnz seff_handle_after_store_system_stack +# movq %rsp, %fs:_seff_system_stack7@tpoff +# seff_handle_after_store_system_stack: + + # Stack + movq (Coroutine_context_offset + SavedContext_rbp_offset)(%rdi), %rax + movq (Coroutine_context_offset + SavedContext_rsp_offset)(%rdi), %rcx + + movq %rbp, (Coroutine_context_offset + SavedContext_rbp_offset)(%rdi) + movq %rsp, (Coroutine_context_offset + SavedContext_rsp_offset)(%rdi) + + movq %rax, %rbp + movq %rcx, %rsp + + + movq %rsi, %rax + jmp *%r10 + + # recover return point and save + # Save system stack?? + # Jump to this return point with arg in rax + # ret + + + +# void coroutine_prelude(seff_start_fun_t *fn, void *arg); +# arg in r14 +# fn in r15 +// Write this in BSC!! +coroutine_prelude: + movq %r15, %rdi + movq %r14, %rsi + + call *%r13 + movq %fs:_current_coroutine@tpoff, %rdi + # FALLTHROUGH TO _out + # set the return value to (seff_result_t){ 0xFF...FF, %rax } + pushq %rax + movq %rax, %rsi + + +# This function should be implemented in ASM and it must: +# - Exhange the current context information with that stored on this +# - Exchange the current_coroutine, stack_top, and other state variables +# - the saved rip should be the return point of this function +# - perform a jmp to whichever rip this had, setting arg as rax (the return) +# void* swap_contexts(void* this, void* arg); + # - rdi has the coroutine pointer + # - rsi has the arg + # Free registers: %rax, %rcx, %r9, %r10, %r11 +_out: + # Swap contexts + movq ( Coroutine_context_offset + SavedContext_rbx_offset )(%rdi), %rax + movq ( Coroutine_context_offset + SavedContext_r12_offset )(%rdi), %rcx + movq ( Coroutine_context_offset + SavedContext_r13_offset )(%rdi), %r9 + movq ( Coroutine_context_offset + SavedContext_r14_offset )(%rdi), %r10 + movq ( Coroutine_context_offset + SavedContext_r15_offset )(%rdi), %r11 + + movq %rbx, ( Coroutine_context_offset + SavedContext_rbx_offset )(%rdi) + movq %r12, ( Coroutine_context_offset + SavedContext_r12_offset )(%rdi) + movq %r13, ( Coroutine_context_offset + SavedContext_r13_offset )(%rdi) + movq %r14, ( Coroutine_context_offset + SavedContext_r14_offset )(%rdi) + movq %r15, ( Coroutine_context_offset + SavedContext_r15_offset )(%rdi) + + movq %rax, %rbx + movq %rcx, %r12 + movq %r9, %r13 + movq %r10, %r14 + movq %r11, %r15 + + movq SEFF_STACK_TOP , %rax + movq %fs:_current_coroutine@tpoff, %rcx + + movq (Coroutine_context_offset + SavedContext_stack_top_offset)(%rdi), %r9 + movq (Coroutine_parent_offset)(%rdi), %r10 + + movq %r9, SEFF_STACK_TOP + movq %r10, %fs:_current_coroutine@tpoff + + movq %rax, (Coroutine_context_offset + SavedContext_stack_top_offset)(%rdi) + movq %rcx, (Coroutine_context_offset + SavedContext_current_coroutine_offset)(%rdi) + + # Save return pointer + popq %r11 + movq (Coroutine_context_offset + SavedContext_rip_offset)(%rdi), %r10 + movq %r11, (Coroutine_context_offset + SavedContext_rip_offset)(%rdi) + + # Stack + movq (Coroutine_context_offset + SavedContext_rbp_offset)(%rdi), %rax + movq (Coroutine_context_offset + SavedContext_rsp_offset)(%rdi), %rcx + + movq %rbp, (Coroutine_context_offset + SavedContext_rbp_offset)(%rdi) + movq %rsp, (Coroutine_context_offset + SavedContext_rsp_offset)(%rdi) + + movq %rax, %rbp + movq %rcx, %rsp + + movq %rsi, %rax + jmp *%r10 + + # recover return point and save + # Save system stack?? + # Jump to this return point with arg in rax + # ret + + + +# void seff_exit(seff_coroutine_t* k, void* result) +# seff_exit: +# .cfi_startproc +# # k in %rdi +# # eff_id in %rsi +# # result in %rdx +# movq $seff_coroutine_state_t__FINISHED, (seff_coroutine_t__state)(%rdi) +# +# # TODO: it would be faster to avoid swapping, since we're never restarting this coroutine +# swap_registers +# +# #ifdef STACK_POLICY_SEGMENTED +# movq (seff_coroutine_t__resume_point + seff_cont_t__stack_top)(%rdi), %r10 +# movq %r10, SEFF_STACK_TOP +# #endif +# movq (seff_coroutine_t__parent_coroutine)(%rdi), %r10 +# movq %r10, %fs:_seff_current_coroutine@tpoff +# +# movq (seff_coroutine_t__resume_point + seff_cont_t__ip)(%rdi), %r10 +# +# movq (seff_coroutine_t__resume_point + seff_cont_t__rbp)(%rdi), %rbp +# movq (seff_coroutine_t__resume_point + seff_cont_t__rsp)(%rdi), %rsp +# +# #ifndef NDEBUG +# # Cleaning up +# movq $0x0, (seff_coroutine_t__resume_point + seff_cont_t__ip)(%rdi) +# +# movq $0x0, (seff_coroutine_t__resume_point + seff_cont_t__rbp)(%rdi) +# movq $0x0, (seff_coroutine_t__resume_point + seff_cont_t__rsp)(%rdi) +# #endif +# +# movq %rsi, %rax +# # The effect id is already in %rdx, no need to move it +# jmp *%r10 +# .cfi_endproc +# .size seff_exit, . - seff_exit +# \ No newline at end of file diff --git a/clang/test/BSC/Positive/StackfulCoroutine/Runtime/coroutine_dont_pause/coroutine_dont_pause.cbs b/clang/test/BSC/Positive/StackfulCoroutine/Runtime/coroutine_dont_pause/coroutine_dont_pause.cbs new file mode 100644 index 000000000000..e9e5ebb67c06 --- /dev/null +++ b/clang/test/BSC/Positive/StackfulCoroutine/Runtime/coroutine_dont_pause/coroutine_dont_pause.cbs @@ -0,0 +1,35 @@ +// RUN: %clang -g %s %S/../Inputs/coroutine.cbs %S/../Inputs/swap_contexts.S -o %t.output +// RUN: %t.output +// RUN: mkdir -p %T/../Inputs +// RUN: %clang -rewrite-bsc %S/../Inputs/coroutine.hbs -o %T/../Inputs/coroutine.h +// RUN: %clang -rewrite-bsc %S/../Inputs/coroutine.cbs -o %T/../Inputs/coroutine.c +// RUN: %clang -rewrite-bsc %s -o %t-rw.c +// RUN: %clang %t-rw.c %T/../Inputs/coroutine.c %S/../Inputs/swap_contexts.S -o %t-rw.output +// RUN: %t-rw.output +// expected-no-diagnostics + +#include "../Inputs/coroutine.hbs" + +suspend int read(int a) { + return a; +} + +int main() { + struct Coroutine* c = create_coro(read, 4); + c->started(); + + assert(!c->started()); + assert(!c->running()); + assert(!c->finished()); + + int res = c->start(); + + assert(res == 4); + assert(c->started()); + assert(!c->running()); + assert(c->finished()); + + c->delete_coro(); + + return 0; +} \ No newline at end of file diff --git a/clang/test/BSC/Positive/StackfulCoroutine/Runtime/coroutine_suspend/coroutine_suspend.cbs b/clang/test/BSC/Positive/StackfulCoroutine/Runtime/coroutine_suspend/coroutine_suspend.cbs new file mode 100644 index 000000000000..c53267d52d06 --- /dev/null +++ b/clang/test/BSC/Positive/StackfulCoroutine/Runtime/coroutine_suspend/coroutine_suspend.cbs @@ -0,0 +1,48 @@ +// RUN: %clang -g %s %S/../Inputs/coroutine.cbs %S/../Inputs/swap_contexts.S -o %t.output +// RUN: %t.output +// RUN: mkdir -p %T/../Inputs +// RUN: %clang -rewrite-bsc %S/../Inputs/coroutine.hbs -o %T/../Inputs/coroutine.h +// RUN: %clang -rewrite-bsc %S/../Inputs/coroutine.cbs -o %T/../Inputs/coroutine.c +// RUN: %clang -rewrite-bsc %s -o %t-rw.c +// RUN: %clang %t-rw.c %T/../Inputs/coroutine.c %S/../Inputs/swap_contexts.S -o %t-rw.output +// RUN: %t-rw.output +// expected-no-diagnostics + +#include "../Inputs/coroutine.hbs" + +suspend int read(int a) { + + struct Coroutine* coro = current_coroutine(); + int b = coro->__suspend(a); + + return b; +} + +int main() { + int a = 4; + int b = 8; + + struct Coroutine* c = create_coro(read, a); + + assert(!c->started()); + assert(!c->running()); + assert(!c->finished()); + + int resa = c->start(); + + assert(resa == a); + assert(c->started()); + assert(!c->running()); + assert(!c->finished()); + + int resb = c->resume(b); + + assert(resb == b); + assert(c->started()); + assert(!c->running()); + assert(c->finished()); + + c->delete_coro(); + + return 0; +} \ No newline at end of file diff --git a/clang/test/BSC/Positive/StackfulCoroutine/Runtime/create_coroutine/create_coroutine.cbs b/clang/test/BSC/Positive/StackfulCoroutine/Runtime/create_coroutine/create_coroutine.cbs new file mode 100644 index 000000000000..27c996843928 --- /dev/null +++ b/clang/test/BSC/Positive/StackfulCoroutine/Runtime/create_coroutine/create_coroutine.cbs @@ -0,0 +1,29 @@ +// RUN: %clang -g %s %S/../Inputs/coroutine.cbs %S/../Inputs/swap_contexts.S -o %t.output +// RUN: %t.output +// RUN: mkdir -p %T/../Inputs +// RUN: %clang -rewrite-bsc %S/../Inputs/coroutine.hbs -o %T/../Inputs/coroutine.h +// RUN: %clang -rewrite-bsc %S/../Inputs/coroutine.cbs -o %T/../Inputs/coroutine.c +// RUN: %clang -rewrite-bsc %s -o %t-rw.c +// RUN: %clang %t-rw.c %T/../Inputs/coroutine.c %S/../Inputs/swap_contexts.S -o %t-rw.output +// RUN: %t-rw.output +// expected-no-diagnostics + +#include "../Inputs/coroutine.hbs" + +suspend int read(int a) { + return 0; +} + +int main() { + struct Coroutine* c = create_coro(read, 4); + + c->started(); // There's a bug, if we don't call this then the type Coroutine is not instantiated + + assert(!c->started()); + assert(!c->running()); + assert(!c->finished()); + + c->delete_coro(); + + return 0; +} \ No newline at end of file -- Gitee From d54fbb287eda6315dc12c890824cb7b372540edd Mon Sep 17 00:00:00 2001 From: Teodoro Freund Date: Thu, 22 Feb 2024 13:44:55 +0000 Subject: [PATCH 10/11] [stackful] basic runtime functionalities ready --- .../Runtime/Inputs/coroutine.cbs | 4 + .../Runtime/Inputs/coroutine.hbs | 128 ++++++++++++++---- .../Runtime/Inputs/swap_contexts.S | 91 ++----------- .../multiple_coroutines.cbs | 73 ++++++++++ 4 files changed, 189 insertions(+), 107 deletions(-) create mode 100644 clang/test/BSC/Positive/StackfulCoroutine/Runtime/multiple_coroutines/multiple_coroutines.cbs diff --git a/clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/coroutine.cbs b/clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/coroutine.cbs index cea3e436a2f1..63dbdd9daefc 100644 --- a/clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/coroutine.cbs +++ b/clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/coroutine.cbs @@ -6,6 +6,9 @@ #include "coroutine.hbs" +// A proper Unit type (unlike void) +Unit unit; + Segment* new_segment(size_t frame_size) { Segment* frame = malloc(frame_size + sizeof(Segment)); @@ -28,3 +31,4 @@ Segment* new_segment(size_t frame_size) { // *rsp = (char *)frame + frame_size; return frame; } + diff --git a/clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/coroutine.hbs b/clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/coroutine.hbs index 2525a1580242..c2a92507705f 100644 --- a/clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/coroutine.hbs +++ b/clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/coroutine.hbs @@ -4,8 +4,23 @@ #include #include -struct Unit {}; +/** + * C's void is similar to a Unit type, but cannot be used anywhere, for example, + * a function can't have a void argument. + * + * Whenever we want to create a coroutine that yields or is resumed with no arguments + * we'd like to specify it with void (like functions returning void), but that becomes a bit tricky. + * + * Therefore we need this trivial type Unit, and a global constant for convenience. + * The Unit type is trivially constructible and all instances of it are equal to each other. +*/ +typedef struct _Unit {} Unit; +extern Unit unit; +/** + * A coroutine can be in 1 of 4 possible states + * NEW, PAUSED, RUNNING, FINISHED +*/ typedef enum _State { NEW, PAUSED, @@ -13,7 +28,11 @@ typedef enum _State { FINISHED } State; -// This structure is architecture dependent (calling convention) +/** + * This is the context (registers) a coroutine needs to save between suspend points + * + * This structure is architecture dependent (calling convention) +*/ typedef struct _SavedContext { void *stack_top; // 0 void *current_coroutine; // 8 @@ -28,18 +47,30 @@ typedef struct _SavedContext { void *r15; // 72 } SavedContext; - +/** + * A segment of the stack + * + * This is a trailing object, having the actual stack at the end. + * + * Right now we're not actually using segments, just a big one at the beginning of the chain + * but we keep the scaffolding in place for later on. +*/ typedef struct _Segment { struct _Segment *prev; struct _Segment *next; size_t size; void *canary; uint8_t padding[128]; - // void* frame; } Segment; -// Note that the attributes are not really parametric, only its methods are -// TODO these fields should be private, does BiShengC allow that? +/** + * An actual Coroutine + * + * The generic parameters have no change on the actual type or its size, only + * on the types of methods that can be invoked. + * + * TODO Does BishengC allow private fields? +*/ struct Coroutine { // All the registers and flags stored between resumption points SavedContext context; @@ -57,7 +88,6 @@ struct Coroutine { Segment* new_segment(size_t frame_size); -extern __thread void *_current_coroutine; /** * _into and _out work in very similar ways. @@ -77,37 +107,65 @@ extern __thread void *_current_coroutine; void* _into(void* coro, void* arg); void* _out(void* coro, void* arg); -// Recover the current coroutine with some type -// Note that this function is a workaround to be able to manually call __suspend, and it's unsafe +/** + * The coroutine prelude (asm part) each coroutine executes before + * + * Its goal is to recover arguments from context saved registers and move them + * TODO it's probably not needed at all, remove +*/ +void* coroutine_prelude(void* (*typed_coroutine_prelude)(void* (*fn)(void*), void *arg), void* (*fn)(void*), void* arg); + +/** + * The currently executing coroutine (if any) +*/ +extern __thread void *_current_coroutine; + +/** + * Recover the current coroutine with some type + * + * Note that this function is a workaround to be able to manually call __suspend, and it's unsafe +*/ struct Coroutine *current_coroutine(){ return (struct Coroutine*) _current_coroutine; } -void* coroutine_prelude(void *fn, void *arg); - -Y typed_coroutine_prelude(Y (*fn)(T), T *arg) { +/** + * The (typed) coroutine prelude that starts any coroutine + * + * The main use of this is to recover values and store them in the stack, + * regardless of their sizes +*/ +Y typed_coroutine_prelude(Y (*fn)(T), T *_arg) { struct Coroutine* coro = current_coroutine(); + T arg = *_arg; + _out(coro, NULL); + assert(coro->state == RUNNING); - Y res = fn(*arg); + Y res = fn(arg); - coro->state = FINISHED; - _out(coro, (void*) &res); + coro->__finish(res); assert(false); // this should never happen } -// Initialize a coroutine on function `fn` with argument `arg` -// It only accepts pointers as it's main argument +/** + * Initializes a coroutine on function fn with argument arg + * + * It will eagerly start the coroutine, pausing before the actual call to fn takes place, + * this is an easy workaround to store required arguments in the stack. +*/ struct Coroutine* create_coro (Y (*fn)(T), T arg) { // TODO fn should be suspend, we can't check it for now - // TODO allow polymorphic parameter types // TODO allow variable number of arguments struct Coroutine* c = malloc(sizeof(struct Coroutine)); + if (!c) { + exit(-1); + } + c->state = NEW; const size_t frame_size = 16 * 1024; // TODO Do it parametric or smth - c->head = new_segment(frame_size); if (!c->head) { exit(-1); @@ -118,13 +176,15 @@ struct Coroutine* create_coro (Y (*fn)(T), T arg) { c->context.rsp = ((char *)c->head) + frame_size + sizeof(Segment); c->context.rbp = NULL; - c->context.ip = (void*) coroutine_prelude; // TODO coroutine_prelude + c->context.ip = (void*) coroutine_prelude; c->context.rbx = (void *)0xcacabbbb; c->context.r12 = (void *)0xcaca1212; c->context.r13 = (void *)typed_coroutine_prelude; c->context.r14 = (void *)&arg; c->context.r15 = (void *)fn; + _into(c, NULL); + /* * Padding to ensure the stack address is 16-byte aligned @@ -141,23 +201,27 @@ struct Coroutine* create_coro (Y (*fn)(T), T arg) { // Free the space used by the coroutine and its segments void struct Coroutine::delete_coro(struct Coroutine* this){ - // TODO (leaking for now) -} + while (this->head) { + // TODO this just frees the stack memory, we should unwind it + Segment* next = this->head->next; + free(this->head); + this->head = next; + } + free(this); +} // Start the coroutine Y struct Coroutine::start(struct Coroutine* this){ assert((this->state) == NEW); - // assert(1); this->state = RUNNING; void* ret = _into((void*)this, NULL); - // At this point, the coroutine either yielded or returned - // which means we need to recover the returned value and return it return *(Y*)ret; } + // Resume a suspended coroutine Y struct Coroutine::resume(struct Coroutine* this, R arg){ assert(this->state == PAUSED); @@ -180,9 +244,9 @@ bool struct Coroutine::finished(struct Coroutine* this){ return this->state == FINISHED; } -// Note that this function is not public and its calls are desugared by -// the compiler from the `suspend` keyword -// We could choose to make it public during a first experimental phase together with the one below +// Note that these functions are not public + +// Suspend a coroutine (the current one) suspend R struct Coroutine::__suspend(struct Coroutine* this, Y arg) { assert(this->state == RUNNING); this->state = PAUSED; @@ -191,3 +255,11 @@ suspend R struct Coroutine::__suspend(struct Coroutine* this, Y arg) return *(R*)ret; } + +// Finish a coroutine (the current one) +suspend void struct Coroutine::__finish(struct Coroutine* this, Y arg) { + assert(this->state == RUNNING); + this->state = FINISHED; + + _out(this, (void*) &arg); +} diff --git a/clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/swap_contexts.S b/clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/swap_contexts.S index 22a44a269a2e..fd896f1f201f 100644 --- a/clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/swap_contexts.S +++ b/clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/swap_contexts.S @@ -31,12 +31,7 @@ _current_coroutine: .text -# This function should be implemented in ASM and it must: -# - Exhange the current context information with that stored on this -# - Exchange the current_coroutine, stack_top, and other state variables -# - the saved rip should be the return point of this function -# - perform a jmp to whichever rip this had, setting arg as rax (the return) -# void* swap_contexts(void* this, void* arg); +# void* _into(void* coro, void* arg); # - rdi has the coroutine pointer # - rsi has the arg # Free registers: %rax, %rcx, %r9, %r10, %r11 @@ -77,6 +72,7 @@ _into: movq (Coroutine_context_offset + SavedContext_rip_offset)(%rdi), %r10 movq %r11, (Coroutine_context_offset + SavedContext_rip_offset)(%rdi) +# TODO save the system stack # # At this point, the stack is aligned on a 16-byte boundary, so we use # # this as the system stack if possible. Note that we need to do this # # before clobbering RCX @@ -99,35 +95,7 @@ _into: movq %rsi, %rax jmp *%r10 - # recover return point and save - # Save system stack?? - # Jump to this return point with arg in rax - # ret - - - -# void coroutine_prelude(seff_start_fun_t *fn, void *arg); -# arg in r14 -# fn in r15 -// Write this in BSC!! -coroutine_prelude: - movq %r15, %rdi - movq %r14, %rsi - - call *%r13 - movq %fs:_current_coroutine@tpoff, %rdi - # FALLTHROUGH TO _out - # set the return value to (seff_result_t){ 0xFF...FF, %rax } - pushq %rax - movq %rax, %rsi - - -# This function should be implemented in ASM and it must: -# - Exhange the current context information with that stored on this -# - Exchange the current_coroutine, stack_top, and other state variables -# - the saved rip should be the return point of this function -# - perform a jmp to whichever rip this had, setting arg as rax (the return) -# void* swap_contexts(void* this, void* arg); +# void* _out(void* coro, void* arg); # - rdi has the coroutine pointer # - rsi has the arg # Free registers: %rax, %rcx, %r9, %r10, %r11 @@ -181,47 +149,12 @@ _out: movq %rsi, %rax jmp *%r10 - # recover return point and save - # Save system stack?? - # Jump to this return point with arg in rax - # ret - - - -# void seff_exit(seff_coroutine_t* k, void* result) -# seff_exit: -# .cfi_startproc -# # k in %rdi -# # eff_id in %rsi -# # result in %rdx -# movq $seff_coroutine_state_t__FINISHED, (seff_coroutine_t__state)(%rdi) -# -# # TODO: it would be faster to avoid swapping, since we're never restarting this coroutine -# swap_registers -# -# #ifdef STACK_POLICY_SEGMENTED -# movq (seff_coroutine_t__resume_point + seff_cont_t__stack_top)(%rdi), %r10 -# movq %r10, SEFF_STACK_TOP -# #endif -# movq (seff_coroutine_t__parent_coroutine)(%rdi), %r10 -# movq %r10, %fs:_seff_current_coroutine@tpoff -# -# movq (seff_coroutine_t__resume_point + seff_cont_t__ip)(%rdi), %r10 -# -# movq (seff_coroutine_t__resume_point + seff_cont_t__rbp)(%rdi), %rbp -# movq (seff_coroutine_t__resume_point + seff_cont_t__rsp)(%rdi), %rsp -# -# #ifndef NDEBUG -# # Cleaning up -# movq $0x0, (seff_coroutine_t__resume_point + seff_cont_t__ip)(%rdi) -# -# movq $0x0, (seff_coroutine_t__resume_point + seff_cont_t__rbp)(%rdi) -# movq $0x0, (seff_coroutine_t__resume_point + seff_cont_t__rsp)(%rdi) -# #endif -# -# movq %rsi, %rax -# # The effect id is already in %rdx, no need to move it -# jmp *%r10 -# .cfi_endproc -# .size seff_exit, . - seff_exit -# \ No newline at end of file +# void* coroutine_prelude(void* (*typed_coroutine_prelude)(void* (*fn)(void*), void *arg), void* (*fn)(void*), void* arg); +# typed_coroutine_prelude in r13 +# arg in r14 +# fn in r15 +coroutine_prelude: + movq %r15, %rdi + movq %r14, %rsi + call *%r13 + # Never returns diff --git a/clang/test/BSC/Positive/StackfulCoroutine/Runtime/multiple_coroutines/multiple_coroutines.cbs b/clang/test/BSC/Positive/StackfulCoroutine/Runtime/multiple_coroutines/multiple_coroutines.cbs new file mode 100644 index 000000000000..0d1e51aae993 --- /dev/null +++ b/clang/test/BSC/Positive/StackfulCoroutine/Runtime/multiple_coroutines/multiple_coroutines.cbs @@ -0,0 +1,73 @@ +// RUN: %clang -g %s %S/../Inputs/coroutine.cbs %S/../Inputs/swap_contexts.S -o %t.output +// RUN: %t.output +// RUN: mkdir -p %T/../Inputs +// RUN: %clang -rewrite-bsc %S/../Inputs/coroutine.hbs -o %T/../Inputs/coroutine.h +// RUN: %clang -rewrite-bsc %S/../Inputs/coroutine.cbs -o %T/../Inputs/coroutine.c +// RUN: %clang -rewrite-bsc %s -o %t-rw.c +// RUN: %clang %t-rw.c %T/../Inputs/coroutine.c %S/../Inputs/swap_contexts.S -o %t-rw.output +// RUN: %t-rw.output +// expected-no-diagnostics + +#include "../Inputs/coroutine.hbs" + +int mem; + +suspend int read(Unit u) { + + struct Coroutine* coro = current_coroutine(); + coro->__suspend(mem); + + return mem; +} + +suspend Unit write(int a) { + mem = a; + + struct Coroutine* coro = current_coroutine(); + mem = coro->__suspend(unit); + + return unit; +} + +int main() { + int a = 4; + int b = 8; + + struct Coroutine* reader = create_coro(read, unit); + struct Coroutine* writer = create_coro(write, a); + + assert(!reader->started()); + assert(!reader->running()); + assert(!reader->finished()); + assert(!writer->started()); + assert(!writer->running()); + assert(!writer->finished()); + + writer->start(); + int resa = reader->start(); + + assert(resa == a); + assert(reader->started()); + assert(!reader->running()); + assert(!reader->finished()); + assert(writer->started()); + assert(!writer->running()); + assert(!writer->finished()); + + writer->resume(b); + int resb = reader->resume(unit); + + assert(resb == b); + assert(reader->started()); + assert(!reader->running()); + assert(reader->finished()); + assert(writer->started()); + assert(!writer->running()); + assert(writer->finished()); + + + reader->delete_coro(); + writer->delete_coro(); + + return 0; +} \ No newline at end of file -- Gitee From f3d45caaa806eb6cb1d01eb1679a2dcc9522fcec Mon Sep 17 00:00:00 2001 From: Teodoro Freund Date: Mon, 26 Feb 2024 13:59:14 +0000 Subject: [PATCH 11/11] [stackful] Ignore Stackful coroutine tests from BSC auto test --- clang/test/BSC/Positive/bsc_test.conf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clang/test/BSC/Positive/bsc_test.conf b/clang/test/BSC/Positive/bsc_test.conf index 7059f18b911c..8e135dc0436c 100755 --- a/clang/test/BSC/Positive/bsc_test.conf +++ b/clang/test/BSC/Positive/bsc_test.conf @@ -36,6 +36,10 @@ BSC/Positive/Method/This/use_This_as_BSCMethod_parameter_with_trait/use_This_as_BSCMethod_parameter_with_trait.cbs BSC/Positive/Driver/rewrite-bsc/Generic/rewrite_bsc_generic_constant/rewrite_bsc_generic_constant.cbs BSC/Positive/Driver/rewrite-bsc/Generic/rewrite_bsc_generic_constant_suffix/rewrite_bsc_generic_constant_suffix.cbs + BSC/Positive/StackfulCoroutine/Runtime/coroutine_dont_pause/coroutine_dont_pause.cbs + BSC/Positive/StackfulCoroutine/Runtime/coroutine_suspend/coroutine_suspend.cbs + BSC/Positive/StackfulCoroutine/Runtime/create_coroutine/create_coroutine.cbs + BSC/Positive/StackfulCoroutine/Runtime/multiple_coroutines/multiple_coroutines.cbs [BAN_REWRITE_TEST_SUITE] BSC/Positive/Driver/rewrite-bsc/rewrite_bsc_with_header/rewrite_bsc_with_header.cbs -- Gitee