diff --git a/clang/include/clang/AST/BSC/DeclBSC.h b/clang/include/clang/AST/BSC/DeclBSC.h index 286c685c934313809c93cf737f5e4c3dcd0a4424..89de32ee09d6356790bfb97a8ec4ec6240c292b1 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/AST/Decl.h b/clang/include/clang/AST/Decl.h index 65860f499565327efbbdb8f7b37daeed122ae23a..1cb99df0814938760f0b14c7207d95551828ab22 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 c8cff012e000cee6d42d15f1f968b2a570b7936c..bc64500f134bcd316873309c7222489c28ed853b 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/BSC/DiagnosticBSCSemaKinds.td b/clang/include/clang/Basic/BSC/DiagnosticBSCSemaKinds.td index 623e78105b4745259ec2b25d8de9804c140537ce..f6b5543056365e01984e1667e69e744a582f3de7 100644 --- a/clang/include/clang/Basic/BSC/DiagnosticBSCSemaKinds.td +++ b/clang/include/clang/Basic/BSC/DiagnosticBSCSemaKinds.td @@ -35,6 +35,17 @@ 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">; +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. def err_owned_inderictOwned_type_check : Error< "%select{" diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index 09dfe7a5ba71ba93fe7b5dfb3967ae0b795212cc..18ec5ec4b4a153c3b17658d4de3172389b1ff57c 100644 --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -463,6 +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 f390fcda37e8447f1991c3ea7430232cee38a1d5..c0408102605fd7086ef6dc4b4a6eb4fb9e521f3c 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/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 680fadad69846720f8e939ca729995f3c74d11bc..b5387119a027ad6f1dd9245952f64b23e7d96d0e 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -3209,6 +3209,13 @@ 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); + + /// Act on sus_await expressions + 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/AST/BSC/DeclBSC.cpp b/clang/lib/AST/BSC/DeclBSC.cpp index 36c59c5e9fb1356603485143882ec1ccb8e1d38d..91e4fd2d95f56461f3eb7b62029022aea250c8fc 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/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index 9bcd519b4d2aa9625af507a173358a38092117f1..9a549f68c5e11426bdadb7bd04ef51cf549efbb6 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 f57b1f93343cb459c1238bc9669b536dc025ae60..d009ad4d4e23dff4c4e9abb24f2bc855aea98009 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); @@ -4013,6 +4015,9 @@ void Parser::ParseDeclarationSpecifiers(DeclSpec &DS, 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: { SafeZoneSpecifier SafeZoneSpec = SZ_Unsafe; @@ -5655,7 +5660,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/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp index 54d2cc0d5b08317763c32d13e8e0722e20cee2ce..00943a45e04430bf471f49073ea919f7e7041967 100644 --- a/clang/lib/Parse/ParseExpr.cpp +++ b/clang/lib/Parse/ParseExpr.cpp @@ -1452,6 +1452,37 @@ ExprResult Parser::ParseCastExpression( Res = Actions.ActOnAwaitExpr(AwaitLoc, Res.get()); return Res; } + + case tok::kw_sus_await: { // unary-expression: 'sus_await' cast-expression + if (NotPrimaryExpression) + *NotPrimaryExpression = true; + SourceLocation SusAwaitLoc = ConsumeToken(); + 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[opt] + if (NotPrimaryExpression) + *NotPrimaryExpression = true; + SourceLocation SuspendLoc = ConsumeToken(); + + if (Tok.is(tok::semi)){ + // Not yielding any value, just return void + 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 { + return Actions.ActOnYieldExpression(SuspendLoc, E.get()); + } + } + } #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 73b48fcaa9b34a4dcfda31a920ab538624485840..91b30bc114873b5b33f49834971e614da748ee21 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/BSC/SemaBSCCoroutine.cpp b/clang/lib/Sema/BSC/SemaBSCCoroutine.cpp index f67a5b3ba50d451d799f40835163057458625c54..7fe2a3a38fc2b0ae5df7db63edf20cfce0b9853e 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(); diff --git a/clang/lib/Sema/BSC/SemaBSCSuspendCoroutine.cpp b/clang/lib/Sema/BSC/SemaBSCSuspendCoroutine.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1400ef4f607875be77270e0d724f092fbcac7406 --- /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 f788bdc6da20053c74abcdf7cf2d238714ee4a42..4ec6104d0836e7f99db440c98203f62d2585efd9 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 diff --git a/clang/lib/Sema/DeclSpec.cpp b/clang/lib/Sema/DeclSpec.cpp index 22423cec487cc8042bced3d1ef0793120359853b..9949e117987a89db0b5a283ce23ad53e35f19e27 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 703a782a8887a0bfad6cf4190bb757d54494f3e5..e4a442c065421ad98a62f81c7bea9dd4332f615f 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 } @@ -9065,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) { @@ -9096,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()); @@ -9114,14 +9117,15 @@ 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, ConstexprSpecKind::Constexpr, /*TrailingRequiresClause=*/nullptr, - isAsync); + isAsync, + isSuspend); } else { #endif NewFD = FunctionDecl::Create( @@ -9131,6 +9135,7 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D, /*TrailingRequiresClause=*/nullptr #if ENABLE_BSC , isAsync + , isSuspend #endif ); #if ENABLE_BSC @@ -11893,6 +11898,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 +13948,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/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 0000000000000000000000000000000000000000..103ecb2e4287199a6c07049f29a2de432279c54d --- /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-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 0000000000000000000000000000000000000000..761bd284d1a7ea655401b32e3a2ead7fc18b2bae --- /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 diff --git a/clang/test/BSC/Negative/StackfulCoroutine/AwaitExpr/await-expression-of-non-suspend/await-expression-of-non-suspend.cbs b/clang/test/BSC/Negative/StackfulCoroutine/AwaitExpr/await-expression-of-non-suspend/await-expression-of-non-suspend.cbs new file mode 100644 index 0000000000000000000000000000000000000000..8e628f0cbfad9a65850975c6d940b93612c9d6a2 --- /dev/null +++ b/clang/test/BSC/Negative/StackfulCoroutine/AwaitExpr/await-expression-of-non-suspend/await-expression-of-non-suspend.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 diff --git a/clang/test/BSC/Negative/StackfulCoroutine/YieldExpr/yield-expression/yield-expression.cbs b/clang/test/BSC/Negative/StackfulCoroutine/YieldExpr/yield-expression/yield-expression.cbs new file mode 100644 index 0000000000000000000000000000000000000000..04faeb409674ed91408a3da70e1f429938a9af78 --- /dev/null +++ b/clang/test/BSC/Negative/StackfulCoroutine/YieldExpr/yield-expression/yield-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 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 0000000000000000000000000000000000000000..8cf57a982f8dc3e47d2b7f409c1cf76bc0896604 --- /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/Runtime/Inputs/coroutine.cbs b/clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/coroutine.cbs new file mode 100644 index 0000000000000000000000000000000000000000..63dbdd9daefc312c58de37f7c7d6d36963aa5c3c --- /dev/null +++ b/clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/coroutine.cbs @@ -0,0 +1,34 @@ +#include +#include +#include +#include +#include + +#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)); + + 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 0000000000000000000000000000000000000000..c2a92507705f49f393074d0f105e46d62148cacc --- /dev/null +++ b/clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/coroutine.hbs @@ -0,0 +1,265 @@ +#include +#include +#include +#include +#include + +/** + * 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, + RUNNING, + FINISHED +} State; + +/** + * 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 + + 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; + +/** + * 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]; +} Segment; + +/** + * 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; + + // coroutine state + State state; + + // The linked list of segments + Segment* head; + + struct Coroutine* parent; +}; + + + +Segment* new_segment(size_t frame_size); + + +/** + * _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); + +/** + * 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; +} + +/** + * 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); + + coro->__finish(res); + assert(false); // this should never happen +} + +/** + * 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 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); + } + + 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; + 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 + * 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){ + 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); + this->state = RUNNING; + + void* ret = _into((void*)this, NULL); + + 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 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; + + void* ret = _out(this, (void*) &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 new file mode 100644 index 0000000000000000000000000000000000000000..fd896f1f201f43d65e85cee3361cdeb450e46dfb --- /dev/null +++ b/clang/test/BSC/Positive/StackfulCoroutine/Runtime/Inputs/swap_contexts.S @@ -0,0 +1,160 @@ +#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 + + +# void* _into(void* coro, 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) + +# 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 +# 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 + +# void* _out(void* coro, 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 + +# 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/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 0000000000000000000000000000000000000000..e9e5ebb67c06c82a2a550c45059ed71a3ed391de --- /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 0000000000000000000000000000000000000000..c53267d52d06945fa08c98924fd24c611f5ce083 --- /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 0000000000000000000000000000000000000000..27c9968439288cab64c3441d3f93550f3e2e1172 --- /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 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 0000000000000000000000000000000000000000..0d1e51aae99390f650aed1286741888f7d3c8528 --- /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 diff --git a/clang/test/BSC/Positive/StackfulCoroutine/SuspendSpecifier/suspend-specifier/suspend-specifier.cbs b/clang/test/BSC/Positive/StackfulCoroutine/SuspendSpecifier/suspend-specifier/suspend-specifier.cbs new file mode 100644 index 0000000000000000000000000000000000000000..33daac06c766f0837a8a50b4a62c7c0c80d46f44 --- /dev/null +++ b/clang/test/BSC/Positive/StackfulCoroutine/SuspendSpecifier/suspend-specifier/suspend-specifier.cbs @@ -0,0 +1,21 @@ +// 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; +} + +// 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/TCPServer/Inputs/tcp.cbs b/clang/test/BSC/Positive/StackfulCoroutine/TCPServer/Inputs/tcp.cbs new file mode 100644 index 0000000000000000000000000000000000000000..02b5d1eef689d84908aad1d3b77479bdd740cb6c --- /dev/null +++ b/clang/test/BSC/Positive/StackfulCoroutine/TCPServer/Inputs/tcp.cbs @@ -0,0 +1,217 @@ + + +#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){ + // Dummy implementation + return 0; +} + + +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/TCPServer/Inputs/tcp.hbs b/clang/test/BSC/Positive/StackfulCoroutine/TCPServer/Inputs/tcp.hbs new file mode 100644 index 0000000000000000000000000000000000000000..082ecfbb3435805cd345ee81b3b700c0a6a800dc --- /dev/null +++ b/clang/test/BSC/Positive/StackfulCoroutine/TCPServer/Inputs/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 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 0000000000000000000000000000000000000000..a7cf305f1c08e90bf0c51ddb39a05631ca761ac5 --- /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/YieldExpr/yield-expression/yield-expression.cbs b/clang/test/BSC/Positive/StackfulCoroutine/YieldExpr/yield-expression/yield-expression.cbs new file mode 100644 index 0000000000000000000000000000000000000000..713f5f97148206c39cbecea2c719e9eb983d1380 --- /dev/null +++ b/clang/test/BSC/Positive/StackfulCoroutine/YieldExpr/yield-expression/yield-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/bsc_test.conf b/clang/test/BSC/Positive/bsc_test.conf index 7059f18b911c7c8b97ff119b0efa9d53486aedac..8e135dc0436c1adff1e74035d2e38791f3b29fcc 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