[clang] Add support for adjust_args extensions in OMP6.0 to take positional a… (PR #225081)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Sep 21 06:18:27 PDT 2026
https://github.com/ykhatav created https://github.com/llvm/llvm-project/pull/225081
…rguments
>From e17db0202d9d0ad4602ff9ab39a7094b1751e949 Mon Sep 17 00:00:00 2001
From: "Khatavkar, Yashasvi" <yashasvi.khatavkar at intel.com>
Date: Mon, 21 Sep 2026 06:17:20 -0700
Subject: [PATCH] Add support for adjust_args extensions in OMP6.0 to take
positional arguments
---
clang/include/clang-c/Index.h | 14 +-
clang/include/clang/AST/ComputeDependence.h | 4 +
clang/include/clang/AST/ExprOpenMP.h | 147 +++++++++++
clang/include/clang/AST/OpenMPClause.h | 23 ++
clang/include/clang/AST/RecursiveASTVisitor.h | 2 +
clang/include/clang/AST/TextNodeDumper.h | 1 +
.../clang/Basic/DiagnosticParseKinds.td | 4 +
.../clang/Basic/DiagnosticSemaKinds.td | 3 +
clang/include/clang/Basic/StmtNodes.td | 2 +
clang/include/clang/Parse/Parser.h | 11 +
clang/include/clang/Sema/SemaOpenMP.h | 13 +
.../include/clang/Serialization/ASTBitCodes.h | 2 +
clang/lib/AST/AttrImpl.cpp | 19 +-
clang/lib/AST/ComputeDependence.cpp | 20 ++
clang/lib/AST/Expr.cpp | 2 +
clang/lib/AST/ExprClassification.cpp | 2 +
clang/lib/AST/ExprConstant.cpp | 2 +
clang/lib/AST/ItaniumMangle.cpp | 2 +
clang/lib/AST/OpenMPClause.cpp | 88 +++++++
clang/lib/AST/StmtPrinter.cpp | 20 ++
clang/lib/AST/StmtProfile.cpp | 13 +
clang/lib/AST/TextNodeDumper.cpp | 6 +
clang/lib/Parse/ParseOpenMP.cpp | 114 ++++++++-
clang/lib/Sema/SemaExceptionSpec.cpp | 2 +
clang/lib/Sema/SemaOpenMP.cpp | 190 +++++++++++++--
clang/lib/Sema/TreeTransform.h | 64 +++++
clang/lib/Serialization/ASTReaderStmt.cpp | 23 ++
clang/lib/Serialization/ASTWriterStmt.cpp | 17 ++
clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 2 +
...riant_adjust_args_positional_ast_print.cpp | 175 ++++++++++++++
...ariant_adjust_args_positional_messages.cpp | 228 ++++++++++++++++++
...ariant_adjust_args_positional_template.cpp | 62 +++++
.../declare_variant_clauses_messages.cpp | 16 +-
clang/tools/libclang/CIndex.cpp | 4 +
clang/tools/libclang/CXCursor.cpp | 8 +
35 files changed, 1270 insertions(+), 35 deletions(-)
create mode 100644 clang/test/OpenMP/declare_variant_adjust_args_positional_ast_print.cpp
create mode 100644 clang/test/OpenMP/declare_variant_adjust_args_positional_messages.cpp
create mode 100644 clang/test/OpenMP/declare_variant_adjust_args_positional_template.cpp
diff --git a/clang/include/clang-c/Index.h b/clang/include/clang-c/Index.h
index 1c8d097f1beab4..1ab3635f2ea3a3 100644
--- a/clang/include/clang-c/Index.h
+++ b/clang/include/clang-c/Index.h
@@ -1691,7 +1691,19 @@ enum CXCursorKind {
*/
CXCursor_PackIndexingExpr = 156,
- CXCursor_LastExpr = CXCursor_PackIndexingExpr,
+ /**
+ * OpenMP 6.0 [5.2.1, Parameter List Items]
+ * The 'omp_num_args' identifier with an optional logical offset.
+ */
+ CXCursor_OMPNumArgsExpr = 157,
+
+ /**
+ * OpenMP 6.0 [5.2.1, Parameter List Items]
+ * A parameter range 'lb:ub', either bound of which may be omitted.
+ */
+ CXCursor_OMPArgumentRangeExpr = 158,
+
+ CXCursor_LastExpr = CXCursor_OMPArgumentRangeExpr,
/* Statements */
CXCursor_FirstStmt = 200,
diff --git a/clang/include/clang/AST/ComputeDependence.h b/clang/include/clang/AST/ComputeDependence.h
index 6430081a40350d..4a36588d399e17 100644
--- a/clang/include/clang/AST/ComputeDependence.h
+++ b/clang/include/clang/AST/ComputeDependence.h
@@ -100,6 +100,8 @@ class AtomicExpr;
class ArraySectionExpr;
class OMPArrayShapingExpr;
class OMPIteratorExpr;
+class OMPNumArgsExpr;
+class OMPArgumentRangeExpr;
class ObjCArrayLiteral;
class ObjCDictionaryLiteral;
class ObjCBoxedExpr;
@@ -200,6 +202,8 @@ ExprDependence computeDependence(AtomicExpr *E);
ExprDependence computeDependence(ArraySectionExpr *E);
ExprDependence computeDependence(OMPArrayShapingExpr *E);
ExprDependence computeDependence(OMPIteratorExpr *E);
+ExprDependence computeDependence(OMPNumArgsExpr *E);
+ExprDependence computeDependence(OMPArgumentRangeExpr *E);
ExprDependence computeDependence(ObjCArrayLiteral *E);
ExprDependence computeDependence(ObjCDictionaryLiteral *E);
diff --git a/clang/include/clang/AST/ExprOpenMP.h b/clang/include/clang/AST/ExprOpenMP.h
index 4d3c5f54ad7dde..4c0bd0cf6974cd 100644
--- a/clang/include/clang/AST/ExprOpenMP.h
+++ b/clang/include/clang/AST/ExprOpenMP.h
@@ -296,6 +296,153 @@ class OMPIteratorExpr final
}
};
+/// OpenMP 6.0 [5.2.1, Parameter List Items]
+/// Represents the 'omp_num_args' identifier used as a bound of a parameter
+/// range, together with an optional logical offset:
+/// \code
+/// omp_num_args [ ('+' | '-') logical_offset ]
+/// \endcode
+/// 'omp_num_args' is not a declaration and is never looked up; it stands for
+/// the number of arguments at a call site, which by OpenMP 6.0 [20.1] is the
+/// number of parameters of the associated declaration plus any variadic
+/// arguments actually passed. For a non-variadic callee that is a compile-time
+/// constant, so only a variadic callee makes the value call-site dependent.
+/// The node is therefore left unresolved in the AST; whoever consumes the
+/// 'adjust_args' parameter list substitutes the value.
+class OMPNumArgsExpr final : public Expr {
+ friend class ASTStmtReader;
+ friend class ASTStmtWriter;
+
+ /// The logical offset, or null if none was written.
+ Stmt *Offset = nullptr;
+ /// Location of the 'omp_num_args' identifier.
+ SourceLocation NumArgsLoc;
+ /// Location of the '+' or '-'; invalid if there is no offset.
+ SourceLocation OpLoc;
+ /// True if the offset was written with '-'.
+ bool IsSubtraction = false;
+
+public:
+ OMPNumArgsExpr(QualType Type, SourceLocation NumArgsLoc, SourceLocation OpLoc,
+ bool IsSubtraction, Expr *Offset)
+ : Expr(OMPNumArgsExprClass, Type, VK_PRValue, OK_Ordinary),
+ Offset(Offset), NumArgsLoc(NumArgsLoc), OpLoc(OpLoc),
+ IsSubtraction(IsSubtraction) {
+ setDependence(computeDependence(this));
+ }
+
+ /// Create an empty 'omp_num_args' expression.
+ explicit OMPNumArgsExpr(EmptyShell Shell)
+ : Expr(OMPNumArgsExprClass, Shell) {}
+
+ /// Gets the logical offset, or null if none was written.
+ Expr *getOffset() { return cast_or_null<Expr>(Offset); }
+ const Expr *getOffset() const { return cast_or_null<Expr>(Offset); }
+ void setOffset(Expr *E) { Offset = E; }
+
+ /// True if the offset was written with '-' rather than '+'.
+ bool isSubtraction() const { return IsSubtraction; }
+ void setIsSubtraction(bool IS) { IsSubtraction = IS; }
+
+ SourceLocation getNumArgsLoc() const { return NumArgsLoc; }
+ void setNumArgsLoc(SourceLocation L) { NumArgsLoc = L; }
+
+ SourceLocation getOperatorLoc() const { return OpLoc; }
+ void setOperatorLoc(SourceLocation L) { OpLoc = L; }
+
+ SourceLocation getBeginLoc() const LLVM_READONLY { return NumArgsLoc; }
+ SourceLocation getEndLoc() const LLVM_READONLY {
+ return Offset ? Offset->getEndLoc() : NumArgsLoc;
+ }
+
+ static bool classof(const Stmt *T) {
+ return T->getStmtClass() == OMPNumArgsExprClass;
+ }
+
+ // Iterators
+ child_range children() { return child_range(&Offset, &Offset + 1); }
+ const_child_range children() const {
+ return const_child_range(&Offset, &Offset + 1);
+ }
+};
+
+/// OpenMP 6.0 [5.2.1, Parameter List Items]
+/// Represents a parameter range, one list item that stands for every parameter
+/// position from a lower to an upper bound:
+/// \code
+/// [ lb ] ':' [ ub ]
+/// \endcode
+/// Either bound may be omitted: an omitted \c lb defaults to 1 and an omitted
+/// \c ub defaults to 'omp_num_args'. A bound may be an 'omp_num_args'
+/// expression, which is why this node's children are general expressions rather
+/// than integer literals.
+///
+/// The type is 'void': a range is never a value, it only ever appears as an
+/// item of an 'adjust_args' parameter list in \c OMPDeclareVariantAttr.
+class OMPArgumentRangeExpr final : public Expr {
+ friend class ASTStmtReader;
+ friend class ASTStmtWriter;
+
+ enum { LOWER_BOUND, UPPER_BOUND, NUM_SUBEXPRS };
+
+ /// The two bounds; either may be null when the bound was omitted.
+ Stmt *SubExprs[NUM_SUBEXPRS] = {nullptr, nullptr};
+ /// Location of the ':' separating the bounds.
+ SourceLocation ColonLoc;
+
+public:
+ OMPArgumentRangeExpr(QualType Type, Expr *LowerBound, SourceLocation ColonLoc,
+ Expr *UpperBound)
+ : Expr(OMPArgumentRangeExprClass, Type, VK_PRValue, OK_Ordinary),
+ ColonLoc(ColonLoc) {
+ SubExprs[LOWER_BOUND] = LowerBound;
+ SubExprs[UPPER_BOUND] = UpperBound;
+ setDependence(computeDependence(this));
+ }
+
+ /// Create an empty parameter range expression.
+ explicit OMPArgumentRangeExpr(EmptyShell Shell)
+ : Expr(OMPArgumentRangeExprClass, Shell) {}
+
+ /// Gets the lower bound, or null if it was omitted (meaning 1).
+ Expr *getLowerBound() { return cast_or_null<Expr>(SubExprs[LOWER_BOUND]); }
+ const Expr *getLowerBound() const {
+ return cast_or_null<Expr>(SubExprs[LOWER_BOUND]);
+ }
+ void setLowerBound(Expr *E) { SubExprs[LOWER_BOUND] = E; }
+
+ /// Gets the upper bound, or null if it was omitted (meaning 'omp_num_args').
+ Expr *getUpperBound() { return cast_or_null<Expr>(SubExprs[UPPER_BOUND]); }
+ const Expr *getUpperBound() const {
+ return cast_or_null<Expr>(SubExprs[UPPER_BOUND]);
+ }
+ void setUpperBound(Expr *E) { SubExprs[UPPER_BOUND] = E; }
+
+ SourceLocation getColonLoc() const { return ColonLoc; }
+ void setColonLoc(SourceLocation L) { ColonLoc = L; }
+
+ SourceLocation getBeginLoc() const LLVM_READONLY {
+ return SubExprs[LOWER_BOUND] ? SubExprs[LOWER_BOUND]->getBeginLoc()
+ : ColonLoc;
+ }
+ SourceLocation getEndLoc() const LLVM_READONLY {
+ return SubExprs[UPPER_BOUND] ? SubExprs[UPPER_BOUND]->getEndLoc()
+ : ColonLoc;
+ }
+
+ static bool classof(const Stmt *T) {
+ return T->getStmtClass() == OMPArgumentRangeExprClass;
+ }
+
+ // Iterators
+ child_range children() {
+ return child_range(&SubExprs[LOWER_BOUND], &SubExprs[NUM_SUBEXPRS]);
+ }
+ const_child_range children() const {
+ return const_child_range(&SubExprs[LOWER_BOUND], &SubExprs[NUM_SUBEXPRS]);
+ }
+};
+
} // end namespace clang
#endif
diff --git a/clang/include/clang/AST/OpenMPClause.h b/clang/include/clang/AST/OpenMPClause.h
index 9e9295e1a0c549..645f0fe8f9281c 100644
--- a/clang/include/clang/AST/OpenMPClause.h
+++ b/clang/include/clang/AST/OpenMPClause.h
@@ -10566,6 +10566,29 @@ class OMPXBareClause : public OMPNoChildClause<llvm::omp::OMPC_ompx_bare> {
OMPXBareClause() = default;
};
+/// Resolve one 'adjust_args' parameter-list item to the 1-based argument
+/// positions it identifies (OpenMP 6.0 [5.2.1] p162).
+///
+/// \param Item A named item (\c DeclRefExpr to a \c ParmVarDecl of \p FD),
+/// a positional item (a constant integer expression), or an
+/// \c OMPArgumentRangeExpr. Callers pass IgnoreParenImpCasts().
+/// \param FD The base function the OMPDeclareVariantAttr is attached to.
+/// \param NumArgs The value of 'omp_num_args' at the point of resolution:
+/// \c max(FD->getNumParams(), Call->getNumArgs()) at a call
+/// site, or \c FD->getNumParams() with no call site available
+/// (OpenMP 6.0 [20.1] p534).
+/// \param Positions Resolved positions are appended here, ascending. Positions
+/// outside [1, NumArgs] are silently dropped
+/// (OpenMP 6.0 [9.6.2] p332 L1-2).
+/// \returns false if \p Item is not a resolvable item shape, or if a bound is
+/// dependent or not a constant expression.
+///
+/// Emits no diagnostics: it lives in the AST library so it can later be
+/// shared with CodeGen. Sema diagnoses separately, before calling this.
+bool resolveOMPAdjustArgsItem(const Expr *Item, const FunctionDecl *FD,
+ unsigned NumArgs, const ASTContext &Ctx,
+ SmallVectorImpl<unsigned> &Positions);
+
} // namespace clang
#endif // LLVM_CLANG_AST_OPENMPCLAUSE_H
diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h
index 1f7c8d762e1b52..5a66c147ac8cab 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -2954,6 +2954,8 @@ DEF_TRAVERSE_STMT(MatrixSubscriptExpr, {})
DEF_TRAVERSE_STMT(ArraySectionExpr, {})
DEF_TRAVERSE_STMT(OMPArrayShapingExpr, {})
DEF_TRAVERSE_STMT(OMPIteratorExpr, {})
+DEF_TRAVERSE_STMT(OMPNumArgsExpr, {})
+DEF_TRAVERSE_STMT(OMPArgumentRangeExpr, {})
DEF_TRAVERSE_STMT(BlockExpr, {
TRY_TO(TraverseDecl(S->getBlockDecl()));
diff --git a/clang/include/clang/AST/TextNodeDumper.h b/clang/include/clang/AST/TextNodeDumper.h
index 1cdd8c37c7fc63..f2fd730d34c5d1 100644
--- a/clang/include/clang/AST/TextNodeDumper.h
+++ b/clang/include/clang/AST/TextNodeDumper.h
@@ -328,6 +328,7 @@ class TextNodeDumper
void VisitObjCIvarRefExpr(const ObjCIvarRefExpr *Node);
void VisitObjCBoolLiteralExpr(const ObjCBoolLiteralExpr *Node);
void VisitOMPIteratorExpr(const OMPIteratorExpr *Node);
+ void VisitOMPNumArgsExpr(const OMPNumArgsExpr *Node);
void VisitConceptSpecializationExpr(const ConceptSpecializationExpr *Node);
void VisitRequiresExpr(const RequiresExpr *Node);
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index 6a48d74079f4ed..5c826aba1c200f 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -1623,6 +1623,10 @@ def err_omp_unknown_adjust_args_op
: Error<
"incorrect 'adjust_args' type, expected 'need_device_ptr'%select{|, "
"'need_device_addr',}0 or 'nothing'">;
+def err_omp_num_args_invalid_form
+ : Error<"'omp_num_args' %select{is only allowed as a bound of a parameter "
+ "range|may only be followed by '+' or '-' and a constant logical "
+ "offset}0">;
def err_omp_declare_variant_wrong_clause : Error<
"expected %select{'match'|'match', 'adjust_args', or 'append_args'}0 clause "
"on 'omp declare variant' directive">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 3a910c9c3f2b92..090acdfa4cac84 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -12815,6 +12815,9 @@ def err_omp_instantiation_not_supported
: Error<"instantiation of '%0' not supported yet">;
def err_omp_adjust_arg_multiple_clauses : Error<
"'adjust_arg' argument %0 used in multiple clauses">;
+def err_omp_adjust_args_invalid_item : Error<
+ "expected a parameter name, a parameter position, or a parameter range in "
+ "'adjust_args' clause">;
def err_omp_clause_requires_dispatch_construct : Error<
"'%0' clause requires 'dispatch' context selector">;
def err_omp_append_args_with_varargs : Error<
diff --git a/clang/include/clang/Basic/StmtNodes.td b/clang/include/clang/Basic/StmtNodes.td
index 84804d6705d2b9..e9f64565677e9d 100644
--- a/clang/include/clang/Basic/StmtNodes.td
+++ b/clang/include/clang/Basic/StmtNodes.td
@@ -85,6 +85,8 @@ def MatrixSingleSubscriptExpr : StmtNode<Expr>;
def MatrixSubscriptExpr : StmtNode<Expr>;
def ArraySectionExpr : StmtNode<Expr>;
def OMPIteratorExpr : StmtNode<Expr>;
+def OMPNumArgsExpr : StmtNode<Expr>;
+def OMPArgumentRangeExpr : StmtNode<Expr>;
def CallExpr : StmtNode<Expr>;
def MemberExpr : StmtNode<Expr>;
def CastExpr : StmtNode<Expr, 1>;
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 163aa483a84e3f..832835b27edc51 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -7053,6 +7053,17 @@ class Parser : public CodeCompletionHandler {
bool ParseOpenMPReservedLocator(OpenMPClauseKind Kind,
SemaOpenMP::OpenMPVarListDataTy &Data,
const LangOptions &LangOpts);
+
+ /// Parses one bound of an OpenMP 6.0 'adjust_args' parameter range, which may
+ /// be 'omp_num_args' with an optional logical offset, or the whole
+ /// parameter-list item when no range colon follows it.
+ ExprResult ParseOpenMPAdjustArgsBound();
+
+ /// Parses an OpenMP 6.0 'adjust_args' parameter list, whose items may be
+ /// parameter names, positions, or ranges with optional bounds.
+ /// Returns true on error.
+ bool ParseOpenMPAdjustArgsList(SmallVectorImpl<Expr *> &Vars);
+
/// Parses clauses with list.
bool ParseOpenMPVarList(OpenMPDirectiveKind DKind, OpenMPClauseKind Kind,
SmallVectorImpl<Expr *> &Vars,
diff --git a/clang/include/clang/Sema/SemaOpenMP.h b/clang/include/clang/Sema/SemaOpenMP.h
index 361473140e2364..1e1870f4690844 100644
--- a/clang/include/clang/Sema/SemaOpenMP.h
+++ b/clang/include/clang/Sema/SemaOpenMP.h
@@ -1491,6 +1491,19 @@ class SemaOpenMP : public SemaBase {
SourceLocation LLoc, SourceLocation RLoc,
ArrayRef<OMPIteratorData> Data);
+ /// Called on a well-formed 'omp_num_args' expression appearing as a bound of
+ /// an 'adjust_args' parameter range. \a Offset is null if no logical offset
+ /// was written, in which case \a OpLoc is invalid.
+ ExprResult ActOnOMPNumArgsExpr(SourceLocation NumArgsLoc,
+ SourceLocation OpLoc, bool IsSubtraction,
+ Expr *Offset);
+
+ /// Called on a well-formed 'adjust_args' parameter range 'lb:ub'. Either
+ /// bound may be null, meaning 1 for \a LowerBound and 'omp_num_args' for
+ /// \a UpperBound.
+ ExprResult ActOnOMPArgumentRangeExpr(Expr *LowerBound, SourceLocation ColonLoc,
+ Expr *UpperBound);
+
ExprResult ActOnOpenMPDimsModifier(OpenMPClauseKind Kind, int Modifier,
Expr *ModifierExpr,
SourceLocation ModifierLoc,
diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h
index 6a52a9e4fa780f..678b17b63a9ffc 100644
--- a/clang/include/clang/Serialization/ASTBitCodes.h
+++ b/clang/include/clang/Serialization/ASTBitCodes.h
@@ -2057,6 +2057,8 @@ enum StmtCode {
EXPR_ARRAY_SECTION,
EXPR_OMP_ARRAY_SHAPING,
EXPR_OMP_ITERATOR,
+ EXPR_OMP_NUM_ARGS,
+ EXPR_OMP_ARGUMENT_RANGE,
// ARC
EXPR_OBJC_BRIDGED_CAST, // ObjCBridgedCastExpr
diff --git a/clang/lib/AST/AttrImpl.cpp b/clang/lib/AST/AttrImpl.cpp
index 7272ad0de9a2cc..c56fd284451712 100644
--- a/clang/lib/AST/AttrImpl.cpp
+++ b/clang/lib/AST/AttrImpl.cpp
@@ -14,6 +14,7 @@
#include "clang/AST/ASTStructuralEquivalence.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Expr.h"
+#include "clang/AST/ExprOpenMP.h"
#include "clang/AST/Type.h"
#include <optional>
#include <type_traits>
@@ -209,12 +210,20 @@ void OMPDeclareVariantAttr::printPrettyPragma(
OS << " match(" << traitInfos << ")";
auto PrintExprs = [&OS, &Policy](Expr **Begin, Expr **End) {
- for (Expr **I = Begin; I != End; ++I) {
- assert(*I && "Expected non-null Stmt");
- if (I != Begin)
- OS << ",";
- (*I)->printPretty(OS, nullptr, Policy);
+ if (Begin != End) {
+ if (const auto *Range = dyn_cast<OMPArgumentRangeExpr>(*Begin);
+ Range && !Range->getLowerBound())
+ // A first item with an omitted lower bound starts with ':', which
+ // would form a '::' token together with the adjust-op separator.
+ OS << " ";
}
+ llvm::interleave(
+ Begin, End,
+ [&](Expr *E) {
+ assert(E && "Expected non-null Stmt");
+ E->printPretty(OS, nullptr, Policy);
+ },
+ [&OS] { OS << ","; });
};
if (adjustArgsNothing_size()) {
OS << " adjust_args(nothing:";
diff --git a/clang/lib/AST/ComputeDependence.cpp b/clang/lib/AST/ComputeDependence.cpp
index 0fc9da18b93940..4f33ed8beca4e3 100644
--- a/clang/lib/AST/ComputeDependence.cpp
+++ b/clang/lib/AST/ComputeDependence.cpp
@@ -500,6 +500,26 @@ ExprDependence clang::computeDependence(OMPIteratorExpr *E) {
return D;
}
+ExprDependence clang::computeDependence(OMPNumArgsExpr *E) {
+ // The type is always 'int', so the expression is never type-dependent; only
+ // the logical offset can make it value- or instantiation-dependent.
+ if (Expr *Offset = E->getOffset())
+ return Offset->getDependence() & ~ExprDependence::Type;
+ return ExprDependence::None;
+}
+
+ExprDependence clang::computeDependence(OMPArgumentRangeExpr *E) {
+ // The type is always 'void', so the expression is never type-dependent.
+ // Either bound may be omitted, meaning 1 for the lower bound and
+ // 'omp_num_args' for the upper bound.
+ auto D = ExprDependence::None;
+ if (Expr *LB = E->getLowerBound())
+ D |= LB->getDependence();
+ if (Expr *UB = E->getUpperBound())
+ D |= UB->getDependence();
+ return D & ~ExprDependence::Type;
+}
+
/// Compute the type-, value-, and instantiation-dependence of a
/// declaration reference
/// based on the declaration being referenced.
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index e501527ed9b04b..90622bde37beda 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -3831,6 +3831,8 @@ bool Expr::HasSideEffects(const ASTContext &Ctx,
case ArraySectionExprClass:
case OMPArrayShapingExprClass:
case OMPIteratorExprClass:
+ case OMPNumArgsExprClass:
+ case OMPArgumentRangeExprClass:
case MemberExprClass:
case ConditionalOperatorClass:
case BinaryConditionalOperatorClass:
diff --git a/clang/lib/AST/ExprClassification.cpp b/clang/lib/AST/ExprClassification.cpp
index eebae17d7b948b..6f3cd087b2660b 100644
--- a/clang/lib/AST/ExprClassification.cpp
+++ b/clang/lib/AST/ExprClassification.cpp
@@ -220,6 +220,8 @@ static Cl::Kinds ClassifyInternal(ASTContext &Ctx, const Expr *E) {
case Expr::RequiresExprClass:
case Expr::CXXReflectExprClass:
case Expr::CXXExpansionSelectExprClass:
+ case Expr::OMPNumArgsExprClass:
+ case Expr::OMPArgumentRangeExprClass:
return Cl::CL_PRValue;
case Expr::EmbedExprClass:
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index d55749100658f7..034142aac4c706 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -22398,6 +22398,8 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) {
case Expr::ArraySectionExprClass:
case Expr::OMPArrayShapingExprClass:
case Expr::OMPIteratorExprClass:
+ case Expr::OMPNumArgsExprClass:
+ case Expr::OMPArgumentRangeExprClass:
case Expr::CompoundAssignOperatorClass:
case Expr::CompoundLiteralExprClass:
case Expr::ExtVectorElementExprClass:
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index 7fb162a68fe886..64d81b17ed278e 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -5013,6 +5013,8 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity,
case Expr::ArraySectionExprClass:
case Expr::OMPArrayShapingExprClass:
case Expr::OMPIteratorExprClass:
+ case Expr::OMPNumArgsExprClass:
+ case Expr::OMPArgumentRangeExprClass:
case Expr::CXXInheritedCtorInitExprClass:
case Expr::CXXParenListInitExprClass:
case Expr::CXXExpansionSelectExprClass:
diff --git a/clang/lib/AST/OpenMPClause.cpp b/clang/lib/AST/OpenMPClause.cpp
index cac701994def82..7e471d25c76b44 100644
--- a/clang/lib/AST/OpenMPClause.cpp
+++ b/clang/lib/AST/OpenMPClause.cpp
@@ -3269,3 +3269,91 @@ bool TargetOMPContext::matchesISATrait(StringRef RawString) const {
DiagUnknownTrait(RawString);
return false;
}
+
+/// Evaluate one bound of an 'adjust_args' parameter range: either an
+/// 'omp_num_args [+- logical_offset]' expression, or a plain constant integer
+/// expression. Returns false if the bound is dependent or not constant.
+static bool evalOMPAdjustArgsBound(const Expr *Bound, unsigned NumArgs,
+ const ASTContext &Ctx, int64_t &Result) {
+ Bound = Bound->IgnoreParenImpCasts();
+ if (const auto *NumArgsExpr = dyn_cast<OMPNumArgsExpr>(Bound)) {
+ int64_t Offset = 0;
+ if (const Expr *OffsetExpr = NumArgsExpr->getOffset()) {
+ if (OffsetExpr->isValueDependent())
+ return false;
+ std::optional<llvm::APSInt> Val =
+ OffsetExpr->getIntegerConstantExpr(Ctx);
+ if (!Val)
+ return false;
+ Offset = Val->getExtValue();
+ }
+ Result = static_cast<int64_t>(NumArgs) +
+ (NumArgsExpr->isSubtraction() ? -Offset : Offset);
+ return true;
+ }
+ if (Bound->isValueDependent())
+ return false;
+ std::optional<llvm::APSInt> Val = Bound->getIntegerConstantExpr(Ctx);
+ if (!Val)
+ return false;
+ Result = Val->getExtValue();
+ return true;
+}
+
+bool clang::resolveOMPAdjustArgsItem(const Expr *Item, const FunctionDecl *FD,
+ unsigned NumArgs, const ASTContext &Ctx,
+ SmallVectorImpl<unsigned> &Positions) {
+ auto AppendIfInRange = [&](int64_t Pos) {
+ if (Pos >= 1 && Pos <= static_cast<int64_t>(NumArgs))
+ Positions.push_back(static_cast<unsigned>(Pos));
+ };
+
+ // A named parameter list item.
+ if (const auto *DRE = dyn_cast<DeclRefExpr>(Item)) {
+ const auto *PVD = dyn_cast<ParmVarDecl>(DRE->getDecl());
+ if (!PVD)
+ return false;
+ unsigned Index = PVD->getFunctionScopeIndex();
+ if (FD->getNumParams() > Index &&
+ FD->getParamDecl(Index)->getCanonicalDecl() ==
+ PVD->getCanonicalDecl())
+ AppendIfInRange(static_cast<int64_t>(Index) + 1);
+ return true;
+ }
+
+ // A parameter range 'lb:ub'. An omitted lb defaults to 1, an omitted ub to
+ // 'NumArgs' (OpenMP 6.0 [5.2.1] p163).
+ if (const auto *Range = dyn_cast<OMPArgumentRangeExpr>(Item)) {
+ int64_t Lower = 1;
+ if (const Expr *LB = Range->getLowerBound()) {
+ if (!evalOMPAdjustArgsBound(LB, NumArgs, Ctx, Lower))
+ return false;
+ }
+ int64_t Upper = NumArgs;
+ if (const Expr *UB = Range->getUpperBound()) {
+ if (!evalOMPAdjustArgsBound(UB, NumArgs, Ctx, Upper))
+ return false;
+ }
+ // Clamp before looping, not just inside it: an out-of-range literal bound
+ // (e.g. 'omp_num_args-1:9223372036854775807') must not turn this into an
+ // unbounded loop.
+ Lower = std::max<int64_t>(Lower, 1);
+ Upper = std::min<int64_t>(Upper, NumArgs);
+ for (int64_t Pos = Lower; Pos <= Upper; ++Pos)
+ AppendIfInRange(Pos);
+ return true;
+ }
+
+ // The position of a parameter, given as a constant integer expression.
+ if (Item->getType()->isIntegerType()) {
+ if (Item->isValueDependent())
+ return false;
+ std::optional<llvm::APSInt> Val = Item->getIntegerConstantExpr(Ctx);
+ if (!Val)
+ return false;
+ AppendIfInRange(Val->getExtValue());
+ return true;
+ }
+
+ return false;
+}
diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp
index e957b03b7e8985..3349f2533c8511 100644
--- a/clang/lib/AST/StmtPrinter.cpp
+++ b/clang/lib/AST/StmtPrinter.cpp
@@ -1814,6 +1814,26 @@ void StmtPrinter::VisitOMPIteratorExpr(OMPIteratorExpr *Node) {
OS << ")";
}
+void StmtPrinter::VisitOMPNumArgsExpr(OMPNumArgsExpr *Node) {
+ OS << "omp_num_args";
+ if (Expr *Offset = Node->getOffset()) {
+ // No surrounding spaces, so that the sign of the logical offset cannot be
+ // misread as part of a neighbouring parameter range colon.
+ OS << (Node->isSubtraction() ? "-" : "+");
+ PrintExpr(Offset);
+ }
+}
+
+void StmtPrinter::VisitOMPArgumentRangeExpr(OMPArgumentRangeExpr *Node) {
+ // An omitted bound is simply not printed: `lb:`, `:ub` and `:` are all
+ // spellings the parser accepts.
+ if (Expr *LowerBound = Node->getLowerBound())
+ PrintExpr(LowerBound);
+ OS << ":";
+ if (Expr *UpperBound = Node->getUpperBound())
+ PrintExpr(UpperBound);
+}
+
void StmtPrinter::PrintCallArgs(CallExpr *Call) {
for (unsigned i = 0, e = Call->getNumArgs(); i != e; ++i) {
if (isa<CXXDefaultArgExpr>(Call->getArg(i))) {
diff --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp
index 08b42c1477664b..c06a1e07def8b6 100644
--- a/clang/lib/AST/StmtProfile.cpp
+++ b/clang/lib/AST/StmtProfile.cpp
@@ -1594,6 +1594,19 @@ void StmtProfiler::VisitOMPIteratorExpr(const OMPIteratorExpr *S) {
VisitDecl(S->getIteratorDecl(I));
}
+void StmtProfiler::VisitOMPNumArgsExpr(const OMPNumArgsExpr *S) {
+ VisitExpr(S);
+ // The sign is not recoverable from the children: 'omp_num_args+1' and
+ // 'omp_num_args-1' have the same offset expression.
+ ID.AddBoolean(S->isSubtraction());
+}
+
+void StmtProfiler::VisitOMPArgumentRangeExpr(const OMPArgumentRangeExpr *S) {
+ // Both bounds are children, and an omitted bound is a null child, so 'lb:'
+ // and ':ub' profile differently.
+ VisitExpr(S);
+}
+
void StmtProfiler::VisitCallExpr(const CallExpr *S) {
VisitExpr(S);
}
diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp
index f58cc4f5761b7b..8afeb97f01042e 100644
--- a/clang/lib/AST/TextNodeDumper.cpp
+++ b/clang/lib/AST/TextNodeDumper.cpp
@@ -2051,6 +2051,12 @@ void TextNodeDumper::VisitOMPIteratorExpr(const OMPIteratorExpr *Node) {
}
}
+void TextNodeDumper::VisitOMPNumArgsExpr(const OMPNumArgsExpr *Node) {
+ // The offset itself is dumped as a child; only its sign needs printing.
+ if (Node->getOffset())
+ OS << " '" << (Node->isSubtraction() ? '-' : '+') << "'";
+}
+
void TextNodeDumper::VisitConceptSpecializationExpr(
const ConceptSpecializationExpr *Node) {
OS << " ";
diff --git a/clang/lib/Parse/ParseOpenMP.cpp b/clang/lib/Parse/ParseOpenMP.cpp
index 30b6c64e69f4c0..170b3955439d4f 100644
--- a/clang/lib/Parse/ParseOpenMP.cpp
+++ b/clang/lib/Parse/ParseOpenMP.cpp
@@ -4805,6 +4805,99 @@ bool Parser::ParseOpenMPReservedLocator(OpenMPClauseKind Kind,
return false;
}
+ExprResult Parser::ParseOpenMPAdjustArgsBound() {
+ // 'omp_num_args' is recognised by spelling: OpenMP 6.0 [5.2.1] gives it no
+ // declaration, so it is never looked up.
+ if (Tok.isNot(tok::identifier) ||
+ !Tok.getIdentifierInfo()->isStr("omp_num_args"))
+ return ParseAssignmentExpression();
+
+ SourceLocation NumArgsLoc = ConsumeToken();
+ SourceLocation OpLoc;
+ bool IsSubtraction = false;
+ ExprResult Offset;
+ if (Tok.isOneOf(tok::plus, tok::minus)) {
+ IsSubtraction = Tok.is(tok::minus);
+ OpLoc = ConsumeToken();
+ // OpenMP 6.0 [5.2.1]: the logical offset is a constant expression.
+ Offset = ParseConstantExpression();
+ if (Offset.isInvalid())
+ return ExprError();
+ }
+ // 'omp_num_args' is a whole bound, not an operand of a larger expression, so
+ // nothing else may follow it.
+ if (!Tok.isOneOf(tok::colon, tok::comma, tok::r_paren,
+ tok::annot_pragma_openmp_end)) {
+ Diag(Tok, diag::err_omp_num_args_invalid_form) << 1;
+ return ExprError();
+ }
+ return Actions.OpenMP().ActOnOMPNumArgsExpr(NumArgsLoc, OpLoc, IsSubtraction,
+ Offset.get());
+}
+
+bool Parser::ParseOpenMPAdjustArgsList(SmallVectorImpl<Expr *> &Vars) {
+ bool IsError = false;
+ while (true) {
+ // An omitted lower bound stands for 1, and is written as a leading ':'.
+ ExprResult LowerBound;
+ if (Tok.isNot(tok::colon)) {
+ LowerBound = ParseOpenMPAdjustArgsBound();
+ if (!LowerBound.isUsable()) {
+ IsError = true;
+ SkipUntil(tok::comma, tok::r_paren, tok::annot_pragma_openmp_end,
+ StopBeforeMatch);
+ }
+ }
+
+ if (Tok.is(tok::colon)) {
+ SourceLocation ColonLoc = ConsumeToken();
+ // An omitted upper bound stands for 'omp_num_args'.
+ ExprResult UpperBound;
+ if (!Tok.isOneOf(tok::comma, tok::r_paren,
+ tok::annot_pragma_openmp_end)) {
+ UpperBound = ParseOpenMPAdjustArgsBound();
+ if (!UpperBound.isUsable()) {
+ IsError = true;
+ SkipUntil(tok::comma, tok::r_paren, tok::annot_pragma_openmp_end,
+ StopBeforeMatch);
+ }
+ }
+ Vars.push_back(Actions.OpenMP()
+ .ActOnOMPArgumentRangeExpr(LowerBound.get(), ColonLoc,
+ UpperBound.get())
+ .get());
+ } else if (LowerBound.isUsable()) {
+ // Without a colon the item is a parameter name or a position, and is
+ // pushed unchanged so that pre-6.0 lists keep their exact AST shape.
+ if (isa<OMPNumArgsExpr>(LowerBound.get())) {
+ Diag(LowerBound.get()->getBeginLoc(),
+ diag::err_omp_num_args_invalid_form)
+ << 0;
+ IsError = true;
+ } else {
+ Vars.push_back(LowerBound.get());
+ }
+ }
+
+ // Separator handling mirrors the shared var-list loop, so that a malformed
+ // item such as '1:2:3' diagnoses instead of silently mis-parsing.
+ if (Tok.isNot(tok::comma)) {
+ if (Tok.isNot(tok::r_paren) &&
+ Tok.isNot(tok::annot_pragma_openmp_end)) {
+ Diag(Tok, diag::err_omp_expected_punc)
+ << getOpenMPClauseName(OMPC_adjust_args) << /*IsDirective=*/0;
+ IsError = true;
+ SkipUntil(tok::comma, tok::r_paren, tok::annot_pragma_openmp_end,
+ StopBeforeMatch);
+ }
+ if (Tok.isNot(tok::comma))
+ break;
+ }
+ ConsumeToken();
+ }
+ return IsError;
+}
+
/// Parse step size expression. Returns true if parsing is successfull,
/// otherwise returns false.
static bool parseStepSize(Parser &P, SemaOpenMP::OpenMPVarListDataTy &Data,
@@ -4927,6 +5020,11 @@ bool Parser::ParseOpenMPVarList(OpenMPDirectiveKind DKind,
bool HasIterator = false;
bool InvalidIterator = false;
bool NeedRParenForLinear = false;
+ // Set when the OpenMP 6.0 'adjust_args' parameter list has already been
+ // parsed by ParseOpenMPAdjustArgsList, so the shared list loop must be
+ // skipped.
+ bool ParsedAdjustArgsList = false;
+ bool InvalidAdjustArgsList = false;
BalancedDelimiterTracker LinearT(*this, tok::l_paren,
tok::annot_pragma_openmp_end);
// Handle reduction-identifier for reduction clause.
@@ -5311,6 +5409,14 @@ bool Parser::ParseOpenMPVarList(OpenMPDirectiveKind DKind,
}
ExpectAndConsume(tok::colon, diag::warn_pragma_expected_colon,
"adjust-op");
+ // OpenMP 6.0 [5.2.1, Parameter List Items] adds positions and parameter
+ // ranges, which the shared list loop below cannot express because a range
+ // colon terminates it.
+ if (getLangOpts().OpenMP >= 60 &&
+ Data.ExtraModifier != OMPC_ADJUST_ARGS_unknown) {
+ ParsedAdjustArgsList = true;
+ InvalidAdjustArgsList = ParseOpenMPAdjustArgsList(Vars);
+ }
}
} else if (Kind == OMPC_use_device_ptr) {
// Handle optional fallback modifier for use_device_ptr clause.
@@ -5413,8 +5519,9 @@ bool Parser::ParseOpenMPVarList(OpenMPDirectiveKind DKind,
(Kind == OMPC_adjust_args &&
Data.ExtraModifier != OMPC_ADJUST_ARGS_unknown);
const bool MayHaveTail = (Kind == OMPC_linear || Kind == OMPC_aligned);
- while (IsComma || (Tok.isNot(tok::r_paren) && Tok.isNot(tok::colon) &&
- Tok.isNot(tok::annot_pragma_openmp_end))) {
+ while (!ParsedAdjustArgsList &&
+ (IsComma || (Tok.isNot(tok::r_paren) && Tok.isNot(tok::colon) &&
+ Tok.isNot(tok::annot_pragma_openmp_end)))) {
ParseScope OMPListScope(this, Scope::OpenMPDirectiveScope);
ColonProtectionRAIIObject ColonRAII(*this, MayHaveTail);
if (!ParseOpenMPReservedLocator(Kind, Data, getLangOpts())) {
@@ -5534,7 +5641,8 @@ bool Parser::ParseOpenMPVarList(OpenMPDirectiveKind DKind,
return (Kind != OMPC_depend && Kind != OMPC_doacross && Kind != OMPC_map &&
Vars.empty()) ||
(MustHaveTail && !Data.DepModOrTailExpr && StepFound) ||
- InvalidReductionId || IsInvalidMapperModifier || InvalidIterator;
+ InvalidReductionId || IsInvalidMapperModifier || InvalidIterator ||
+ InvalidAdjustArgsList;
}
OMPClause *Parser::ParseOpenMPVarListClause(OpenMPDirectiveKind DKind,
diff --git a/clang/lib/Sema/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp
index 8f08e17276ef38..fb7b1d109cf5c7 100644
--- a/clang/lib/Sema/SemaExceptionSpec.cpp
+++ b/clang/lib/Sema/SemaExceptionSpec.cpp
@@ -1323,6 +1323,8 @@ CanThrowResult Sema::canThrow(const Stmt *S) {
case Expr::ArraySectionExprClass:
case Expr::OMPArrayShapingExprClass:
case Expr::OMPIteratorExprClass:
+ case Expr::OMPNumArgsExprClass:
+ case Expr::OMPArgumentRangeExprClass:
case Expr::BinaryOperatorClass:
case Expr::DependentCoawaitExprClass:
case Expr::CompoundAssignOperatorClass:
diff --git a/clang/lib/Sema/SemaOpenMP.cpp b/clang/lib/Sema/SemaOpenMP.cpp
index b39dd853ab378b..f32e37d85c11bb 100644
--- a/clang/lib/Sema/SemaOpenMP.cpp
+++ b/clang/lib/Sema/SemaOpenMP.cpp
@@ -7900,6 +7900,67 @@ static bool checkPreferTypeArgs(SemaOpenMP &S, const OMPInteropInfo &Info) {
return true;
}
+enum class OMPAdjustArgsVal { Known, Dependent, Invalid };
+
+/// Check one 'adjust_args' integer expression for the constant property plus
+/// either the positive or the non-negative property (OpenMP 6.0 [5.2.1] p162
+/// L32-33, p163 L1).
+static OMPAdjustArgsVal checkOMPAdjustArgsValue(SemaOpenMP &S, Expr *E,
+ bool StrictlyPositive,
+ llvm::APSInt &Result) {
+ if (E->isValueDependent() || E->isTypeDependent() ||
+ E->isInstantiationDependent())
+ return OMPAdjustArgsVal::Dependent; // re-checked on instantiation
+ if (S.SemaRef.VerifyIntegerConstantExpression(E, &Result).isInvalid())
+ return OMPAdjustArgsVal::Invalid; // already diagnosed
+ if (StrictlyPositive ? !Result.isStrictlyPositive() : Result.isNegative()) {
+ S.Diag(E->getExprLoc(), diag::err_omp_negative_expression_in_clause)
+ << getOpenMPClauseNameForDiag(OMPC_adjust_args)
+ << (StrictlyPositive ? 1 : 0) << E->getSourceRange();
+ return OMPAdjustArgsVal::Invalid;
+ }
+ return OMPAdjustArgsVal::Known;
+}
+
+/// Check both bounds of a parameter range 'lb:ub' (OpenMP 6.0 [5.2.1]
+/// p162-163). A plain bound has the positive property, checked below via
+/// checkOMPAdjustArgsValue. An 'omp_num_args' bound's logical_offset has the
+/// non-negative property instead; its constant property was already checked
+/// when it was parsed (ActOnOMPNumArgsExpr), so only the sign is checked here
+/// via a direct read of the already-verified value.
+///
+/// Deliberately does not diagnose 'lb > ub': an empty range specifies no
+/// parameters, which OpenMP 6.0 [5.2.1] p163's "as if specified individually"
+/// makes harmless, and the spec places no restriction on it.
+static bool checkOMPAdjustArgsRange(SemaOpenMP &S,
+ OMPArgumentRangeExpr *Range) {
+ for (Expr *Bound : {Range->getLowerBound(), Range->getUpperBound()}) {
+ if (!Bound)
+ continue;
+ if (auto *NumArgsExpr =
+ dyn_cast<OMPNumArgsExpr>(Bound->IgnoreParenImpCasts())) {
+ Expr *Offset = NumArgsExpr->getOffset();
+ if (!Offset || Offset->isValueDependent())
+ continue;
+ std::optional<llvm::APSInt> Val =
+ Offset->getIntegerConstantExpr(S.getASTContext());
+ if (Val && Val->isNegative()) {
+ S.Diag(Offset->getExprLoc(),
+ diag::err_omp_negative_expression_in_clause)
+ << getOpenMPClauseNameForDiag(OMPC_adjust_args) << 0
+ << Offset->getSourceRange();
+ return false;
+ }
+ continue;
+ }
+ llvm::APSInt Result;
+ if (checkOMPAdjustArgsValue(S, Bound, /*StrictlyPositive=*/true, Result) ==
+ OMPAdjustArgsVal::Invalid)
+ return false;
+ }
+ return true;
+}
+
void SemaOpenMP::ActOnOpenMPDeclareVariantDirective(
FunctionDecl *FD, Expr *VariantRef, OMPTraitInfo &TI,
ArrayRef<Expr *> AdjustArgsNothing,
@@ -7934,20 +7995,26 @@ void SemaOpenMP::ActOnOpenMPDeclareVariantDirective(
}
}
- // OpenMP 5.1 [2.3.5, declare variant directive, Restrictions]
- // Each argument can only appear in a single adjust_args clause for each
- // declare variant directive.
- llvm::SmallPtrSet<const VarDecl *, 4> AdjustVars;
+ // OpenMP 6.0 [5.2.1] Restrictions (p162): unless otherwise specified, any
+ // given parameter list item can only be specified once across all clauses
+ // of the same type in a given directive. The subject is the *item*, not the
+ // parameter it identifies, so a range never participates in the check below
+ // and a name does not collide with a position that happens to resolve to
+ // it. In 5.1 this restriction lived in the declare variant directive's own
+ // restrictions and was worded per-argument rather than per-item.
+ llvm::SmallPtrSet<const VarDecl *, 4> AdjustVars; // named items
+ llvm::SmallSet<uint64_t, 4> AdjustPositions; // literal positional items
for (Expr *E : AllAdjustArgs) {
- E = E->IgnoreParenImpCasts();
- if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) {
+ Expr *Item = E->IgnoreParenImpCasts();
+
+ // A named parameter list item — unchanged from OpenMP 5.1.
+ if (const auto *DRE = dyn_cast<DeclRefExpr>(Item)) {
if (const auto *PVD = dyn_cast<ParmVarDecl>(DRE->getDecl())) {
const VarDecl *CanonPVD = PVD->getCanonicalDecl();
if (FD->getNumParams() > PVD->getFunctionScopeIndex() &&
FD->getParamDecl(PVD->getFunctionScopeIndex())
->getCanonicalDecl() == CanonPVD) {
- // It's a parameter of the function, check duplicates.
if (!AdjustVars.insert(CanonPVD).second) {
Diag(DRE->getLocation(), diag::err_omp_adjust_arg_multiple_clauses)
<< PVD;
@@ -7957,23 +8024,76 @@ void SemaOpenMP::ActOnOpenMPDeclareVariantDirective(
}
}
}
- // Anything that is not a function parameter is an error.
- Diag(E->getExprLoc(), diag::err_omp_param_or_this_in_clause) << FD << 0;
+
+ if (getLangOpts().OpenMP < 60) {
+ // Anything that is not a function parameter is an error before 6.0.
+ Diag(Item->getExprLoc(), diag::err_omp_param_or_this_in_clause)
+ << FD << 0;
+ return;
+ }
+
+ // OpenMP 6.0 [5.2.1] p162 L32-33: a parameter range 'lb:ub'. A range is
+ // exempt from the duplicate restriction above — it is one item
+ // identifying one or more parameters — so nothing is recorded for dedup.
+ if (auto *Range = dyn_cast<OMPArgumentRangeExpr>(Item)) {
+ if (!checkOMPAdjustArgsRange(*this, Range))
+ return;
+ continue;
+ }
+
+ // A bare 'omp_num_args' is rejected by the parser (ParseOpenMPAdjustArgs-
+ // List), so it can only ever reach here as a range bound, already handled
+ // above.
+ assert(!isa<OMPNumArgsExpr>(Item) && "bare omp_num_args reached Sema");
+
+ // OpenMP 6.0 [5.2.1] p162 L30-31: the position of a parameter, given as a
+ // positive constant integer expression. A dependent item is skipped here
+ // and rechecked when the template is instantiated.
+ if (Item->getType()->isIntegerType()) {
+ llvm::APSInt Pos;
+ switch (checkOMPAdjustArgsValue(*this, Item, /*StrictlyPositive=*/true,
+ Pos)) {
+ case OMPAdjustArgsVal::Invalid:
+ return;
+ case OMPAdjustArgsVal::Dependent:
+ continue;
+ case OMPAdjustArgsVal::Known:
+ if (!AdjustPositions.insert(Pos.getZExtValue()).second) {
+ Diag(Item->getExprLoc(), diag::err_omp_adjust_arg_multiple_clauses)
+ << static_cast<unsigned>(Pos.getZExtValue());
+ return;
+ }
+ continue;
+ }
+ }
+
+ // Not a name, a range, or a position: none of the three forms OpenMP 6.0
+ // [5.2.1] p162 L28-33 allows.
+ Diag(Item->getExprLoc(), diag::err_omp_adjust_args_invalid_item);
return;
}
- // OpenMP 6.0 [9.6.2 (page 332, line 31-33, adjust_args clause, Restrictions]
- // If the `need_device_addr` adjust-op modifier is present, each list item
- // that appears in the clause must refer to an argument in the declaration of
- // the function variant that has a reference type
+ // OpenMP 6.0 [9.6.2] p332 L31-33: if the need_device_addr adjust-op modifier
+ // is present, each list item that appears in the clause must refer to an
+ // argument in the declaration of the function variant that has a reference
+ // type. Unlike the need_device_ptr restriction, this one is not scoped to
+ // named items ("that refers to a specific named argument" is absent here),
+ // so positions and ranges are checked too, via the shared item-to-positions
+ // resolver.
if (getLangOpts().OpenMP >= 60) {
for (Expr *E : AdjustArgsNeedDeviceAddr) {
- E = E->IgnoreParenImpCasts();
- if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) {
- if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
- if (!VD->getType()->isReferenceType())
- Diag(E->getExprLoc(),
- diag::err_omp_non_by_ref_need_device_addr_modifier_argument);
+ SmallVector<unsigned, 8> Positions;
+ // With no call site in hand, 'omp_num_args' is the declared parameter
+ // count (OpenMP 6.0 [20.1] p534). Positions past it denote variadic
+ // actuals, which have no declared parameter to check against, and are
+ // dropped by the resolver (OpenMP 6.0 [9.6.2] p332 L1-2).
+ resolveOMPAdjustArgsItem(E->IgnoreParenImpCasts(), FD, FD->getNumParams(),
+ getASTContext(), Positions);
+ for (unsigned Pos : Positions) {
+ if (!FD->getParamDecl(Pos - 1)->getType()->isReferenceType()) {
+ Diag(E->getExprLoc(),
+ diag::err_omp_non_by_ref_need_device_addr_modifier_argument);
+ break; // One diagnostic per written item, not per swept position.
}
}
}
@@ -26968,6 +27088,38 @@ ExprResult SemaOpenMP::ActOnOMPIteratorExpr(Scope *S,
LLoc, RLoc, ID, Helpers);
}
+ExprResult SemaOpenMP::ActOnOMPNumArgsExpr(SourceLocation NumArgsLoc,
+ SourceLocation OpLoc,
+ bool IsSubtraction, Expr *Offset) {
+ ASTContext &Context = getASTContext();
+ // OpenMP 6.0 [5.2.1, Parameter List Items]
+ // The logical offset is a constant integer expression. Leave a dependent
+ // offset alone; it is rechecked when the template is instantiated.
+ if (Offset && !Offset->isValueDependent()) {
+ ExprResult Res =
+ SemaRef.VerifyIntegerConstantExpression(Offset, /*Result=*/nullptr);
+ if (Res.isInvalid())
+ return ExprError();
+ Offset = Res.get();
+ }
+ // The value stands for a number of arguments, so the expression is an int
+ // even though it cannot be evaluated until the parameter list is consumed.
+ return new (Context) OMPNumArgsExpr(Context.IntTy, NumArgsLoc, OpLoc,
+ IsSubtraction, Offset);
+}
+
+ExprResult SemaOpenMP::ActOnOMPArgumentRangeExpr(Expr *LowerBound,
+ SourceLocation ColonLoc,
+ Expr *UpperBound) {
+ ASTContext &Context = getASTContext();
+ // Whether the bounds denote parameters that actually exist depends on the
+ // function the enclosing 'adjust_args' clause is attached to, so it is
+ // checked in ActOnOpenMPDeclareVariantDirective rather than here. A range is
+ // never a value, hence 'void'.
+ return new (Context)
+ OMPArgumentRangeExpr(Context.VoidTy, LowerBound, ColonLoc, UpperBound);
+}
+
/// Check if \p AssumptionStr is a known assumption and warn if not.
static void checkOMPAssumeAttr(Sema &S, SourceLocation Loc,
StringRef AssumptionStr) {
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 4799f72dd61774..28d83cce1f78a2 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -2947,6 +2947,28 @@ class TreeTransform {
/*Scope=*/nullptr, IteratorKwLoc, LLoc, RLoc, Data);
}
+ /// Build a new 'omp_num_args' expression.
+ ///
+ /// By default, performs semantic analysis to build the new expression.
+ /// Subclasses may override this routine to provide different behavior.
+ ExprResult RebuildOMPNumArgsExpr(SourceLocation NumArgsLoc,
+ SourceLocation OpLoc, bool IsSubtraction,
+ Expr *Offset) {
+ return getSema().OpenMP().ActOnOMPNumArgsExpr(NumArgsLoc, OpLoc,
+ IsSubtraction, Offset);
+ }
+
+ /// Build a new parameter range expression.
+ ///
+ /// By default, performs semantic analysis to build the new expression.
+ /// Subclasses may override this routine to provide different behavior.
+ ExprResult RebuildOMPArgumentRangeExpr(Expr *LowerBound,
+ SourceLocation ColonLoc,
+ Expr *UpperBound) {
+ return getSema().OpenMP().ActOnOMPArgumentRangeExpr(LowerBound, ColonLoc,
+ UpperBound);
+ }
+
/// Build a new call expression.
///
/// By default, performs semantic analysis to build the new expression.
@@ -14026,6 +14048,48 @@ TreeTransform<Derived>::TransformOMPIteratorExpr(OMPIteratorExpr *E) {
return Res;
}
+template <typename Derived>
+ExprResult TreeTransform<Derived>::TransformOMPNumArgsExpr(OMPNumArgsExpr *E) {
+ ExprResult Offset;
+ if (E->getOffset()) {
+ Offset = getDerived().TransformExpr(E->getOffset());
+ if (Offset.isInvalid())
+ return ExprError();
+ }
+
+ if (!getDerived().AlwaysRebuild() && Offset.get() == E->getOffset())
+ return E;
+
+ return getDerived().RebuildOMPNumArgsExpr(
+ E->getNumArgsLoc(), E->getOperatorLoc(), E->isSubtraction(), Offset.get());
+}
+
+template <typename Derived>
+ExprResult
+TreeTransform<Derived>::TransformOMPArgumentRangeExpr(OMPArgumentRangeExpr *E) {
+ ExprResult LowerBound;
+ if (E->getLowerBound()) {
+ LowerBound = getDerived().TransformExpr(E->getLowerBound());
+ if (LowerBound.isInvalid())
+ return ExprError();
+ }
+
+ ExprResult UpperBound;
+ if (E->getUpperBound()) {
+ UpperBound = getDerived().TransformExpr(E->getUpperBound());
+ if (UpperBound.isInvalid())
+ return ExprError();
+ }
+
+ if (!getDerived().AlwaysRebuild() &&
+ LowerBound.get() == E->getLowerBound() &&
+ UpperBound.get() == E->getUpperBound())
+ return E;
+
+ return getDerived().RebuildOMPArgumentRangeExpr(
+ LowerBound.get(), E->getColonLoc(), UpperBound.get());
+}
+
template<typename Derived>
ExprResult
TreeTransform<Derived>::TransformCallExpr(CallExpr *E) {
diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp
index 92c555dc427b3d..dace5720f038d8 100644
--- a/clang/lib/Serialization/ASTReaderStmt.cpp
+++ b/clang/lib/Serialization/ASTReaderStmt.cpp
@@ -1063,6 +1063,21 @@ void ASTStmtReader::VisitOMPIteratorExpr(OMPIteratorExpr *E) {
}
}
+void ASTStmtReader::VisitOMPNumArgsExpr(OMPNumArgsExpr *E) {
+ VisitExpr(E);
+ E->setOffset(Record.readSubExpr());
+ E->setNumArgsLoc(readSourceLocation());
+ E->setOperatorLoc(readSourceLocation());
+ E->setIsSubtraction(Record.readInt() != 0);
+}
+
+void ASTStmtReader::VisitOMPArgumentRangeExpr(OMPArgumentRangeExpr *E) {
+ VisitExpr(E);
+ E->setLowerBound(Record.readSubExpr());
+ E->setUpperBound(Record.readSubExpr());
+ E->setColonLoc(readSourceLocation());
+}
+
void ASTStmtReader::VisitCallExpr(CallExpr *E) {
VisitExpr(E);
@@ -3395,6 +3410,14 @@ Stmt *ASTReader::ReadStmtFromStream(ModuleFile &F) {
Record[ASTStmtReader::NumExprFields]);
break;
+ case EXPR_OMP_NUM_ARGS:
+ S = new (Context) OMPNumArgsExpr(Empty);
+ break;
+
+ case EXPR_OMP_ARGUMENT_RANGE:
+ S = new (Context) OMPArgumentRangeExpr(Empty);
+ break;
+
case EXPR_CALL: {
auto NumArgs = Record[ASTStmtReader::NumExprFields];
BitsUnpacker CallExprBits(Record[ASTStmtReader::NumExprFields + 1]);
diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp
index 782fecdbd0c805..c3f60b736cdbdd 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -998,6 +998,23 @@ void ASTStmtWriter::VisitOMPIteratorExpr(OMPIteratorExpr *E) {
Code = serialization::EXPR_OMP_ITERATOR;
}
+void ASTStmtWriter::VisitOMPNumArgsExpr(OMPNumArgsExpr *E) {
+ VisitExpr(E);
+ Record.AddStmt(E->getOffset());
+ Record.AddSourceLocation(E->getNumArgsLoc());
+ Record.AddSourceLocation(E->getOperatorLoc());
+ Record.push_back(E->isSubtraction() ? 1 : 0);
+ Code = serialization::EXPR_OMP_NUM_ARGS;
+}
+
+void ASTStmtWriter::VisitOMPArgumentRangeExpr(OMPArgumentRangeExpr *E) {
+ VisitExpr(E);
+ Record.AddStmt(E->getLowerBound());
+ Record.AddStmt(E->getUpperBound());
+ Record.AddSourceLocation(E->getColonLoc());
+ Code = serialization::EXPR_OMP_ARGUMENT_RANGE;
+}
+
void ASTStmtWriter::VisitCallExpr(CallExpr *E) {
VisitExpr(E);
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index e6349eb4eba2ae..19c179f77402ec 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -1939,6 +1939,8 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
case Stmt::ArraySectionExprClass:
case Stmt::OMPArrayShapingExprClass:
case Stmt::OMPIteratorExprClass:
+ case Stmt::OMPNumArgsExprClass:
+ case Stmt::OMPArgumentRangeExprClass:
case Stmt::SYCLUniqueStableNameExprClass:
case Stmt::OpenACCAsteriskSizeExprClass:
case Stmt::TypeTraitExprClass: {
diff --git a/clang/test/OpenMP/declare_variant_adjust_args_positional_ast_print.cpp b/clang/test/OpenMP/declare_variant_adjust_args_positional_ast_print.cpp
new file mode 100644
index 00000000000000..7945b2dbd2679d
--- /dev/null
+++ b/clang/test/OpenMP/declare_variant_adjust_args_positional_ast_print.cpp
@@ -0,0 +1,175 @@
+// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 \
+// RUN: -x c++ -std=c++14 -fsyntax-only -verify %s
+
+// expected-no-diagnostics
+
+// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 \
+// RUN: -x c++ -std=c++14 -ast-print %s | FileCheck %s --check-prefix=PRINT
+
+// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 \
+// RUN: -x c++ -std=c++14 -emit-pch -o %t %s
+
+// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 \
+// RUN: -x c++ -std=c++14 -include-pch %t -ast-print %s \
+// RUN: | FileCheck %s --check-prefix=PRINT
+
+// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 \
+// RUN: -x c++ -std=c++14 -ast-dump %s | FileCheck %s --check-prefix=DUMP
+
+#ifndef HEADER
+#define HEADER
+
+void v_pos(int *A, int *B, int *C, int *D);
+void v_range(int *A, int *B, int *C, int *D);
+void v_variadic(int *A, int *B, int *C, int *D, ...);
+void v_offset(int *A, int *B, int *C, ...);
+void v_lb(int *A, int *B);
+void v_ub(int *A, int *B);
+void v_both(int *A, int *B);
+void v_named(int *A, int *B);
+void v_mixed(int *A, int *B);
+void v_cond(int *A, int *B, int *C);
+void v_addr(int &A, int &B);
+
+// A list item may be a position: a constant integer expression.
+// PRINT: #pragma omp declare variant(v_pos) match(construct={dispatch}) adjust_args(need_device_ptr:2,4)
+// DUMP: FunctionDecl{{.*}}pos 'void (int *, int *, int *, int *)'
+// DUMP: OMPDeclareVariantAttr
+// DUMP: IntegerLiteral{{.*}}'int' 2
+// DUMP-NEXT: IntegerLiteral{{.*}}'int' 4
+#pragma omp declare variant(v_pos) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: 2, 4)
+void pos(int *A, int *B, int *C, int *D);
+
+// A list item may be a parameter range 'lb:ub'.
+// PRINT: #pragma omp declare variant(v_range) match(construct={dispatch}) adjust_args(need_device_ptr:1:3)
+// DUMP: FunctionDecl{{.*}}range 'void (int *, int *, int *, int *)'
+// DUMP: OMPArgumentRangeExpr{{.*}}'void'
+// DUMP-NEXT: IntegerLiteral{{.*}}'int' 1
+// DUMP-NEXT: IntegerLiteral{{.*}}'int' 3
+#pragma omp declare variant(v_range) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: 1:3)
+void range(int *A, int *B, int *C, int *D);
+
+// 'omp_num_args' is spelled only as a range bound, optionally with a logical
+// offset. The offset is a constant expression, hence the ConstantExpr wrapper.
+// PRINT: #pragma omp declare variant(v_variadic) match(construct={dispatch}) adjust_args(need_device_ptr:1:3,5,omp_num_args-1:omp_num_args)
+// DUMP: FunctionDecl{{.*}}variadic 'void (int *, int *, int *, int *, ...)'
+// DUMP: IntegerLiteral{{.*}}'int' 5
+// DUMP-NEXT: OMPArgumentRangeExpr{{.*}}'void'
+// DUMP-NEXT: OMPNumArgsExpr{{.*}}'int' '-'
+// DUMP-NEXT: ConstantExpr{{.*}}'int'
+// DUMP: IntegerLiteral{{.*}}'int' 1
+// DUMP-NEXT: OMPNumArgsExpr{{.*}}'int'
+// DUMP-NEXT: <<<NULL>>>
+#pragma omp declare variant(v_variadic) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: 1:3, 5, omp_num_args-1:omp_num_args)
+void variadic(int *A, int *B, int *C, int *D, ...);
+
+// Both signs of the logical offset are accepted. An item that resolves outside
+// the parameter list is ignored rather than diagnosed, per OpenMP 6.0 [9.6.2].
+// PRINT: #pragma omp declare variant(v_offset) match(construct={dispatch}) adjust_args(nothing:omp_num_args-2:omp_num_args,omp_num_args+1:)
+// DUMP: FunctionDecl{{.*}}offset 'void (int *, int *, int *, ...)'
+// DUMP: OMPNumArgsExpr{{.*}}'int' '+'
+#pragma omp declare variant(v_offset) match(construct={dispatch}) \
+ adjust_args(nothing: omp_num_args-2:omp_num_args, omp_num_args+1:)
+void offset(int *A, int *B, int *C, ...);
+
+// OpenMP 6.0 [5.2.1] p163: an omitted 'lb' stands for 1. As the first list item
+// it must still be separated from the adjust-op colon, or the two colons would
+// lex as a single '::' and the printed clause would not parse back.
+// PRINT: #pragma omp declare variant(v_lb) match(construct={dispatch}) adjust_args(need_device_ptr: :2)
+// DUMP: FunctionDecl{{.*}}lb 'void (int *, int *)'
+// DUMP: OMPArgumentRangeExpr{{.*}}'void'
+// DUMP-NEXT: <<<NULL>>>
+// DUMP-NEXT: IntegerLiteral{{.*}}'int' 2
+#pragma omp declare variant(v_lb) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: :2)
+void lb(int *A, int *B);
+
+// An omitted 'ub' stands for 'omp_num_args'.
+// PRINT: #pragma omp declare variant(v_ub) match(construct={dispatch}) adjust_args(need_device_ptr:1:)
+// DUMP: FunctionDecl{{.*}}ub 'void (int *, int *)'
+// DUMP: OMPArgumentRangeExpr{{.*}}'void'
+// DUMP-NEXT: IntegerLiteral{{.*}}'int' 1
+// DUMP-NEXT: <<<NULL>>>
+#pragma omp declare variant(v_ub) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: 1:)
+void ub(int *A, int *B);
+
+// The two bounds are optional independently, so omitting both denotes
+// 1:omp_num_args, that is every parameter.
+// PRINT: #pragma omp declare variant(v_both) match(construct={dispatch}) adjust_args(need_device_ptr: :)
+// DUMP: FunctionDecl{{.*}}both 'void (int *, int *)'
+// DUMP: OMPArgumentRangeExpr{{.*}}'void'
+// DUMP-NEXT: <<<NULL>>>
+// DUMP-NEXT: <<<NULL>>>
+#pragma omp declare variant(v_both) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: :)
+void both(int *A, int *B);
+
+// The pre-6.0 named form keeps its exact spelling and AST shape.
+// PRINT: #pragma omp declare variant(v_named) match(construct={dispatch}) adjust_args(need_device_ptr:A,B)
+// DUMP: FunctionDecl{{.*}}named 'void (int *, int *)'
+// DUMP: DeclRefExpr{{.*}}'int *' lvalue ParmVar{{.*}}'A' 'int *'
+// DUMP-NEXT: DeclRefExpr{{.*}}'int *' lvalue ParmVar{{.*}}'B' 'int *'
+#pragma omp declare variant(v_named) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: A, B)
+void named(int *A, int *B);
+
+// Named items, positions and ranges may be mixed in one clause.
+// PRINT: #pragma omp declare variant(v_mixed) match(construct={dispatch}) adjust_args(need_device_ptr:A,2:2)
+// DUMP: FunctionDecl{{.*}}mixed 'void (int *, int *)'
+// DUMP: DeclRefExpr{{.*}}'int *' lvalue ParmVar{{.*}}'A' 'int *'
+// DUMP-NEXT: OMPArgumentRangeExpr{{.*}}'void'
+#pragma omp declare variant(v_mixed) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: A, 2:2)
+void mixed(int *A, int *B);
+
+// A conditional operator keeps its own colon: the item is one position, not a
+// range, so a bound is never split at a '?:' colon.
+// PRINT: #pragma omp declare variant(v_cond) match(construct={dispatch}) adjust_args(need_device_ptr:1 ? 2 : 3)
+// DUMP: FunctionDecl{{.*}}cond 'void (int *, int *, int *)'
+// DUMP: ConditionalOperator{{.*}}'int'
+#pragma omp declare variant(v_cond) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: 1 ? 2 : 3)
+void cond(int *A, int *B, int *C);
+
+// A range does not name a parameter, so the 'need_device_addr' reference-type
+// restriction of OpenMP 6.0 [9.6.2] does not apply to it.
+// PRINT: #pragma omp declare variant(v_addr) match(construct={dispatch}) adjust_args(need_device_addr:1:2)
+#pragma omp declare variant(v_addr) match(construct={dispatch}) \
+ adjust_args(need_device_addr: 1:2)
+void addr(int &A, int &B);
+
+// Dependent bounds are accepted in the template pattern and rechecked on
+// instantiation.
+template <int N>
+void tmpl_v(int *A, int *B, int *C, ...);
+
+template <int N>
+void tmpl(int *A, int *B, int *C, ...);
+
+// The instantiation is dumped before the pattern it came from, so its checks
+// come first. Substituting N=2 makes the logical offset a constant expression.
+// DUMP: FunctionDecl{{.*}}tmpl 'void (int *, int *, int *, ...)' explicit_instantiation_definition
+// DUMP: OMPArgumentRangeExpr{{.*}}'void'
+// DUMP-NEXT: SubstNonTypeTemplateParmExpr{{.*}}'int'
+// DUMP: BinaryOperator{{.*}}'int' '+'
+// DUMP: OMPNumArgsExpr{{.*}}'int' '-'
+// DUMP-NEXT: ConstantExpr{{.*}}'int'
+// DUMP-NEXT: value: Int 2
+//
+// In the pattern the offset stays dependent, so it is not wrapped.
+// DUMP: OMPNumArgsExpr{{.*}}'int' '-'
+// DUMP-NEXT: DeclRefExpr{{.*}}'int' NonTypeTemplateParm{{.*}}'N' 'int'
+//
+// PRINT: #pragma omp declare variant(tmpl_v<N>) match(construct={dispatch}) adjust_args(need_device_ptr:N:N + 1,omp_num_args-N:omp_num_args)
+#pragma omp declare variant(tmpl_v<N>) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: N:N + 1, omp_num_args-N:omp_num_args)
+template <int N>
+void tmpl(int *A, int *B, int *C, ...) {}
+
+template void tmpl<2>(int *, int *, int *, ...);
+
+#endif // HEADER
diff --git a/clang/test/OpenMP/declare_variant_adjust_args_positional_messages.cpp b/clang/test/OpenMP/declare_variant_adjust_args_positional_messages.cpp
new file mode 100644
index 00000000000000..6b38d6a7f3a113
--- /dev/null
+++ b/clang/test/OpenMP/declare_variant_adjust_args_positional_messages.cpp
@@ -0,0 +1,228 @@
+// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 \
+// RUN: -x c++ -std=c++14 -ferror-limit 100 -fsyntax-only -verify=expected %s
+
+// The same syntax must still be rejected before OpenMP 6.0, which pins that the
+// new parsing is version-gated.
+// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=51 \
+// RUN: -x c++ -std=c++14 -ferror-limit 100 -DOMP51 -fsyntax-only \
+// RUN: -verify=omp51 %s
+
+int G; // expected-note 2 {{declared here}}
+
+void v1(int *aaa, int *bbb, ...);
+
+#ifndef OMP51
+
+// OpenMP 6.0 [5.2.1] p162: 'omp_num_args' may only be followed by a signed
+// constant logical offset, so no other operator may continue the bound.
+// expected-error at +2 {{'omp_num_args' may only be followed by '+' or '-' and a constant logical offset}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: omp_num_args/2)
+void f1(int *aaa, int *bbb, ...);
+
+// expected-error at +2 {{'omp_num_args' may only be followed by '+' or '-' and a constant logical offset}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: omp_num_args*2)
+void f2(int *aaa, int *bbb, ...);
+
+// 'omp_num_args' is recognised by spelling, and only where a bound starts. It
+// has no declaration, so anywhere else it is just an unknown identifier.
+// expected-error at +2 {{use of undeclared identifier 'omp_num_args'}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: 2*omp_num_args)
+void f3(int *aaa, int *bbb, ...);
+
+// expected-error at +2 {{use of undeclared identifier 'omp_num_args'}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: (omp_num_args):2)
+void f4(int *aaa, int *bbb, ...);
+
+// A sign must be followed by an actual offset expression.
+// expected-error at +2 {{expected expression}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: omp_num_args +)
+void f5(int *aaa, int *bbb, ...);
+
+// expected-error at +2 {{expected expression}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: 1:omp_num_args-)
+void f6(int *aaa, int *bbb, ...);
+
+// The logical offset must be a constant integer expression.
+// expected-error at +3 {{expression is not an integral constant expression}}
+// expected-note at +2 {{read of non-const variable 'G' is not allowed in a constant expression}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: omp_num_args-G:omp_num_args)
+void f7(int *aaa, int *bbb, ...);
+
+// expected-error at +2 {{integral constant expression must have integral or unscoped enumeration type, not 'double'}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: omp_num_args-1.5:omp_num_args)
+void f8(int *aaa, int *bbb, ...);
+
+// OpenMP 6.0 [5.2.1] p162 lists three forms of parameter list item, and a bare
+// 'omp_num_args' is not one of them: it is legal only as a range bound.
+// expected-error at +2 {{'omp_num_args' is only allowed as a bound of a parameter range}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: omp_num_args)
+void f9(int *aaa, int *bbb, ...);
+
+// expected-error at +2 {{'omp_num_args' is only allowed as a bound of a parameter range}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: omp_num_args-1)
+void f10(int *aaa, int *bbb, ...);
+
+// The rejection is per item, not only for the first one.
+// expected-error at +2 {{'omp_num_args' is only allowed as a bound of a parameter range}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: aaa, omp_num_args)
+void f11(int *aaa, int *bbb, ...);
+
+// A range has exactly one colon, so a third bound is a malformed item rather
+// than a silently mis-parsed one.
+// expected-error at +2 {{expected ',' or ')' in 'adjust_args' clause}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: 1:2:3)
+void f12(int *aaa, int *bbb, ...);
+
+// A non-constant, non-parameter item is still not a position: it must be a
+// constant integer expression (OpenMP 6.0 [5.2.1] p162 L30-31).
+// expected-error at +3 {{expression is not an integral constant expression}}
+// expected-note at +2 {{read of non-const variable 'G' is not allowed in a constant expression}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: G)
+void f13(int *aaa, int *bbb, ...);
+
+// The list must have at least one item.
+// expected-error at +2 {{expected expression}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: )
+void f14(int *aaa, int *bbb, ...);
+
+// A trailing separator starts an item that is not there.
+// expected-error at +2 {{expected expression}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: 1:2,)
+void f15(int *aaa, int *bbb, ...);
+
+// A leading separator is not an omitted item either; only a colon may open one.
+// expected-error at +2 {{expected expression}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: , 1)
+void f16(int *aaa, int *bbb, ...);
+
+// The list loop must give up at the end of the directive rather than spin on a
+// clause that is never closed.
+// expected-error at +3 {{expected ')'}}
+// expected-note at +2 {{to match this '('}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: 1:2
+void f17(int *aaa, int *bbb, ...);
+
+// A malformed item is skipped up to the separator, so it neither swallows nor
+// duplicates a diagnostic for the item that follows it.
+// expected-error at +2 {{'omp_num_args' is only allowed as a bound of a parameter range}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: omp_num_args, 2)
+void f18(int *aaa, int *bbb, ...);
+
+// OpenMP 6.0 [5.2.1] p162 L30-31: a position has the positive property.
+// expected-error at +2 {{argument to 'adjust_args' clause must be a strictly positive integer value}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: 0)
+void h1(int *aaa, int *bbb, ...);
+
+// expected-error at +2 {{argument to 'adjust_args' clause must be a strictly positive integer value}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: -1)
+void h2(int *aaa, int *bbb, ...);
+
+// A plain range bound has the positive property too.
+// expected-error at +2 {{argument to 'adjust_args' clause must be a strictly positive integer value}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: 0:5)
+void h3(int *aaa, int *bbb, ...);
+
+// The logical offset has the non-negative property, checked independently of
+// its already-verified constant property (OpenMP 6.0 [5.2.1] p163 L1).
+// expected-error at +2 {{argument to 'adjust_args' clause must be a non-negative integer value}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: omp_num_args-(-1):omp_num_args)
+void h4(int *aaa, int *bbb, ...);
+
+// The duplicate restriction (OpenMP 6.0 [5.2.1] p162) applies to positions,
+// not only to names.
+// expected-error at +2 {{'adjust_arg' argument 2 used in multiple clauses}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: 2, 2)
+void h5(int *aaa, int *bbb, ...);
+
+// A name and a position that happen to resolve to the same parameter are two
+// distinct items (OpenMP 6.0 [5.2.1] p162), so this is accepted, not a
+// duplicate.
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: aaa, 1)
+void h6(int *aaa, int *bbb, ...);
+
+// Not a name, a range, or a position: none of the three forms is satisfied.
+// expected-error at +2 {{expected a parameter name, a parameter position, or a parameter range in 'adjust_args' clause}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: 1.5)
+void h7(int *aaa, int *bbb, ...);
+
+// need_device_addr's reference-type restriction is not scoped to named items
+// (OpenMP 6.0 [9.6.2] p332 L31-33), so a position is checked too.
+// expected-error at +2 {{expected reference type argument on 'adjust_args' clause with 'need_device_addr' modifier}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_addr: 1)
+void h8(int *aaa, int *bbb, ...);
+
+// A huge literal upper bound must not turn range resolution into an unbounded
+// loop: out-of-range positions are dropped (OpenMP 6.0 [9.6.2] p332 L1-2), not
+// enumerated one at a time up to the written value.
+void v2(int &aaa, int &bbb, ...);
+#pragma omp declare variant(v2) match(construct={dispatch}) \
+ adjust_args(need_device_addr: 1:9223372036854775807)
+void h9(int &aaa, int &bbb, ...);
+
+// need_device_addr's reference-type check via a range covers every position
+// it sweeps, but reports only one diagnostic per written item, not one per
+// position (OpenMP 6.0 [9.6.2] p332 L31-33).
+// expected-error at +2 {{expected reference type argument on 'adjust_args' clause with 'need_device_addr' modifier}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_addr: 1:2)
+void h10(int *aaa, int *bbb, ...);
+
+// 'lb > ub' specifies no parameters and is accepted silently: the spec places
+// no restriction on it (OpenMP 6.0 [5.2.1] p163).
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: 5:2)
+void h11(int *aaa, int *bbb, ...);
+
+#else // OMP51
+
+// Before 6.0 a range is not parsed at all: the list stops at the colon, so the
+// clause is left unterminated and the leading bound is then rejected by Sema.
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: 1:2) // omp51-error {{expected ',' or ')' in 'adjust_args' clause}} omp51-error {{expected ')'}} omp51-note {{to match this '('}} omp51-error {{expected reference to one of the parameters of function 'g1'}}
+void g1(int *aaa, int *bbb, ...);
+
+// A position is not a parameter name before 6.0.
+// omp51-error at +2 {{expected reference to one of the parameters of function 'g2'}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: 2)
+void g2(int *aaa, int *bbb, ...);
+
+// An omitted lower bound is not an item, so the list is simply empty.
+// omp51-error at +2 {{expected expression}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: :)
+void g3(int *aaa, int *bbb, ...);
+
+// 'omp_num_args' is not recognised at all, and is looked up as a name.
+// omp51-error at +2 {{use of undeclared identifier 'omp_num_args'}}
+#pragma omp declare variant(v1) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: omp_num_args-1:omp_num_args)
+void g4(int *aaa, int *bbb, ...);
+
+#endif // OMP51
diff --git a/clang/test/OpenMP/declare_variant_adjust_args_positional_template.cpp b/clang/test/OpenMP/declare_variant_adjust_args_positional_template.cpp
new file mode 100644
index 00000000000000..a6c60221e57ef6
--- /dev/null
+++ b/clang/test/OpenMP/declare_variant_adjust_args_positional_template.cpp
@@ -0,0 +1,62 @@
+// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 \
+// RUN: -x c++ -std=c++14 -fsyntax-only -verify %s
+
+// OpenMP 6.0 [5.2.1]: a dependent parameter-list item is accepted at the
+// template definition and rechecked only once substitution makes it
+// non-dependent (S6). SemaTemplateInstantiateDecl re-invokes
+// ActOnOpenMPDeclareVariantDirective per specialization, so nothing at
+// definition time is lost by deferring.
+
+template <int N>
+void v_tmpl(int *aaa, int *bbb, ...);
+
+// No error here: 'N' is value-dependent at definition time.
+#pragma omp declare variant(v_tmpl<N>) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: N)
+template <int N>
+void tmpl_pos(int *aaa, int *bbb, ...) {}
+
+// Clean once substituted: position 2 is 'bbb', a valid, positive position.
+template void tmpl_pos<2>(int *, int *, ...);
+
+// The positive property (OpenMP 6.0 [5.2.1] p162 L30-31) is only checkable
+// once 'N' is substituted.
+// expected-error at 15 {{argument to 'adjust_args' clause must be a strictly positive integer value}}
+// expected-note at +1 {{in instantiation of function template specialization 'tmpl_pos<0>' requested here}}
+template void tmpl_pos<0>(int *, int *, ...);
+
+template <int N>
+void v_tmpl_range(int *aaa, int *bbb, int *ccc, ...);
+
+// A dependent range bound is accepted at definition time too.
+#pragma omp declare variant(v_tmpl_range<N>) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: N:N + 1)
+template <int N>
+void tmpl_range(int *aaa, int *bbb, int *ccc, ...) {}
+
+// Clean once substituted: range 2:3 covers 'bbb' and 'ccc'.
+template void tmpl_range<2>(int *, int *, int *, ...);
+
+// The positive property on a range bound is likewise only checkable once 'N'
+// is substituted.
+// expected-error at 33 {{argument to 'adjust_args' clause must be a strictly positive integer value}}
+// expected-note at +1 {{in instantiation of function template specialization 'tmpl_range<0>' requested here}}
+template void tmpl_range<0>(int *, int *, int *, ...);
+
+template <int N>
+void v_tmpl_offset(int *aaa, int *bbb, ...);
+
+// A dependent 'omp_num_args' logical offset is accepted at definition time.
+#pragma omp declare variant(v_tmpl_offset<N>) match(construct={dispatch}) \
+ adjust_args(need_device_ptr: omp_num_args-N:omp_num_args)
+template <int N>
+void tmpl_offset(int *aaa, int *bbb, ...) {}
+
+// Clean once substituted: offset 1 is non-negative.
+template void tmpl_offset<1>(int *, int *, ...);
+
+// The non-negative property on the offset (OpenMP 6.0 [5.2.1] p163 L1) is
+// likewise only checkable once 'N' is substituted.
+// expected-error at 51 {{argument to 'adjust_args' clause must be a non-negative integer value}}
+// expected-note at +1 {{in instantiation of function template specialization 'tmpl_offset<-1>' requested here}}
+template void tmpl_offset<-1>(int *, int *, ...);
diff --git a/clang/test/OpenMP/declare_variant_clauses_messages.cpp b/clang/test/OpenMP/declare_variant_clauses_messages.cpp
index 916d15fde9ff28..c09619f2b7fcc9 100644
--- a/clang/test/OpenMP/declare_variant_clauses_messages.cpp
+++ b/clang/test/OpenMP/declare_variant_clauses_messages.cpp
@@ -1,9 +1,9 @@
-// RUN: %clang_cc1 -verify -triple x86_64-unknown-linux -fopenmp -fopenmp-version=60 -std=c++11 -o - %s
-// RUN: %clang_cc1 -verify -triple x86_64-unknown-linux -fopenmp -fopenmp-version=60 -std=c++11 \
+// RUN: %clang_cc1 -verify=expected,cxx -triple x86_64-unknown-linux -fopenmp -fopenmp-version=60 -std=c++11 -o - %s
+// RUN: %clang_cc1 -verify=expected,cxx -triple x86_64-unknown-linux -fopenmp -fopenmp-version=60 -std=c++11 \
// RUN: -DNO_INTEROP_T_DEF -o - %s
-// RUN: %clang_cc1 -verify -triple x86_64-unknown-linux -fopenmp -fopenmp-version=60 -std=c++11 -o - %s
-// RUN: %clang_cc1 -verify -triple x86_64-unknown-linux -fopenmp -fopenmp-version=60 -Wno-strict-prototypes -DC -x c -o - %s
-// RUN: %clang_cc1 -verify -triple x86_64-pc-windows-msvc -fms-compatibility \
+// RUN: %clang_cc1 -verify=expected,cxx -triple x86_64-unknown-linux -fopenmp -fopenmp-version=60 -std=c++11 -o - %s
+// RUN: %clang_cc1 -verify=expected,c -triple x86_64-unknown-linux -fopenmp -fopenmp-version=60 -Wno-strict-prototypes -DC -x c -o - %s
+// RUN: %clang_cc1 -verify=expected,c -triple x86_64-pc-windows-msvc -fms-compatibility \
// RUN: -fopenmp -fopenmp-version=60 -Wno-strict-prototypes -DC -DWIN -x c -o - %s
#ifdef NO_INTEROP_T_DEF
@@ -15,7 +15,7 @@ void foo_v1(float *);
#else
typedef void *omp_interop_t;
-int Other;
+int Other; // cxx-note {{declared here}}
#if _OPENMP >= 202011 // At least OpenMP 5.1
#ifdef __cplusplus
@@ -135,7 +135,9 @@ void vararg_bar2(const char *fmt) { return; }
adjust_args(need_device_addr:AAA) \
match(construct={dispatch}, device={arch(x86,x86_64)})
-// expected-error at +2 {{expected reference to one of the parameters of function 'foo'}}
+// cxx-error at +4 {{expression is not an integral constant expression}}
+// cxx-note at +3 {{read of non-const variable 'Other' is not allowed in a constant expression}}
+// c-error at +2 {{expression is not an integer constant expression}}
#pragma omp declare variant(foo_v3) \
adjust_args(nothing:Other) \
match(construct={dispatch}, device={arch(x86,x86_64)})
diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp
index 2c5e799d12f181..271fc889fb6e23 100644
--- a/clang/tools/libclang/CIndex.cpp
+++ b/clang/tools/libclang/CIndex.cpp
@@ -6043,6 +6043,10 @@ CXString clang_getCursorKindSpelling(enum CXCursorKind Kind) {
return cxstring::createRef("OMPArrayShapingExpr");
case CXCursor_OMPIteratorExpr:
return cxstring::createRef("OMPIteratorExpr");
+ case CXCursor_OMPNumArgsExpr:
+ return cxstring::createRef("OMPNumArgsExpr");
+ case CXCursor_OMPArgumentRangeExpr:
+ return cxstring::createRef("OMPArgumentRangeExpr");
case CXCursor_BinaryOperator:
return cxstring::createRef("BinaryOperator");
case CXCursor_CompoundAssignOperator:
diff --git a/clang/tools/libclang/CXCursor.cpp b/clang/tools/libclang/CXCursor.cpp
index 768f5465a166a6..8ad3136bf1b95d 100644
--- a/clang/tools/libclang/CXCursor.cpp
+++ b/clang/tools/libclang/CXCursor.cpp
@@ -458,6 +458,14 @@ CXCursor cxcursor::MakeCXCursor(const Stmt *S, const Decl *Parent,
K = CXCursor_OMPIteratorExpr;
break;
+ case Stmt::OMPNumArgsExprClass:
+ K = CXCursor_OMPNumArgsExpr;
+ break;
+
+ case Stmt::OMPArgumentRangeExprClass:
+ K = CXCursor_OMPArgumentRangeExpr;
+ break;
+
case Stmt::BinaryOperatorClass:
K = CXCursor_BinaryOperator;
break;
More information about the cfe-commits
mailing list