//===--- StmtOpenACC.cpp - Classes 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 // //===----------------------------------------------------------------------===// // // This file implements the subclasses of Stmt class declared in StmtOpenACC.h // //===----------------------------------------------------------------------===// #include "clang/AST/StmtOpenACC.h" #include "clang/AST/ASTContext.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/StmtCXX.h" using namespace clang; OpenACCComputeConstruct * OpenACCComputeConstruct::CreateEmpty(const ASTContext &C, unsigned NumClauses) { void *Mem = C.Allocate( OpenACCComputeConstruct::totalSizeToAlloc( NumClauses)); auto *Inst = new (Mem) OpenACCComputeConstruct(NumClauses); return Inst; } OpenACCComputeConstruct *OpenACCComputeConstruct::Create( const ASTContext &C, OpenACCDirectiveKind K, SourceLocation BeginLoc, SourceLocation DirLoc, SourceLocation EndLoc, ArrayRef Clauses, Stmt *StructuredBlock) { void *Mem = C.Allocate( OpenACCComputeConstruct::totalSizeToAlloc( Clauses.size())); auto *Inst = new (Mem) OpenACCComputeConstruct(K, BeginLoc, DirLoc, EndLoc, Clauses, StructuredBlock); return Inst; } OpenACCLoopConstruct::OpenACCLoopConstruct(unsigned NumClauses) : OpenACCAssociatedStmtConstruct( OpenACCLoopConstructClass, OpenACCDirectiveKind::Loop, SourceLocation{}, SourceLocation{}, SourceLocation{}, /*AssociatedStmt=*/nullptr) { std::uninitialized_value_construct_n(getTrailingObjects(), NumClauses); setClauseList(getTrailingObjects(NumClauses)); } OpenACCLoopConstruct::OpenACCLoopConstruct( OpenACCDirectiveKind ParentKind, SourceLocation Start, SourceLocation DirLoc, SourceLocation End, ArrayRef Clauses, Stmt *Loop) : OpenACCAssociatedStmtConstruct(OpenACCLoopConstructClass, OpenACCDirectiveKind::Loop, Start, DirLoc, End, Loop), ParentComputeConstructKind(ParentKind) { // accept 'nullptr' for the loop. This is diagnosed somewhere, but this gives // us some level of AST fidelity in the error case. assert((Loop == nullptr || isa(Loop)) && "Associated Loop not a for loop?"); // Initialize the trailing storage. llvm::uninitialized_copy(Clauses, getTrailingObjects()); setClauseList(getTrailingObjects(Clauses.size())); } OpenACCLoopConstruct *OpenACCLoopConstruct::CreateEmpty(const ASTContext &C, unsigned NumClauses) { void *Mem = C.Allocate(OpenACCLoopConstruct::totalSizeToAlloc( NumClauses)); auto *Inst = new (Mem) OpenACCLoopConstruct(NumClauses); return Inst; } OpenACCLoopConstruct *OpenACCLoopConstruct::Create( const ASTContext &C, OpenACCDirectiveKind ParentKind, SourceLocation BeginLoc, SourceLocation DirLoc, SourceLocation EndLoc, ArrayRef Clauses, Stmt *Loop) { void *Mem = C.Allocate(OpenACCLoopConstruct::totalSizeToAlloc( Clauses.size())); auto *Inst = new (Mem) OpenACCLoopConstruct(ParentKind, BeginLoc, DirLoc, EndLoc, Clauses, Loop); return Inst; } OpenACCCombinedConstruct * OpenACCCombinedConstruct::CreateEmpty(const ASTContext &C, unsigned NumClauses) { void *Mem = C.Allocate( OpenACCCombinedConstruct::totalSizeToAlloc( NumClauses)); auto *Inst = new (Mem) OpenACCCombinedConstruct(NumClauses); return Inst; } OpenACCCombinedConstruct *OpenACCCombinedConstruct::Create( const ASTContext &C, OpenACCDirectiveKind DK, SourceLocation BeginLoc, SourceLocation DirLoc, SourceLocation EndLoc, ArrayRef Clauses, Stmt *Loop) { void *Mem = C.Allocate( OpenACCCombinedConstruct::totalSizeToAlloc( Clauses.size())); auto *Inst = new (Mem) OpenACCCombinedConstruct(DK, BeginLoc, DirLoc, EndLoc, Clauses, Loop); return Inst; } OpenACCDataConstruct *OpenACCDataConstruct::CreateEmpty(const ASTContext &C, unsigned NumClauses) { void *Mem = C.Allocate(OpenACCDataConstruct::totalSizeToAlloc( NumClauses)); auto *Inst = new (Mem) OpenACCDataConstruct(NumClauses); return Inst; } OpenACCDataConstruct * OpenACCDataConstruct::Create(const ASTContext &C, SourceLocation Start, SourceLocation DirectiveLoc, SourceLocation End, ArrayRef Clauses, Stmt *StructuredBlock) { void *Mem = C.Allocate(OpenACCDataConstruct::totalSizeToAlloc( Clauses.size())); auto *Inst = new (Mem) OpenACCDataConstruct(Start, DirectiveLoc, End, Clauses, StructuredBlock); return Inst; } OpenACCEnterDataConstruct * OpenACCEnterDataConstruct::CreateEmpty(const ASTContext &C, unsigned NumClauses) { void *Mem = C.Allocate( OpenACCEnterDataConstruct::totalSizeToAlloc( NumClauses)); auto *Inst = new (Mem) OpenACCEnterDataConstruct(NumClauses); return Inst; } OpenACCEnterDataConstruct *OpenACCEnterDataConstruct::Create( const ASTContext &C, SourceLocation Start, SourceLocation DirectiveLoc, SourceLocation End, ArrayRef Clauses) { void *Mem = C.Allocate( OpenACCEnterDataConstruct::totalSizeToAlloc( Clauses.size())); auto *Inst = new (Mem) OpenACCEnterDataConstruct(Start, DirectiveLoc, End, Clauses); return Inst; } OpenACCExitDataConstruct * OpenACCExitDataConstruct::CreateEmpty(const ASTContext &C, unsigned NumClauses) { void *Mem = C.Allocate( OpenACCExitDataConstruct::totalSizeToAlloc( NumClauses)); auto *Inst = new (Mem) OpenACCExitDataConstruct(NumClauses); return Inst; } OpenACCExitDataConstruct *OpenACCExitDataConstruct::Create( const ASTContext &C, SourceLocation Start, SourceLocation DirectiveLoc, SourceLocation End, ArrayRef Clauses) { void *Mem = C.Allocate( OpenACCExitDataConstruct::totalSizeToAlloc( Clauses.size())); auto *Inst = new (Mem) OpenACCExitDataConstruct(Start, DirectiveLoc, End, Clauses); return Inst; } OpenACCHostDataConstruct * OpenACCHostDataConstruct::CreateEmpty(const ASTContext &C, unsigned NumClauses) { void *Mem = C.Allocate( OpenACCHostDataConstruct::totalSizeToAlloc( NumClauses)); auto *Inst = new (Mem) OpenACCHostDataConstruct(NumClauses); return Inst; } OpenACCHostDataConstruct *OpenACCHostDataConstruct::Create( const ASTContext &C, SourceLocation Start, SourceLocation DirectiveLoc, SourceLocation End, ArrayRef Clauses, Stmt *StructuredBlock) { void *Mem = C.Allocate( OpenACCHostDataConstruct::totalSizeToAlloc( Clauses.size())); auto *Inst = new (Mem) OpenACCHostDataConstruct(Start, DirectiveLoc, End, Clauses, StructuredBlock); return Inst; } OpenACCWaitConstruct *OpenACCWaitConstruct::CreateEmpty(const ASTContext &C, unsigned NumExprs, unsigned NumClauses) { void *Mem = C.Allocate( OpenACCWaitConstruct::totalSizeToAlloc( NumExprs, NumClauses)); auto *Inst = new (Mem) OpenACCWaitConstruct(NumExprs, NumClauses); return Inst; } OpenACCWaitConstruct *OpenACCWaitConstruct::Create( const ASTContext &C, SourceLocation Start, SourceLocation DirectiveLoc, SourceLocation LParenLoc, Expr *DevNumExpr, SourceLocation QueuesLoc, ArrayRef QueueIdExprs, SourceLocation RParenLoc, SourceLocation End, ArrayRef Clauses) { assert(!llvm::is_contained(QueueIdExprs, nullptr)); void *Mem = C.Allocate( OpenACCWaitConstruct::totalSizeToAlloc( QueueIdExprs.size() + 1, Clauses.size())); auto *Inst = new (Mem) OpenACCWaitConstruct(Start, DirectiveLoc, LParenLoc, DevNumExpr, QueuesLoc, QueueIdExprs, RParenLoc, End, Clauses); return Inst; } OpenACCInitConstruct *OpenACCInitConstruct::CreateEmpty(const ASTContext &C, unsigned NumClauses) { void *Mem = C.Allocate(OpenACCInitConstruct::totalSizeToAlloc( NumClauses)); auto *Inst = new (Mem) OpenACCInitConstruct(NumClauses); return Inst; } OpenACCInitConstruct * OpenACCInitConstruct::Create(const ASTContext &C, SourceLocation Start, SourceLocation DirectiveLoc, SourceLocation End, ArrayRef Clauses) { void *Mem = C.Allocate(OpenACCInitConstruct::totalSizeToAlloc( Clauses.size())); auto *Inst = new (Mem) OpenACCInitConstruct(Start, DirectiveLoc, End, Clauses); return Inst; } OpenACCShutdownConstruct * OpenACCShutdownConstruct::CreateEmpty(const ASTContext &C, unsigned NumClauses) { void *Mem = C.Allocate( OpenACCShutdownConstruct::totalSizeToAlloc( NumClauses)); auto *Inst = new (Mem) OpenACCShutdownConstruct(NumClauses); return Inst; } OpenACCShutdownConstruct *OpenACCShutdownConstruct::Create( const ASTContext &C, SourceLocation Start, SourceLocation DirectiveLoc, SourceLocation End, ArrayRef Clauses) { void *Mem = C.Allocate( OpenACCShutdownConstruct::totalSizeToAlloc( Clauses.size())); auto *Inst = new (Mem) OpenACCShutdownConstruct(Start, DirectiveLoc, End, Clauses); return Inst; } OpenACCSetConstruct *OpenACCSetConstruct::CreateEmpty(const ASTContext &C, unsigned NumClauses) { void *Mem = C.Allocate( OpenACCSetConstruct::totalSizeToAlloc(NumClauses)); auto *Inst = new (Mem) OpenACCSetConstruct(NumClauses); return Inst; } OpenACCSetConstruct * OpenACCSetConstruct::Create(const ASTContext &C, SourceLocation Start, SourceLocation DirectiveLoc, SourceLocation End, ArrayRef Clauses) { void *Mem = C.Allocate(OpenACCSetConstruct::totalSizeToAlloc( Clauses.size())); auto *Inst = new (Mem) OpenACCSetConstruct(Start, DirectiveLoc, End, Clauses); return Inst; } OpenACCUpdateConstruct * OpenACCUpdateConstruct::CreateEmpty(const ASTContext &C, unsigned NumClauses) { void *Mem = C.Allocate( OpenACCUpdateConstruct::totalSizeToAlloc( NumClauses)); auto *Inst = new (Mem) OpenACCUpdateConstruct(NumClauses); return Inst; } OpenACCUpdateConstruct * OpenACCUpdateConstruct::Create(const ASTContext &C, SourceLocation Start, SourceLocation DirectiveLoc, SourceLocation End, ArrayRef Clauses) { void *Mem = C.Allocate( OpenACCUpdateConstruct::totalSizeToAlloc( Clauses.size())); auto *Inst = new (Mem) OpenACCUpdateConstruct(Start, DirectiveLoc, End, Clauses); return Inst; } OpenACCAtomicConstruct * OpenACCAtomicConstruct::CreateEmpty(const ASTContext &C, unsigned NumClauses) { void *Mem = C.Allocate( OpenACCAtomicConstruct::totalSizeToAlloc( NumClauses)); auto *Inst = new (Mem) OpenACCAtomicConstruct(NumClauses); return Inst; } OpenACCAtomicConstruct *OpenACCAtomicConstruct::Create( const ASTContext &C, SourceLocation Start, SourceLocation DirectiveLoc, OpenACCAtomicKind AtKind, SourceLocation End, ArrayRef Clauses, Stmt *AssociatedStmt) { void *Mem = C.Allocate( OpenACCAtomicConstruct::totalSizeToAlloc( Clauses.size())); auto *Inst = new (Mem) OpenACCAtomicConstruct(Start, DirectiveLoc, AtKind, End, Clauses, AssociatedStmt); return Inst; } static std::optional> getBinaryAssignOpArgs(const Expr *Op, bool &IsCompoundAssign) { if (const auto *BO = dyn_cast(Op)) { if (!BO->isAssignmentOp()) return std::nullopt; IsCompoundAssign = BO->isCompoundAssignmentOp(); return std::pair(BO->getLHS(), BO->getRHS()); } if (const auto *OO = dyn_cast(Op)) { if (!OO->isAssignmentOp()) return std::nullopt; IsCompoundAssign = OO->getOperator() != OO_Equal; return std::pair(OO->getArg(0), OO->getArg(1)); } return std::nullopt; } static std::optional> getBinaryAssignOpArgs(const Expr *Op) { bool IsCompoundAssign; return getBinaryAssignOpArgs(Op, IsCompoundAssign); } static std::optional> getUnaryOpArgs(const Expr *Op) { if (const auto *UO = dyn_cast(Op)) return {{UO->getSubExpr(), UO->isPostfix()}}; if (const auto *OpCall = dyn_cast(Op)) { // Post-inc/dec have a second unused argument to differentiate it, so we // accept -- or ++ as unary, or any operator call with only 1 arg. if (OpCall->getNumArgs() == 1 || OpCall->getOperator() == OO_PlusPlus || OpCall->getOperator() == OO_MinusMinus) return {{OpCall->getArg(0), /*IsPostfix=*/OpCall->getNumArgs() == 1}}; } return std::nullopt; } // Read is of the form `v = x;`, where both sides are scalar L-values. This is a // BinaryOperator or CXXOperatorCallExpr. static std::optional getReadStmtInfo(const Expr *E, bool ForAtomicComputeSingleStmt = false) { std::optional> BinaryArgs = getBinaryAssignOpArgs(E); if (!BinaryArgs) return std::nullopt; // We want the L-value for each side, so we ignore implicit casts. auto Res = OpenACCAtomicConstruct::SingleStmtInfo::createRead( E, BinaryArgs->first->IgnoreImpCasts(), BinaryArgs->second->IgnoreImpCasts()); // The atomic compute single-stmt variant has to do a 'fixup' step for the 'X' // value, since it is dependent on the RHS. So if we're in that version, we // skip the checks on X. if ((!ForAtomicComputeSingleStmt && (!Res.X->isLValue() || !Res.X->getType()->isScalarType())) || !Res.V->isLValue() || !Res.V->getType()->isScalarType()) return std::nullopt; return Res; } // Write supports only the format 'x = expr', where the expression is scalar // type, and 'x' is a scalar l value. As above, this can come in 2 forms; // Binary Operator or CXXOperatorCallExpr. static std::optional getWriteStmtInfo(const Expr *E) { std::optional> BinaryArgs = getBinaryAssignOpArgs(E); if (!BinaryArgs) return std::nullopt; // We want the L-value for ONLY the X side, so we ignore implicit casts. For // the right side (the expr), we emit it as an r-value so we need to // maintain implicit casts. auto Res = OpenACCAtomicConstruct::SingleStmtInfo::createWrite( E, BinaryArgs->first->IgnoreImpCasts(), BinaryArgs->second); if (!Res.X->isLValue() || !Res.X->getType()->isScalarType()) return std::nullopt; return Res; } static std::optional getUpdateStmtInfo(const Expr *E) { std::optional> UnaryArgs = getUnaryOpArgs(E); if (UnaryArgs) { auto Res = OpenACCAtomicConstruct::SingleStmtInfo::createUpdate( E, UnaryArgs->first->IgnoreImpCasts(), UnaryArgs->second); if (!Res.X->isLValue() || !Res.X->getType()->isScalarType()) return std::nullopt; return Res; } bool IsRHSCompoundAssign = false; std::optional> BinaryArgs = getBinaryAssignOpArgs(E, IsRHSCompoundAssign); if (!BinaryArgs) return std::nullopt; auto Res = OpenACCAtomicConstruct::SingleStmtInfo::createUpdate( E, BinaryArgs->first->IgnoreImpCasts(), /*PostFixIncDec=*/false); if (!Res.X->isLValue() || !Res.X->getType()->isScalarType()) return std::nullopt; // 'update' has to be either a compound-assignment operation, or // assignment-to-a-binary-op. Return nullopt if these are not the case. // If we are already compound-assign, we're done! if (IsRHSCompoundAssign) return Res; // else we have to check that we have a binary operator. const Expr *RHS = BinaryArgs->second->IgnoreImpCasts(); if (isa(RHS)) { return Res; } else if (const auto *OO = dyn_cast(RHS)) { if (OO->isInfixBinaryOp()) return Res; } return std::nullopt; } /// The statement associated with an atomic capture comes in 1 of two forms: A /// compound statement containing two statements, or a single statement. In /// either case, the compound/single statement is decomposed into 2 separate /// operations, eihter a read/write, read/update, or update/read. This function /// figures out that information in the form listed in the standard (filling in /// V, X, or Expr) for each of these operations. static OpenACCAtomicConstruct::StmtInfo getCaptureStmtInfo(const Stmt *AssocStmt) { if (const auto *CmpdStmt = dyn_cast(AssocStmt)) { // We checked during Sema to ensure we only have 2 statements here, and // that both are expressions, we can look at these to see what the valid // options are. const Expr *Stmt1 = cast(*CmpdStmt->body().begin())->IgnoreImpCasts(); const Expr *Stmt2 = cast(*(CmpdStmt->body().begin() + 1))->IgnoreImpCasts(); // The compound statement form allows read/write, read/update, or // update/read. First we get the information for a 'Read' to see if this is // one of the former two. std::optional Read = getReadStmtInfo(Stmt1); if (Read) { // READ : WRITE // v = x; x = expr // READ : UPDATE // v = x; x binop = expr // v = x; x = x binop expr // v = x; x = expr binop x // v = x; x++ // v = x; ++x // v = x; x-- // v = x; --x std::optional Update = getUpdateStmtInfo(Stmt2); // Since we already know the first operation is a read, the second is // either an update, which we check, or a write, which we can assume next. if (Update) return OpenACCAtomicConstruct::StmtInfo::createReadUpdate(*Read, *Update); std::optional Write = getWriteStmtInfo(Stmt2); return OpenACCAtomicConstruct::StmtInfo::createReadWrite(*Read, *Write); } // UPDATE: READ // x binop = expr; v = x // x = x binop expr; v = x // x = expr binop x ; v = x // ++ x; v = x // x++; v = x // --x; v = x // x--; v = x // Otherwise, it is one of the above forms for update/read. std::optional Update = getUpdateStmtInfo(Stmt1); Read = getReadStmtInfo(Stmt2); return OpenACCAtomicConstruct::StmtInfo::createUpdateRead(*Update, *Read); } else { // All of the forms that can be done in a single line fall into 2 // categories: update/read, or read/update. The special cases are the // postfix unary operators, which we have to make sure we do the 'read' // first. However, we still parse these as the RHS first, so we have a // 'reversing' step. READ: UPDATE v = x++; v = x--; UPDATE: READ v = ++x; v // = --x; v = x binop=expr v = x = x binop expr v = x = expr binop x const Expr *E = cast(AssocStmt); std::optional Read = getReadStmtInfo(E, /*ForAtomicComputeSingleStmt=*/true); std::optional Update = getUpdateStmtInfo(Read->X); // Fixup this, since the 'X' for the read is the result after write, but is // the same value as the LHS-most variable of the update(its X). Read->X = Update->X; // Postfix is a read FIRST, then an update. if (Update->IsPostfixIncDec) return OpenACCAtomicConstruct::StmtInfo::createReadUpdate(*Read, *Update); return OpenACCAtomicConstruct::StmtInfo::createUpdateRead(*Update, *Read); } return {}; } const OpenACCAtomicConstruct::StmtInfo OpenACCAtomicConstruct::getAssociatedStmtInfo() const { // This ends up being a vastly simplified version of SemaOpenACCAtomic, since // it doesn't have to worry about erroring out, but we should do a lot of // asserts to ensure we don't get off into the weeds. assert(getAssociatedStmt() && "invalid associated stmt?"); switch (AtomicKind) { case OpenACCAtomicKind::Read: return OpenACCAtomicConstruct::StmtInfo{ OpenACCAtomicConstruct::StmtInfo::StmtForm::Read, *getReadStmtInfo(cast(getAssociatedStmt())), OpenACCAtomicConstruct::SingleStmtInfo::Empty()}; case OpenACCAtomicKind::Write: return OpenACCAtomicConstruct::StmtInfo{ OpenACCAtomicConstruct::StmtInfo::StmtForm::Write, *getWriteStmtInfo(cast(getAssociatedStmt())), OpenACCAtomicConstruct::SingleStmtInfo::Empty()}; case OpenACCAtomicKind::None: case OpenACCAtomicKind::Update: return OpenACCAtomicConstruct::StmtInfo{ OpenACCAtomicConstruct::StmtInfo::StmtForm::Update, *getUpdateStmtInfo(cast(getAssociatedStmt())), OpenACCAtomicConstruct::SingleStmtInfo::Empty()}; case OpenACCAtomicKind::Capture: return getCaptureStmtInfo(getAssociatedStmt()); } llvm_unreachable("unknown OpenACC atomic kind"); } OpenACCCacheConstruct *OpenACCCacheConstruct::CreateEmpty(const ASTContext &C, unsigned NumVars) { void *Mem = C.Allocate(OpenACCCacheConstruct::totalSizeToAlloc(NumVars)); auto *Inst = new (Mem) OpenACCCacheConstruct(NumVars); return Inst; } OpenACCCacheConstruct *OpenACCCacheConstruct::Create( const ASTContext &C, SourceLocation Start, SourceLocation DirectiveLoc, SourceLocation LParenLoc, SourceLocation ReadOnlyLoc, ArrayRef VarList, SourceLocation RParenLoc, SourceLocation End) { void *Mem = C.Allocate( OpenACCCacheConstruct::totalSizeToAlloc(VarList.size())); auto *Inst = new (Mem) OpenACCCacheConstruct( Start, DirectiveLoc, LParenLoc, ReadOnlyLoc, VarList, RParenLoc, End); return Inst; }