//== SemaOpenACCAtomic.cpp - Semantic Analysis for OpenACC Atomic Construct===// // // 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 the OpenACC atomic construct. /// //===----------------------------------------------------------------------===// #include "clang/AST/ExprCXX.h" #include "clang/Basic/DiagnosticSema.h" #include "clang/Sema/SemaOpenACC.h" #include #include using namespace clang; namespace { class AtomicOperandChecker { SemaOpenACC &SemaRef; OpenACCAtomicKind AtKind; SourceLocation AtomicDirLoc; StmtResult AssocStmt; // Do a diagnostic, which sets the correct error, then displays passed note. bool DiagnoseInvalidAtomic(SourceLocation Loc, PartialDiagnostic NoteDiag) { SemaRef.Diag(AtomicDirLoc, diag::err_acc_invalid_atomic) << (AtKind != OpenACCAtomicKind::None) << AtKind; SemaRef.Diag(Loc, NoteDiag); return true; } // Create a replacement recovery expr in case we find an error here. This // allows us to ignore this during template instantiation so we only get a // single error. StmtResult getRecoveryExpr() { if (!AssocStmt.isUsable()) return AssocStmt; if (!SemaRef.getASTContext().getLangOpts().RecoveryAST) return StmtError(); Expr *E = dyn_cast(AssocStmt.get()); QualType T = E ? E->getType() : SemaRef.getASTContext().DependentTy; return RecoveryExpr::Create(SemaRef.getASTContext(), T, AssocStmt.get()->getBeginLoc(), AssocStmt.get()->getEndLoc(), E ? ArrayRef{E} : ArrayRef{}); } // OpenACC 3.3 2.12: 'expr' is an expression with scalar type. bool CheckOperandExpr(const Expr *E, PartialDiagnostic PD) { QualType ExprTy = E->getType(); // Scalar allowed, plus we allow instantiation dependent to support // templates. if (ExprTy->isInstantiationDependentType() || ExprTy->isScalarType()) return false; return DiagnoseInvalidAtomic(E->getExprLoc(), PD << diag::OACCLValScalar::Scalar << ExprTy); } // OpenACC 3.3 2.12: 'x' and 'v' (as applicable) are boht l-value expressoins // with scalar type. bool CheckOperandVariable(const Expr *E, PartialDiagnostic PD) { if (CheckOperandExpr(E, PD)) return true; if (E->isLValue()) return false; return DiagnoseInvalidAtomic(E->getExprLoc(), PD << diag::OACCLValScalar::LVal); } Expr *RequireExpr(Stmt *Stmt, PartialDiagnostic ExpectedNote) { if (Expr *E = dyn_cast(Stmt)) return E->IgnoreImpCasts(); DiagnoseInvalidAtomic(Stmt->getBeginLoc(), ExpectedNote); return nullptr; } // A struct to hold the return the inner components of any operands, which // allows for compound checking. struct BinaryOpInfo { const Expr *FoundExpr = nullptr; const Expr *LHS = nullptr; const Expr *RHS = nullptr; BinaryOperatorKind Operator; }; struct UnaryOpInfo { const Expr *FoundExpr = nullptr; const Expr *SubExpr = nullptr; UnaryOperatorKind Operator; bool IsIncrementOp() { return Operator == UO_PostInc || Operator == UO_PreInc; } }; std::optional GetUnaryOperatorInfo(const Expr *E) { // If this is a simple unary operator, just return its details. if (const auto *UO = dyn_cast(E)) return UnaryOpInfo{UO, UO->getSubExpr()->IgnoreImpCasts(), UO->getOpcode()}; // This might be an overloaded operator or a dependent context, so make sure // we can get as many details out of this as we can. if (const auto *OpCall = dyn_cast(E)) { UnaryOpInfo Inf; Inf.FoundExpr = OpCall; switch (OpCall->getOperator()) { default: return std::nullopt; case OO_PlusPlus: Inf.Operator = OpCall->getNumArgs() == 1 ? UO_PreInc : UO_PostInc; break; case OO_MinusMinus: Inf.Operator = OpCall->getNumArgs() == 1 ? UO_PreDec : UO_PostDec; break; case OO_Amp: Inf.Operator = UO_AddrOf; break; case OO_Star: Inf.Operator = UO_Deref; break; case OO_Plus: Inf.Operator = UO_Plus; break; case OO_Minus: Inf.Operator = UO_Minus; break; case OO_Tilde: Inf.Operator = UO_Not; break; case OO_Exclaim: Inf.Operator = UO_LNot; break; case OO_Coawait: Inf.Operator = UO_Coawait; break; } // Some of the above can be both binary and unary operations, so make sure // we get the right one. if (Inf.Operator != UO_PostInc && Inf.Operator != UO_PostDec && OpCall->getNumArgs() != 1) return std::nullopt; Inf.SubExpr = OpCall->getArg(0); return Inf; } return std::nullopt; } // Get a normalized version of a binary operator. std::optional GetBinaryOperatorInfo(const Expr *E) { if (const auto *BO = dyn_cast(E)) return BinaryOpInfo{BO, BO->getLHS()->IgnoreImpCasts(), BO->getRHS()->IgnoreImpCasts(), BO->getOpcode()}; // In case this is an operator-call, which allows us to support overloaded // operators and dependent expression. if (const auto *OpCall = dyn_cast(E)) { BinaryOpInfo Inf; Inf.FoundExpr = OpCall; switch (OpCall->getOperator()) { default: return std::nullopt; case OO_Plus: Inf.Operator = BO_Add; break; case OO_Minus: Inf.Operator = BO_Sub; break; case OO_Star: Inf.Operator = BO_Mul; break; case OO_Slash: Inf.Operator = BO_Div; break; case OO_Percent: Inf.Operator = BO_Rem; break; case OO_Caret: Inf.Operator = BO_Xor; break; case OO_Amp: Inf.Operator = BO_And; break; case OO_Pipe: Inf.Operator = BO_Or; break; case OO_Equal: Inf.Operator = BO_Assign; break; case OO_Spaceship: Inf.Operator = BO_Cmp; break; case OO_Less: Inf.Operator = BO_LT; break; case OO_Greater: Inf.Operator = BO_GT; break; case OO_PlusEqual: Inf.Operator = BO_AddAssign; break; case OO_MinusEqual: Inf.Operator = BO_SubAssign; break; case OO_StarEqual: Inf.Operator = BO_MulAssign; break; case OO_SlashEqual: Inf.Operator = BO_DivAssign; break; case OO_PercentEqual: Inf.Operator = BO_RemAssign; break; case OO_CaretEqual: Inf.Operator = BO_XorAssign; break; case OO_AmpEqual: Inf.Operator = BO_AndAssign; break; case OO_PipeEqual: Inf.Operator = BO_OrAssign; break; case OO_LessLess: Inf.Operator = BO_Shl; break; case OO_GreaterGreater: Inf.Operator = BO_Shr; break; case OO_LessLessEqual: Inf.Operator = BO_ShlAssign; break; case OO_GreaterGreaterEqual: Inf.Operator = BO_ShrAssign; break; case OO_EqualEqual: Inf.Operator = BO_EQ; break; case OO_ExclaimEqual: Inf.Operator = BO_NE; break; case OO_LessEqual: Inf.Operator = BO_LE; break; case OO_GreaterEqual: Inf.Operator = BO_GE; break; case OO_AmpAmp: Inf.Operator = BO_LAnd; break; case OO_PipePipe: Inf.Operator = BO_LOr; break; case OO_Comma: Inf.Operator = BO_Comma; break; case OO_ArrowStar: Inf.Operator = BO_PtrMemI; break; } // This isn't a binary operator unless there are two arguments. if (OpCall->getNumArgs() != 2) return std::nullopt; // Callee is the call-operator, so we only need to extract the two // arguments here. Inf.LHS = OpCall->getArg(0)->IgnoreImpCasts(); Inf.RHS = OpCall->getArg(1)->IgnoreImpCasts(); return Inf; } return std::nullopt; } // Checks a required assignment operation, but don't check the LHS or RHS, // callers have to do that here. std::optional CheckAssignment(const Expr *E) { std::optional Inf = GetBinaryOperatorInfo(E); if (!Inf) { DiagnoseInvalidAtomic(E->getExprLoc(), SemaRef.PDiag(diag::note_acc_atomic_expr_must_be) << diag::OACCAtomicExpr::Assign); return std::nullopt; } if (Inf->Operator != BO_Assign) { DiagnoseInvalidAtomic(Inf->FoundExpr->getExprLoc(), SemaRef.PDiag(diag::note_acc_atomic_expr_must_be) << diag::OACCAtomicExpr::Assign); return std::nullopt; } // Assignment always requires an lvalue/scalar on the LHS. if (CheckOperandVariable( Inf->LHS, SemaRef.PDiag(diag::note_acc_atomic_operand_lvalue_scalar) << /*left=*/0 << diag::OACCAtomicOpKind::Assign)) return std::nullopt; return Inf; } struct IDACInfo { bool Failed = false; enum ExprKindTy { Invalid, // increment/decrement ops. Unary, // v = x SimpleAssign, // x = expr ExprAssign, // x binop= expr CompoundAssign, // x = x binop expr // x = expr binop x AssignBinOp } ExprKind; // The variable referred to as 'x' in all of the grammar, such that it is // needed in compound statement checking of capture to check between the two // expressions. const Expr *X_Var = nullptr; static IDACInfo Fail() { return IDACInfo{true, Invalid, nullptr}; }; }; // Helper for CheckIncDecAssignCompoundAssign, does checks for inc/dec. IDACInfo CheckIncDec(UnaryOpInfo Inf) { if (!UnaryOperator::isIncrementDecrementOp(Inf.Operator)) { DiagnoseInvalidAtomic( Inf.FoundExpr->getExprLoc(), SemaRef.PDiag(diag::note_acc_atomic_unsupported_unary_operator)); return IDACInfo::Fail(); } bool Failed = CheckOperandVariable( Inf.SubExpr, SemaRef.PDiag(diag::note_acc_atomic_operand_lvalue_scalar) << /*none=*/2 << (Inf.IsIncrementOp() ? diag::OACCAtomicOpKind::Inc : diag::OACCAtomicOpKind::Dec)); // For increment/decrements, the subexpr is the 'x' (x++, ++x, etc). return IDACInfo{Failed, IDACInfo::Unary, Inf.SubExpr}; } enum class SimpleAssignKind { None, Var, Expr }; // Check an assignment, and ensure the RHS is either x binop expr or expr // binop x. // If AllowSimpleAssign, also allows v = x; IDACInfo CheckAssignmentWithBinOpOnRHS(BinaryOpInfo AssignInf, SimpleAssignKind SAK) { PartialDiagnostic PD = SemaRef.PDiag(diag::note_acc_atomic_operand_lvalue_scalar) << /*left=*/0 << diag::OACCAtomicOpKind::Assign; if (CheckOperandVariable(AssignInf.LHS, PD)) return IDACInfo::Fail(); std::optional BinInf = GetBinaryOperatorInfo(AssignInf.RHS); if (!BinInf) { // Capture in a compound statement allows v = x assignment. So make sure // we permit that here. if (SAK != SimpleAssignKind::None) { PartialDiagnostic PD = SemaRef.PDiag(diag::note_acc_atomic_operand_lvalue_scalar) << /*right=*/1 << diag::OACCAtomicOpKind::Assign; if (SAK == SimpleAssignKind::Var) { // In the var version, everywhere we allow v = x;, X is the RHS. return IDACInfo{CheckOperandVariable(AssignInf.RHS, PD), IDACInfo::SimpleAssign, AssignInf.RHS}; } assert(SAK == SimpleAssignKind::Expr); // In the expression version, supported by v=x; x = expr;, we need to // set to the LHS here. return IDACInfo{CheckOperandExpr(AssignInf.RHS, PD), IDACInfo::ExprAssign, AssignInf.LHS}; } DiagnoseInvalidAtomic( AssignInf.RHS->getExprLoc(), SemaRef.PDiag(diag::note_acc_atomic_expected_binop)); return IDACInfo::Fail(); } switch (BinInf->Operator) { default: DiagnoseInvalidAtomic( BinInf->FoundExpr->getExprLoc(), SemaRef.PDiag(diag::note_acc_atomic_unsupported_binary_operator)); return IDACInfo::Fail(); // binop is one of +, *, -, /, &, ^, |, <<, or >> case BO_Add: case BO_Mul: case BO_Sub: case BO_Div: case BO_And: case BO_Xor: case BO_Or: case BO_Shl: case BO_Shr: // Handle these outside of the switch. break; } llvm::FoldingSetNodeID LHS_ID, InnerLHS_ID, InnerRHS_ID; AssignInf.LHS->Profile(LHS_ID, SemaRef.getASTContext(), /*Canonical=*/true); BinInf->LHS->Profile(InnerLHS_ID, SemaRef.getASTContext(), /*Canonical=*/true); // This is X = X binop expr; // Check the RHS is an expression. if (LHS_ID == InnerLHS_ID) return IDACInfo{ CheckOperandExpr( BinInf->RHS, SemaRef.PDiag(diag::note_acc_atomic_operand_lvalue_scalar << /*right=*/1 << diag::OACCAtomicOpKind::CompoundAssign)), IDACInfo::AssignBinOp, AssignInf.LHS}; BinInf->RHS->Profile(InnerRHS_ID, SemaRef.getASTContext(), /*Canonical=*/true); // This is X = expr binop X; // Check the LHS is an expression if (LHS_ID == InnerRHS_ID) return IDACInfo{ CheckOperandExpr( BinInf->LHS, SemaRef.PDiag(diag::note_acc_atomic_operand_lvalue_scalar) << /*left=*/0 << diag::OACCAtomicOpKind::CompoundAssign), IDACInfo::AssignBinOp, AssignInf.LHS}; // If nothing matches, error out. DiagnoseInvalidAtomic(BinInf->FoundExpr->getExprLoc(), SemaRef.PDiag(diag::note_acc_atomic_mismatch_operand) << const_cast(AssignInf.LHS) << const_cast(BinInf->LHS) << const_cast(BinInf->RHS)); return IDACInfo::Fail(); } // Ensures that the expression is an increment/decrement, an assignment, or a // compound assignment. If its an assignment, allows the x binop expr/x binop // expr syntax. If it is a compound-assignment, allows any expr on the RHS. IDACInfo CheckIncDecAssignCompoundAssign(const Expr *E, SimpleAssignKind SAK) { std::optional UInf = GetUnaryOperatorInfo(E); // If this is a unary operator, only increment/decrement are allowed, so get // unary operator, then check everything we can. if (UInf) return CheckIncDec(*UInf); std::optional BinInf = GetBinaryOperatorInfo(E); // Unary or binary operator were the only choices, so error here. if (!BinInf) { DiagnoseInvalidAtomic(E->getExprLoc(), SemaRef.PDiag(diag::note_acc_atomic_expr_must_be) << diag::OACCAtomicExpr::UnaryCompAssign); return IDACInfo::Fail(); } switch (BinInf->Operator) { default: DiagnoseInvalidAtomic( BinInf->FoundExpr->getExprLoc(), SemaRef.PDiag( diag::note_acc_atomic_unsupported_compound_binary_operator)); return IDACInfo::Fail(); case BO_Assign: return CheckAssignmentWithBinOpOnRHS(*BinInf, SAK); case BO_AddAssign: case BO_MulAssign: case BO_SubAssign: case BO_DivAssign: case BO_AndAssign: case BO_XorAssign: case BO_OrAssign: case BO_ShlAssign: case BO_ShrAssign: { PartialDiagnostic LPD = SemaRef.PDiag(diag::note_acc_atomic_operand_lvalue_scalar) << /*left=*/0 << diag::OACCAtomicOpKind::CompoundAssign; PartialDiagnostic RPD = SemaRef.PDiag(diag::note_acc_atomic_operand_lvalue_scalar) << /*right=*/1 << diag::OACCAtomicOpKind::CompoundAssign; // nothing to do other than check the variable expressions. // success or failure bool Failed = CheckOperandVariable(BinInf->LHS, LPD) || CheckOperandExpr(BinInf->RHS, RPD); return IDACInfo{Failed, IDACInfo::CompoundAssign, BinInf->LHS}; } } llvm_unreachable("all binary operator kinds should be checked above"); } StmtResult CheckRead() { Expr *AssocExpr = RequireExpr( AssocStmt.get(), SemaRef.PDiag(diag::note_acc_atomic_expr_must_be) << diag::OACCAtomicExpr::Assign); if (!AssocExpr) return getRecoveryExpr(); std::optional AssignRes = CheckAssignment(AssocExpr); if (!AssignRes) return getRecoveryExpr(); PartialDiagnostic PD = SemaRef.PDiag(diag::note_acc_atomic_operand_lvalue_scalar) << /*right=*/1 << diag::OACCAtomicOpKind::Assign; // Finally, check the RHS. if (CheckOperandVariable(AssignRes->RHS, PD)) return getRecoveryExpr(); return AssocStmt; } StmtResult CheckWrite() { Expr *AssocExpr = RequireExpr( AssocStmt.get(), SemaRef.PDiag(diag::note_acc_atomic_expr_must_be) << diag::OACCAtomicExpr::Assign); if (!AssocExpr) return getRecoveryExpr(); std::optional AssignRes = CheckAssignment(AssocExpr); if (!AssignRes) return getRecoveryExpr(); PartialDiagnostic PD = SemaRef.PDiag(diag::note_acc_atomic_operand_lvalue_scalar) << /*right=*/1 << diag::OACCAtomicOpKind::Assign; // Finally, check the RHS. if (CheckOperandExpr(AssignRes->RHS, PD)) return getRecoveryExpr(); return AssocStmt; } StmtResult CheckUpdate() { Expr *AssocExpr = RequireExpr( AssocStmt.get(), SemaRef.PDiag(diag::note_acc_atomic_expr_must_be) << diag::OACCAtomicExpr::UnaryCompAssign); if (!AssocExpr || CheckIncDecAssignCompoundAssign(AssocExpr, SimpleAssignKind::None) .Failed) return getRecoveryExpr(); return AssocStmt; } bool CheckVarRefsSame(IDACInfo::ExprKindTy FirstKind, const Expr *FirstX, IDACInfo::ExprKindTy SecondKind, const Expr *SecondX) { llvm::FoldingSetNodeID First_ID, Second_ID; FirstX->Profile(First_ID, SemaRef.getASTContext(), /*Canonical=*/true); SecondX->Profile(Second_ID, SemaRef.getASTContext(), /*Canonical=*/true); if (First_ID == Second_ID) return false; PartialDiagnostic PD = SemaRef.PDiag(diag::note_acc_atomic_mismatch_compound_operand) << FirstKind << const_cast(FirstX) << SecondKind << const_cast(SecondX); return DiagnoseInvalidAtomic(SecondX->getExprLoc(), PD); } StmtResult CheckCapture() { if (const auto *CmpdStmt = dyn_cast(AssocStmt.get())) { auto *const *BodyItr = CmpdStmt->body().begin(); PartialDiagnostic PD = SemaRef.PDiag(diag::note_acc_atomic_expr_must_be) << diag::OACCAtomicExpr::UnaryCompAssign; // If we don't have at least 1 statement, error. if (BodyItr == CmpdStmt->body().end()) { DiagnoseInvalidAtomic(CmpdStmt->getBeginLoc(), PD); return getRecoveryExpr(); } // First Expr can be inc/dec, assign, or compound assign. Expr *FirstExpr = RequireExpr(*BodyItr, PD); if (!FirstExpr) return getRecoveryExpr(); IDACInfo FirstExprResults = CheckIncDecAssignCompoundAssign(FirstExpr, SimpleAssignKind::Var); if (FirstExprResults.Failed) return getRecoveryExpr(); ++BodyItr; // If we don't have second statement, error. if (BodyItr == CmpdStmt->body().end()) { DiagnoseInvalidAtomic(CmpdStmt->getEndLoc(), PD); return getRecoveryExpr(); } Expr *SecondExpr = RequireExpr(*BodyItr, PD); if (!SecondExpr) return getRecoveryExpr(); assert(FirstExprResults.ExprKind != IDACInfo::Invalid); switch (FirstExprResults.ExprKind) { case IDACInfo::Invalid: case IDACInfo::ExprAssign: llvm_unreachable("Should have error'ed out by now"); case IDACInfo::Unary: case IDACInfo::CompoundAssign: case IDACInfo::AssignBinOp: { // Everything but simple-assign can only be followed by a simple // assignment. std::optional AssignRes = CheckAssignment(SecondExpr); if (!AssignRes) return getRecoveryExpr(); PartialDiagnostic PD = SemaRef.PDiag(diag::note_acc_atomic_operand_lvalue_scalar) << /*right=*/1 << diag::OACCAtomicOpKind::Assign; if (CheckOperandVariable(AssignRes->RHS, PD)) return getRecoveryExpr(); if (CheckVarRefsSame(FirstExprResults.ExprKind, FirstExprResults.X_Var, IDACInfo::SimpleAssign, AssignRes->RHS)) return getRecoveryExpr(); break; } case IDACInfo::SimpleAssign: { // If the first was v = x, anything but simple expression is allowed. IDACInfo SecondExprResults = CheckIncDecAssignCompoundAssign(SecondExpr, SimpleAssignKind::Expr); if (SecondExprResults.Failed) return getRecoveryExpr(); if (CheckVarRefsSame(FirstExprResults.ExprKind, FirstExprResults.X_Var, SecondExprResults.ExprKind, SecondExprResults.X_Var)) return getRecoveryExpr(); break; } } ++BodyItr; if (BodyItr != CmpdStmt->body().end()) { DiagnoseInvalidAtomic( (*BodyItr)->getBeginLoc(), SemaRef.PDiag(diag::note_acc_atomic_too_many_stmts)); return getRecoveryExpr(); } } else { // This check doesn't need to happen if it is a compound stmt. Expr *AssocExpr = RequireExpr( AssocStmt.get(), SemaRef.PDiag(diag::note_acc_atomic_expr_must_be) << diag::OACCAtomicExpr::Assign); if (!AssocExpr) return getRecoveryExpr(); // First, we require an assignment. std::optional AssignRes = CheckAssignment(AssocExpr); if (!AssignRes) return getRecoveryExpr(); if (CheckIncDecAssignCompoundAssign(AssignRes->RHS, SimpleAssignKind::None) .Failed) return getRecoveryExpr(); } return AssocStmt; } public: AtomicOperandChecker(SemaOpenACC &S, OpenACCAtomicKind AtKind, SourceLocation DirLoc, StmtResult AssocStmt) : SemaRef(S), AtKind(AtKind), AtomicDirLoc(DirLoc), AssocStmt(AssocStmt) { } StmtResult Check() { switch (AtKind) { case OpenACCAtomicKind::Read: return CheckRead(); case OpenACCAtomicKind::Write: return CheckWrite(); case OpenACCAtomicKind::None: case OpenACCAtomicKind::Update: return CheckUpdate(); case OpenACCAtomicKind::Capture: return CheckCapture(); } llvm_unreachable("Unhandled atomic kind?"); } }; } // namespace StmtResult SemaOpenACC::CheckAtomicAssociatedStmt(SourceLocation AtomicDirLoc, OpenACCAtomicKind AtKind, StmtResult AssocStmt) { if (!AssocStmt.isUsable()) return AssocStmt; if (isa(AssocStmt.get())) return AssocStmt; AtomicOperandChecker Checker{*this, AtKind, AtomicDirLoc, AssocStmt}; return Checker.Check(); }