//===--- SemaOpenACC.cpp - Semantic Analysis for OpenACC constructs -------===// // // 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 // //===----------------------------------------------------------------------===// /// \file /// This file implements semantic analysis for OpenACC constructs, and things /// that are not clause specific. /// //===----------------------------------------------------------------------===// #include "clang/Sema/SemaOpenACC.h" #include "clang/AST/DeclOpenACC.h" #include "clang/AST/StmtOpenACC.h" #include "clang/Basic/DiagnosticSema.h" #include "clang/Basic/OpenACCKinds.h" #include "clang/Basic/SourceManager.h" #include "clang/Sema/Scope.h" #include "clang/Sema/Sema.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/Casting.h" using namespace clang; namespace { bool diagnoseConstructAppertainment(SemaOpenACC &S, OpenACCDirectiveKind K, SourceLocation StartLoc, bool IsStmt) { switch (K) { default: case OpenACCDirectiveKind::Invalid: // Nothing to do here, both invalid and unimplemented don't really need to // do anything. break; case OpenACCDirectiveKind::Parallel: case OpenACCDirectiveKind::ParallelLoop: case OpenACCDirectiveKind::Serial: case OpenACCDirectiveKind::SerialLoop: case OpenACCDirectiveKind::Kernels: case OpenACCDirectiveKind::KernelsLoop: case OpenACCDirectiveKind::Loop: case OpenACCDirectiveKind::Data: case OpenACCDirectiveKind::EnterData: case OpenACCDirectiveKind::ExitData: case OpenACCDirectiveKind::HostData: case OpenACCDirectiveKind::Wait: case OpenACCDirectiveKind::Update: case OpenACCDirectiveKind::Init: case OpenACCDirectiveKind::Shutdown: case OpenACCDirectiveKind::Cache: case OpenACCDirectiveKind::Atomic: if (!IsStmt) return S.Diag(StartLoc, diag::err_acc_construct_appertainment) << K; break; } return false; } void CollectActiveReductionClauses( llvm::SmallVector &ActiveClauses, ArrayRef CurClauses) { for (auto *CurClause : CurClauses) { if (auto *RedClause = dyn_cast(CurClause); RedClause && !RedClause->getVarList().empty()) ActiveClauses.push_back(RedClause); } } // Depth needs to be preserved for all associated statements that aren't // supposed to modify the compute/combined/loop construct information. bool PreserveLoopRAIIDepthInAssociatedStmtRAII(OpenACCDirectiveKind DK) { switch (DK) { case OpenACCDirectiveKind::Parallel: case OpenACCDirectiveKind::ParallelLoop: case OpenACCDirectiveKind::Serial: case OpenACCDirectiveKind::SerialLoop: case OpenACCDirectiveKind::Kernels: case OpenACCDirectiveKind::KernelsLoop: case OpenACCDirectiveKind::Loop: return false; case OpenACCDirectiveKind::Data: case OpenACCDirectiveKind::HostData: case OpenACCDirectiveKind::Atomic: return true; case OpenACCDirectiveKind::Cache: case OpenACCDirectiveKind::Routine: case OpenACCDirectiveKind::Declare: case OpenACCDirectiveKind::EnterData: case OpenACCDirectiveKind::ExitData: case OpenACCDirectiveKind::Wait: case OpenACCDirectiveKind::Init: case OpenACCDirectiveKind::Shutdown: case OpenACCDirectiveKind::Set: case OpenACCDirectiveKind::Update: llvm_unreachable("Doesn't have an associated stmt"); case OpenACCDirectiveKind::Invalid: llvm_unreachable("Unhandled directive kind?"); } llvm_unreachable("Unhandled directive kind?"); } } // namespace SemaOpenACC::SemaOpenACC(Sema &S) : SemaBase(S) {} SemaOpenACC::AssociatedStmtRAII::AssociatedStmtRAII( SemaOpenACC &S, OpenACCDirectiveKind DK, SourceLocation DirLoc, ArrayRef UnInstClauses, ArrayRef Clauses) : SemaRef(S), OldActiveComputeConstructInfo(S.ActiveComputeConstructInfo), DirKind(DK), OldLoopGangClauseOnKernel(S.LoopGangClauseOnKernel), OldLoopWorkerClauseLoc(S.LoopWorkerClauseLoc), OldLoopVectorClauseLoc(S.LoopVectorClauseLoc), OldLoopWithoutSeqInfo(S.LoopWithoutSeqInfo), ActiveReductionClauses(S.ActiveReductionClauses), LoopRAII(SemaRef, PreserveLoopRAIIDepthInAssociatedStmtRAII(DirKind)) { // Compute constructs end up taking their 'loop'. if (DirKind == OpenACCDirectiveKind::Parallel || DirKind == OpenACCDirectiveKind::Serial || DirKind == OpenACCDirectiveKind::Kernels) { CollectActiveReductionClauses(S.ActiveReductionClauses, Clauses); SemaRef.ActiveComputeConstructInfo.Kind = DirKind; SemaRef.ActiveComputeConstructInfo.Clauses = Clauses; // OpenACC 3.3 2.9.2: When the parent compute construct is a kernels // construct, the gang clause behaves as follows. ... The region of a loop // with a gang clause may not contain another loop with a gang clause unless // within a nested compute region. // // Implement the 'unless within a nested compute region' part. SemaRef.LoopGangClauseOnKernel = {}; SemaRef.LoopWorkerClauseLoc = {}; SemaRef.LoopVectorClauseLoc = {}; SemaRef.LoopWithoutSeqInfo = {}; } else if (DirKind == OpenACCDirectiveKind::ParallelLoop || DirKind == OpenACCDirectiveKind::SerialLoop || DirKind == OpenACCDirectiveKind::KernelsLoop) { SemaRef.ActiveComputeConstructInfo.Kind = DirKind; SemaRef.ActiveComputeConstructInfo.Clauses = Clauses; CollectActiveReductionClauses(S.ActiveReductionClauses, Clauses); SetCollapseInfoBeforeAssociatedStmt(UnInstClauses, Clauses); SetTileInfoBeforeAssociatedStmt(UnInstClauses, Clauses); SemaRef.LoopGangClauseOnKernel = {}; SemaRef.LoopWorkerClauseLoc = {}; SemaRef.LoopVectorClauseLoc = {}; // Set the active 'loop' location if there isn't a 'seq' on it, so we can // diagnose the for loops. SemaRef.LoopWithoutSeqInfo = {}; if (Clauses.end() == llvm::find_if(Clauses, llvm::IsaPred)) SemaRef.LoopWithoutSeqInfo = {DirKind, DirLoc}; // OpenACC 3.3 2.9.2: When the parent compute construct is a kernels // construct, the gang clause behaves as follows. ... The region of a loop // with a gang clause may not contain another loop with a gang clause unless // within a nested compute region. // // We don't bother doing this when this is a template instantiation, as // there is no reason to do these checks: the existance of a // gang/kernels/etc cannot be dependent. if (DirKind == OpenACCDirectiveKind::KernelsLoop && UnInstClauses.empty()) { // This handles the 'outer loop' part of this. auto *Itr = llvm::find_if(Clauses, llvm::IsaPred); if (Itr != Clauses.end()) SemaRef.LoopGangClauseOnKernel = {(*Itr)->getBeginLoc(), DirKind}; } if (UnInstClauses.empty()) { auto *Itr = llvm::find_if(Clauses, llvm::IsaPred); if (Itr != Clauses.end()) SemaRef.LoopWorkerClauseLoc = (*Itr)->getBeginLoc(); auto *Itr2 = llvm::find_if(Clauses, llvm::IsaPred); if (Itr2 != Clauses.end()) SemaRef.LoopVectorClauseLoc = (*Itr2)->getBeginLoc(); } } else if (DirKind == OpenACCDirectiveKind::Loop) { CollectActiveReductionClauses(S.ActiveReductionClauses, Clauses); SetCollapseInfoBeforeAssociatedStmt(UnInstClauses, Clauses); SetTileInfoBeforeAssociatedStmt(UnInstClauses, Clauses); // Set the active 'loop' location if there isn't a 'seq' on it, so we can // diagnose the for loops. SemaRef.LoopWithoutSeqInfo = {}; if (Clauses.end() == llvm::find_if(Clauses, llvm::IsaPred)) SemaRef.LoopWithoutSeqInfo = {DirKind, DirLoc}; // OpenACC 3.3 2.9.2: When the parent compute construct is a kernels // construct, the gang clause behaves as follows. ... The region of a loop // with a gang clause may not contain another loop with a gang clause unless // within a nested compute region. // // We don't bother doing this when this is a template instantiation, as // there is no reason to do these checks: the existance of a // gang/kernels/etc cannot be dependent. if (SemaRef.getActiveComputeConstructInfo().Kind == OpenACCDirectiveKind::Kernels && UnInstClauses.empty()) { // This handles the 'outer loop' part of this. auto *Itr = llvm::find_if(Clauses, llvm::IsaPred); if (Itr != Clauses.end()) SemaRef.LoopGangClauseOnKernel = {(*Itr)->getBeginLoc(), OpenACCDirectiveKind::Kernels}; } if (UnInstClauses.empty()) { auto *Itr = llvm::find_if(Clauses, llvm::IsaPred); if (Itr != Clauses.end()) SemaRef.LoopWorkerClauseLoc = (*Itr)->getBeginLoc(); auto *Itr2 = llvm::find_if(Clauses, llvm::IsaPred); if (Itr2 != Clauses.end()) SemaRef.LoopVectorClauseLoc = (*Itr2)->getBeginLoc(); } } } namespace { // Given two collapse clauses, and the uninstanted version of the new one, // return the 'best' one for the purposes of setting the collapse checking // values. const OpenACCCollapseClause * getBestCollapseCandidate(const OpenACCCollapseClause *Old, const OpenACCCollapseClause *New, const OpenACCCollapseClause *UnInstNew) { // If the loop count is nullptr, it is because instantiation failed, so this // can't be the best one. if (!New->getLoopCount()) return Old; // If the loop-count had an error, than 'new' isn't a candidate. if (!New->getLoopCount()) return Old; // Don't consider uninstantiated ones, since we can't really check these. if (New->getLoopCount()->isInstantiationDependent()) return Old; // If this is an instantiation, and the old version wasn't instantation // dependent, than nothing has changed and we've already done a diagnostic // based on this one, so don't consider it. if (UnInstNew && !UnInstNew->getLoopCount()->isInstantiationDependent()) return Old; // New is now a valid candidate, so if there isn't an old one at this point, // New is the only valid one. if (!Old) return New; // If the 'New' expression has a larger value than 'Old', then it is the new // best candidate. if (cast(Old->getLoopCount())->getResultAsAPSInt() < cast(New->getLoopCount())->getResultAsAPSInt()) return New; return Old; } } // namespace void SemaOpenACC::AssociatedStmtRAII::SetCollapseInfoBeforeAssociatedStmt( ArrayRef UnInstClauses, ArrayRef Clauses) { // Reset this checking for loops that aren't covered in a RAII object. SemaRef.LoopInfo.CurLevelHasLoopAlready = false; SemaRef.CollapseInfo.CollapseDepthSatisfied = true; SemaRef.CollapseInfo.CurCollapseCount = 0; SemaRef.TileInfo.TileDepthSatisfied = true; // We make sure to take an optional list of uninstantiated clauses, so that // we can check to make sure we don't 'double diagnose' in the event that // the value of 'N' was not dependent in a template. Since we cannot count on // there only being a single collapse clause, we count on the order to make // sure get the matching ones, and we count on TreeTransform not removing // these, even if loop-count instantiation failed. We can check the // non-dependent ones right away, and realize that subsequent instantiation // can only make it more specific. auto *UnInstClauseItr = llvm::find_if(UnInstClauses, llvm::IsaPred); auto *ClauseItr = llvm::find_if(Clauses, llvm::IsaPred); const OpenACCCollapseClause *FoundClause = nullptr; // Loop through the list of Collapse clauses and find the one that: // 1- Has a non-dependent, non-null loop count (null means error, likely // during instantiation). // 2- If UnInstClauses isn't empty, its corresponding // loop count was dependent. // 3- Has the largest 'loop count' of all. while (ClauseItr != Clauses.end()) { const OpenACCCollapseClause *CurClause = cast(*ClauseItr); const OpenACCCollapseClause *UnInstCurClause = UnInstClauseItr == UnInstClauses.end() ? nullptr : cast(*UnInstClauseItr); FoundClause = getBestCollapseCandidate(FoundClause, CurClause, UnInstCurClause); UnInstClauseItr = UnInstClauseItr == UnInstClauses.end() ? UnInstClauseItr : std::find_if(std::next(UnInstClauseItr), UnInstClauses.end(), llvm::IsaPred); ClauseItr = std::find_if(std::next(ClauseItr), Clauses.end(), llvm::IsaPred); } if (!FoundClause) return; SemaRef.CollapseInfo.ActiveCollapse = FoundClause; SemaRef.CollapseInfo.CollapseDepthSatisfied = false; SemaRef.CollapseInfo.CurCollapseCount = cast(FoundClause->getLoopCount())->getResultAsAPSInt(); SemaRef.CollapseInfo.DirectiveKind = DirKind; } void SemaOpenACC::AssociatedStmtRAII::SetTileInfoBeforeAssociatedStmt( ArrayRef UnInstClauses, ArrayRef Clauses) { // We don't diagnose if this is during instantiation, since the only thing we // care about is the number of arguments, which we can figure out without // instantiation, so we don't want to double-diagnose. if (UnInstClauses.size() > 0) return; auto *TileClauseItr = llvm::find_if(Clauses, llvm::IsaPred); if (Clauses.end() == TileClauseItr) return; OpenACCTileClause *TileClause = cast(*TileClauseItr); // Multiple tile clauses are allowed, so ensure that we use the one with the // largest 'tile count'. while (Clauses.end() != (TileClauseItr = std::find_if(std::next(TileClauseItr), Clauses.end(), llvm::IsaPred))) { OpenACCTileClause *NewClause = cast(*TileClauseItr); if (NewClause->getSizeExprs().size() > TileClause->getSizeExprs().size()) TileClause = NewClause; } SemaRef.TileInfo.ActiveTile = TileClause; SemaRef.TileInfo.TileDepthSatisfied = false; SemaRef.TileInfo.CurTileCount = static_cast(TileClause->getSizeExprs().size()); SemaRef.TileInfo.DirectiveKind = DirKind; } SemaOpenACC::AssociatedStmtRAII::~AssociatedStmtRAII() { if (DirKind == OpenACCDirectiveKind::Parallel || DirKind == OpenACCDirectiveKind::Serial || DirKind == OpenACCDirectiveKind::Kernels || DirKind == OpenACCDirectiveKind::Loop || DirKind == OpenACCDirectiveKind::ParallelLoop || DirKind == OpenACCDirectiveKind::SerialLoop || DirKind == OpenACCDirectiveKind::KernelsLoop) { SemaRef.ActiveComputeConstructInfo = OldActiveComputeConstructInfo; SemaRef.LoopGangClauseOnKernel = OldLoopGangClauseOnKernel; SemaRef.LoopWorkerClauseLoc = OldLoopWorkerClauseLoc; SemaRef.LoopVectorClauseLoc = OldLoopVectorClauseLoc; SemaRef.LoopWithoutSeqInfo = OldLoopWithoutSeqInfo; SemaRef.ActiveReductionClauses.swap(ActiveReductionClauses); } else if (DirKind == OpenACCDirectiveKind::Data || DirKind == OpenACCDirectiveKind::HostData) { // Intentionally doesn't reset the Loop, Compute Construct, or reduction // effects. } } void SemaOpenACC::ActOnConstruct(OpenACCDirectiveKind K, SourceLocation DirLoc) { // Start an evaluation context to parse the clause arguments on. SemaRef.PushExpressionEvaluationContext( Sema::ExpressionEvaluationContext::PotentiallyEvaluated); // There is nothing do do here as all we have at this point is the name of the // construct itself. } ExprResult SemaOpenACC::ActOnIntExpr(OpenACCDirectiveKind DK, OpenACCClauseKind CK, SourceLocation Loc, Expr *IntExpr) { assert(((DK != OpenACCDirectiveKind::Invalid && CK == OpenACCClauseKind::Invalid) || (DK == OpenACCDirectiveKind::Invalid && CK != OpenACCClauseKind::Invalid) || (DK == OpenACCDirectiveKind::Invalid && CK == OpenACCClauseKind::Invalid)) && "Only one of directive or clause kind should be provided"); class IntExprConverter : public Sema::ICEConvertDiagnoser { OpenACCDirectiveKind DirectiveKind; OpenACCClauseKind ClauseKind; Expr *IntExpr; // gets the index into the diagnostics so we can use this for clauses, // directives, and sub array.s unsigned getDiagKind() const { if (ClauseKind != OpenACCClauseKind::Invalid) return 0; if (DirectiveKind != OpenACCDirectiveKind::Invalid) return 1; return 2; } public: IntExprConverter(OpenACCDirectiveKind DK, OpenACCClauseKind CK, Expr *IntExpr) : ICEConvertDiagnoser(/*AllowScopedEnumerations=*/false, /*Suppress=*/false, /*SuppressConversion=*/true), DirectiveKind(DK), ClauseKind(CK), IntExpr(IntExpr) {} bool match(QualType T) override { // OpenACC spec just calls this 'integer expression' as having an // 'integer type', so fall back on C99's 'integer type'. return T->isIntegerType(); } SemaBase::SemaDiagnosticBuilder diagnoseNotInt(Sema &S, SourceLocation Loc, QualType T) override { return S.Diag(Loc, diag::err_acc_int_expr_requires_integer) << getDiagKind() << ClauseKind << DirectiveKind << T; } SemaBase::SemaDiagnosticBuilder diagnoseIncomplete(Sema &S, SourceLocation Loc, QualType T) override { return S.Diag(Loc, diag::err_acc_int_expr_incomplete_class_type) << T << IntExpr->getSourceRange(); } SemaBase::SemaDiagnosticBuilder diagnoseExplicitConv(Sema &S, SourceLocation Loc, QualType T, QualType ConvTy) override { return S.Diag(Loc, diag::err_acc_int_expr_explicit_conversion) << T << ConvTy; } SemaBase::SemaDiagnosticBuilder noteExplicitConv(Sema &S, CXXConversionDecl *Conv, QualType ConvTy) override { return S.Diag(Conv->getLocation(), diag::note_acc_int_expr_conversion) << ConvTy->isEnumeralType() << ConvTy; } SemaBase::SemaDiagnosticBuilder diagnoseAmbiguous(Sema &S, SourceLocation Loc, QualType T) override { return S.Diag(Loc, diag::err_acc_int_expr_multiple_conversions) << T; } SemaBase::SemaDiagnosticBuilder noteAmbiguous(Sema &S, CXXConversionDecl *Conv, QualType ConvTy) override { return S.Diag(Conv->getLocation(), diag::note_acc_int_expr_conversion) << ConvTy->isEnumeralType() << ConvTy; } SemaBase::SemaDiagnosticBuilder diagnoseConversion(Sema &S, SourceLocation Loc, QualType T, QualType ConvTy) override { llvm_unreachable("conversion functions are permitted"); } } IntExprDiagnoser(DK, CK, IntExpr); if (!IntExpr) return ExprError(); ExprResult IntExprResult = SemaRef.PerformContextualImplicitConversion( Loc, IntExpr, IntExprDiagnoser); if (IntExprResult.isInvalid()) return ExprError(); IntExpr = IntExprResult.get(); if (!IntExpr->isTypeDependent() && !IntExpr->getType()->isIntegerType()) return ExprError(); // TODO OpenACC: Do we want to perform usual unary conversions here? When // doing codegen we might find that is necessary, but skip it for now. return IntExpr; } bool SemaOpenACC::CheckVarIsPointerType(OpenACCClauseKind ClauseKind, Expr *VarExpr) { // We already know that VarExpr is a proper reference to a variable, so we // should be able to just take the type of the expression to get the type of // the referenced variable. // We've already seen an error, don't diagnose anything else. if (!VarExpr || VarExpr->containsErrors()) return false; if (isa(VarExpr->IgnoreParenImpCasts()) || VarExpr->hasPlaceholderType(BuiltinType::ArraySection)) { Diag(VarExpr->getExprLoc(), diag::err_array_section_use) << /*OpenACC=*/0; Diag(VarExpr->getExprLoc(), diag::note_acc_expected_pointer_var); return true; } QualType Ty = VarExpr->getType(); Ty = Ty.getNonReferenceType().getUnqualifiedType(); // Nothing we can do if this is a dependent type. if (Ty->isDependentType()) return false; if (!Ty->isPointerType()) return Diag(VarExpr->getExprLoc(), diag::err_acc_var_not_pointer_type) << ClauseKind << Ty; return false; } void SemaOpenACC::ActOnStartParseVar(OpenACCDirectiveKind DK, OpenACCClauseKind CK) { if (DK == OpenACCDirectiveKind::Cache) { CacheInfo.ParsingCacheVarList = true; CacheInfo.IsInvalidCacheRef = false; } } void SemaOpenACC::ActOnInvalidParseVar() { CacheInfo.ParsingCacheVarList = false; CacheInfo.IsInvalidCacheRef = false; } ExprResult SemaOpenACC::ActOnCacheVar(Expr *VarExpr) { Expr *CurVarExpr = VarExpr->IgnoreParenImpCasts(); // Clear this here, so we can do the returns based on the invalid cache ref // here. Note all return statements in this function must return ExprError if // IsInvalidCacheRef. However, instead of doing an 'early return' in that // case, we can let the rest of the diagnostics happen, as the invalid decl // ref is a warning. bool WasParsingInvalidCacheRef = CacheInfo.ParsingCacheVarList && CacheInfo.IsInvalidCacheRef; CacheInfo.ParsingCacheVarList = false; CacheInfo.IsInvalidCacheRef = false; if (!isa(CurVarExpr)) { Diag(VarExpr->getExprLoc(), diag::err_acc_not_a_var_ref_cache); return ExprError(); } // It isn't clear what 'simple array element or simple subarray' means, so we // will just allow arbitrary depth. while (isa(CurVarExpr)) { if (auto *SubScrpt = dyn_cast(CurVarExpr)) CurVarExpr = SubScrpt->getBase()->IgnoreParenImpCasts(); else CurVarExpr = cast(CurVarExpr)->getBase()->IgnoreParenImpCasts(); } // References to a VarDecl are fine. if (const auto *DRE = dyn_cast(CurVarExpr)) { if (isa( DRE->getFoundDecl()->getCanonicalDecl())) return WasParsingInvalidCacheRef ? ExprEmpty() : VarExpr; } if (const auto *ME = dyn_cast(CurVarExpr)) { if (isa(ME->getMemberDecl()->getCanonicalDecl())) { return WasParsingInvalidCacheRef ? ExprEmpty() : VarExpr; } } // Nothing really we can do here, as these are dependent. So just return they // are valid. if (isa(CurVarExpr)) return WasParsingInvalidCacheRef ? ExprEmpty() : VarExpr; // There isn't really anything we can do in the case of a recovery expr, so // skip the diagnostic rather than produce a confusing diagnostic. if (isa(CurVarExpr)) return ExprError(); Diag(VarExpr->getExprLoc(), diag::err_acc_not_a_var_ref_cache); return ExprError(); } void SemaOpenACC::CheckDeclReference(SourceLocation Loc, Expr *E, Decl *D) { if (!getLangOpts().OpenACC || !CacheInfo.ParsingCacheVarList || !D || D->isInvalidDecl()) return; // A 'cache' variable reference MUST be declared before the 'acc.loop' we // generate in codegen, so we have to mark it invalid here in some way. We do // so in a bit of a convoluted way as there is no good way to put this into // the AST, so we store it in SemaOpenACC State. We can check the Scope // during parsing to make sure there is a 'loop' before the decl is // declared(and skip during instantiation). // We only diagnose this as a warning, as this isn't required by the standard // (unless you take a VERY awkward reading of some awkward prose). Scope *CurScope = SemaRef.getCurScope(); // if we are at TU level, we are either doing some EXTRA wacky, or are in a // template instantiation, so just give up. if (CurScope->getDepth() == 0) return; while (CurScope) { // If we run into a loop construct scope, than this is 'correct' in that the // declaration is outside of the loop. if (CurScope->isOpenACCLoopConstructScope()) return; if (CurScope->isDeclScope(D)) { Diag(Loc, diag::warn_acc_cache_var_not_outside_loop); CacheInfo.IsInvalidCacheRef = true; } CurScope = CurScope->getParent(); } // If we don't find the decl at all, we assume that it must be outside of the // loop (or we aren't in a loop!) so skip the diagnostic. } namespace { // Check whether the type of the thing we are referencing is OK for things like // private, firstprivate, and reduction, which require certain operators to be // available. ExprResult CheckVarType(SemaOpenACC &S, OpenACCClauseKind CK, Expr *VarExpr, Expr *InnerExpr) { // There is nothing to do here, only these three have these sorts of // restrictions. if (CK != OpenACCClauseKind::Private && CK != OpenACCClauseKind::FirstPrivate && CK != OpenACCClauseKind::Reduction) return VarExpr; // We can't test this if it isn't here, or if the type isn't clear yet. if (!InnerExpr || InnerExpr->isTypeDependent()) return VarExpr; const auto *RD = InnerExpr->getType()->getAsCXXRecordDecl(); // if this isn't a C++ record decl, we can create/copy/destroy this thing at // will without problem, so this is a success. if (!RD) return VarExpr; // TODO: OpenACC: // Private must have default ctor + dtor in InnerExpr // FirstPrivate must have copyctor + dtor in InnerExpr // Reduction must have copyctor + dtor + operation in InnerExpr // TODO OpenACC: It isn't clear what the requirements are for default // constructor/copy constructor are for First private and reduction, but // private requires a default constructor. if (CK == OpenACCClauseKind::Private) { bool HasNonDeletedDefaultCtor = llvm::find_if(RD->ctors(), [](const CXXConstructorDecl *CD) { return CD->isDefaultConstructor() && !CD->isDeleted(); }) != RD->ctors().end(); if (!HasNonDeletedDefaultCtor && !RD->needsImplicitDefaultConstructor()) { S.Diag(InnerExpr->getBeginLoc(), clang::diag::warn_acc_var_referenced_lacks_op) << InnerExpr->getType() << CK << clang::diag::AccVarReferencedReason::DefCtor; return ExprError(); } } // All 3 things need to make sure they have a dtor. bool DestructorDeleted = RD->getDestructor() && RD->getDestructor()->isDeleted(); if (DestructorDeleted && !RD->needsImplicitDestructor()) { S.Diag(InnerExpr->getBeginLoc(), clang::diag::warn_acc_var_referenced_lacks_op) << InnerExpr->getType() << CK << clang::diag::AccVarReferencedReason::Dtor; return ExprError(); } return VarExpr; } } // namespace ExprResult SemaOpenACC::ActOnVar(OpenACCDirectiveKind DK, OpenACCClauseKind CK, Expr *VarExpr) { // This has unique enough restrictions that we should split it to a separate // function. if (DK == OpenACCDirectiveKind::Cache) return ActOnCacheVar(VarExpr); Expr *CurVarExpr = VarExpr->IgnoreParenImpCasts(); // 'use_device' doesn't allow array subscript or array sections. // OpenACC3.3 2.8: // A 'var' in a 'use_device' clause must be the name of a variable or array. // OpenACC3.3 2.13: // A 'var' in a 'declare' directive must be a variable or array name. if ((CK == OpenACCClauseKind::UseDevice || DK == OpenACCDirectiveKind::Declare) && isa(CurVarExpr)) { Diag(VarExpr->getExprLoc(), diag::err_acc_not_a_var_ref_use_device_declare) << (DK == OpenACCDirectiveKind::Declare); return ExprError(); } // Sub-arrays/subscript-exprs are fine as long as the base is a // VarExpr/MemberExpr. So strip all of those off. while (isa(CurVarExpr)) { if (auto *SubScrpt = dyn_cast(CurVarExpr)) CurVarExpr = SubScrpt->getBase()->IgnoreParenImpCasts(); else CurVarExpr = cast(CurVarExpr)->getBase()->IgnoreParenImpCasts(); } // References to a VarDecl are fine. if (const auto *DRE = dyn_cast(CurVarExpr)) { if (isa( DRE->getFoundDecl()->getCanonicalDecl())) return CheckVarType(*this, CK, VarExpr, CurVarExpr); } // If CK is a Reduction, this special cases for OpenACC3.3 2.5.15: "A var in a // reduction clause must be a scalar variable name, an aggregate variable // name, an array element, or a subarray. // If CK is a 'use_device', this also isn't valid, as it isn't the name of a // variable or array, if not done as a member expr. // A MemberExpr that references a Field is valid for other clauses. if (const auto *ME = dyn_cast(CurVarExpr)) { if (isa(ME->getMemberDecl()->getCanonicalDecl())) { if (DK == OpenACCDirectiveKind::Declare || CK == OpenACCClauseKind::Reduction || CK == OpenACCClauseKind::UseDevice) { // We can allow 'member expr' if the 'this' is implicit in the case of // declare, reduction, and use_device. const auto *This = dyn_cast(ME->getBase()); if (This && This->isImplicit()) return CheckVarType(*this, CK, VarExpr, CurVarExpr); } else { return CheckVarType(*this, CK, VarExpr, CurVarExpr); } } } // Referring to 'this' is ok for the most part, but for 'use_device'/'declare' // doesn't fall into 'variable or array name' if (CK != OpenACCClauseKind::UseDevice && DK != OpenACCDirectiveKind::Declare && isa(CurVarExpr)) return CheckVarType(*this, CK, VarExpr, CurVarExpr); // Nothing really we can do here, as these are dependent. So just return they // are valid. if (isa(CurVarExpr) || (CK != OpenACCClauseKind::Reduction && isa(CurVarExpr))) return CheckVarType(*this, CK, VarExpr, CurVarExpr); // There isn't really anything we can do in the case of a recovery expr, so // skip the diagnostic rather than produce a confusing diagnostic. if (isa(CurVarExpr)) return ExprError(); if (DK == OpenACCDirectiveKind::Declare) Diag(VarExpr->getExprLoc(), diag::err_acc_not_a_var_ref_use_device_declare) << /*declare*/ 1; else if (CK == OpenACCClauseKind::UseDevice) Diag(VarExpr->getExprLoc(), diag::err_acc_not_a_var_ref_use_device_declare) << /*use_device*/ 0; else Diag(VarExpr->getExprLoc(), diag::err_acc_not_a_var_ref) << (CK != OpenACCClauseKind::Reduction); return ExprError(); } ExprResult SemaOpenACC::ActOnArraySectionExpr(Expr *Base, SourceLocation LBLoc, Expr *LowerBound, SourceLocation ColonLoc, Expr *Length, SourceLocation RBLoc) { ASTContext &Context = getASTContext(); // Handle placeholders. if (Base->hasPlaceholderType() && !Base->hasPlaceholderType(BuiltinType::ArraySection)) { ExprResult Result = SemaRef.CheckPlaceholderExpr(Base); if (Result.isInvalid()) return ExprError(); Base = Result.get(); } if (LowerBound && LowerBound->getType()->isNonOverloadPlaceholderType()) { ExprResult Result = SemaRef.CheckPlaceholderExpr(LowerBound); if (Result.isInvalid()) return ExprError(); Result = SemaRef.DefaultLvalueConversion(Result.get()); if (Result.isInvalid()) return ExprError(); LowerBound = Result.get(); } if (Length && Length->getType()->isNonOverloadPlaceholderType()) { ExprResult Result = SemaRef.CheckPlaceholderExpr(Length); if (Result.isInvalid()) return ExprError(); Result = SemaRef.DefaultLvalueConversion(Result.get()); if (Result.isInvalid()) return ExprError(); Length = Result.get(); } // Check the 'base' value, it must be an array or pointer type, and not to/of // a function type. QualType OriginalBaseTy = ArraySectionExpr::getBaseOriginalType(Base); QualType ResultTy; if (!Base->isTypeDependent()) { if (OriginalBaseTy->isAnyPointerType()) { ResultTy = OriginalBaseTy->getPointeeType(); } else if (OriginalBaseTy->isArrayType()) { ResultTy = OriginalBaseTy->getAsArrayTypeUnsafe()->getElementType(); } else { return ExprError( Diag(Base->getExprLoc(), diag::err_acc_typecheck_subarray_value) << Base->getSourceRange()); } if (ResultTy->isFunctionType()) { Diag(Base->getExprLoc(), diag::err_acc_subarray_function_type) << ResultTy << Base->getSourceRange(); return ExprError(); } if (SemaRef.RequireCompleteType(Base->getExprLoc(), ResultTy, diag::err_acc_subarray_incomplete_type, Base)) return ExprError(); if (!Base->hasPlaceholderType(BuiltinType::ArraySection)) { ExprResult Result = SemaRef.DefaultFunctionArrayLvalueConversion(Base); if (Result.isInvalid()) return ExprError(); Base = Result.get(); } } auto GetRecovery = [&](Expr *E, QualType Ty) { ExprResult Recovery = SemaRef.CreateRecoveryExpr(E->getBeginLoc(), E->getEndLoc(), E, Ty); return Recovery.isUsable() ? Recovery.get() : nullptr; }; // Ensure both of the expressions are int-exprs. if (LowerBound && !LowerBound->isTypeDependent()) { ExprResult LBRes = ActOnIntExpr(OpenACCDirectiveKind::Invalid, OpenACCClauseKind::Invalid, LowerBound->getExprLoc(), LowerBound); if (LBRes.isUsable()) LBRes = SemaRef.DefaultLvalueConversion(LBRes.get()); LowerBound = LBRes.isUsable() ? LBRes.get() : GetRecovery(LowerBound, Context.IntTy); } if (Length && !Length->isTypeDependent()) { ExprResult LenRes = ActOnIntExpr(OpenACCDirectiveKind::Invalid, OpenACCClauseKind::Invalid, Length->getExprLoc(), Length); if (LenRes.isUsable()) LenRes = SemaRef.DefaultLvalueConversion(LenRes.get()); Length = LenRes.isUsable() ? LenRes.get() : GetRecovery(Length, Context.IntTy); } // Length is required if the base type is not an array of known bounds. if (!Length && (OriginalBaseTy.isNull() || (!OriginalBaseTy->isDependentType() && !OriginalBaseTy->isConstantArrayType() && !OriginalBaseTy->isDependentSizedArrayType()))) { bool IsArray = !OriginalBaseTy.isNull() && OriginalBaseTy->isArrayType(); SourceLocation DiagLoc = ColonLoc.isInvalid() ? LBLoc : ColonLoc; Diag(DiagLoc, diag::err_acc_subarray_no_length) << IsArray; // Fill in a dummy 'length' so that when we instantiate this we don't // double-diagnose here. ExprResult Recovery = SemaRef.CreateRecoveryExpr( DiagLoc, SourceLocation(), ArrayRef(), Context.IntTy); Length = Recovery.isUsable() ? Recovery.get() : nullptr; } // Check the values of each of the arguments, they cannot be negative(we // assume), and if the array bound is known, must be within range. As we do // so, do our best to continue with evaluation, we can set the // value/expression to nullptr/nullopt if they are invalid, and treat them as // not present for the rest of evaluation. // We don't have to check for dependence, because the dependent size is // represented as a different AST node. std::optional BaseSize; if (!OriginalBaseTy.isNull() && OriginalBaseTy->isConstantArrayType()) { const auto *ArrayTy = Context.getAsConstantArrayType(OriginalBaseTy); BaseSize = ArrayTy->getSize(); } auto GetBoundValue = [&](Expr *E) -> std::optional { if (!E || E->isInstantiationDependent()) return std::nullopt; Expr::EvalResult Res; if (!E->EvaluateAsInt(Res, Context)) return std::nullopt; return Res.Val.getInt(); }; std::optional LowerBoundValue = GetBoundValue(LowerBound); std::optional LengthValue = GetBoundValue(Length); // Check lower bound for negative or out of range. if (LowerBoundValue.has_value()) { if (LowerBoundValue->isNegative()) { Diag(LowerBound->getExprLoc(), diag::err_acc_subarray_negative) << /*LowerBound=*/0 << toString(*LowerBoundValue, /*Radix=*/10); LowerBoundValue.reset(); LowerBound = GetRecovery(LowerBound, LowerBound->getType()); } else if (BaseSize.has_value() && llvm::APSInt::compareValues(*LowerBoundValue, *BaseSize) >= 0) { // Lower bound (start index) must be less than the size of the array. Diag(LowerBound->getExprLoc(), diag::err_acc_subarray_out_of_range) << /*LowerBound=*/0 << toString(*LowerBoundValue, /*Radix=*/10) << toString(*BaseSize, /*Radix=*/10); LowerBoundValue.reset(); LowerBound = GetRecovery(LowerBound, LowerBound->getType()); } } // Check length for negative or out of range. if (LengthValue.has_value()) { if (LengthValue->isNegative()) { Diag(Length->getExprLoc(), diag::err_acc_subarray_negative) << /*Length=*/1 << toString(*LengthValue, /*Radix=*/10); LengthValue.reset(); Length = GetRecovery(Length, Length->getType()); } else if (BaseSize.has_value() && llvm::APSInt::compareValues(*LengthValue, *BaseSize) > 0) { // Length must be lessthan or EQUAL to the size of the array. Diag(Length->getExprLoc(), diag::err_acc_subarray_out_of_range) << /*Length=*/1 << toString(*LengthValue, /*Radix=*/10) << toString(*BaseSize, /*Radix=*/10); LengthValue.reset(); Length = GetRecovery(Length, Length->getType()); } } // Adding two APSInts requires matching sign, so extract that here. auto AddAPSInt = [](llvm::APSInt LHS, llvm::APSInt RHS) -> llvm::APSInt { if (LHS.isSigned() == RHS.isSigned()) return LHS + RHS; unsigned Width = std::max(LHS.getBitWidth(), RHS.getBitWidth()) + 1; return llvm::APSInt(LHS.sext(Width) + RHS.sext(Width), /*Signed=*/true); }; // If we know all 3 values, we can diagnose that the total value would be out // of range. if (BaseSize.has_value() && LowerBoundValue.has_value() && LengthValue.has_value() && llvm::APSInt::compareValues(AddAPSInt(*LowerBoundValue, *LengthValue), *BaseSize) > 0) { Diag(Base->getExprLoc(), diag::err_acc_subarray_base_plus_length_out_of_range) << toString(*LowerBoundValue, /*Radix=*/10) << toString(*LengthValue, /*Radix=*/10) << toString(*BaseSize, /*Radix=*/10); LowerBoundValue.reset(); LowerBound = GetRecovery(LowerBound, LowerBound->getType()); LengthValue.reset(); Length = GetRecovery(Length, Length->getType()); } // If any part of the expression is dependent, return a dependent sub-array. QualType ArrayExprTy = Context.ArraySectionTy; if (Base->isTypeDependent() || (LowerBound && LowerBound->isInstantiationDependent()) || (Length && Length->isInstantiationDependent())) ArrayExprTy = Context.DependentTy; return new (Context) ArraySectionExpr(Base, LowerBound, Length, ArrayExprTy, VK_LValue, OK_Ordinary, ColonLoc, RBLoc); } void SemaOpenACC::ActOnWhileStmt(SourceLocation WhileLoc) { if (!getLangOpts().OpenACC) return; if (!LoopInfo.TopLevelLoopSeen) return; if (CollapseInfo.CurCollapseCount && *CollapseInfo.CurCollapseCount > 0) { Diag(WhileLoc, diag::err_acc_invalid_in_loop) << /*while loop*/ 1 << CollapseInfo.DirectiveKind << OpenACCClauseKind::Collapse; assert(CollapseInfo.ActiveCollapse && "Collapse count without object?"); Diag(CollapseInfo.ActiveCollapse->getBeginLoc(), diag::note_acc_active_clause_here) << OpenACCClauseKind::Collapse; // Remove the value so that we don't get cascading errors in the body. The // caller RAII object will restore this. CollapseInfo.CurCollapseCount = std::nullopt; } if (TileInfo.CurTileCount && *TileInfo.CurTileCount > 0) { Diag(WhileLoc, diag::err_acc_invalid_in_loop) << /*while loop*/ 1 << TileInfo.DirectiveKind << OpenACCClauseKind::Tile; assert(TileInfo.ActiveTile && "tile count without object?"); Diag(TileInfo.ActiveTile->getBeginLoc(), diag::note_acc_active_clause_here) << OpenACCClauseKind::Tile; // Remove the value so that we don't get cascading errors in the body. The // caller RAII object will restore this. TileInfo.CurTileCount = std::nullopt; } } void SemaOpenACC::ActOnDoStmt(SourceLocation DoLoc) { if (!getLangOpts().OpenACC) return; if (!LoopInfo.TopLevelLoopSeen) return; if (CollapseInfo.CurCollapseCount && *CollapseInfo.CurCollapseCount > 0) { Diag(DoLoc, diag::err_acc_invalid_in_loop) << /*do loop*/ 2 << CollapseInfo.DirectiveKind << OpenACCClauseKind::Collapse; assert(CollapseInfo.ActiveCollapse && "Collapse count without object?"); Diag(CollapseInfo.ActiveCollapse->getBeginLoc(), diag::note_acc_active_clause_here) << OpenACCClauseKind::Collapse; // Remove the value so that we don't get cascading errors in the body. The // caller RAII object will restore this. CollapseInfo.CurCollapseCount = std::nullopt; } if (TileInfo.CurTileCount && *TileInfo.CurTileCount > 0) { Diag(DoLoc, diag::err_acc_invalid_in_loop) << /*do loop*/ 2 << TileInfo.DirectiveKind << OpenACCClauseKind::Tile; assert(TileInfo.ActiveTile && "tile count without object?"); Diag(TileInfo.ActiveTile->getBeginLoc(), diag::note_acc_active_clause_here) << OpenACCClauseKind::Tile; // Remove the value so that we don't get cascading errors in the body. The // caller RAII object will restore this. TileInfo.CurTileCount = std::nullopt; } } void SemaOpenACC::ForStmtBeginHelper(SourceLocation ForLoc, ForStmtBeginChecker &C) { assert(getLangOpts().OpenACC && "Check enabled when not OpenACC?"); // Enable the while/do-while checking. LoopInfo.TopLevelLoopSeen = true; if (CollapseInfo.CurCollapseCount && *CollapseInfo.CurCollapseCount > 0) { // Check the format of this loop if it is affected by the collapse. C.check(); // OpenACC 3.3 2.9.1: // Each associated loop, except the innermost, must contain exactly one loop // or loop nest. // This checks for more than 1 loop at the current level, the // 'depth'-satisifed checking manages the 'not zero' case. if (LoopInfo.CurLevelHasLoopAlready) { Diag(ForLoc, diag::err_acc_clause_multiple_loops) << CollapseInfo.DirectiveKind << OpenACCClauseKind::Collapse; assert(CollapseInfo.ActiveCollapse && "No collapse object?"); Diag(CollapseInfo.ActiveCollapse->getBeginLoc(), diag::note_acc_active_clause_here) << OpenACCClauseKind::Collapse; } else { --(*CollapseInfo.CurCollapseCount); // Once we've hit zero here, we know we have deep enough 'for' loops to // get to the bottom. if (*CollapseInfo.CurCollapseCount == 0) CollapseInfo.CollapseDepthSatisfied = true; } } if (TileInfo.CurTileCount && *TileInfo.CurTileCount > 0) { // Check the format of this loop if it is affected by the tile. C.check(); if (LoopInfo.CurLevelHasLoopAlready) { Diag(ForLoc, diag::err_acc_clause_multiple_loops) << TileInfo.DirectiveKind << OpenACCClauseKind::Tile; assert(TileInfo.ActiveTile && "No tile object?"); Diag(TileInfo.ActiveTile->getBeginLoc(), diag::note_acc_active_clause_here) << OpenACCClauseKind::Tile; } else { TileInfo.CurTileCount = *TileInfo.CurTileCount - 1; // Once we've hit zero here, we know we have deep enough 'for' loops to // get to the bottom. if (*TileInfo.CurTileCount == 0) TileInfo.TileDepthSatisfied = true; } } // Set this to 'false' for the body of this loop, so that the next level // checks independently. LoopInfo.CurLevelHasLoopAlready = false; } namespace { bool isValidLoopVariableType(QualType LoopVarTy) { // Just skip if it is dependent, it could be any of the below. if (LoopVarTy->isDependentType()) return true; // The loop variable must be of integer, if (LoopVarTy->isIntegerType()) return true; // C/C++ pointer, if (LoopVarTy->isPointerType()) return true; // or C++ random-access iterator type. if (const auto *RD = LoopVarTy->getAsCXXRecordDecl()) { // Note: Only do CXXRecordDecl because RecordDecl can't be a random access // iterator type! // We could either do a lot of work to see if this matches // random-access-iterator, but it seems that just checking that the // 'iterator_category' typedef is more than sufficient. If programmers are // willing to lie about this, we can let them. for (const auto *TD : llvm::make_filter_range(RD->decls(), llvm::IsaPred)) { const auto *TDND = cast(TD)->getCanonicalDecl(); if (TDND->getName() != "iterator_category") continue; // If there is no type for this decl, return false. if (TDND->getUnderlyingType().isNull()) return false; const CXXRecordDecl *ItrCategoryDecl = TDND->getUnderlyingType()->getAsCXXRecordDecl(); // If the category isn't a record decl, it isn't the tag type. if (!ItrCategoryDecl) return false; auto IsRandomAccessIteratorTag = [](const CXXRecordDecl *RD) { if (RD->getName() != "random_access_iterator_tag") return false; // Checks just for std::random_access_iterator_tag. return RD->getEnclosingNamespaceContext()->isStdNamespace(); }; if (IsRandomAccessIteratorTag(ItrCategoryDecl)) return true; // We can also support tag-types inherited from the // random_access_iterator_tag. for (CXXBaseSpecifier BS : ItrCategoryDecl->bases()) if (IsRandomAccessIteratorTag(BS.getType()->getAsCXXRecordDecl())) return true; return false; } } return false; } const ValueDecl *getDeclFromExpr(const Expr *E) { E = E->IgnoreParenImpCasts(); if (const auto *FE = dyn_cast(E)) E = FE->getSubExpr(); E = E->IgnoreParenImpCasts(); if (!E) return nullptr; if (const auto *DRE = dyn_cast(E)) return dyn_cast(DRE->getDecl()); if (const auto *ME = dyn_cast(E)) if (isa(ME->getBase()->IgnoreParenImpCasts())) return ME->getMemberDecl(); return nullptr; } } // namespace void SemaOpenACC::ForStmtBeginChecker::checkRangeFor() { const RangeForInfo &RFI = std::get(Info); // If this hasn't changed since last instantiated we're done. if (RFI.Uninstantiated == RFI.CurrentVersion) return; const DeclStmt *UninstRangeStmt = IsInstantiation ? RFI.Uninstantiated->getBeginStmt() : nullptr; const DeclStmt *RangeStmt = RFI.CurrentVersion->getBeginStmt(); // If this isn't the first time we've checked this loop, suppress any cases // where we previously diagnosed. if (UninstRangeStmt) { const ValueDecl *InitVar = cast(UninstRangeStmt->getSingleDecl()); QualType VarType = InitVar->getType().getNonReferenceType(); if (!isValidLoopVariableType(VarType)) return; } // In some dependent contexts, the autogenerated range statement doesn't get // included until instantiation, so skip for now. if (RangeStmt) { const ValueDecl *InitVar = cast(RangeStmt->getSingleDecl()); QualType VarType = InitVar->getType().getNonReferenceType(); if (!isValidLoopVariableType(VarType)) { SemaRef.Diag(InitVar->getBeginLoc(), diag::err_acc_loop_variable_type) << SemaRef.LoopWithoutSeqInfo.Kind << VarType; SemaRef.Diag(SemaRef.LoopWithoutSeqInfo.Loc, diag::note_acc_construct_here) << SemaRef.LoopWithoutSeqInfo.Kind; return; } } } bool SemaOpenACC::ForStmtBeginChecker::checkForInit(const Stmt *InitStmt, const ValueDecl *&InitVar, bool Diag) { // Init statement is required. if (!InitStmt) { if (Diag) { SemaRef.Diag(ForLoc, diag::err_acc_loop_variable) << SemaRef.LoopWithoutSeqInfo.Kind; SemaRef.Diag(SemaRef.LoopWithoutSeqInfo.Loc, diag::note_acc_construct_here) << SemaRef.LoopWithoutSeqInfo.Kind; } return true; } auto DiagLoopVar = [this, Diag, InitStmt]() { if (Diag) { SemaRef.Diag(InitStmt->getBeginLoc(), diag::err_acc_loop_variable) << SemaRef.LoopWithoutSeqInfo.Kind; SemaRef.Diag(SemaRef.LoopWithoutSeqInfo.Loc, diag::note_acc_construct_here) << SemaRef.LoopWithoutSeqInfo.Kind; } return true; }; if (const auto *ExprTemp = dyn_cast(InitStmt)) InitStmt = ExprTemp->getSubExpr(); if (const auto *E = dyn_cast(InitStmt)) InitStmt = E->IgnoreParenImpCasts(); InitVar = nullptr; if (const auto *BO = dyn_cast(InitStmt)) { // Allow assignment operator here. if (!BO->isAssignmentOp()) return DiagLoopVar(); const Expr *LHS = BO->getLHS()->IgnoreParenImpCasts(); if (const auto *DRE = dyn_cast(LHS)) InitVar = DRE->getDecl(); } else if (const auto *DS = dyn_cast(InitStmt)) { // Allow T t = if (!DS->isSingleDecl()) return DiagLoopVar(); InitVar = dyn_cast(DS->getSingleDecl()); // Ensure we have an initializer, unless this is a record/dependent type. if (InitVar) { if (!isa(InitVar)) return DiagLoopVar(); if (!InitVar->getType()->isRecordType() && !InitVar->getType()->isDependentType() && !cast(InitVar)->hasInit()) return DiagLoopVar(); } } else if (auto *CE = dyn_cast(InitStmt)) { // Allow assignment operator call. if (CE->getOperator() != OO_Equal) return DiagLoopVar(); const Expr *LHS = CE->getArg(0)->IgnoreParenImpCasts(); if (auto *DRE = dyn_cast(LHS)) { InitVar = DRE->getDecl(); } else if (auto *ME = dyn_cast(LHS)) { if (isa(ME->getBase()->IgnoreParenImpCasts())) InitVar = ME->getMemberDecl(); } } // If after all of that, we haven't found a variable, give up. if (!InitVar) return DiagLoopVar(); InitVar = cast(InitVar->getCanonicalDecl()); QualType VarType = InitVar->getType().getNonReferenceType(); // Since we have one, all we need to do is ensure it is the right type. if (!isValidLoopVariableType(VarType)) { if (Diag) { SemaRef.Diag(InitVar->getBeginLoc(), diag::err_acc_loop_variable_type) << SemaRef.LoopWithoutSeqInfo.Kind << VarType; SemaRef.Diag(SemaRef.LoopWithoutSeqInfo.Loc, diag::note_acc_construct_here) << SemaRef.LoopWithoutSeqInfo.Kind; } return true; } return false; } bool SemaOpenACC::ForStmtBeginChecker::checkForCond(const Stmt *CondStmt, const ValueDecl *InitVar, bool Diag) { // A condition statement is required. if (!CondStmt) { if (Diag) { SemaRef.Diag(ForLoc, diag::err_acc_loop_terminating_condition) << SemaRef.LoopWithoutSeqInfo.Kind; SemaRef.Diag(SemaRef.LoopWithoutSeqInfo.Loc, diag::note_acc_construct_here) << SemaRef.LoopWithoutSeqInfo.Kind; } return true; } auto DiagCondVar = [this, Diag, CondStmt] { if (Diag) { SemaRef.Diag(CondStmt->getBeginLoc(), diag::err_acc_loop_terminating_condition) << SemaRef.LoopWithoutSeqInfo.Kind; SemaRef.Diag(SemaRef.LoopWithoutSeqInfo.Loc, diag::note_acc_construct_here) << SemaRef.LoopWithoutSeqInfo.Kind; } return true; }; if (const auto *ExprTemp = dyn_cast(CondStmt)) CondStmt = ExprTemp->getSubExpr(); if (const auto *E = dyn_cast(CondStmt)) CondStmt = E->IgnoreParenImpCasts(); const ValueDecl *CondVar = nullptr; if (const auto *BO = dyn_cast(CondStmt)) { switch (BO->getOpcode()) { default: return DiagCondVar(); case BO_EQ: case BO_LT: case BO_GT: case BO_NE: case BO_LE: case BO_GE: break; } // Assign the condition-var to the LHS. If it either comes back null, or // the LHS doesn't match the InitVar, assign it to the RHS so that 5 < N is // allowed. CondVar = getDeclFromExpr(BO->getLHS()); if (!CondVar || (InitVar && CondVar->getCanonicalDecl() != InitVar->getCanonicalDecl())) CondVar = getDeclFromExpr(BO->getRHS()); } else if (const auto *CE = dyn_cast(CondStmt)) { // Any of the comparison ops should be ok here, but we don't know how to // handle spaceship, so disallow for now. if (!CE->isComparisonOp() || CE->getOperator() == OO_Spaceship) return DiagCondVar(); // Same logic here: Assign it to the LHS, unless the LHS comes back null or // not equal to the init var. CondVar = getDeclFromExpr(CE->getArg(0)); if (!CondVar || (InitVar && CondVar->getCanonicalDecl() != InitVar->getCanonicalDecl() && CE->getNumArgs() > 1)) CondVar = getDeclFromExpr(CE->getArg(1)); } else { return DiagCondVar(); } if (!CondVar) return DiagCondVar(); // Don't consider this an error unless the init variable was properly set, // else check to make sure they are the same variable. if (InitVar && CondVar->getCanonicalDecl() != InitVar->getCanonicalDecl()) return DiagCondVar(); return false; } namespace { // Helper to check the RHS of an assignment during for's step. We can allow // InitVar = InitVar + N, InitVar = N + InitVar, and Initvar = Initvar - N, // where N is an integer. bool isValidForIncRHSAssign(const ValueDecl *InitVar, const Expr *RHS) { auto isValid = [](const ValueDecl *InitVar, const Expr *InnerLHS, const Expr *InnerRHS, bool IsAddition) { // ONE of the sides has to be an integer type. if (!InnerLHS->getType()->isIntegerType() && !InnerRHS->getType()->isIntegerType()) return false; // If the init var is already an error, don't bother trying to check for // it. if (!InitVar) return true; const ValueDecl *LHSDecl = getDeclFromExpr(InnerLHS); const ValueDecl *RHSDecl = getDeclFromExpr(InnerRHS); // If we can't get a declaration, this is probably an error, so give up. if (!LHSDecl || !RHSDecl) return true; // If the LHS is the InitVar, the other must be int, so this is valid. if (LHSDecl->getCanonicalDecl() == InitVar->getCanonicalDecl()) return true; // Subtraction doesn't allow the RHS to be init var, so this is invalid. if (!IsAddition) return false; return RHSDecl->getCanonicalDecl() == InitVar->getCanonicalDecl(); }; if (const auto *BO = dyn_cast(RHS)) { BinaryOperatorKind OpC = BO->getOpcode(); if (OpC != BO_Add && OpC != BO_Sub) return false; return isValid(InitVar, BO->getLHS(), BO->getRHS(), OpC == BO_Add); } else if (const auto *CE = dyn_cast(RHS)) { OverloadedOperatorKind Op = CE->getOperator(); if (Op != OO_Plus && Op != OO_Minus) return false; return isValid(InitVar, CE->getArg(0), CE->getArg(1), Op == OO_Plus); } return false; } } // namespace bool SemaOpenACC::ForStmtBeginChecker::checkForInc(const Stmt *IncStmt, const ValueDecl *InitVar, bool Diag) { if (!IncStmt) { if (Diag) { SemaRef.Diag(ForLoc, diag::err_acc_loop_not_monotonic) << SemaRef.LoopWithoutSeqInfo.Kind; SemaRef.Diag(SemaRef.LoopWithoutSeqInfo.Loc, diag::note_acc_construct_here) << SemaRef.LoopWithoutSeqInfo.Kind; } return true; } auto DiagIncVar = [this, Diag, IncStmt] { if (Diag) { SemaRef.Diag(IncStmt->getBeginLoc(), diag::err_acc_loop_not_monotonic) << SemaRef.LoopWithoutSeqInfo.Kind; SemaRef.Diag(SemaRef.LoopWithoutSeqInfo.Loc, diag::note_acc_construct_here) << SemaRef.LoopWithoutSeqInfo.Kind; } return true; }; if (const auto *ExprTemp = dyn_cast(IncStmt)) IncStmt = ExprTemp->getSubExpr(); if (const auto *E = dyn_cast(IncStmt)) IncStmt = E->IgnoreParenImpCasts(); const ValueDecl *IncVar = nullptr; // Here we enforce the monotonically increase/decrease: if (const auto *UO = dyn_cast(IncStmt)) { // Allow increment/decrement ops. if (!UO->isIncrementDecrementOp()) return DiagIncVar(); IncVar = getDeclFromExpr(UO->getSubExpr()); } else if (const auto *BO = dyn_cast(IncStmt)) { switch (BO->getOpcode()) { default: return DiagIncVar(); case BO_AddAssign: case BO_SubAssign: break; case BO_Assign: // For assignment we also allow InitVar = InitVar + N, InitVar = N + // InitVar, and InitVar = InitVar - N; BUT only if 'N' is integral. if (!isValidForIncRHSAssign(InitVar, BO->getRHS())) return DiagIncVar(); break; } IncVar = getDeclFromExpr(BO->getLHS()); } else if (const auto *CE = dyn_cast(IncStmt)) { switch (CE->getOperator()) { default: return DiagIncVar(); case OO_PlusPlus: case OO_MinusMinus: case OO_PlusEqual: case OO_MinusEqual: break; case OO_Equal: // For assignment we also allow InitVar = InitVar + N, InitVar = N + // InitVar, and InitVar = InitVar - N; BUT only if 'N' is integral. if (!isValidForIncRHSAssign(InitVar, CE->getArg(1))) return DiagIncVar(); break; } IncVar = getDeclFromExpr(CE->getArg(0)); } else { return DiagIncVar(); } if (!IncVar) return DiagIncVar(); // InitVar shouldn't be null unless there was an error, so don't diagnose if // that is the case. Else we should ensure that it refers to the loop // value. if (InitVar && IncVar->getCanonicalDecl() != InitVar->getCanonicalDecl()) return DiagIncVar(); return false; } void SemaOpenACC::ForStmtBeginChecker::checkFor() { const CheckForInfo &CFI = std::get(Info); if (!IsInstantiation) { // If this isn't an instantiation, we can just check all of these and // diagnose. const ValueDecl *CurInitVar = nullptr; checkForInit(CFI.Current.Init, CurInitVar, /*Diag=*/true); checkForCond(CFI.Current.Condition, CurInitVar, /*Diag=*/true); checkForInc(CFI.Current.Increment, CurInitVar, /*DIag=*/true); } else { const ValueDecl *UninstInitVar = nullptr; // Checking the 'init' section first. We have to always run both versions, // at minimum with the 'diag' off, so that we can ensure we get the correct // instantiation var for checking by later ones. bool UninstInitFailed = checkForInit(CFI.Uninst.Init, UninstInitVar, /*Diag=*/false); // VarDecls are always rebuild because they are dependent, so we can do a // little work to suppress some of the double checking based on whether the // type is instantiation dependent. This is imperfect, but will get us most // cases suppressed. Currently this only handles the 'T t =' case. auto InitChanged = [=]() { if (CFI.Uninst.Init == CFI.Current.Init) return false; QualType OldVDTy; QualType NewVDTy; if (const auto *DS = dyn_cast(CFI.Uninst.Init)) if (const VarDecl *VD = dyn_cast_if_present( DS->isSingleDecl() ? DS->getSingleDecl() : nullptr)) OldVDTy = VD->getType(); if (const auto *DS = dyn_cast(CFI.Current.Init)) if (const VarDecl *VD = dyn_cast_if_present( DS->isSingleDecl() ? DS->getSingleDecl() : nullptr)) NewVDTy = VD->getType(); if (OldVDTy.isNull() || NewVDTy.isNull()) return true; return OldVDTy->isInstantiationDependentType() != NewVDTy->isInstantiationDependentType(); }; // Only diagnose the new 'init' if the previous version didn't fail, AND the // current init changed meaningfully. bool ShouldDiagNewInit = !UninstInitFailed && InitChanged(); const ValueDecl *CurInitVar = nullptr; checkForInit(CFI.Current.Init, CurInitVar, /*Diag=*/ShouldDiagNewInit); // Check the condition and increment only if the previous version passed, // and this changed. if (CFI.Uninst.Condition != CFI.Current.Condition && !checkForCond(CFI.Uninst.Condition, UninstInitVar, /*Diag=*/false)) checkForCond(CFI.Current.Condition, CurInitVar, /*Diag=*/true); if (CFI.Uninst.Increment != CFI.Current.Increment && !checkForInc(CFI.Uninst.Increment, UninstInitVar, /*Diag=*/false)) checkForInc(CFI.Current.Increment, CurInitVar, /*Diag=*/true); } } void SemaOpenACC::ForStmtBeginChecker::check() { // If this isn't an active loop without a seq, immediately return, nothing to // check. if (SemaRef.LoopWithoutSeqInfo.Kind == OpenACCDirectiveKind::Invalid) return; // If we've already checked, because this is a 'top level' one (and asking // again because 'tile' and 'collapse' might apply), just return, nothing to // do here. if (AlreadyChecked) return; AlreadyChecked = true; // OpenACC3.3 2.1: // A loop associated with a loop construct that does not have a seq clause // must be written to meet all the following conditions: // - The loop variable must be of integer, C/C++ pointer, or C++ random-access // iterator type. // - The loop variable must monotonically increase or decrease in the // direction of its termination condition. // - The loop trip count must be computable in constant time when entering the // loop construct. // // For a C++ range-based for loop, the loop variable // identified by the above conditions is the internal iterator, such as a // pointer, that the compiler generates to iterate the range. it is not the // variable declared by the for loop. if (std::holds_alternative(Info)) return checkRangeFor(); return checkFor(); } void SemaOpenACC::ActOnForStmtBegin(SourceLocation ForLoc, const Stmt *OldFirst, const Stmt *First, const Stmt *OldSecond, const Stmt *Second, const Stmt *OldThird, const Stmt *Third) { if (!getLangOpts().OpenACC) return; ForStmtBeginChecker FSBC{*this, ForLoc, OldFirst, OldSecond, OldThird, First, Second, Third}; // Check if this is the top-level 'for' for a 'loop'. Else it will be checked // as a part of the helper if a tile/collapse applies. if (!LoopInfo.TopLevelLoopSeen) { FSBC.check(); } ForStmtBeginHelper(ForLoc, FSBC); } void SemaOpenACC::ActOnForStmtBegin(SourceLocation ForLoc, const Stmt *First, const Stmt *Second, const Stmt *Third) { if (!getLangOpts().OpenACC) return; ForStmtBeginChecker FSBC{*this, ForLoc, First, Second, Third}; // Check if this is the top-level 'for' for a 'loop'. Else it will be checked // as a part of the helper if a tile/collapse applies. if (!LoopInfo.TopLevelLoopSeen) FSBC.check(); ForStmtBeginHelper(ForLoc, FSBC); } void SemaOpenACC::ActOnRangeForStmtBegin(SourceLocation ForLoc, const Stmt *OldRangeFor, const Stmt *RangeFor) { if (!getLangOpts().OpenACC || OldRangeFor == nullptr || RangeFor == nullptr) return; ForStmtBeginChecker FSBC{*this, ForLoc, cast_if_present(OldRangeFor), cast_if_present(RangeFor)}; // Check if this is the top-level 'for' for a 'loop'. Else it will be checked // as a part of the helper if a tile/collapse applies. if (!LoopInfo.TopLevelLoopSeen) { FSBC.check(); } ForStmtBeginHelper(ForLoc, FSBC); } void SemaOpenACC::ActOnRangeForStmtBegin(SourceLocation ForLoc, const Stmt *RangeFor) { if (!getLangOpts().OpenACC || RangeFor == nullptr) return; ForStmtBeginChecker FSBC = {*this, ForLoc, cast_if_present(RangeFor)}; // Check if this is the top-level 'for' for a 'loop'. Else it will be checked // as a part of the helper if a tile/collapse applies. if (!LoopInfo.TopLevelLoopSeen) FSBC.check(); ForStmtBeginHelper(ForLoc, FSBC); } namespace { SourceLocation FindInterveningCodeInLoop(const Stmt *CurStmt) { // We should diagnose on anything except `CompoundStmt`, `NullStmt`, // `ForStmt`, `CXXForRangeStmt`, since those are legal, and `WhileStmt` and // `DoStmt`, as those are caught as a violation elsewhere. // For `CompoundStmt` we need to search inside of it. if (!CurStmt || isa( CurStmt)) return SourceLocation{}; // Any other construct is an error anyway, so it has already been diagnosed. if (isa(CurStmt)) return SourceLocation{}; // Search inside the compound statement, this allows for arbitrary nesting // of compound statements, as long as there isn't any code inside. if (const auto *CS = dyn_cast(CurStmt)) { for (const auto *ChildStmt : CS->children()) { SourceLocation ChildStmtLoc = FindInterveningCodeInLoop(ChildStmt); if (ChildStmtLoc.isValid()) return ChildStmtLoc; } // Empty/not invalid compound statements are legal. return SourceLocation{}; } return CurStmt->getBeginLoc(); } } // namespace void SemaOpenACC::ActOnForStmtEnd(SourceLocation ForLoc, StmtResult Body) { if (!getLangOpts().OpenACC) return; // Set this to 'true' so if we find another one at this level we can diagnose. LoopInfo.CurLevelHasLoopAlready = true; if (!Body.isUsable()) return; bool IsActiveCollapse = CollapseInfo.CurCollapseCount && *CollapseInfo.CurCollapseCount > 0 && !CollapseInfo.ActiveCollapse->hasForce(); bool IsActiveTile = TileInfo.CurTileCount && *TileInfo.CurTileCount > 0; if (IsActiveCollapse || IsActiveTile) { SourceLocation OtherStmtLoc = FindInterveningCodeInLoop(Body.get()); if (OtherStmtLoc.isValid() && IsActiveCollapse) { Diag(OtherStmtLoc, diag::err_acc_intervening_code) << OpenACCClauseKind::Collapse << CollapseInfo.DirectiveKind; Diag(CollapseInfo.ActiveCollapse->getBeginLoc(), diag::note_acc_active_clause_here) << OpenACCClauseKind::Collapse; } if (OtherStmtLoc.isValid() && IsActiveTile) { Diag(OtherStmtLoc, diag::err_acc_intervening_code) << OpenACCClauseKind::Tile << TileInfo.DirectiveKind; Diag(TileInfo.ActiveTile->getBeginLoc(), diag::note_acc_active_clause_here) << OpenACCClauseKind::Tile; } } } namespace { // Helper that should mirror ActOnRoutineName to get the FunctionDecl out for // magic-static checking. FunctionDecl *getFunctionFromRoutineName(Expr *RoutineName) { if (!RoutineName) return nullptr; RoutineName = RoutineName->IgnoreParenImpCasts(); if (isa(RoutineName)) { // There is nothing we can do here, this isn't a function we can count on. return nullptr; } else if (isa( RoutineName)) { // The lookup is dependent, so we'll have to figure this out later. return nullptr; } else if (auto *DRE = dyn_cast(RoutineName)) { ValueDecl *VD = DRE->getDecl(); if (auto *FD = dyn_cast(VD)) return FD; // Allow lambdas. if (auto *VarD = dyn_cast(VD)) { QualType VarDTy = VarD->getType(); if (!VarDTy.isNull()) { if (auto *RD = VarDTy->getAsCXXRecordDecl()) { if (RD->isGenericLambda()) return nullptr; if (RD->isLambda()) return RD->getLambdaCallOperator(); } else if (VarDTy->isDependentType()) { // We don't really know what this is going to be. return nullptr; } } return nullptr; } else if (isa(RoutineName)) { return nullptr; } } return nullptr; } } // namespace ExprResult SemaOpenACC::ActOnRoutineName(Expr *RoutineName) { assert(RoutineName && "Routine name cannot be null here"); RoutineName = RoutineName->IgnoreParenImpCasts(); if (isa(RoutineName)) { // This has already been diagnosed, so we can skip it. return ExprError(); } else if (isa( RoutineName)) { // These are dependent and we can't really check them, so delay until // instantiation. return RoutineName; } else if (const auto *DRE = dyn_cast(RoutineName)) { const ValueDecl *VD = DRE->getDecl(); if (isa(VD)) return RoutineName; // Allow lambdas. if (const auto *VarD = dyn_cast(VD)) { QualType VarDTy = VarD->getType(); if (!VarDTy.isNull()) { if (const auto *RD = VarDTy->getAsCXXRecordDecl()) { if (RD->isGenericLambda()) { Diag(RoutineName->getBeginLoc(), diag::err_acc_routine_overload_set) << RoutineName; return ExprError(); } if (RD->isLambda()) return RoutineName; } else if (VarDTy->isDependentType()) { // If this is a dependent variable, it might be a lambda. So we just // accept this and catch it next time. return RoutineName; } } } Diag(RoutineName->getBeginLoc(), diag::err_acc_routine_not_func) << RoutineName; return ExprError(); } else if (isa(RoutineName)) { // This happens in function templates, even when the template arguments are // fully specified. We could possibly do some sort of matching to make sure // that this is looked up/deduced, but GCC does not do this, so there // doesn't seem to be a good reason for us to do it either. Diag(RoutineName->getBeginLoc(), diag::err_acc_routine_overload_set) << RoutineName; return ExprError(); } Diag(RoutineName->getBeginLoc(), diag::err_acc_routine_not_func) << RoutineName; return ExprError(); } void SemaOpenACC::ActOnVariableDeclarator(VarDecl *VD) { if (!getLangOpts().OpenACC || VD->isInvalidDecl() || !VD->isStaticLocal()) return; // This cast should be safe, since a static-local can only happen in a // function declaration. auto *ContextDecl = cast(getCurContext()); // OpenACC 3.3 2.15: // In C and C++, function static variables are not supported in functions to // which a routine directive applies. for (const auto *A : ContextDecl->attrs()) { if (isa(A)) { Diag(VD->getBeginLoc(), diag::err_acc_magic_static_in_routine); Diag(A->getLocation(), diag::note_acc_construct_here) << OpenACCDirectiveKind::Routine; return; } } MagicStaticLocs.insert({ContextDecl->getCanonicalDecl(), VD->getBeginLoc()}); } void SemaOpenACC::CheckLastRoutineDeclNameConflict(const NamedDecl *ND) { // OpenACC 3.3 A.3.4 // When a procedure with that name is in scope and it is not the same // procedure as the immediately following procedure declaration or // definition, the resolution of the name can be confusing. Implementations // should then issue a compile-time warning diagnostic even though the // application is conforming. // If we haven't created one, also can't diagnose. if (!LastRoutineDecl) return; // If the currently created function doesn't have a name, we can't diagnose on // a match. if (!ND->getDeclName().isIdentifier()) return; // If the two are in different decl contexts, it doesn't make sense to // diagnose. if (LastRoutineDecl->getDeclContext() != ND->getLexicalDeclContext()) return; // If we don't have a referenced thing yet, we can't diagnose. FunctionDecl *RoutineTarget = getFunctionFromRoutineName(LastRoutineDecl->getFunctionReference()); if (!RoutineTarget) return; // If the Routine target doesn't have a name, we can't diagnose. if (!RoutineTarget->getDeclName().isIdentifier()) return; // Of course don't diagnose if the names don't match. if (ND->getName() != RoutineTarget->getName()) return; long NDLine = SemaRef.SourceMgr.getSpellingLineNumber(ND->getBeginLoc()); long LastLine = SemaRef.SourceMgr.getSpellingLineNumber(LastRoutineDecl->getBeginLoc()); // Do some line-number math to make sure they are within a line of eachother. // Comments or newlines can be inserted to clarify intent. if (NDLine - LastLine > 1) return; // Don't warn if it actually DOES apply to this function via redecls. if (ND->getCanonicalDecl() == RoutineTarget->getCanonicalDecl()) return; Diag(LastRoutineDecl->getFunctionReference()->getBeginLoc(), diag::warn_acc_confusing_routine_name); Diag(RoutineTarget->getBeginLoc(), diag::note_previous_decl) << ND; } void SemaOpenACC::ActOnVariableInit(VarDecl *VD, QualType InitType) { if (!VD || !getLangOpts().OpenACC || InitType.isNull()) return; // To avoid double-diagnostic, just diagnose this during instantiation. We'll // get 1 warning per instantiation, but this permits us to be more sensible // for cases where the lookup is confusing. if (VD->getLexicalDeclContext()->isDependentContext()) return; const auto *RD = InitType->getAsCXXRecordDecl(); // If this isn't a lambda, no sense in diagnosing. if (!RD || !RD->isLambda()) return; CheckLastRoutineDeclNameConflict(VD); } void SemaOpenACC::ActOnFunctionDeclarator(FunctionDecl *FD) { if (!FD || !getLangOpts().OpenACC) return; CheckLastRoutineDeclNameConflict(FD); } bool SemaOpenACC::ActOnStartStmtDirective( OpenACCDirectiveKind K, SourceLocation StartLoc, ArrayRef Clauses) { // Declaration directives an appear in a statement location, so call into that // function here. if (K == OpenACCDirectiveKind::Declare || K == OpenACCDirectiveKind::Routine) return ActOnStartDeclDirective(K, StartLoc, Clauses); SemaRef.DiscardCleanupsInEvaluationContext(); SemaRef.PopExpressionEvaluationContext(); // OpenACC 3.3 2.9.1: // Intervening code must not contain other OpenACC directives or calls to API // routines. // // ALL constructs are ill-formed if there is an active 'collapse' if (CollapseInfo.CurCollapseCount && *CollapseInfo.CurCollapseCount > 0) { Diag(StartLoc, diag::err_acc_invalid_in_loop) << /*OpenACC Construct*/ 0 << CollapseInfo.DirectiveKind << OpenACCClauseKind::Collapse << K; assert(CollapseInfo.ActiveCollapse && "Collapse count without object?"); Diag(CollapseInfo.ActiveCollapse->getBeginLoc(), diag::note_acc_active_clause_here) << OpenACCClauseKind::Collapse; } if (TileInfo.CurTileCount && *TileInfo.CurTileCount > 0) { Diag(StartLoc, diag::err_acc_invalid_in_loop) << /*OpenACC Construct*/ 0 << TileInfo.DirectiveKind << OpenACCClauseKind::Tile << K; assert(TileInfo.ActiveTile && "Tile count without object?"); Diag(TileInfo.ActiveTile->getBeginLoc(), diag::note_acc_active_clause_here) << OpenACCClauseKind::Tile; } if (DiagnoseRequiredClauses(K, StartLoc, Clauses)) return true; return diagnoseConstructAppertainment(*this, K, StartLoc, /*IsStmt=*/true); } StmtResult SemaOpenACC::ActOnEndStmtDirective( OpenACCDirectiveKind K, SourceLocation StartLoc, SourceLocation DirLoc, SourceLocation LParenLoc, SourceLocation MiscLoc, ArrayRef Exprs, OpenACCAtomicKind AtomicKind, SourceLocation RParenLoc, SourceLocation EndLoc, ArrayRef Clauses, StmtResult AssocStmt) { switch (K) { case OpenACCDirectiveKind::Invalid: return StmtError(); case OpenACCDirectiveKind::Parallel: case OpenACCDirectiveKind::Serial: case OpenACCDirectiveKind::Kernels: { return OpenACCComputeConstruct::Create( getASTContext(), K, StartLoc, DirLoc, EndLoc, Clauses, AssocStmt.isUsable() ? AssocStmt.get() : nullptr); } case OpenACCDirectiveKind::ParallelLoop: case OpenACCDirectiveKind::SerialLoop: case OpenACCDirectiveKind::KernelsLoop: { return OpenACCCombinedConstruct::Create( getASTContext(), K, StartLoc, DirLoc, EndLoc, Clauses, AssocStmt.isUsable() ? AssocStmt.get() : nullptr); } case OpenACCDirectiveKind::Loop: { return OpenACCLoopConstruct::Create( getASTContext(), ActiveComputeConstructInfo.Kind, StartLoc, DirLoc, EndLoc, Clauses, AssocStmt.isUsable() ? AssocStmt.get() : nullptr); } case OpenACCDirectiveKind::Data: { return OpenACCDataConstruct::Create( getASTContext(), StartLoc, DirLoc, EndLoc, Clauses, AssocStmt.isUsable() ? AssocStmt.get() : nullptr); } case OpenACCDirectiveKind::EnterData: { return OpenACCEnterDataConstruct::Create(getASTContext(), StartLoc, DirLoc, EndLoc, Clauses); } case OpenACCDirectiveKind::ExitData: { return OpenACCExitDataConstruct::Create(getASTContext(), StartLoc, DirLoc, EndLoc, Clauses); } case OpenACCDirectiveKind::HostData: { return OpenACCHostDataConstruct::Create( getASTContext(), StartLoc, DirLoc, EndLoc, Clauses, AssocStmt.isUsable() ? AssocStmt.get() : nullptr); } case OpenACCDirectiveKind::Wait: { return OpenACCWaitConstruct::Create( getASTContext(), StartLoc, DirLoc, LParenLoc, Exprs.front(), MiscLoc, Exprs.drop_front(), RParenLoc, EndLoc, Clauses); } case OpenACCDirectiveKind::Init: { return OpenACCInitConstruct::Create(getASTContext(), StartLoc, DirLoc, EndLoc, Clauses); } case OpenACCDirectiveKind::Shutdown: { return OpenACCShutdownConstruct::Create(getASTContext(), StartLoc, DirLoc, EndLoc, Clauses); } case OpenACCDirectiveKind::Set: { return OpenACCSetConstruct::Create(getASTContext(), StartLoc, DirLoc, EndLoc, Clauses); } case OpenACCDirectiveKind::Update: { return OpenACCUpdateConstruct::Create(getASTContext(), StartLoc, DirLoc, EndLoc, Clauses); } case OpenACCDirectiveKind::Atomic: { return OpenACCAtomicConstruct::Create( getASTContext(), StartLoc, DirLoc, AtomicKind, EndLoc, Clauses, AssocStmt.isUsable() ? AssocStmt.get() : nullptr); } case OpenACCDirectiveKind::Cache: { assert(Clauses.empty() && "Cache doesn't allow clauses"); return OpenACCCacheConstruct::Create(getASTContext(), StartLoc, DirLoc, LParenLoc, MiscLoc, Exprs, RParenLoc, EndLoc); } case OpenACCDirectiveKind::Routine: llvm_unreachable("routine shouldn't handled here"); case OpenACCDirectiveKind::Declare: { // Declare and routine arei declaration directives, but can be used here as // long as we wrap it in a DeclStmt. So make sure we do that here. DeclGroupRef DR = ActOnEndDeclDirective(K, StartLoc, DirLoc, LParenLoc, RParenLoc, EndLoc, Clauses); return SemaRef.ActOnDeclStmt(DeclGroupPtrTy::make(DR), StartLoc, EndLoc); } } llvm_unreachable("Unhandled case in directive handling?"); } StmtResult SemaOpenACC::ActOnAssociatedStmt( SourceLocation DirectiveLoc, OpenACCDirectiveKind K, OpenACCAtomicKind AtKind, ArrayRef Clauses, StmtResult AssocStmt) { switch (K) { default: llvm_unreachable("Unimplemented associated statement application"); case OpenACCDirectiveKind::EnterData: case OpenACCDirectiveKind::ExitData: case OpenACCDirectiveKind::Wait: case OpenACCDirectiveKind::Init: case OpenACCDirectiveKind::Shutdown: case OpenACCDirectiveKind::Set: case OpenACCDirectiveKind::Cache: llvm_unreachable( "these don't have associated statements, so shouldn't get here"); case OpenACCDirectiveKind::Atomic: return CheckAtomicAssociatedStmt(DirectiveLoc, AtKind, AssocStmt); case OpenACCDirectiveKind::Parallel: case OpenACCDirectiveKind::Serial: case OpenACCDirectiveKind::Kernels: case OpenACCDirectiveKind::Data: case OpenACCDirectiveKind::HostData: // There really isn't any checking here that could happen. As long as we // have a statement to associate, this should be fine. // OpenACC 3.3 Section 6: // Structured Block: in C or C++, an executable statement, possibly // compound, with a single entry at the top and a single exit at the // bottom. // FIXME: Should we reject DeclStmt's here? The standard isn't clear, and // an interpretation of it is to allow this and treat the initializer as // the 'structured block'. return AssocStmt; case OpenACCDirectiveKind::Loop: case OpenACCDirectiveKind::ParallelLoop: case OpenACCDirectiveKind::SerialLoop: case OpenACCDirectiveKind::KernelsLoop: if (!AssocStmt.isUsable()) return StmtError(); if (!isa(AssocStmt.get())) { Diag(AssocStmt.get()->getBeginLoc(), diag::err_acc_loop_not_for_loop) << K; Diag(DirectiveLoc, diag::note_acc_construct_here) << K; return StmtError(); } if (!CollapseInfo.CollapseDepthSatisfied || !TileInfo.TileDepthSatisfied) { if (!CollapseInfo.CollapseDepthSatisfied) { Diag(DirectiveLoc, diag::err_acc_insufficient_loops) << OpenACCClauseKind::Collapse; assert(CollapseInfo.ActiveCollapse && "Collapse count without object?"); Diag(CollapseInfo.ActiveCollapse->getBeginLoc(), diag::note_acc_active_clause_here) << OpenACCClauseKind::Collapse; } if (!TileInfo.TileDepthSatisfied) { Diag(DirectiveLoc, diag::err_acc_insufficient_loops) << OpenACCClauseKind::Tile; assert(TileInfo.ActiveTile && "Collapse count without object?"); Diag(TileInfo.ActiveTile->getBeginLoc(), diag::note_acc_active_clause_here) << OpenACCClauseKind::Tile; } return StmtError(); } return AssocStmt.get(); } llvm_unreachable("Invalid associated statement application"); } namespace { // Routine has some pretty complicated set of rules for how device_type // interacts with 'gang', 'worker', 'vector', and 'seq'. Enforce part of it // here. bool CheckValidRoutineGangWorkerVectorSeqClauses( SemaOpenACC &SemaRef, SourceLocation DirectiveLoc, ArrayRef Clauses) { auto RequiredPred = llvm::IsaPred; // The clause handling has assured us that there is no duplicates. That is, // if there is 1 before a device_type, there are none after a device_type. // If not, there is at most 1 applying to each device_type. // What is left to legalize is that either: // 1- there is 1 before the first device_type. // 2- there is 1 AFTER each device_type. auto *FirstDeviceType = llvm::find_if(Clauses, llvm::IsaPred); // If there is 1 before the first device_type (or at all if no device_type), // we are legal. auto *ClauseItr = std::find_if(Clauses.begin(), FirstDeviceType, RequiredPred); if (ClauseItr != FirstDeviceType) return false; // If there IS no device_type, and no clause, diagnose. if (FirstDeviceType == Clauses.end()) return SemaRef.Diag(DirectiveLoc, diag::err_acc_construct_one_clause_of) << OpenACCDirectiveKind::Routine << "'gang', 'seq', 'vector', or 'worker'"; // Else, we have to check EACH device_type group. PrevDeviceType is the // device-type before the current group. auto *PrevDeviceType = FirstDeviceType; while (PrevDeviceType != Clauses.end()) { auto *NextDeviceType = std::find_if(std::next(PrevDeviceType), Clauses.end(), llvm::IsaPred); ClauseItr = std::find_if(PrevDeviceType, NextDeviceType, RequiredPred); if (ClauseItr == NextDeviceType) return SemaRef.Diag((*PrevDeviceType)->getBeginLoc(), diag::err_acc_clause_routine_one_of_in_region); PrevDeviceType = NextDeviceType; } return false; } } // namespace bool SemaOpenACC::ActOnStartDeclDirective( OpenACCDirectiveKind K, SourceLocation StartLoc, ArrayRef Clauses) { // OpenCC3.3 2.1 (line 889) // A program must not depend on the order of evaluation of expressions in // clause arguments or on any side effects of the evaluations. SemaRef.DiscardCleanupsInEvaluationContext(); SemaRef.PopExpressionEvaluationContext(); if (DiagnoseRequiredClauses(K, StartLoc, Clauses)) return true; if (K == OpenACCDirectiveKind::Routine && CheckValidRoutineGangWorkerVectorSeqClauses(*this, StartLoc, Clauses)) return true; return diagnoseConstructAppertainment(*this, K, StartLoc, /*IsStmt=*/false); } DeclGroupRef SemaOpenACC::ActOnEndDeclDirective( OpenACCDirectiveKind K, SourceLocation StartLoc, SourceLocation DirLoc, SourceLocation LParenLoc, SourceLocation RParenLoc, SourceLocation EndLoc, ArrayRef Clauses) { switch (K) { default: case OpenACCDirectiveKind::Invalid: return DeclGroupRef{}; case OpenACCDirectiveKind::Declare: { // OpenACC3.3 2.13: At least one clause must appear on a declare directive. if (Clauses.empty()) { Diag(EndLoc, diag::err_acc_declare_required_clauses); // No reason to add this to the AST, as we would just end up trying to // instantiate this, which would double-diagnose here, which we wouldn't // want to do. return DeclGroupRef{}; } auto *DeclareDecl = OpenACCDeclareDecl::Create( getASTContext(), getCurContext(), StartLoc, DirLoc, EndLoc, Clauses); DeclareDecl->setAccess(AS_public); getCurContext()->addDecl(DeclareDecl); return DeclGroupRef{DeclareDecl}; } case OpenACCDirectiveKind::Routine: llvm_unreachable("routine shouldn't be handled here"); } llvm_unreachable("unhandled case in directive handling?"); } namespace { // Given the decl on the next line, figure out if it is one that is acceptable // to `routine`, or looks like the sort of decl we should be diagnosing against. FunctionDecl *LegalizeNextParsedDecl(Decl *D) { if (!D) return nullptr; // Functions are per-fact acceptable as-is. if (auto *FD = dyn_cast(D)) return FD; // Function templates are functions, so attach to the templated decl. if (auto *FTD = dyn_cast(D)) return FTD->getTemplatedDecl(); if (auto *FD = dyn_cast(D)) { auto *RD = FD->getType().isNull() ? nullptr : FD->getType()->getAsCXXRecordDecl(); if (RD && RD->isGenericLambda()) return RD->getDependentLambdaCallOperator()->getTemplatedDecl(); if (RD && RD->isLambda()) return RD->getLambdaCallOperator(); } // VarDecl we can look at the init instead of the type of the variable, this // makes us more tolerant of the 'auto' deduced type. if (auto *VD = dyn_cast(D)) { Expr *Init = VD->getInit(); if (!Init || Init->getType().isNull()) return nullptr; const auto *RD = Init->getType()->getAsCXXRecordDecl(); if (RD && RD->isGenericLambda()) return RD->getDependentLambdaCallOperator()->getTemplatedDecl(); if (RD && RD->isLambda()) return RD->getLambdaCallOperator(); // FIXME: We could try harder in the case where this is a dependent thing // that ends up being a lambda (that is, the init is an unresolved lookup // expr), but we can't attach to the call/lookup expr. If we instead try to // attach to the VarDecl, when we go to instantiate it, attributes are // instantiated before the init, so we can't actually see the type at any // point where it would be relevant/able to be checked. We could perhaps do // some sort of 'after-init' instantiation/checking here, but that doesn't // seem valuable for a situation that other compilers don't handle. } return nullptr; } void CreateRoutineDeclAttr(SemaOpenACC &SemaRef, SourceLocation DirLoc, ArrayRef Clauses, ValueDecl *AddTo) { OpenACCRoutineDeclAttr *A = OpenACCRoutineDeclAttr::Create(SemaRef.getASTContext(), DirLoc); A->Clauses.assign(Clauses.begin(), Clauses.end()); AddTo->addAttr(A); } } // namespace // Variant that adds attributes, because this is the unnamed case. void SemaOpenACC::CheckRoutineDecl(SourceLocation DirLoc, ArrayRef Clauses, Decl *NextParsedDecl) { FunctionDecl *NextParsedFDecl = LegalizeNextParsedDecl(NextParsedDecl); if (!NextParsedFDecl) { // If we don't have a valid 'next thing', just diagnose. SemaRef.Diag(DirLoc, diag::err_acc_decl_for_routine); return; } // OpenACC 3.3 2.15: // In C and C++, function static variables are not supported in functions to // which a routine directive applies. if (auto Itr = MagicStaticLocs.find(NextParsedFDecl->getCanonicalDecl()); Itr != MagicStaticLocs.end()) { Diag(Itr->second, diag::err_acc_magic_static_in_routine); Diag(DirLoc, diag::note_acc_construct_here) << OpenACCDirectiveKind::Routine; return; } auto BindItr = llvm::find_if(Clauses, llvm::IsaPred); for (auto *A : NextParsedFDecl->attrs()) { // OpenACC 3.3 2.15: // If a procedure has a bind clause on both the declaration and definition // than they both must bind to the same name. if (auto *RA = dyn_cast(A)) { auto OtherBindItr = llvm::find_if(RA->Clauses, llvm::IsaPred); if (OtherBindItr != RA->Clauses.end() && (*cast(*BindItr)) != (*cast(*OtherBindItr))) { Diag((*BindItr)->getBeginLoc(), diag::err_acc_duplicate_unnamed_bind); Diag((*OtherBindItr)->getEndLoc(), diag::note_acc_previous_clause_here) << (*BindItr)->getClauseKind(); return; } } // OpenACC 3.3 2.15: // A bind clause may not bind to a routine name that has a visible bind // clause. // We take the combo of these two 2.15 restrictions to mean that the // 'declaration'/'definition' quote is an exception to this. So we're going // to disallow mixing of the two types entirely. if (auto *RA = dyn_cast(A); RA && RA->getRange().getEnd().isValid()) { Diag((*BindItr)->getBeginLoc(), diag::err_acc_duplicate_bind); Diag(RA->getRange().getEnd(), diag::note_acc_previous_clause_here) << "bind"; return; } } CreateRoutineDeclAttr(*this, DirLoc, Clauses, NextParsedFDecl); } // Variant that adds a decl, because this is the named case. OpenACCRoutineDecl *SemaOpenACC::CheckRoutineDecl( SourceLocation StartLoc, SourceLocation DirLoc, SourceLocation LParenLoc, Expr *FuncRef, SourceLocation RParenLoc, ArrayRef Clauses, SourceLocation EndLoc) { assert(LParenLoc.isValid()); if (FunctionDecl *FD = getFunctionFromRoutineName(FuncRef)) { // OpenACC 3.3 2.15: // In C and C++, function static variables are not supported in functions to // which a routine directive applies. if (auto Itr = MagicStaticLocs.find(FD->getCanonicalDecl()); Itr != MagicStaticLocs.end()) { Diag(Itr->second, diag::err_acc_magic_static_in_routine); Diag(DirLoc, diag::note_acc_construct_here) << OpenACCDirectiveKind::Routine; return nullptr; } // OpenACC 3.3 2.15: // A bind clause may not bind to a routine name that has a visible bind // clause. auto BindItr = llvm::find_if(Clauses, llvm::IsaPred); SourceLocation BindLoc; if (BindItr != Clauses.end()) { BindLoc = (*BindItr)->getBeginLoc(); // Since this is adding a 'named' routine, we aren't allowed to combine // with ANY other visible bind clause. Error if we see either. for (auto *A : FD->attrs()) { if (auto *RA = dyn_cast(A)) { auto OtherBindItr = llvm::find_if(RA->Clauses, llvm::IsaPred); if (OtherBindItr != RA->Clauses.end()) { Diag((*BindItr)->getBeginLoc(), diag::err_acc_duplicate_bind); Diag((*OtherBindItr)->getEndLoc(), diag::note_acc_previous_clause_here) << (*BindItr)->getClauseKind(); return nullptr; } } if (auto *RA = dyn_cast(A); RA && RA->getRange().getEnd().isValid()) { Diag((*BindItr)->getBeginLoc(), diag::err_acc_duplicate_bind); Diag(RA->getRange().getEnd(), diag::note_acc_previous_clause_here) << (*BindItr)->getClauseKind(); return nullptr; } } } // Set the end-range to the 'bind' clause here, so we can look it up // later. auto *RAA = OpenACCRoutineAnnotAttr::CreateImplicit(getASTContext(), {DirLoc, BindLoc}); FD->addAttr(RAA); // In case we are referencing not the 'latest' version, make sure we add // the attribute to all declarations. while (FD != FD->getMostRecentDecl()) { FD = FD->getMostRecentDecl(); FD->addAttr(RAA); } } LastRoutineDecl = OpenACCRoutineDecl::Create( getASTContext(), getCurContext(), StartLoc, DirLoc, LParenLoc, FuncRef, RParenLoc, EndLoc, Clauses); LastRoutineDecl->setAccess(AS_public); getCurContext()->addDecl(LastRoutineDecl); return LastRoutineDecl; } DeclGroupRef SemaOpenACC::ActOnEndRoutineDeclDirective( SourceLocation StartLoc, SourceLocation DirLoc, SourceLocation LParenLoc, Expr *ReferencedFunc, SourceLocation RParenLoc, ArrayRef Clauses, SourceLocation EndLoc, DeclGroupPtrTy NextDecl) { assert((!ReferencedFunc || !NextDecl) && "Only one of these should be filled"); if (LParenLoc.isInvalid()) { Decl *NextLineDecl = nullptr; if (NextDecl && NextDecl.get().isSingleDecl()) NextLineDecl = NextDecl.get().getSingleDecl(); CheckRoutineDecl(DirLoc, Clauses, NextLineDecl); return NextDecl.get(); } return DeclGroupRef{CheckRoutineDecl( StartLoc, DirLoc, LParenLoc, ReferencedFunc, RParenLoc, Clauses, EndLoc)}; } StmtResult SemaOpenACC::ActOnEndRoutineStmtDirective( SourceLocation StartLoc, SourceLocation DirLoc, SourceLocation LParenLoc, Expr *ReferencedFunc, SourceLocation RParenLoc, ArrayRef Clauses, SourceLocation EndLoc, Stmt *NextStmt) { assert((!ReferencedFunc || !NextStmt) && "Only one of these should be filled"); if (LParenLoc.isInvalid()) { Decl *NextLineDecl = nullptr; if (NextStmt) if (DeclStmt *DS = dyn_cast(NextStmt); DS && DS->isSingleDecl()) NextLineDecl = DS->getSingleDecl(); CheckRoutineDecl(DirLoc, Clauses, NextLineDecl); return NextStmt; } DeclGroupRef DR{CheckRoutineDecl(StartLoc, DirLoc, LParenLoc, ReferencedFunc, RParenLoc, Clauses, EndLoc)}; return SemaRef.ActOnDeclStmt(DeclGroupPtrTy::make(DR), StartLoc, EndLoc); } OpenACCRoutineDeclAttr * SemaOpenACC::mergeRoutineDeclAttr(const OpenACCRoutineDeclAttr &Old) { OpenACCRoutineDeclAttr *New = OpenACCRoutineDeclAttr::Create(getASTContext(), Old.getLocation()); // We should jsut be able to copy these, there isn't really any // merging/inheriting we have to do, so no worry about doing a deep copy. New->Clauses = Old.Clauses; return New; } ExprResult SemaOpenACC::BuildOpenACCAsteriskSizeExpr(SourceLocation AsteriskLoc) { return OpenACCAsteriskSizeExpr::Create(getASTContext(), AsteriskLoc); } ExprResult SemaOpenACC::ActOnOpenACCAsteriskSizeExpr(SourceLocation AsteriskLoc) { return BuildOpenACCAsteriskSizeExpr(AsteriskLoc); }