From cfd53b4e999301c5d6c7785c34c49d4e952e8aa4 Mon Sep 17 00:00:00 2001 From: Richard Smith <richard-llvm@metafoo.co.uk> Date: Thu, 22 Oct 2015 06:13:50 +0000 Subject: [PATCH] [coroutines] Initial stub Sema functionality for handling coroutine await / yield / return. llvm-svn: 250993 --- .../clang/Basic/DiagnosticSemaKinds.td | 21 ++++ clang/include/clang/Sema/ScopeInfo.h | 5 + clang/include/clang/Sema/Sema.h | 16 ++- clang/lib/Parse/ParseExpr.cpp | 6 +- clang/lib/Parse/ParseExprCXX.cpp | 4 +- clang/lib/Parse/ParseStmt.cpp | 7 +- clang/lib/Sema/CMakeLists.txt | 1 + clang/lib/Sema/SemaCoroutine.cpp | 106 ++++++++++++++++++ clang/lib/Sema/SemaDecl.cpp | 3 + clang/lib/Sema/SemaStmt.cpp | 26 ++++- clang/lib/Sema/TreeTransform.h | 4 +- clang/test/SemaCXX/coroutines.cpp | 51 +++++++++ 12 files changed, 234 insertions(+), 16 deletions(-) create mode 100644 clang/lib/Sema/SemaCoroutine.cpp create mode 100644 clang/test/SemaCXX/coroutines.cpp diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index fe6d5a6c56a6..20a92269da77 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -7827,6 +7827,27 @@ def err_module_import_in_implementation : Error< "@import of module '%0' in implementation of '%1'; use #import">; } +let CategoryName = "Coroutines Issue" in { +def err_return_in_coroutine : Error<"return statement in coroutine">; +def note_declared_coroutine_here : Note< + "function is a coroutine due to use of " + "'%select{co_await|co_yield|co_return}0' here">; +def err_coroutine_objc_method : Error< + "Objective-C methods as coroutines are not yet supported">; +def err_coroutine_outside_function : Error< + "'%0' cannot be used outside a function">; +def err_coroutine_ctor_dtor : Error< + "'%1' cannot be used in a %select{constructor|destructor}0">; +def err_coroutine_constexpr : Error< + "'%0' cannot be used in a constexpr function">; +def err_coroutine_varargs : Error< + "'%0' cannot be used in a varargs function">; +def ext_coroutine_without_coawait_coyield : ExtWarn< + "'co_return' used in a function " + "that uses neither 'co_await' nor 'co_yield'">, + InGroup<DiagGroup<"coreturn-without-coawait">>; +} + let CategoryName = "Documentation Issue" in { def warn_not_a_doxygen_trailing_member_comment : Warning< "not a Doxygen trailing comment">, InGroup<Documentation>, DefaultIgnore; diff --git a/clang/include/clang/Sema/ScopeInfo.h b/clang/include/clang/Sema/ScopeInfo.h index a267ed1c957b..bc0fd20195fd 100644 --- a/clang/include/clang/Sema/ScopeInfo.h +++ b/clang/include/clang/Sema/ScopeInfo.h @@ -142,6 +142,11 @@ public: /// optimization, or if we need to infer a return type. SmallVector<ReturnStmt*, 4> Returns; + /// \brief The list of coroutine control flow constructs (co_await, co_yield, + /// co_return) that occur within the function or block. Empty if and only if + /// this function or block is not (yet known to be) a coroutine. + SmallVector<Stmt*, 4> CoroutineStmts; + /// \brief The stack of currently active compound stamement scopes in the /// function. SmallVector<CompoundScopeInfo, 4> CompoundScopes; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 6252d6ffcb23..15791d1a58e7 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -3334,11 +3334,14 @@ public: BFRK_Check }; - StmtResult ActOnCXXForRangeStmt(SourceLocation ForLoc, Stmt *LoopVar, + StmtResult ActOnCXXForRangeStmt(SourceLocation ForLoc, + SourceLocation CoawaitLoc, + Stmt *LoopVar, SourceLocation ColonLoc, Expr *Collection, SourceLocation RParenLoc, BuildForRangeKind Kind); StmtResult BuildCXXForRangeStmt(SourceLocation ForLoc, + SourceLocation CoawaitLoc, SourceLocation ColonLoc, Stmt *RangeDecl, Stmt *BeginEndDecl, Expr *Cond, Expr *Inc, @@ -7703,7 +7706,18 @@ public: void AddLaunchBoundsAttr(SourceRange AttrRange, Decl *D, Expr *MaxThreads, Expr *MinBlocks, unsigned SpellingListIndex); + //===--------------------------------------------------------------------===// + // C++ Coroutines TS + // + ExprResult ActOnCoawaitExpr(SourceLocation KwLoc, Expr *E); + ExprResult ActOnCoyieldExpr(SourceLocation KwLoc, Expr *E); + StmtResult ActOnCoreturnStmt(SourceLocation KwLoc, Expr *E); + + void CheckCompletedCoroutineBody(FunctionDecl *FD, Stmt *Body); + + //===--------------------------------------------------------------------===// // OpenMP directives and clauses. + // private: void *VarDataSharingAttributesStack; /// \brief Initialization of data-sharing attributes stack. diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp index 4b42a73dd24a..f3316461ac05 100644 --- a/clang/lib/Parse/ParseExpr.cpp +++ b/clang/lib/Parse/ParseExpr.cpp @@ -1045,10 +1045,10 @@ ExprResult Parser::ParseCastExpression(bool isUnaryExpression, } case tok::kw_co_await: { // unary-expression: 'co_await' cast-expression - SourceLocation SavedLoc = ConsumeToken(); + SourceLocation CoawaitLoc = ConsumeToken(); Res = ParseCastExpression(false); - (void)SavedLoc; - // FIXME: Pass to Sema. + if (!Res.isInvalid()) + Res = Actions.ActOnCoawaitExpr(CoawaitLoc, Res.get()); return Res; } diff --git a/clang/lib/Parse/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp index c3df77300cad..bd9b9f97313e 100644 --- a/clang/lib/Parse/ParseExprCXX.cpp +++ b/clang/lib/Parse/ParseExprCXX.cpp @@ -1567,8 +1567,8 @@ ExprResult Parser::ParseCoyieldExpression() { SourceLocation Loc = ConsumeToken(); ExprResult Expr = ParseAssignmentExpression(); - (void)Loc; - // FIXME: Pass to Sema. + if (!Expr.isInvalid()) + Expr = Actions.ActOnCoyieldExpr(Loc, Expr.get()); return Expr; } diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp index 6ea8f4ec8912..af7008f8e784 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -1691,8 +1691,8 @@ StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc) { StmtResult ForEachStmt; if (ForRange) { - // FIXME: Pass CoawaitLoc to Sema. - ForRangeStmt = Actions.ActOnCXXForRangeStmt(ForLoc, FirstPart.get(), + ForRangeStmt = Actions.ActOnCXXForRangeStmt(ForLoc, CoawaitLoc, + FirstPart.get(), ForRangeInit.ColonLoc, ForRangeInit.RangeExpr.get(), T.getCloseLocation(), @@ -1851,7 +1851,8 @@ StmtResult Parser::ParseReturnStatement() { return StmtError(); } } - // FIXME: Pass IsCoreturn to Sema. + if (IsCoreturn) + return Actions.ActOnCoreturnStmt(ReturnLoc, R.get()); return Actions.ActOnReturnStmt(ReturnLoc, R.get(), getCurScope()); } diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt index 4a772d8972a9..8aa005102fe4 100644 --- a/clang/lib/Sema/CMakeLists.txt +++ b/clang/lib/Sema/CMakeLists.txt @@ -21,6 +21,7 @@ add_clang_library(clangSema SemaChecking.cpp SemaCodeComplete.cpp SemaConsumer.cpp + SemaCoroutine.cpp SemaCUDA.cpp SemaDecl.cpp SemaDeclAttr.cpp diff --git a/clang/lib/Sema/SemaCoroutine.cpp b/clang/lib/Sema/SemaCoroutine.cpp new file mode 100644 index 000000000000..6545b6709a20 --- /dev/null +++ b/clang/lib/Sema/SemaCoroutine.cpp @@ -0,0 +1,106 @@ +//===--- SemaCoroutines.cpp - Semantic Analysis for Coroutines ------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements semantic analysis for C++ Coroutines. +// +//===----------------------------------------------------------------------===// + +#include "clang/Sema/SemaInternal.h" +using namespace clang; +using namespace sema; + +static FunctionScopeInfo * +checkCoroutineContext(Sema &S, SourceLocation Loc, StringRef Keyword) { + // 'co_await' and 'co_yield' are permitted in unevaluated operands. + if (S.isUnevaluatedContext()) + return nullptr; + + // Any other usage must be within a function. + auto *FD = dyn_cast<FunctionDecl>(S.CurContext); + if (!FD) { + S.Diag(Loc, isa<ObjCMethodDecl>(S.CurContext) + ? diag::err_coroutine_objc_method + : diag::err_coroutine_outside_function) << Keyword; + } else if (isa<CXXConstructorDecl>(FD) || isa<CXXDestructorDecl>(FD)) { + // Coroutines TS [special]/6: + // A special member function shall not be a coroutine. + // + // FIXME: We assume that this really means that a coroutine cannot + // be a constructor or destructor. + S.Diag(Loc, diag::err_coroutine_ctor_dtor) + << isa<CXXDestructorDecl>(FD) << Keyword; + } else if (FD->isConstexpr()) { + S.Diag(Loc, diag::err_coroutine_constexpr) << Keyword; + } else if (FD->isVariadic()) { + S.Diag(Loc, diag::err_coroutine_varargs) << Keyword; + } else { + auto *ScopeInfo = S.getCurFunction(); + assert(ScopeInfo && "missing function scope for function"); + return ScopeInfo; + } + + return nullptr; +} + +ExprResult Sema::ActOnCoawaitExpr(SourceLocation Loc, Expr *E) { + auto *Context = checkCoroutineContext(*this, Loc, "co_await"); + ExprResult Res = ExprError(); + + if (Context && !Res.isInvalid()) + Context->CoroutineStmts.push_back(Res.get()); + return Res; +} + +ExprResult Sema::ActOnCoyieldExpr(SourceLocation Loc, Expr *E) { + auto *Context = checkCoroutineContext(*this, Loc, "co_yield"); + ExprResult Res = ExprError(); + + if (Context && !Res.isInvalid()) + Context->CoroutineStmts.push_back(Res.get()); + return Res; +} + +StmtResult Sema::ActOnCoreturnStmt(SourceLocation Loc, Expr *E) { + auto *Context = checkCoroutineContext(*this, Loc, "co_return"); + StmtResult Res = StmtError(); + + if (Context && !Res.isInvalid()) + Context->CoroutineStmts.push_back(Res.get()); + return Res; +} + +void Sema::CheckCompletedCoroutineBody(FunctionDecl *FD, Stmt *Body) { + FunctionScopeInfo *Fn = getCurFunction(); + assert(Fn && !Fn->CoroutineStmts.empty() && "not a coroutine"); + + // Coroutines [stmt.return]p1: + // A return statement shall not appear in a coroutine. + if (!Fn->Returns.empty()) { + Diag(Fn->Returns.front()->getLocStart(), diag::err_return_in_coroutine); + auto *First = Fn->CoroutineStmts[0]; + Diag(First->getLocStart(), diag::note_declared_coroutine_here) + << 0; // FIXME: Indicate the kind here + } + + bool AnyCoawaits = false; + bool AnyCoyields = false; + for (auto *CoroutineStmt : Fn->CoroutineStmts) { + (void)CoroutineStmt; + AnyCoawaits = AnyCoyields = true; // FIXME + } + + if (!AnyCoawaits && !AnyCoyields) + Diag(Fn->CoroutineStmts.front()->getLocStart(), + diag::ext_coroutine_without_coawait_coyield); + + // FIXME: If we have a deduced return type, resolve it now. + // FIXME: Compute the promise type. + // FIXME: Perform analysis of initial and final suspend, and set_exception call. + // FIXME: Complete the semantic analysis of the CoroutineStmts. +} diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 174b9cf50f2b..047ff152cf8e 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -10907,6 +10907,9 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body, sema::AnalysisBasedWarnings::Policy WP = AnalysisWarnings.getDefaultPolicy(); sema::AnalysisBasedWarnings::Policy *ActivePolicy = nullptr; + if (getLangOpts().Coroutines && !getCurFunction()->CoroutineStmts.empty()) + CheckCompletedCoroutineBody(FD, Body); + if (FD) { FD->setBody(Body); diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp index 0afff06f15d3..fec12000950c 100644 --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -1924,7 +1924,7 @@ static bool ObjCEnumerationCollection(Expr *Collection) { /// The body of the loop is not available yet, since it cannot be analysed until /// we have determined the type of the for-range-declaration. StmtResult -Sema::ActOnCXXForRangeStmt(SourceLocation ForLoc, +Sema::ActOnCXXForRangeStmt(SourceLocation ForLoc, SourceLocation CoawaitLoc, Stmt *First, SourceLocation ColonLoc, Expr *Range, SourceLocation RParenLoc, BuildForRangeKind Kind) { if (!First) @@ -1948,6 +1948,13 @@ Sema::ActOnCXXForRangeStmt(SourceLocation ForLoc, return StmtError(); } + // Coroutines: 'for co_await' implicitly co_awaits its range. + if (CoawaitLoc.isValid()) { + ExprResult Coawait = ActOnCoawaitExpr(CoawaitLoc, Range); + if (Coawait.isInvalid()) return StmtError(); + Range = Coawait.get(); + } + // Build auto && __range = range-init SourceLocation RangeLoc = Range->getLocStart(); VarDecl *RangeVar = BuildForRangeVarDecl(*this, RangeLoc, @@ -1969,7 +1976,7 @@ Sema::ActOnCXXForRangeStmt(SourceLocation ForLoc, return StmtError(); } - return BuildCXXForRangeStmt(ForLoc, ColonLoc, RangeDecl.get(), + return BuildCXXForRangeStmt(ForLoc, CoawaitLoc, ColonLoc, RangeDecl.get(), /*BeginEndDecl=*/nullptr, /*Cond=*/nullptr, /*Inc=*/nullptr, DS, RParenLoc, Kind); } @@ -2063,6 +2070,7 @@ static Sema::ForRangeStatus BuildNonArrayForRange(Sema &SemaRef, Scope *S, /// and emit no diagnostics. static StmtResult RebuildForRangeWithDereference(Sema &SemaRef, Scope *S, SourceLocation ForLoc, + SourceLocation CoawaitLoc, Stmt *LoopVarDecl, SourceLocation ColonLoc, Expr *Range, @@ -2079,7 +2087,7 @@ static StmtResult RebuildForRangeWithDereference(Sema &SemaRef, Scope *S, return StmtResult(); StmtResult SR = - SemaRef.ActOnCXXForRangeStmt(ForLoc, LoopVarDecl, ColonLoc, + SemaRef.ActOnCXXForRangeStmt(ForLoc, CoawaitLoc, LoopVarDecl, ColonLoc, AdjustedRange.get(), RParenLoc, Sema::BFRK_Check); if (SR.isInvalid()) @@ -2091,7 +2099,7 @@ static StmtResult RebuildForRangeWithDereference(Sema &SemaRef, Scope *S, // case there are any other (non-fatal) problems with it. SemaRef.Diag(RangeLoc, diag::err_for_range_dereference) << Range->getType() << FixItHint::CreateInsertion(RangeLoc, "*"); - return SemaRef.ActOnCXXForRangeStmt(ForLoc, LoopVarDecl, ColonLoc, + return SemaRef.ActOnCXXForRangeStmt(ForLoc, CoawaitLoc, LoopVarDecl, ColonLoc, AdjustedRange.get(), RParenLoc, Sema::BFRK_Rebuild); } @@ -2114,7 +2122,8 @@ struct InvalidateOnErrorScope { /// BuildCXXForRangeStmt - Build or instantiate a C++11 for-range statement. StmtResult -Sema::BuildCXXForRangeStmt(SourceLocation ForLoc, SourceLocation ColonLoc, +Sema::BuildCXXForRangeStmt(SourceLocation ForLoc, SourceLocation CoawaitLoc, + SourceLocation ColonLoc, Stmt *RangeDecl, Stmt *BeginEnd, Expr *Cond, Expr *Inc, Stmt *LoopVarDecl, SourceLocation RParenLoc, BuildForRangeKind Kind) { @@ -2244,6 +2253,7 @@ Sema::BuildCXXForRangeStmt(SourceLocation ForLoc, SourceLocation ColonLoc, // If building the range failed, try dereferencing the range expression // unless a diagnostic was issued or the end function is problematic. StmtResult SR = RebuildForRangeWithDereference(*this, S, ForLoc, + CoawaitLoc, LoopVarDecl, ColonLoc, Range, RangeLoc, RParenLoc); @@ -2314,7 +2324,10 @@ Sema::BuildCXXForRangeStmt(SourceLocation ForLoc, SourceLocation ColonLoc, return StmtError(); IncrExpr = ActOnUnaryOp(S, ColonLoc, tok::plusplus, BeginRef.get()); - IncrExpr = ActOnFinishFullExpr(IncrExpr.get()); + if (!IncrExpr.isInvalid() && CoawaitLoc.isValid()) + IncrExpr = ActOnCoawaitExpr(CoawaitLoc, IncrExpr.get()); + if (!IncrExpr.isInvalid()) + IncrExpr = ActOnFinishFullExpr(IncrExpr.get()); if (IncrExpr.isInvalid()) { Diag(RangeLoc, diag::note_for_range_invalid_iterator) << RangeLoc << 2 << BeginRangeRef.get()->getType() ; @@ -2351,6 +2364,7 @@ Sema::BuildCXXForRangeStmt(SourceLocation ForLoc, SourceLocation ColonLoc, if (Kind == BFRK_Check) return StmtResult(); + // FIXME: Pass in CoawaitLoc in the dependent case. return new (Context) CXXForRangeStmt( RangeDS, cast_or_null<DeclStmt>(BeginEndDecl.get()), NotEqExpr.get(), IncrExpr.get(), LoopVarDS, /*Body=*/nullptr, ForLoc, ColonLoc, RParenLoc); diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 29073debcd73..117ae56969dc 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -1737,7 +1737,9 @@ public: } } - return getSema().BuildCXXForRangeStmt(ForLoc, ColonLoc, Range, BeginEnd, + SourceLocation CoawaitLoc; // FIXME + return getSema().BuildCXXForRangeStmt(ForLoc, CoawaitLoc, ColonLoc, + Range, BeginEnd, Cond, Inc, LoopVar, RParenLoc, Sema::BFRK_Rebuild); } diff --git a/clang/test/SemaCXX/coroutines.cpp b/clang/test/SemaCXX/coroutines.cpp new file mode 100644 index 000000000000..ae24dd2979f7 --- /dev/null +++ b/clang/test/SemaCXX/coroutines.cpp @@ -0,0 +1,51 @@ +// RUN: %clang_cc1 -std=c++14 -fcoroutines -verify %s + +void mixed_yield() { + // FIXME: diagnose + co_yield 0; + return; +} + +void mixed_await() { + // FIXME: diagnose + co_await 0; + return; +} + +void only_coreturn() { + // FIXME: diagnose + co_return; +} + +void mixed_coreturn(bool b) { + // FIXME: diagnose + if (b) + co_return; + else + return; +} + +struct CtorDtor { + CtorDtor() { + co_yield 0; // expected-error {{'co_yield' cannot be used in a constructor}} + } + CtorDtor(int n) { + // The spec doesn't say this is ill-formed, but it must be. + co_await n; // expected-error {{'co_await' cannot be used in a constructor}} + } + ~CtorDtor() { + co_return 0; // expected-error {{'co_return' cannot be used in a destructor}} + } + // FIXME: The spec says this is ill-formed. + void operator=(CtorDtor&) { + co_yield 0; + } +}; + +constexpr void constexpr_coroutine() { + co_yield 0; // expected-error {{'co_yield' cannot be used in a constexpr function}} +} + +void varargs_coroutine(const char *, ...) { + co_await 0; // expected-error {{'co_await' cannot be used in a varargs function}} +}