From: Alexey Bataev Date: Thu, 7 Apr 2016 12:45:37 +0000 (+0000) Subject: [OPENMP 4.0] Parsing/sema analysis for 'simdlen' clause in 'declare simd' X-Git-Tag: llvmorg-3.9.0-rc1~9745 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=2af33e3d3ff1cd0535602cf50b06539949a2b16b;p=platform%2Fupstream%2Fllvm.git [OPENMP 4.0] Parsing/sema analysis for 'simdlen' clause in 'declare simd' construct. OpenMP 4.0 defines '#pragma omp declare simd' construct that may have associated 'simdlen' clause with constant positive expression as an argument: simdlen() Patch adds parsin and semantic analysis for simdlen clause. llvm-svn: 265668 --- diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index c7c54c7..e673b3b 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -2265,13 +2265,22 @@ def OMPDeclareSimdDecl : Attr { let SemaHandler = 0; let HasCustomParsing = 1; let Documentation = [OMPDeclareSimdDocs]; - let Args = [EnumArgument<"BranchState", "BranchStateTy", - ["", "inbranch", "notinbranch"], - ["BS_Undefined", "BS_Inbranch", "BS_Notinbranch"]>]; + let Args = [ + EnumArgument<"BranchState", "BranchStateTy", + [ "", "inbranch", "notinbranch" ], + [ "BS_Undefined", "BS_Inbranch", "BS_Notinbranch" ]>, + ExprArgument<"Simdlen"> + ]; let AdditionalMembers = [{ void printPrettyPragma(raw_ostream & OS, const PrintingPolicy &Policy) const { - OS << ' ' << ConvertBranchStateTyToStr(getBranchState()); + if (getBranchState() != BS_Undefined) + OS << ConvertBranchStateTyToStr(getBranchState()) << " "; + if (auto *E = getSimdlen()) { + OS << "simdlen("; + E->printPretty(OS, nullptr, Policy); + OS << ") "; + } } }]; } diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 4305982..9500e57 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -2456,6 +2456,10 @@ private: //===--------------------------------------------------------------------===// // OpenMP: Directives and clauses. + /// Parse clauses for '#pragma omp declare simd'. + DeclGroupPtrTy ParseOMPDeclareSimdClauses(DeclGroupPtrTy Ptr, + CachedTokens &Toks, + SourceLocation Loc); /// \brief Parses declarative OpenMP directives. DeclGroupPtrTy ParseOpenMPDeclarativeDirectiveWithExtDecl( AccessSpecifier &AS, ParsedAttributesWithRange &Attrs, @@ -2520,6 +2524,10 @@ private: OpenMPClauseKind Kind); public: + /// Parses simple expression in parens for single-expression clauses of OpenMP + /// constructs. + /// \param LLoc Returned location of left paren. + ExprResult ParseOpenMPParensExpr(StringRef ClauseName, SourceLocation &RLoc); bool ParseUnqualifiedId(CXXScopeSpec &SS, bool EnteringContext, bool AllowDestructorName, bool AllowConstructorName, diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 92d6131..32f0566 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -8107,7 +8107,7 @@ public: DeclGroupPtrTy ActOnOpenMPDeclareSimdDirective(DeclGroupPtrTy DG, OMPDeclareSimdDeclAttr::BranchStateTy BS, - SourceRange SR); + Expr *Simdlen, SourceRange SR); OMPClause *ActOnOpenMPSingleExprClause(OpenMPClauseKind Kind, Expr *Expr, diff --git a/clang/lib/Parse/ParseOpenMP.cpp b/clang/lib/Parse/ParseOpenMP.cpp index a280bb9..988916a 100644 --- a/clang/lib/Parse/ParseOpenMP.cpp +++ b/clang/lib/Parse/ParseOpenMP.cpp @@ -332,32 +332,134 @@ Parser::ParseOpenMPDeclareReductionDirective(AccessSpecifier AS) { /// Parses clauses for 'declare simd' directive. /// clause: /// 'inbranch' | 'notinbranch' -static void parseDeclareSimdClauses(Parser &P, - OMPDeclareSimdDeclAttr::BranchStateTy &BS) { +/// 'simdlen' '(' ')' +static bool parseDeclareSimdClauses(Parser &P, + OMPDeclareSimdDeclAttr::BranchStateTy &BS, + ExprResult &SimdLen) { SourceRange BSRange; const Token &Tok = P.getCurToken(); + bool IsError = false; while (Tok.isNot(tok::annot_pragma_openmp_end)) { if (Tok.isNot(tok::identifier)) break; OMPDeclareSimdDeclAttr::BranchStateTy Out; - StringRef TokName = Tok.getIdentifierInfo()->getName(); + IdentifierInfo *II = Tok.getIdentifierInfo(); + StringRef ClauseName = II->getName(); // Parse 'inranch|notinbranch' clauses. - if (OMPDeclareSimdDeclAttr::ConvertStrToBranchStateTy(TokName, Out)) { + if (OMPDeclareSimdDeclAttr::ConvertStrToBranchStateTy(ClauseName, Out)) { if (BS != OMPDeclareSimdDeclAttr::BS_Undefined && BS != Out) { P.Diag(Tok, diag::err_omp_declare_simd_inbranch_notinbranch) - << TokName << OMPDeclareSimdDeclAttr::ConvertBranchStateTyToStr(BS) - << BSRange; + << ClauseName + << OMPDeclareSimdDeclAttr::ConvertBranchStateTyToStr(BS) << BSRange; + IsError = true; } BS = Out; BSRange = SourceRange(Tok.getLocation(), Tok.getEndLoc()); + P.ConsumeToken(); + } else if (ClauseName.equals("simdlen")) { + if (SimdLen.isUsable()) { + P.Diag(Tok, diag::err_omp_more_one_clause) + << getOpenMPDirectiveName(OMPD_declare_simd) << ClauseName << 0; + IsError = true; + } + P.ConsumeToken(); + SourceLocation RLoc; + SimdLen = P.ParseOpenMPParensExpr(ClauseName, RLoc); + if (SimdLen.isInvalid()) + IsError = true; } else // TODO: add parsing of other clauses. break; - P.ConsumeToken(); // Skip ',' if any. if (Tok.is(tok::comma)) P.ConsumeToken(); } + return IsError; +} + +namespace { +/// RAII that recreates function context for correct parsing of clauses of +/// 'declare simd' construct. +/// OpenMP, 2.8.2 declare simd Construct +/// The expressions appearing in the clauses of this directive are evaluated in +/// the scope of the arguments of the function declaration or definition. +class FNContextRAII final { + Parser &P; + Sema::CXXThisScopeRAII *ThisScope; + Parser::ParseScope *TempScope; + Parser::ParseScope *FnScope; + bool HasTemplateScope = false; + bool HasFunScope = false; + FNContextRAII() = delete; + FNContextRAII(const FNContextRAII &) = delete; + FNContextRAII &operator=(const FNContextRAII &) = delete; + +public: + FNContextRAII(Parser &P, Parser::DeclGroupPtrTy Ptr) : P(P) { + Decl *D = *Ptr.get().begin(); + NamedDecl *ND = dyn_cast(D); + RecordDecl *RD = dyn_cast_or_null(D->getDeclContext()); + Sema &Actions = P.getActions(); + + // Allow 'this' within late-parsed attributes. + ThisScope = new Sema::CXXThisScopeRAII(Actions, RD, /*TypeQuals=*/0, + ND && ND->isCXXInstanceMember()); + + // If the Decl is templatized, add template parameters to scope. + HasTemplateScope = D->isTemplateDecl(); + TempScope = + new Parser::ParseScope(&P, Scope::TemplateParamScope, HasTemplateScope); + if (HasTemplateScope) + Actions.ActOnReenterTemplateScope(Actions.getCurScope(), D); + + // If the Decl is on a function, add function parameters to the scope. + HasFunScope = D->isFunctionOrFunctionTemplate(); + FnScope = new Parser::ParseScope(&P, Scope::FnScope | Scope::DeclScope, + HasFunScope); + if (HasFunScope) + Actions.ActOnReenterFunctionContext(Actions.getCurScope(), D); + } + ~FNContextRAII() { + if (HasFunScope) { + P.getActions().ActOnExitFunctionContext(); + FnScope->Exit(); // Pop scope, and remove Decls from IdResolver + } + if (HasTemplateScope) + TempScope->Exit(); + delete FnScope; + delete TempScope; + delete ThisScope; + } +}; +} // namespace + +/// Parse clauses for '#pragma omp declare simd'. +Parser::DeclGroupPtrTy +Parser::ParseOMPDeclareSimdClauses(Parser::DeclGroupPtrTy Ptr, + CachedTokens &Toks, SourceLocation Loc) { + PP.EnterToken(Tok); + PP.EnterTokenStream(Toks, /*DisableMacroExpansion=*/true); + // Consume the previously pushed token. + ConsumeAnyToken(/*ConsumeCodeCompletionTok=*/true); + + FNContextRAII FnContext(*this, Ptr); + OMPDeclareSimdDeclAttr::BranchStateTy BS = + OMPDeclareSimdDeclAttr::BS_Undefined; + ExprResult Simdlen; + bool IsError = parseDeclareSimdClauses(*this, BS, Simdlen); + // Need to check for extra tokens. + if (Tok.isNot(tok::annot_pragma_openmp_end)) { + Diag(Tok, diag::warn_omp_extra_tokens_at_eol) + << getOpenMPDirectiveName(OMPD_declare_simd); + while (Tok.isNot(tok::annot_pragma_openmp_end)) + ConsumeAnyToken(); + } + // Skip the last annot_pragma_openmp_end. + SourceLocation EndLoc = ConsumeToken(); + if (!IsError) + return Actions.ActOnOpenMPDeclareSimdDirective(Ptr, BS, Simdlen.get(), + SourceRange(Loc, EndLoc)); + return Ptr; } /// \brief Parsing of declarative OpenMP directives. @@ -382,7 +484,7 @@ Parser::DeclGroupPtrTy Parser::ParseOpenMPDeclarativeDirectiveWithExtDecl( ParenBraceBracketBalancer BalancerRAIIObj(*this); SourceLocation Loc = ConsumeToken(); - SmallVector Identifiers; + SmallVector Identifiers; auto DKind = ParseOpenMPDirectiveKind(*this); switch (DKind) { @@ -422,21 +524,10 @@ Parser::DeclGroupPtrTy Parser::ParseOpenMPDeclarativeDirectiveWithExtDecl( // { #pragma omp declare simd } // // - ConsumeToken(); - OMPDeclareSimdDeclAttr::BranchStateTy BS = - OMPDeclareSimdDeclAttr::BS_Undefined; - parseDeclareSimdClauses(*this, BS); - - // Need to check for extra tokens. - if (Tok.isNot(tok::annot_pragma_openmp_end)) { - Diag(Tok, diag::warn_omp_extra_tokens_at_eol) - << getOpenMPDirectiveName(OMPD_declare_simd); - while (Tok.isNot(tok::annot_pragma_openmp_end)) - ConsumeAnyToken(); - } - // Skip the last annot_pragma_openmp_end. - SourceLocation EndLoc = ConsumeToken(); + CachedTokens Toks; + ConsumeAndStoreUntil(tok::annot_pragma_openmp_end, Toks, + /*StopAtSemi=*/false, /*ConsumeFinalToken=*/true); DeclGroupPtrTy Ptr; if (Tok.is(tok::annot_pragma_openmp)) @@ -458,9 +549,7 @@ Parser::DeclGroupPtrTy Parser::ParseOpenMPDeclarativeDirectiveWithExtDecl( Diag(Loc, diag::err_omp_decl_in_declare_simd); return DeclGroupPtrTy(); } - - return Actions.ActOnOpenMPDeclareSimdDirective(Ptr, BS, - SourceRange(Loc, EndLoc)); + return ParseOMPDeclareSimdClauses(Ptr, Toks, Loc); } case OMPD_declare_target: { SourceLocation DTLoc = ConsumeAnyToken(); @@ -1000,6 +1089,28 @@ OMPClause *Parser::ParseOpenMPClause(OpenMPDirectiveKind DKind, return ErrorFound ? nullptr : Clause; } +/// Parses simple expression in parens for single-expression clauses of OpenMP +/// constructs. +/// \param RLoc Returned location of right paren. +ExprResult Parser::ParseOpenMPParensExpr(StringRef ClauseName, + SourceLocation &RLoc) { + BalancedDelimiterTracker T(*this, tok::l_paren, tok::annot_pragma_openmp_end); + if (T.expectAndConsume(diag::err_expected_lparen_after, ClauseName.data())) + return ExprError(); + + SourceLocation ELoc = Tok.getLocation(); + ExprResult LHS(ParseCastExpression( + /*isUnaryExpression=*/false, /*isAddressOfOperand=*/false, NotTypeCast)); + ExprResult Val(ParseRHSOfBinaryExpression(LHS, prec::Conditional)); + Val = Actions.ActOnFinishFullExpr(Val.get(), ELoc); + + // Parse ')'. + T.consumeClose(); + + RLoc = T.getCloseLocation(); + return Val; +} + /// \brief Parsing of OpenMP clauses with single expressions like 'final', /// 'collapse', 'safelen', 'num_threads', 'simdlen', 'num_teams', /// 'thread_limit', 'simdlen', 'priority', 'grainsize', 'num_tasks' or 'hint'. @@ -1033,25 +1144,15 @@ OMPClause *Parser::ParseOpenMPClause(OpenMPDirectiveKind DKind, /// OMPClause *Parser::ParseOpenMPSingleExprClause(OpenMPClauseKind Kind) { SourceLocation Loc = ConsumeToken(); + SourceLocation LLoc = Tok.getLocation(); + SourceLocation RLoc; - BalancedDelimiterTracker T(*this, tok::l_paren, tok::annot_pragma_openmp_end); - if (T.expectAndConsume(diag::err_expected_lparen_after, - getOpenMPClauseName(Kind))) - return nullptr; - - SourceLocation ELoc = Tok.getLocation(); - ExprResult LHS(ParseCastExpression(false, false, NotTypeCast)); - ExprResult Val(ParseRHSOfBinaryExpression(LHS, prec::Conditional)); - Val = Actions.ActOnFinishFullExpr(Val.get(), ELoc); - - // Parse ')'. - T.consumeClose(); + ExprResult Val = ParseOpenMPParensExpr(getOpenMPClauseName(Kind), RLoc); if (Val.isInvalid()) return nullptr; - return Actions.ActOnOpenMPSingleExprClause( - Kind, Val.get(), Loc, T.getOpenLocation(), T.getCloseLocation()); + return Actions.ActOnOpenMPSingleExprClause(Kind, Val.get(), Loc, LLoc, RLoc); } /// \brief Parsing of simple OpenMP clauses like 'default' or 'proc_bind'. diff --git a/clang/lib/Sema/SemaOpenMP.cpp b/clang/lib/Sema/SemaOpenMP.cpp index 0ef7965..3705cf2 100644 --- a/clang/lib/Sema/SemaOpenMP.cpp +++ b/clang/lib/Sema/SemaOpenMP.cpp @@ -3193,7 +3193,7 @@ StmtResult Sema::ActOnOpenMPExecutableDirective( Sema::DeclGroupPtrTy Sema::ActOnOpenMPDeclareSimdDirective(DeclGroupPtrTy DG, OMPDeclareSimdDeclAttr::BranchStateTy BS, - SourceRange SR) { + Expr *Simdlen, SourceRange SR) { if (!DG || DG.get().isNull()) return DeclGroupPtrTy(); @@ -3211,7 +3211,17 @@ Sema::ActOnOpenMPDeclareSimdDirective(DeclGroupPtrTy DG, return DeclGroupPtrTy(); } - auto *NewAttr = OMPDeclareSimdDeclAttr::CreateImplicit(Context, BS, SR); + // OpenMP [2.8.2, declare simd construct, Description] + // The parameter of the simdlen clause must be a constant positive integer + // expression. + ExprResult SL; + if (Simdlen) { + SL = VerifyPositiveIntegerConstantInClause(Simdlen, OMPC_simdlen); + if (SL.isInvalid()) + return DG; + } + auto *NewAttr = + OMPDeclareSimdDeclAttr::CreateImplicit(Context, BS, SL.get(), SR); ADecl->addAttr(NewAttr); return ConvertDeclToDeclGroup(ADecl); } diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index 2d87b04..c60a17a 100644 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -235,6 +235,22 @@ instantiateDependentModeAttr(Sema &S, Attr.getSpellingListIndex(), /*InInstantiation=*/true); } +/// Instantiation of 'declare simd' attribute and its arguments. +static void instantiateOMPDeclareSimdDeclAttr( + Sema &S, const MultiLevelTemplateArgumentList &TemplateArgs, + const OMPDeclareSimdDeclAttr &Attr, Decl *New) { + ExprResult Simdlen; + if (auto *E = Attr.getSimdlen()) { + Simdlen = S.SubstExpr(E, TemplateArgs); + if (Simdlen.isInvalid()) + return; + } + + (void)S.ActOnOpenMPDeclareSimdDirective(S.ConvertDeclToDeclGroup(New), + Attr.getBranchState(), Simdlen.get(), + Attr.getRange()); +} + void Sema::InstantiateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs, const Decl *Tmpl, Decl *New, LateInstantiatedAttrVec *LateAttrs, @@ -278,6 +294,11 @@ void Sema::InstantiateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs, continue; } + if (const auto *OMPAttr = dyn_cast(TmplAttr)) { + instantiateOMPDeclareSimdDeclAttr(*this, TemplateArgs, *OMPAttr, New); + continue; + } + // Existing DLL attribute on the instantiation takes precedence. if (TmplAttr->getKind() == attr::DLLExport || TmplAttr->getKind() == attr::DLLImport) { diff --git a/clang/test/OpenMP/declare_simd_ast_print.c b/clang/test/OpenMP/declare_simd_ast_print.c index 5cadd00..49dd102 100644 --- a/clang/test/OpenMP/declare_simd_ast_print.c +++ b/clang/test/OpenMP/declare_simd_ast_print.c @@ -7,14 +7,14 @@ #define HEADER #pragma omp declare simd -#pragma omp declare simd +#pragma omp declare simd simdlen(32) #pragma omp declare simd inbranch -#pragma omp declare simd notinbranch +#pragma omp declare simd notinbranch simdlen(2) void add_1(float *d, float *s1, float *s2) __attribute__((cold)); -// CHECK: #pragma omp declare simd notinbranch +// CHECK: #pragma omp declare simd notinbranch simdlen(2) // CHECK-NEXT: #pragma omp declare simd inbranch -// CHECK-NEXT: #pragma omp declare simd +// CHECK-NEXT: #pragma omp declare simd simdlen(32) // CHECK-NEXT: #pragma omp declare simd // CHECK-NEXT: void add_1(float *d, float *s1, float *s2) __attribute__((cold)) diff --git a/clang/test/OpenMP/declare_simd_ast_print.cpp b/clang/test/OpenMP/declare_simd_ast_print.cpp index 4cee8df..5adbb95 100644 --- a/clang/test/OpenMP/declare_simd_ast_print.cpp +++ b/clang/test/OpenMP/declare_simd_ast_print.cpp @@ -7,12 +7,12 @@ #define HEADER #pragma omp declare simd -#pragma omp declare simd inbranch +#pragma omp declare simd inbranch simdlen(32) #pragma omp declare simd notinbranch void add_1(float *d) __attribute__((cold)); // CHECK: #pragma omp declare simd notinbranch -// CHECK-NEXT: #pragma omp declare simd inbranch +// CHECK-NEXT: #pragma omp declare simd inbranch simdlen(32) // CHECK-NEXT: #pragma omp declare simd // CHECK-NEXT: void add_1(float *d) __attribute__((cold)); // @@ -92,10 +92,10 @@ template class TVV { public: // CHECK: template class TVV { - #pragma omp declare simd + #pragma omp declare simd simdlen(X) int tadd(int a, int b) { return a + b; } -// CHECK: #pragma omp declare simd +// CHECK: #pragma omp declare simd simdlen(X) // CHECK-NEXT: int tadd(int a, int b) { // CHECK-NEXT: return a + b; // CHECK-NEXT: } @@ -123,6 +123,14 @@ private: }; // CHECK: }; +// CHECK: #pragma omp declare simd simdlen(64) +// CHECK: template void foo(int (&)[64]) +// CHECK: #pragma omp declare simd simdlen(N) +// CHECK: template void foo(int (&)[N]) +#pragma omp declare simd simdlen(N) +template +void foo(int (&)[N]); + // CHECK: TVV<16> t16; TVV<16> t16; @@ -130,6 +138,8 @@ void f() { float a = 1.0f, b = 2.0f; float r = t16.taddpf(&a, &b); int res = t16.tadd(b); + int c[64]; + foo(c); } #endif diff --git a/clang/test/OpenMP/declare_simd_messages.cpp b/clang/test/OpenMP/declare_simd_messages.cpp index 76fdc2d..b222388 100644 --- a/clang/test/OpenMP/declare_simd_messages.cpp +++ b/clang/test/OpenMP/declare_simd_messages.cpp @@ -32,9 +32,12 @@ int main(); // expected-error@+1 {{single declaration is expected after 'declare simd' directive}} #pragma omp declare simd +// expected-note@+1 {{declared here}} int b, c; -#pragma omp declare simd +// expected-error@+1 {{'C' does not refer to a value}} +#pragma omp declare simd simdlen(C) +// expected-note@+1 {{declared here}} template void h(C *hp, C *hp2, C *hq, C *lin) { b = 0; @@ -50,8 +53,38 @@ void h(int *hp, int *hp2, int *hq, int *lin) { #pragma omp declare simd notinbranch notinbranch #pragma omp declare simd inbranch inbranch notinbranch // expected-error {{unexpected 'notinbranch' clause, 'inbranch' is specified already}} #pragma omp declare simd notinbranch notinbranch inbranch // expected-error {{unexpected 'inbranch' clause, 'notinbranch' is specified already}} +// expected-note@+2 {{read of non-const variable 'b' is not allowed in a constant expression}} +// expected-error@+1 {{expression is not an integral constant expression}} +#pragma omp declare simd simdlen(b) +// expected-error@+1 {{directive '#pragma omp declare simd' cannot contain more than one 'simdlen' clause}} +#pragma omp declare simd simdlen(32) simdlen(c) +// expected-error@+1 {{expected '(' after 'simdlen'}} +#pragma omp declare simd simdlen +// expected-note@+3 {{to match this '('}} +// expected-error@+2 {{expected ')'}} +// expected-error@+1 {{expected expression}} +#pragma omp declare simd simdlen( +// expected-error@+2 {{expected '(' after 'simdlen'}} +// expected-error@+1 {{expected expression}} +#pragma omp declare simd simdlen(), simdlen +// expected-error@+1 2 {{expected expression}} +#pragma omp declare simd simdlen(), simdlen() +// expected-warning@+3 {{extra tokens at the end of '#pragma omp declare simd' are ignored}} +// expected-error@+2 {{expected '(' after 'simdlen'}} +// expected-error@+1 {{expected expression}} +#pragma omp declare simd simdlen() simdlen) void foo(); +// expected-error@+1 {{argument to 'simdlen' clause must be a strictly positive integer value}} +#pragma omp declare simd simdlen(N) +template +void foo() {} + +void test() { + // expected-note@+1 {{in instantiation of function template specialization 'foo<-3>' requested here}} + foo<-3>(); +} + template struct St { // expected-error@+2 {{function declaration is expected after 'declare simd' directive}} diff --git a/clang/test/OpenMP/dump.cpp b/clang/test/OpenMP/dump.cpp index b08df67..378b53c 100644 --- a/clang/test/OpenMP/dump.cpp +++ b/clang/test/OpenMP/dump.cpp @@ -64,5 +64,5 @@ void foo(); // CHECK: `-FunctionDecl {{.+}} col:6 foo 'void (void)' // CHECK-NEXT: |-OMPDeclareSimdDeclAttr {{.+}} Implicit BS_Inbranch -// CHECK-NEXT: `-OMPDeclareSimdDeclAttr {{.+}} Implicit BS_Undefined +// CHECK: `-OMPDeclareSimdDeclAttr {{.+}} Implicit BS_Undefined