https://github.com/loopacino updated https://github.com/llvm/llvm-project/pull/206977
>From 0fb5bd42f1e127faea963c359b1e3c69cb969c60 Mon Sep 17 00:00:00 2001 From: amtiwari <[email protected]> Date: Wed, 1 Jul 2026 09:06:58 -0400 Subject: [PATCH 01/14] add OMPFlattenDirective ast node --- clang/include/clang/AST/RecursiveASTVisitor.h | 3 + clang/include/clang/AST/StmtOpenMP.h | 75 ++++++++++++++++++- clang/include/clang/Basic/StmtNodes.td | 1 + clang/lib/AST/StmtOpenMP.cpp | 20 +++++ clang/lib/AST/StmtPrinter.cpp | 5 ++ clang/lib/AST/StmtProfile.cpp | 4 + clang/lib/Basic/OpenMPKinds.cpp | 2 +- 7 files changed, 108 insertions(+), 2 deletions(-) diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h index b000a34043696..e425f7a31f59c 100644 --- a/clang/include/clang/AST/RecursiveASTVisitor.h +++ b/clang/include/clang/AST/RecursiveASTVisitor.h @@ -3227,6 +3227,9 @@ DEF_TRAVERSE_STMT(OMPFuseDirective, DEF_TRAVERSE_STMT(OMPInterchangeDirective, { TRY_TO(TraverseOMPExecutableDirective(S)); }) +DEF_TRAVERSE_STMT(OMPFlattenDirective, + { TRY_TO(TraverseOMPExecutableDirective(S)); }) + DEF_TRAVERSE_STMT(OMPSplitDirective, { TRY_TO(TraverseOMPExecutableDirective(S)); }) diff --git a/clang/include/clang/AST/StmtOpenMP.h b/clang/include/clang/AST/StmtOpenMP.h index dbc76e7df8ecd..1420331f37c1f 100644 --- a/clang/include/clang/AST/StmtOpenMP.h +++ b/clang/include/clang/AST/StmtOpenMP.h @@ -1041,7 +1041,7 @@ class OMPCanonicalLoopNestTransformationDirective Stmt::StmtClass C = T->getStmtClass(); return C == OMPTileDirectiveClass || C == OMPUnrollDirectiveClass || C == OMPReverseDirectiveClass || C == OMPInterchangeDirectiveClass || - C == OMPStripeDirectiveClass; + C == OMPStripeDirectiveClass || C == OMPFlattenDirectiveClass; } }; @@ -5959,6 +5959,79 @@ class OMPInterchangeDirective final } }; +/// Represents the '#pragma omp flatten' loop transformation directive. +/// +/// \code{c} +/// #pragma omp flatten +/// for (int i = 0; i < m; ++i) +/// for (int j = 0; j < n; ++j) +/// .. +/// \endcode +class OMPFlattenDirective final + : public OMPCanonicalLoopNestTransformationDirective { + friend class ASTStmtReader; + friend class OMPExecutableDirective; + + /// Offsets of child members. + enum { + PreInitsOffset = 0, + TransformedStmtOffset, + }; + + explicit OMPFlattenDirective(SourceLocation StartLoc, SourceLocation EndLoc, + unsigned NumLoops) + : OMPCanonicalLoopNestTransformationDirective( + OMPFlattenDirectiveClass, llvm::omp::OMPD_flatten, StartLoc, EndLoc, + NumLoops) {} + + void setPreInits(Stmt *PreInits) { + Data->getChildren()[PreInitsOffset] = PreInits; + } + + void setTransformedStmt(Stmt *S) { + Data->getChildren()[TransformedStmtOffset] = S; + } + +public: + /// Create a new AST node representation for '#pragma omp flatten'. + /// + /// \param C Context of the AST. + /// \param StartLoc Location of the introducer (e.g. the 'omp' token). + /// \param EndLoc Location of the directive's end (e.g. the tok::eod). + /// \param Clauses The directive's clauses. + /// \param NumLoops Number of affected loops (the flatten depth, currently + /// always 2). + /// \param AssociatedStmt The outermost associated loop. + /// \param TransformedStmt The flattened loop, or nullptr in dependent + /// contexts. + /// \param PreInits Helper preinits statements for the loop nest. + static OMPFlattenDirective * + Create(const ASTContext &C, SourceLocation StartLoc, SourceLocation EndLoc, + ArrayRef<OMPClause *> Clauses, unsigned NumLoops, Stmt *AssociatedStmt, + Stmt *TransformedStmt, Stmt *PreInits); + + /// Build an empty '#pragma omp flatten' AST node for deserialization. + /// + /// \param C Context of the AST. + /// \param NumClauses Number of clauses to allocate. + /// \param NumLoops Number of associated loops to allocate. + static OMPFlattenDirective * + CreateEmpty(const ASTContext &C, unsigned NumClauses, unsigned NumLoops); + + /// Gets the flattened loop after the transformation. This is the de-sugared + /// replacement or nullptr in dependent contexts. + Stmt *getTransformedStmt() const { + return Data->getChildren()[TransformedStmtOffset]; + } + + /// Return preinits statement. + Stmt *getPreInits() const { return Data->getChildren()[PreInitsOffset]; } + + static bool classof(const Stmt *T) { + return T->getStmtClass() == OMPFlattenDirectiveClass; + } +}; + /// The base class for all transformation directives of canonical loop /// sequences (currently only 'fuse') class OMPCanonicalLoopSequenceTransformationDirective diff --git a/clang/include/clang/Basic/StmtNodes.td b/clang/include/clang/Basic/StmtNodes.td index e166894ea024b..0d7a0c1490a1e 100644 --- a/clang/include/clang/Basic/StmtNodes.td +++ b/clang/include/clang/Basic/StmtNodes.td @@ -247,6 +247,7 @@ def OMPReverseDirective : StmtNode<OMPCanonicalLoopNestTransformationDirective>; def OMPSplitDirective : StmtNode<OMPCanonicalLoopNestTransformationDirective>; def OMPInterchangeDirective : StmtNode<OMPCanonicalLoopNestTransformationDirective>; +def OMPFlattenDirective : StmtNode<OMPCanonicalLoopNestTransformationDirective>; def OMPCanonicalLoopSequenceTransformationDirective : StmtNode<OMPExecutableDirective, 1>; def OMPFuseDirective diff --git a/clang/lib/AST/StmtOpenMP.cpp b/clang/lib/AST/StmtOpenMP.cpp index 9d6b315effb41..477604ef3dafb 100644 --- a/clang/lib/AST/StmtOpenMP.cpp +++ b/clang/lib/AST/StmtOpenMP.cpp @@ -552,6 +552,26 @@ OMPInterchangeDirective::CreateEmpty(const ASTContext &C, unsigned NumClauses, SourceLocation(), SourceLocation(), NumLoops); } +OMPFlattenDirective *OMPFlattenDirective::Create( + const ASTContext &C, SourceLocation StartLoc, SourceLocation EndLoc, + ArrayRef<OMPClause *> Clauses, unsigned NumLoops, Stmt *AssociatedStmt, + Stmt *TransformedStmt, Stmt *PreInits) { + OMPFlattenDirective *Dir = createDirective<OMPFlattenDirective>( + C, Clauses, AssociatedStmt, TransformedStmtOffset + 1, StartLoc, EndLoc, + NumLoops); + Dir->setTransformedStmt(TransformedStmt); + Dir->setPreInits(PreInits); + return Dir; +} + +OMPFlattenDirective *OMPFlattenDirective::CreateEmpty(const ASTContext &C, + unsigned NumClauses, + unsigned NumLoops) { + return createEmptyDirective<OMPFlattenDirective>( + C, NumClauses, /*HasAssociatedStmt=*/true, TransformedStmtOffset + 1, + SourceLocation(), SourceLocation(), NumLoops); +} + OMPSplitDirective * OMPSplitDirective::Create(const ASTContext &C, SourceLocation StartLoc, SourceLocation EndLoc, ArrayRef<OMPClause *> Clauses, diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp index 6c3294573e9d4..0bacb79ed86e3 100644 --- a/clang/lib/AST/StmtPrinter.cpp +++ b/clang/lib/AST/StmtPrinter.cpp @@ -800,6 +800,11 @@ void StmtPrinter::VisitOMPInterchangeDirective(OMPInterchangeDirective *Node) { PrintOMPExecutableDirective(Node); } +void StmtPrinter::VisitOMPFlattenDirective(OMPFlattenDirective *Node) { + Indent() << "#pragma omp flatten"; + PrintOMPExecutableDirective(Node); +} + void StmtPrinter::VisitOMPSplitDirective(OMPSplitDirective *Node) { Indent() << "#pragma omp split"; PrintOMPExecutableDirective(Node); diff --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp index 90eab530e0c2e..de7ed322d7424 100644 --- a/clang/lib/AST/StmtProfile.cpp +++ b/clang/lib/AST/StmtProfile.cpp @@ -1071,6 +1071,10 @@ void StmtProfiler::VisitOMPInterchangeDirective( VisitOMPCanonicalLoopNestTransformationDirective(S); } +void StmtProfiler::VisitOMPFlattenDirective(const OMPFlattenDirective *S) { + VisitOMPCanonicalLoopNestTransformationDirective(S); +} + void StmtProfiler::VisitOMPSplitDirective(const OMPSplitDirective *S) { VisitOMPCanonicalLoopNestTransformationDirective(S); } diff --git a/clang/lib/Basic/OpenMPKinds.cpp b/clang/lib/Basic/OpenMPKinds.cpp index 675d86349c933..57e0347f8ea77 100644 --- a/clang/lib/Basic/OpenMPKinds.cpp +++ b/clang/lib/Basic/OpenMPKinds.cpp @@ -818,7 +818,7 @@ bool clang::isOpenMPCanonicalLoopNestTransformationDirective( OpenMPDirectiveKind DKind) { return DKind == OMPD_tile || DKind == OMPD_unroll || DKind == OMPD_reverse || DKind == OMPD_split || DKind == OMPD_interchange || - DKind == OMPD_stripe; + DKind == OMPD_stripe || DKind == OMPD_flatten; } bool clang::isOpenMPCanonicalLoopSequenceTransformationDirective( >From 933007b6333584fb358adc4f89a61db1ae15d4cb Mon Sep 17 00:00:00 2001 From: amtiwari <[email protected]> Date: Wed, 8 Jul 2026 16:02:27 -0400 Subject: [PATCH 02/14] add OMPDepthClause ast node --- clang/include/clang/AST/OpenMPClause.h | 68 +++++++++++++++++++ clang/include/clang/AST/RecursiveASTVisitor.h | 6 ++ clang/lib/AST/OpenMPClause.cpp | 25 +++++++ clang/lib/AST/StmtProfile.cpp | 5 ++ clang/lib/Parse/ParseOpenMP.cpp | 1 + llvm/include/llvm/Frontend/OpenMP/OMP.td | 1 + 6 files changed, 106 insertions(+) diff --git a/clang/include/clang/AST/OpenMPClause.h b/clang/include/clang/AST/OpenMPClause.h index 8ceafc4669297..50d1364a9726d 100644 --- a/clang/include/clang/AST/OpenMPClause.h +++ b/clang/include/clang/AST/OpenMPClause.h @@ -1380,6 +1380,74 @@ class OMPPartialClause final : public OMPClause { } }; +/// This represents the 'depth' clause on the '#pragma omp flatten' (and +/// '#pragma omp fuse') loop-transformation directives. +/// +/// \code +/// #pragma omp flatten depth(3) +/// \endcode +/// In this example the 'flatten' directive has a 'depth' clause whose argument +/// '3' specifies how many perfectly nested loops are combined into one. +/// The argument must be a positive integer constant expression that evaluates +/// to at most the loop nest depth of the associated loop nest. +class OMPDepthClause final : public OMPClause { + friend class OMPClauseReader; + + /// Location of '('. + SourceLocation LParenLoc; + + /// The depth expression (number of loops to flatten). + Stmt *Depth = nullptr; + + /// Build an empty clause. + explicit OMPDepthClause() : OMPClause(llvm::omp::OMPC_depth, {}, {}) {} + + /// Set the depth expression. + void setDepth(Expr *E) { Depth = E; } + + /// Sets the location of '('. + void setLParenLoc(SourceLocation Loc) { LParenLoc = Loc; } + +public: + /// Build an AST node for a 'depth' clause. + /// + /// \param C Context of the AST. + /// \param StartLoc Location of the 'depth' identifier. + /// \param LParenLoc Location of '('. + /// \param EndLoc Location of ')'. + /// \param Depth The depth expression. + static OMPDepthClause *Create(const ASTContext &C, SourceLocation StartLoc, + SourceLocation LParenLoc, SourceLocation EndLoc, + Expr *Depth); + + /// Build an empty 'depth' AST node for deserialization. + /// + /// \param C Context of the AST. + static OMPDepthClause *CreateEmpty(const ASTContext &C); + + /// Returns the location of '('. + SourceLocation getLParenLoc() const { return LParenLoc; } + + /// Returns the depth expression or nullptr if not set. + Expr *getDepth() const { return cast_or_null<Expr>(Depth); } + + child_range children() { return child_range(&Depth, &Depth + 1); } + const_child_range children() const { + return const_child_range(&Depth, &Depth + 1); + } + + child_range used_children() { + return child_range(child_iterator(), child_iterator()); + } + const_child_range used_children() const { + return const_child_range(const_child_iterator(), const_child_iterator()); + } + + static bool classof(const OMPClause *T) { + return T->getClauseKind() == llvm::omp::OMPC_depth; + } +}; + /// This represents 'collapse' clause in the '#pragma omp ...' /// directive. /// diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h index e425f7a31f59c..da55e97fdfcb5 100644 --- a/clang/include/clang/AST/RecursiveASTVisitor.h +++ b/clang/include/clang/AST/RecursiveASTVisitor.h @@ -3568,6 +3568,12 @@ bool RecursiveASTVisitor<Derived>::VisitOMPPartialClause(OMPPartialClause *C) { return true; } +template <typename Derived> +bool RecursiveASTVisitor<Derived>::VisitOMPDepthClause(OMPDepthClause *C) { + TRY_TO(TraverseStmt(C->getDepth())); + return true; +} + template <typename Derived> bool RecursiveASTVisitor<Derived>::VisitOMPCollapseClause(OMPCollapseClause *C) { diff --git a/clang/lib/AST/OpenMPClause.cpp b/clang/lib/AST/OpenMPClause.cpp index ed00e80144c25..7b2f67a31cad9 100644 --- a/clang/lib/AST/OpenMPClause.cpp +++ b/clang/lib/AST/OpenMPClause.cpp @@ -1057,6 +1057,22 @@ OMPPartialClause *OMPPartialClause::CreateEmpty(const ASTContext &C) { return new (C) OMPPartialClause(); } +OMPDepthClause *OMPDepthClause::Create(const ASTContext &C, + SourceLocation StartLoc, + SourceLocation LParenLoc, + SourceLocation EndLoc, Expr *Depth) { + OMPDepthClause *Clause = CreateEmpty(C); + Clause->setLocStart(StartLoc); + Clause->setLParenLoc(LParenLoc); + Clause->setLocEnd(EndLoc); + Clause->setDepth(Depth); + return Clause; +} + +OMPDepthClause *OMPDepthClause::CreateEmpty(const ASTContext &C) { + return new (C) OMPDepthClause(); +} + OMPLoopRangeClause * OMPLoopRangeClause::Create(const ASTContext &C, SourceLocation StartLoc, SourceLocation LParenLoc, SourceLocation FirstLoc, @@ -2069,6 +2085,15 @@ void OMPClausePrinter::VisitOMPPermutationClause(OMPPermutationClause *Node) { void OMPClausePrinter::VisitOMPFullClause(OMPFullClause *Node) { OS << "full"; } +void OMPClausePrinter::VisitOMPDepthClause(OMPDepthClause *Node) { + OS << "depth"; + if (Expr *Depth = Node->getDepth()) { + OS << '('; + Depth->printPretty(OS, nullptr, Policy, 0); + OS << ')'; + } +} + void OMPClausePrinter::VisitOMPPartialClause(OMPPartialClause *Node) { OS << "partial"; diff --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp index de7ed322d7424..838d6d02f7446 100644 --- a/clang/lib/AST/StmtProfile.cpp +++ b/clang/lib/AST/StmtProfile.cpp @@ -521,6 +521,11 @@ void OMPClauseProfiler::VisitOMPPartialClause(const OMPPartialClause *C) { Profiler->VisitExpr(Factor); } +void OMPClauseProfiler::VisitOMPDepthClause(const OMPDepthClause *C) { + if (const Expr *Depth = C->getDepth()) + Profiler->VisitExpr(Depth); +} + void OMPClauseProfiler::VisitOMPLoopRangeClause(const OMPLoopRangeClause *C) { if (const Expr *First = C->getFirst()) Profiler->VisitExpr(First); diff --git a/clang/lib/Parse/ParseOpenMP.cpp b/clang/lib/Parse/ParseOpenMP.cpp index af52295df2d8b..952a517205381 100644 --- a/clang/lib/Parse/ParseOpenMP.cpp +++ b/clang/lib/Parse/ParseOpenMP.cpp @@ -3247,6 +3247,7 @@ OMPClause *Parser::ParseOpenMPClause(OpenMPDirectiveKind DKind, case OMPC_nocontext: case OMPC_filter: case OMPC_partial: + case OMPC_depth: case OMPC_align: case OMPC_message: case OMPC_ompx_dyn_cgroup_mem: diff --git a/llvm/include/llvm/Frontend/OpenMP/OMP.td b/llvm/include/llvm/Frontend/OpenMP/OMP.td index 679a944fc4358..d49c5ee1a0db3 100644 --- a/llvm/include/llvm/Frontend/OpenMP/OMP.td +++ b/llvm/include/llvm/Frontend/OpenMP/OMP.td @@ -161,6 +161,7 @@ def OMPC_Depobj : Clause<[Spelling<"depobj">]> { let isImplicit = true; } def OMPC_Depth : Clause<[Spelling<"depth">]> { + let clangClass = "OMPDepthClause"; let flangClass = "ScalarIntConstantExpr"; } def OMPC_Destroy : Clause<[Spelling<"destroy">]> { >From b63024e1e342cd168ef128a3f6a24909df2a82db Mon Sep 17 00:00:00 2001 From: amtiwari <[email protected]> Date: Wed, 8 Jul 2026 16:04:44 -0400 Subject: [PATCH 03/14] template-transform support for flatten directive & depth clause --- clang/lib/Sema/TreeTransform.h | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 3b99ff4bb9e23..865622ad50f1c 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -1804,6 +1804,14 @@ class TreeTransform { LParenLoc, EndLoc); } + /// Build a new OpenMP 'depth' clause. + OMPClause *RebuildOMPDepthClause(Expr *Depth, SourceLocation StartLoc, + SourceLocation LParenLoc, + SourceLocation EndLoc) { + return getSema().OpenMP().ActOnOpenMPDepthClause(Depth, StartLoc, LParenLoc, + EndLoc); + } + OMPClause * RebuildOMPLoopRangeClause(Expr *First, Expr *Count, SourceLocation StartLoc, SourceLocation LParenLoc, SourceLocation FirstLoc, @@ -9791,6 +9799,17 @@ TreeTransform<Derived>::TransformOMPSplitDirective(OMPSplitDirective *D) { return Res; } +template <typename Derived> +StmtResult +TreeTransform<Derived>::TransformOMPFlattenDirective(OMPFlattenDirective *D) { + DeclarationNameInfo DirName; + getDerived().getSema().OpenMP().StartOpenMPDSABlock( + D->getDirectiveKind(), DirName, nullptr, D->getBeginLoc()); + StmtResult Res = getDerived().TransformOMPExecutableDirective(D); + getDerived().getSema().OpenMP().EndOpenMPDSABlock(Res.get()); + return Res; +} + template <typename Derived> StmtResult TreeTransform<Derived>::TransformOMPFuseDirective(OMPFuseDirective *D) { @@ -10717,6 +10736,20 @@ TreeTransform<Derived>::TransformOMPPartialClause(OMPPartialClause *C) { C->getEndLoc()); } +template <typename Derived> +OMPClause *TreeTransform<Derived>::TransformOMPDepthClause(OMPDepthClause *C) { + ExprResult T = getDerived().TransformExpr(C->getDepth()); + if (T.isInvalid()) + return nullptr; + Expr *Depth = T.get(); + bool Changed = Depth != C->getDepth(); + + if (!Changed && !getDerived().AlwaysRebuild()) + return C; + return RebuildOMPDepthClause(Depth, C->getBeginLoc(), C->getLParenLoc(), + C->getEndLoc()); +} + template <typename Derived> OMPClause * TreeTransform<Derived>::TransformOMPLoopRangeClause(OMPLoopRangeClause *C) { >From deafb222a7d251069c68a44fa267e58e750f1064 Mon Sep 17 00:00:00 2001 From: amtiwari <[email protected]> Date: Wed, 8 Jul 2026 16:08:12 -0400 Subject: [PATCH 04/14] serialize flatten --- clang/include/clang/Serialization/ASTBitCodes.h | 1 + clang/lib/Serialization/ASTReaderStmt.cpp | 11 +++++++++++ clang/lib/Serialization/ASTWriterStmt.cpp | 5 +++++ 3 files changed, 17 insertions(+) diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h index 279380de2f7fe..93a0e0a454b68 100644 --- a/clang/include/clang/Serialization/ASTBitCodes.h +++ b/clang/include/clang/Serialization/ASTBitCodes.h @@ -1970,6 +1970,7 @@ enum StmtCode { STMT_OMP_REVERSE_DIRECTIVE, STMT_OMP_SPLIT_DIRECTIVE, STMT_OMP_INTERCHANGE_DIRECTIVE, + STMT_OMP_FLATTEN_DIRECTIVE, STMT_OMP_FUSE_DIRECTIVE, STMT_OMP_FOR_DIRECTIVE, STMT_OMP_FOR_SIMD_DIRECTIVE, diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp index 02ccf8d4d41c2..8c2debd6cdf32 100644 --- a/clang/lib/Serialization/ASTReaderStmt.cpp +++ b/clang/lib/Serialization/ASTReaderStmt.cpp @@ -2537,6 +2537,10 @@ void ASTStmtReader::VisitOMPInterchangeDirective(OMPInterchangeDirective *D) { VisitOMPCanonicalLoopNestTransformationDirective(D); } +void ASTStmtReader::VisitOMPFlattenDirective(OMPFlattenDirective *D) { + VisitOMPCanonicalLoopNestTransformationDirective(D); +} + void ASTStmtReader::VisitOMPSplitDirective(OMPSplitDirective *D) { VisitOMPCanonicalLoopNestTransformationDirective(D); } @@ -3719,6 +3723,13 @@ Stmt *ASTReader::ReadStmtFromStream(ModuleFile &F) { break; } + case STMT_OMP_FLATTEN_DIRECTIVE: { + unsigned NumLoops = Record[ASTStmtReader::NumStmtFields]; + unsigned NumClauses = Record[ASTStmtReader::NumStmtFields + 1]; + S = OMPFlattenDirective::CreateEmpty(Context, NumClauses, NumLoops); + break; + } + case STMT_OMP_FOR_DIRECTIVE: { unsigned CollapsedNum = Record[ASTStmtReader::NumStmtFields]; unsigned NumClauses = Record[ASTStmtReader::NumStmtFields + 1]; diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp index 4e9eadd730a56..dfe28a9b915e3 100644 --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -2547,6 +2547,11 @@ void ASTStmtWriter::VisitOMPInterchangeDirective(OMPInterchangeDirective *D) { Code = serialization::STMT_OMP_INTERCHANGE_DIRECTIVE; } +void ASTStmtWriter::VisitOMPFlattenDirective(OMPFlattenDirective *D) { + VisitOMPCanonicalLoopNestTransformationDirective(D); + Code = serialization::STMT_OMP_FLATTEN_DIRECTIVE; +} + void ASTStmtWriter::VisitOMPSplitDirective(OMPSplitDirective *D) { VisitOMPCanonicalLoopNestTransformationDirective(D); Code = serialization::STMT_OMP_SPLIT_DIRECTIVE; >From 3e2dfeb99230057b7cb9c484182ae33befbe1e41 Mon Sep 17 00:00:00 2001 From: amtiwari <[email protected]> Date: Wed, 8 Jul 2026 16:09:03 -0400 Subject: [PATCH 05/14] serialize depth --- clang/lib/Serialization/ASTReader.cpp | 8 ++++++++ clang/lib/Serialization/ASTWriter.cpp | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp index f8a6a38bb9b5c..4f51d79a52105 100644 --- a/clang/lib/Serialization/ASTReader.cpp +++ b/clang/lib/Serialization/ASTReader.cpp @@ -11495,6 +11495,9 @@ OMPClause *OMPClauseReader::readClause() { case llvm::omp::OMPC_partial: C = OMPPartialClause::CreateEmpty(Context); break; + case llvm::omp::OMPC_depth: + C = OMPDepthClause::CreateEmpty(Context); + break; case llvm::omp::OMPC_looprange: C = OMPLoopRangeClause::CreateEmpty(Context); break; @@ -11924,6 +11927,11 @@ void OMPClauseReader::VisitOMPPartialClause(OMPPartialClause *C) { C->setLParenLoc(Record.readSourceLocation()); } +void OMPClauseReader::VisitOMPDepthClause(OMPDepthClause *C) { + C->setDepth(Record.readSubExpr()); + C->setLParenLoc(Record.readSourceLocation()); +} + void OMPClauseReader::VisitOMPLoopRangeClause(OMPLoopRangeClause *C) { C->setFirst(Record.readSubExpr()); C->setCount(Record.readSubExpr()); diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp index 357a7f7e95fa0..cbfeaa9e6638a 100644 --- a/clang/lib/Serialization/ASTWriter.cpp +++ b/clang/lib/Serialization/ASTWriter.cpp @@ -8106,6 +8106,11 @@ void OMPClauseWriter::VisitOMPPartialClause(OMPPartialClause *C) { Record.AddSourceLocation(C->getLParenLoc()); } +void OMPClauseWriter::VisitOMPDepthClause(OMPDepthClause *C) { + Record.AddStmt(C->getDepth()); + Record.AddSourceLocation(C->getLParenLoc()); +} + void OMPClauseWriter::VisitOMPLoopRangeClause(OMPLoopRangeClause *C) { Record.AddStmt(C->getFirst()); Record.AddStmt(C->getCount()); >From 4ae746ca1eeaf11db10e859572a4500029d01cce Mon Sep 17 00:00:00 2001 From: amtiwari <[email protected]> Date: Wed, 8 Jul 2026 16:17:29 -0400 Subject: [PATCH 06/14] add analysis traversal support for flatten directive --- clang/lib/Sema/SemaExceptionSpec.cpp | 1 + clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/clang/lib/Sema/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp index 40d530a1f3925..995b5b6010978 100644 --- a/clang/lib/Sema/SemaExceptionSpec.cpp +++ b/clang/lib/Sema/SemaExceptionSpec.cpp @@ -1508,6 +1508,7 @@ CanThrowResult Sema::canThrow(const Stmt *S) { case Stmt::OMPUnrollDirectiveClass: case Stmt::OMPReverseDirectiveClass: case Stmt::OMPInterchangeDirectiveClass: + case Stmt::OMPFlattenDirectiveClass: case Stmt::OMPSplitDirectiveClass: case Stmt::OMPFuseDirectiveClass: case Stmt::OMPSingleDirectiveClass: diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index cfb294736ee02..3f666be00cc24 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -1783,6 +1783,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::OMPStripeDirectiveClass: case Stmt::OMPTileDirectiveClass: case Stmt::OMPInterchangeDirectiveClass: + case Stmt::OMPFlattenDirectiveClass: case Stmt::OMPSplitDirectiveClass: case Stmt::OMPFuseDirectiveClass: case Stmt::OMPInteropDirectiveClass: >From 415eba88ad0af63c552b4c5c2eabd660bcb29c02 Mon Sep 17 00:00:00 2001 From: amtiwari <[email protected]> Date: Wed, 8 Jul 2026 16:21:25 -0400 Subject: [PATCH 07/14] codegen flatten directive --- clang/lib/CodeGen/CGStmt.cpp | 3 +++ clang/lib/CodeGen/CGStmtOpenMP.cpp | 8 ++++++++ clang/lib/CodeGen/CodeGenFunction.h | 1 + 3 files changed, 12 insertions(+) diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp index 232094777f233..27f48cb63b553 100644 --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -236,6 +236,9 @@ void CodeGenFunction::EmitStmt(const Stmt *S, ArrayRef<const Attr *> Attrs) { case Stmt::OMPInterchangeDirectiveClass: EmitOMPInterchangeDirective(cast<OMPInterchangeDirective>(*S)); break; + case Stmt::OMPFlattenDirectiveClass: + EmitOMPFlattenDirective(cast<OMPFlattenDirective>(*S)); + break; case Stmt::OMPFuseDirectiveClass: EmitOMPFuseDirective(cast<OMPFuseDirective>(*S)); break; diff --git a/clang/lib/CodeGen/CGStmtOpenMP.cpp b/clang/lib/CodeGen/CGStmtOpenMP.cpp index 95fd6694437fe..25da3430a23fe 100644 --- a/clang/lib/CodeGen/CGStmtOpenMP.cpp +++ b/clang/lib/CodeGen/CGStmtOpenMP.cpp @@ -218,6 +218,8 @@ class OMPLoopScope : public CodeGenFunction::RunCleanupsScope { } else if (const auto *Interchange = dyn_cast<OMPInterchangeDirective>(&S)) { PreInits = Interchange->getPreInits(); + } else if (const auto *Flatten = dyn_cast<OMPFlattenDirective>(&S)) { + PreInits = Flatten->getPreInits(); } else { llvm_unreachable("Unknown loop-based directive kind."); } @@ -3248,6 +3250,12 @@ void CodeGenFunction::EmitOMPInterchangeDirective( EmitStmt(S.getTransformedStmt()); } +void CodeGenFunction::EmitOMPFlattenDirective(const OMPFlattenDirective &S) { + // Emit the de-sugared statement. + OMPTransformDirectiveScopeRAII FlattenScope(*this, &S); + EmitStmt(S.getTransformedStmt()); +} + void CodeGenFunction::EmitOMPFuseDirective(const OMPFuseDirective &S) { // Emit the de-sugared statement OMPTransformDirectiveScopeRAII FuseScope(*this, &S); diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index 6d0718c243812..eea0163422cd3 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -3934,6 +3934,7 @@ class CodeGenFunction : public CodeGenTypeCache { void EmitOMPReverseDirective(const OMPReverseDirective &S); void EmitOMPSplitDirective(const OMPSplitDirective &S); void EmitOMPInterchangeDirective(const OMPInterchangeDirective &S); + void EmitOMPFlattenDirective(const OMPFlattenDirective &S); void EmitOMPFuseDirective(const OMPFuseDirective &S); void EmitOMPForDirective(const OMPForDirective &S); void EmitOMPForSimdDirective(const OMPForSimdDirective &S); >From 814e2e9fe9bf4e28e4557999fbc16beec144cc39 Mon Sep 17 00:00:00 2001 From: amtiwari <[email protected]> Date: Wed, 8 Jul 2026 16:24:42 -0400 Subject: [PATCH 08/14] add NYI CIR stub --- clang/lib/CIR/CodeGen/CIRGenFunction.h | 1 + clang/lib/CIR/CodeGen/CIRGenStmt.cpp | 2 ++ clang/lib/CIR/CodeGen/CIRGenStmtOpenMP.cpp | 5 +++++ 3 files changed, 8 insertions(+) diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index 322355fde3957..8374583ffd4d0 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -2559,6 +2559,7 @@ class CIRGenFunction : public CIRGenTypeCache { mlir::LogicalResult emitOMPSplitDirective(const OMPSplitDirective &s); mlir::LogicalResult emitOMPInterchangeDirective(const OMPInterchangeDirective &s); + mlir::LogicalResult emitOMPFlattenDirective(const OMPFlattenDirective &s); mlir::LogicalResult emitOMPAssumeDirective(const OMPAssumeDirective &s); mlir::LogicalResult emitOMPMaskedDirective(const OMPMaskedDirective &s); mlir::LogicalResult emitOMPStripeDirective(const OMPStripeDirective &s); diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp index d3acac5801e74..33d425b22d61a 100644 --- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp @@ -408,6 +408,8 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *s, return emitOMPSplitDirective(cast<OMPSplitDirective>(*s)); case Stmt::OMPInterchangeDirectiveClass: return emitOMPInterchangeDirective(cast<OMPInterchangeDirective>(*s)); + case Stmt::OMPFlattenDirectiveClass: + return emitOMPFlattenDirective(cast<OMPFlattenDirective>(*s)); case Stmt::OMPAssumeDirectiveClass: return emitOMPAssumeDirective(cast<OMPAssumeDirective>(*s)); case Stmt::OMPMaskedDirectiveClass: diff --git a/clang/lib/CIR/CodeGen/CIRGenStmtOpenMP.cpp b/clang/lib/CIR/CodeGen/CIRGenStmtOpenMP.cpp index 17a1fb8090f5c..4fffd17b25a16 100644 --- a/clang/lib/CIR/CodeGen/CIRGenStmtOpenMP.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenStmtOpenMP.cpp @@ -587,6 +587,11 @@ CIRGenFunction::emitOMPInterchangeDirective(const OMPInterchangeDirective &s) { return mlir::failure(); } mlir::LogicalResult +CIRGenFunction::emitOMPFlattenDirective(const OMPFlattenDirective &s) { + getCIRGenModule().errorNYI(s.getSourceRange(), "OpenMP OMPFlattenDirective"); + return mlir::failure(); +} +mlir::LogicalResult CIRGenFunction::emitOMPAssumeDirective(const OMPAssumeDirective &s) { getCIRGenModule().errorNYI(s.getSourceRange(), "OpenMP OMPAssumeDirective"); return mlir::failure(); >From 0027ec6ab982a83766bd65f3e1bc45d9a671fc62 Mon Sep 17 00:00:00 2001 From: amtiwari <[email protected]> Date: Wed, 8 Jul 2026 16:26:31 -0400 Subject: [PATCH 09/14] add libclang traversal support for depth clause --- clang/tools/libclang/CIndex.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp index ac2fad38a1348..f8ab63e3e0dae 100644 --- a/clang/tools/libclang/CIndex.cpp +++ b/clang/tools/libclang/CIndex.cpp @@ -2378,6 +2378,10 @@ void OMPClauseEnqueue::VisitOMPLoopRangeClause(const OMPLoopRangeClause *C) { Visitor->AddStmt(C->getCount()); } +void OMPClauseEnqueue::VisitOMPDepthClause(const OMPDepthClause *C) { + Visitor->AddStmt(C->getDepth()); +} + void OMPClauseEnqueue::VisitOMPAllocatorClause(const OMPAllocatorClause *C) { Visitor->AddStmt(C->getAllocator()); } >From f5ac5f740c414310c8482f652f1bed936ae7cda3 Mon Sep 17 00:00:00 2001 From: amtiwari <[email protected]> Date: Fri, 10 Jul 2026 06:54:16 -0400 Subject: [PATCH 10/14] sema --- clang/include/clang/Sema/SemaOpenMP.h | 10 + clang/lib/Sema/SemaOpenMP.cpp | 295 ++++++++++++++++++++++++++ 2 files changed, 305 insertions(+) diff --git a/clang/include/clang/Sema/SemaOpenMP.h b/clang/include/clang/Sema/SemaOpenMP.h index 3621ce96b8724..7b086388416a0 100644 --- a/clang/include/clang/Sema/SemaOpenMP.h +++ b/clang/include/clang/Sema/SemaOpenMP.h @@ -470,6 +470,12 @@ class SemaOpenMP : public SemaBase { SourceLocation StartLoc, SourceLocation EndLoc); + /// Called on well-formed '#pragma omp flatten' after parsing of its + /// clauses and the associated statement. + StmtResult ActOnOpenMPFlattenDirective(ArrayRef<OMPClause *> Clauses, + Stmt *AStmt, SourceLocation StartLoc, + SourceLocation EndLoc); + /// Called on well-formed '#pragma omp fuse' after parsing of its /// clauses and the associated statement. StmtResult ActOnOpenMPFuseDirective(ArrayRef<OMPClause *> Clauses, @@ -935,6 +941,10 @@ class SemaOpenMP : public SemaBase { OMPClause *ActOnOpenMPPartialClause(Expr *FactorExpr, SourceLocation StartLoc, SourceLocation LParenLoc, SourceLocation EndLoc); + /// Called on well-formed 'depth' clause. + OMPClause *ActOnOpenMPDepthClause(Expr *DepthExpr, SourceLocation StartLoc, + SourceLocation LParenLoc, + SourceLocation EndLoc); /// Called on well-formed 'collapse' clause. OMPClause *ActOnOpenMPCollapseClause(Expr *NumForLoops, SourceLocation StartLoc, diff --git a/clang/lib/Sema/SemaOpenMP.cpp b/clang/lib/Sema/SemaOpenMP.cpp index 247a4a6ad9271..fcc256dec3c88 100644 --- a/clang/lib/Sema/SemaOpenMP.cpp +++ b/clang/lib/Sema/SemaOpenMP.cpp @@ -4629,6 +4629,7 @@ void SemaOpenMP::ActOnOpenMPRegionStart(OpenMPDirectiveKind DKind, case OMPD_reverse: case OMPD_split: case OMPD_interchange: + case OMPD_flatten: case OMPD_fuse: case OMPD_assume: break; @@ -6475,6 +6476,10 @@ StmtResult SemaOpenMP::ActOnOpenMPExecutableDirective( Res = ActOnOpenMPInterchangeDirective(ClausesWithImplicit, AStmt, StartLoc, EndLoc); break; + case OMPD_flatten: + Res = ActOnOpenMPFlattenDirective(ClausesWithImplicit, AStmt, StartLoc, + EndLoc); + break; case OMPD_fuse: Res = ActOnOpenMPFuseDirective(ClausesWithImplicit, AStmt, StartLoc, EndLoc); @@ -16390,6 +16395,277 @@ StmtResult SemaOpenMP::ActOnOpenMPInterchangeDirective( buildPreInits(Context, PreInits)); } +/// Counts the perfectly nested canonical loop depth at \p AStmt. Returns +/// std::nullopt if a loop-generating transformation prevents a static count. +static std::optional<unsigned> getCanonicalLoopNestDepth(Stmt *AStmt) { + unsigned Depth = 0; + Stmt *CurStmt = AStmt ? AStmt->IgnoreContainers() : nullptr; + while (CurStmt) { + if (isa<OMPLoopTransformationDirective>(CurStmt)) + return std::nullopt; + if (auto *CanonLoop = dyn_cast<OMPCanonicalLoop>(CurStmt)) + CurStmt = CanonLoop->getLoopStmt(); + Stmt *Body = nullptr; + if (auto *For = dyn_cast<ForStmt>(CurStmt)) + Body = For->getBody(); + else if (auto *RangeFor = dyn_cast<CXXForRangeStmt>(CurStmt)) + Body = RangeFor->getBody(); + else + break; + ++Depth; + CurStmt = Body ? Body->IgnoreContainers() : nullptr; + } + return Depth; +} + +StmtResult +SemaOpenMP::ActOnOpenMPFlattenDirective(ArrayRef<OMPClause *> Clauses, + Stmt *AStmt, SourceLocation StartLoc, + SourceLocation EndLoc) { + ASTContext &Context = getASTContext(); + DeclContext *CurContext = SemaRef.CurContext; + Scope *CurScope = SemaRef.getCurScope(); + + // Empty statement should only be possible if there already was an error. + if (!AStmt) + return StmtError(); + + // flatten without 'depth' clause combines two loops; 'depth(k)' selects k. + unsigned NumLoops = 2; + bool DepthIsValueDependent = false; + const auto *DepthClause = + OMPExecutableDirective::getSingleClause<OMPDepthClause>(Clauses); + if (DepthClause) { + Expr *DepthExpr = DepthClause->getDepth(); + if (DepthExpr && DepthExpr->isValueDependent()) { + DepthIsValueDependent = true; + } else if (DepthExpr) { + Expr::EvalResult EvalResult; + if (DepthExpr->EvaluateAsInt(EvalResult, Context)) + NumLoops = EvalResult.Val.getInt().getZExtValue(); + } + } + + // Report "expected k for loops, but found only n" when depth exceeds the + // perfect nest (same form as 'collapse'); skip if depth or nest is unknown. + if (DepthClause && !DepthIsValueDependent) { + if (std::optional<unsigned> NestDepth = getCanonicalLoopNestDepth(AStmt); + NestDepth && NumLoops > *NestDepth) { + Diag(AStmt->getBeginLoc(), diag::err_omp_not_for) + << /*expected N for loops form=*/1 + << getOpenMPDirectiveName(OMPD_flatten) << NumLoops + << (*NestDepth > 0) << *NestDepth; + return StmtError(); + } + } + + // Defer when 'depth' is value-dependent (concrete k unknown until + // instantiation). + if (DepthIsValueDependent) + return OMPFlattenDirective::Create(Context, StartLoc, EndLoc, Clauses, + NumLoops, AStmt, nullptr, nullptr); + + // Verify and diagnose loop nest. + SmallVector<OMPLoopBasedDirective::HelperExprs, 4> LoopHelpers(NumLoops); + Stmt *Body = nullptr; + SmallVector<SmallVector<Stmt *>, 4> OriginalInits; + if (!checkTransformableLoopNest(OMPD_flatten, AStmt, NumLoops, LoopHelpers, + Body, OriginalInits)) + return StmtError(); + + // Delay flattening to when template is completely instantiated. + if (CurContext->isDependentContext()) + return OMPFlattenDirective::Create(Context, StartLoc, EndLoc, Clauses, + NumLoops, AStmt, nullptr, nullptr); + + assert(LoopHelpers.size() == NumLoops && + "Expecting loop iteration space dimensionality to match number of " + "affected loops"); + assert(OriginalInits.size() == NumLoops && + "Expecting loop iteration space dimensionality to match number of " + "affected loops"); + + // Find the affected loops. + SmallVector<Stmt *> LoopStmts(NumLoops, nullptr); + collectLoopStmts(AStmt, LoopStmts); + + // Collect pre-init statements in outer-to-inner order. + SmallVector<Stmt *> PreInits; + for (auto I : llvm::seq<int>(NumLoops)) { + OMPLoopBasedDirective::HelperExprs &LoopHelper = LoopHelpers[I]; + assert(LoopHelper.Counters.size() == 1 && + "Single-dimensional loop iteration space expected"); + addLoopPreInits(Context, LoopHelper, LoopStmts[I], OriginalInits[I], + PreInits); + } + + CaptureVars CopyTransformer(SemaRef); + auto MakeNumIterations = [&CopyTransformer, + &LoopHelpers](unsigned I) -> Expr * { + return AssertSuccess( + CopyTransformer.TransformExpr(LoopHelpers[I].NumIterations)); + }; + + OMPLoopBasedDirective::HelperExprs &OutermostHelper = LoopHelpers[0]; + auto *OutermostCntVar = cast<DeclRefExpr>(OutermostHelper.Counters.front()); + SourceLocation OrigVarLoc = OutermostCntVar->getExprLoc(); + SourceLocation OrigVarLocBegin = OutermostCntVar->getBeginLoc(); + SourceLocation OrigVarLocEnd = OutermostCntVar->getEndLoc(); + SourceLocation CondLoc = OutermostHelper.Cond->getExprLoc(); + + // Product of trip counts; mirror 'collapse' IV-width selection to avoid + // overflow when several counts are multiplied. + auto BuildTripCount = [&](unsigned Bits) -> ExprResult { + ExprResult Product; + for (unsigned I = 0; I < NumLoops; ++I) { + ExprResult N = widenIterationCount(Bits, MakeNumIterations(I), SemaRef); + if (!N.isUsable()) + return ExprError(); + if (I == 0) + Product = N; + else + Product = SemaRef.BuildBinOp(CurScope, CondLoc, BO_Mul, Product.get(), + N.get()); + if (!Product.isUsable()) + return ExprError(); + } + return Product; + }; + + bool AllCountsLessThan32Bits = true; + for (unsigned I = 0; I < NumLoops; ++I) + AllCountsLessThan32Bits &= + Context.getTypeSize(LoopHelpers[I].NumIterations->getType()) < 32; + + ExprResult TripCount32 = BuildTripCount(/*Bits=*/32); + ExprResult TripCount64 = BuildTripCount(/*Bits=*/64); + if (!TripCount32.isUsable() || !TripCount64.isUsable()) + return StmtError(); + + ExprResult TripCount = TripCount64; + if (Context.getTypeSize(TripCount32.get()->getType()) == 32 && + (AllCountsLessThan32Bits || NumLoops == 1 || + fitsInto( + /*Bits=*/32, + TripCount32.get()->getType()->hasSignedIntegerRepresentation(), + TripCount64.get(), SemaRef))) + TripCount = TripCount32; + + QualType IVTy = TripCount.get()->getType(); + uint64_t IVWidth = Context.getTypeSize(IVTy); + + auto MakeNumIterationsInIVTy = [&](unsigned I) -> Expr * { + return AssertSuccess(SemaRef.PerformImplicitConversion( + MakeNumIterations(I), IVTy, AssignmentAction::Converting, + /*AllowExplicit=*/true)); + }; + + // \code{.cpp} + // for (auto .flatten.iv = 0; .flatten.iv < n0 * n1 * ...; ++.flatten.iv) { + // .flatten.iv.0 = .flatten.iv / (n1 * ...); + // i0 = ...; // Updates[0] + // .flatten.iv.1 = (.flatten.iv / ...) % n1; + // i1 = ...; // Updates[1] + // ... + // body(i0, i1, ...); + // } + // \endcode + SmallString<64> FlattenedIVName(".flatten.iv"); + VarDecl *FlattenedIVDecl = buildVarDecl(SemaRef, {}, IVTy, FlattenedIVName, + nullptr, OutermostCntVar); + auto MakeFlattenedRef = [&SemaRef = this->SemaRef, FlattenedIVDecl, IVTy, + OrigVarLoc]() { + return buildDeclRefExpr(SemaRef, FlattenedIVDecl, IVTy, OrigVarLoc); + }; + + // For init-statement: + // \code{.cpp} + // auto .flatten.iv = 0; + // \endcode + auto *Zero = IntegerLiteral::Create(Context, llvm::APInt::getZero(IVWidth), + IVTy, OrigVarLoc); + SemaRef.AddInitializerToDecl(FlattenedIVDecl, Zero, /*DirectInit=*/false); + StmtResult Init = new (Context) + DeclStmt(DeclGroupRef(FlattenedIVDecl), OrigVarLocBegin, OrigVarLocEnd); + if (!Init.isUsable()) + return StmtError(); + + // For cond-expression: + // \code{.cpp} + // .flatten.iv < n0 * n1 * ... * n(k-1) + // \endcode + ExprResult Cond = SemaRef.BuildBinOp(CurScope, CondLoc, BO_LT, + MakeFlattenedRef(), TripCount.get()); + if (!Cond.isUsable()) + return StmtError(); + + // For incr-statement: + // \code{.cpp} + // ++.flatten.iv + // \endcode + ExprResult Incr = + SemaRef.BuildUnaryOp(CurScope, OutermostHelper.Inc->getExprLoc(), + UO_PreInc, MakeFlattenedRef()); + if (!Incr.isUsable()) + return StmtError(); + + // Recover each logical iteration counter via mixed-radix div/mod; reuse the + // iteration variables from checkOpenMPLoop so Updates compute user counters. + SmallVector<Stmt *, 8> BodyStmts; + for (unsigned I = 0; I < NumLoops; ++I) { + OMPLoopBasedDirective::HelperExprs &LoopHelper = LoopHelpers[I]; + auto *IVRef = cast<DeclRefExpr>(LoopHelper.IterationVarRef); + auto *IVDecl = cast<VarDecl>(IVRef->getDecl()); + std::string IVName = (".flatten.iv." + llvm::Twine(I)).str(); + IVDecl->setDeclName(&SemaRef.PP.getIdentifierTable().get(IVName)); + + ExprResult Value = MakeFlattenedRef(); + if (I + 1 < NumLoops) { + ExprResult Divisor = MakeNumIterationsInIVTy(I + 1); + for (unsigned J = I + 2; J < NumLoops; ++J) { + Divisor = SemaRef.BuildBinOp(CurScope, OrigVarLoc, BO_Mul, + Divisor.get(), MakeNumIterationsInIVTy(J)); + if (!Divisor.isUsable()) + return StmtError(); + } + Value = SemaRef.BuildBinOp(CurScope, OrigVarLoc, BO_Div, Value.get(), + Divisor.get()); + if (!Value.isUsable()) + return StmtError(); + } + if (I > 0) { + Value = SemaRef.BuildBinOp(CurScope, OrigVarLoc, BO_Rem, Value.get(), + MakeNumIterationsInIVTy(I)); + if (!Value.isUsable()) + return StmtError(); + } + + SemaRef.AddInitializerToDecl(IVDecl, Value.get(), /*DirectInit=*/false); + StmtResult IVStmt = new (Context) + DeclStmt(DeclGroupRef(IVDecl), OrigVarLocBegin, OrigVarLocEnd); + if (!IVStmt.isUsable()) + return StmtError(); + + BodyStmts.push_back(IVStmt.get()); + llvm::append_range(BodyStmts, LoopHelper.Updates); + if (auto *CXXFor = dyn_cast<CXXForRangeStmt>(LoopStmts[I])) + BodyStmts.push_back(CXXFor->getLoopVarStmt()); + } + BodyStmts.push_back(Body); + auto *FlattenedBody = + CompoundStmt::Create(Context, BodyStmts, FPOptionsOverride(), + Body->getBeginLoc(), Body->getEndLoc()); + + auto *FlattenedFor = new (Context) ForStmt( + Context, Init.get(), Cond.get(), nullptr, Incr.get(), FlattenedBody, + OutermostHelper.Init->getBeginLoc(), OutermostHelper.Init->getBeginLoc(), + OutermostHelper.Inc->getEndLoc()); + + return OMPFlattenDirective::Create(Context, StartLoc, EndLoc, Clauses, + NumLoops, AStmt, FlattenedFor, + buildPreInits(Context, PreInits)); +} + StmtResult SemaOpenMP::ActOnOpenMPFuseDirective(ArrayRef<OMPClause *> Clauses, Stmt *AStmt, SourceLocation StartLoc, @@ -16920,6 +17196,9 @@ OMPClause *SemaOpenMP::ActOnOpenMPSingleExprClause(OpenMPClauseKind Kind, case OMPC_partial: Res = ActOnOpenMPPartialClause(Expr, StartLoc, LParenLoc, EndLoc); break; + case OMPC_depth: + Res = ActOnOpenMPDepthClause(Expr, StartLoc, LParenLoc, EndLoc); + break; case OMPC_message: Res = ActOnOpenMPMessageClause(Expr, StartLoc, LParenLoc, EndLoc); break; @@ -18241,6 +18520,22 @@ OMPClause *SemaOpenMP::ActOnOpenMPPartialClause(Expr *FactorExpr, FactorExpr); } +OMPClause *SemaOpenMP::ActOnOpenMPDepthClause(Expr *DepthExpr, + SourceLocation StartLoc, + SourceLocation LParenLoc, + SourceLocation EndLoc) { + // The depth-expr must be a positive integer constant expression and + // not greater than the number of loops in the associated loop nest. + ExprResult DepthResult = VerifyPositiveIntegerConstantInClause( + DepthExpr, OMPC_depth, /*StrictlyPositive=*/true); + if (DepthResult.isInvalid()) + return nullptr; + DepthExpr = DepthResult.get(); + + return OMPDepthClause::Create(getASTContext(), StartLoc, LParenLoc, EndLoc, + DepthExpr); +} + OMPClause *SemaOpenMP::ActOnOpenMPLoopRangeClause( Expr *First, Expr *Count, SourceLocation StartLoc, SourceLocation LParenLoc, SourceLocation FirstLoc, SourceLocation CountLoc, SourceLocation EndLoc) { >From 54be7347979638725cd4b6c421d11ec1cd618c43 Mon Sep 17 00:00:00 2001 From: amtiwari <[email protected]> Date: Mon, 27 Jul 2026 07:03:06 -0400 Subject: [PATCH 11/14] add libclang traversal support for flatten directive --- clang/tools/libclang/CIndex.cpp | 7 +++++++ clang/tools/libclang/CXCursor.cpp | 3 +++ 2 files changed, 10 insertions(+) diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp index f8ab63e3e0dae..9eb0ec8c21158 100644 --- a/clang/tools/libclang/CIndex.cpp +++ b/clang/tools/libclang/CIndex.cpp @@ -2160,6 +2160,7 @@ class EnqueueVisitor : public ConstStmtVisitor<EnqueueVisitor, void>, void VisitOMPUnrollDirective(const OMPUnrollDirective *D); void VisitOMPReverseDirective(const OMPReverseDirective *D); void VisitOMPInterchangeDirective(const OMPInterchangeDirective *D); + void VisitOMPFlattenDirective(const OMPFlattenDirective *D); void VisitOMPCanonicalLoopSequenceTransformationDirective( const OMPCanonicalLoopSequenceTransformationDirective *D); void VisitOMPFuseDirective(const OMPFuseDirective *D); @@ -3358,6 +3359,10 @@ void EnqueueVisitor::VisitOMPInterchangeDirective( VisitOMPCanonicalLoopNestTransformationDirective(D); } +void EnqueueVisitor::VisitOMPFlattenDirective(const OMPFlattenDirective *D) { + VisitOMPCanonicalLoopNestTransformationDirective(D); +} + void EnqueueVisitor::VisitOMPCanonicalLoopSequenceTransformationDirective( const OMPCanonicalLoopSequenceTransformationDirective *D) { VisitOMPExecutableDirective(D); @@ -6329,6 +6334,8 @@ CXString clang_getCursorKindSpelling(enum CXCursorKind Kind) { return cxstring::createRef("OMPReverseDirective"); case CXCursor_OMPInterchangeDirective: return cxstring::createRef("OMPInterchangeDirective"); + case CXCursor_OMPFlattenDirective: + return cxstring::createRef("OMPFlattenDirective"); case CXCursor_OMPFuseDirective: return cxstring::createRef("OMPFuseDirective"); case CXCursor_OMPSplitDirective: diff --git a/clang/tools/libclang/CXCursor.cpp b/clang/tools/libclang/CXCursor.cpp index b2feed578fc0f..97bbfd36558a9 100644 --- a/clang/tools/libclang/CXCursor.cpp +++ b/clang/tools/libclang/CXCursor.cpp @@ -703,6 +703,9 @@ CXCursor cxcursor::MakeCXCursor(const Stmt *S, const Decl *Parent, case Stmt::OMPInterchangeDirectiveClass: K = CXCursor_OMPInterchangeDirective; break; + case Stmt::OMPFlattenDirectiveClass: + K = CXCursor_OMPFlattenDirective; + break; case Stmt::OMPFuseDirectiveClass: K = CXCursor_OMPFuseDirective; break; >From cdedb1045baf373937b0da858624a6ace14a3d21 Mon Sep 17 00:00:00 2001 From: amtiwari <[email protected]> Date: Mon, 27 Jul 2026 07:06:36 -0400 Subject: [PATCH 12/14] bindings --- clang/bindings/python/clang/cindex.py | 3 +++ clang/include/clang-c/Index.h | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py index 24b737139dba8..ccad140e8c861 100644 --- a/clang/bindings/python/clang/cindex.py +++ b/clang/bindings/python/clang/cindex.py @@ -1456,6 +1456,9 @@ def is_unexposed(self): # OpenMP split directive. OMP_SPLIT_DIRECTIVE = 312 + # OpenMP flatten directive. + OMP_FLATTEN_DIRECTIVE = 313 + # OpenACC Compute Construct. OPEN_ACC_COMPUTE_DIRECTIVE = 320 diff --git a/clang/include/clang-c/Index.h b/clang/include/clang-c/Index.h index 8427236e0b444..f561fb5724897 100644 --- a/clang/include/clang-c/Index.h +++ b/clang/include/clang-c/Index.h @@ -2170,6 +2170,10 @@ enum CXCursorKind { */ CXCursor_OMPSplitDirective = 312, + /** OpenMP flatten directive. + */ + CXCursor_OMPFlattenDirective = 313, + /** OpenACC Compute Construct. */ CXCursor_OpenACCComputeConstruct = 320, >From 94dee8fe1351f74760df0fb2d9903a291a5ccf66 Mon Sep 17 00:00:00 2001 From: amtiwari <[email protected]> Date: Wed, 5 Aug 2026 04:46:14 -0400 Subject: [PATCH 13/14] core tests-flatten and depth --- clang/test/OpenMP/flatten_codegen.cpp | 65 ++++++++ clang/test/OpenMP/flatten_depth_codegen.cpp | 154 ++++++++++++++++++ .../test/transform/flatten/depth-intfor.c | 40 +++++ .../runtime/test/transform/flatten/intfor.c | 33 ++++ .../test/transform/flatten/lit.local.cfg | 5 + 5 files changed, 297 insertions(+) create mode 100644 clang/test/OpenMP/flatten_codegen.cpp create mode 100644 clang/test/OpenMP/flatten_depth_codegen.cpp create mode 100644 openmp/runtime/test/transform/flatten/depth-intfor.c create mode 100644 openmp/runtime/test/transform/flatten/intfor.c create mode 100644 openmp/runtime/test/transform/flatten/lit.local.cfg diff --git a/clang/test/OpenMP/flatten_codegen.cpp b/clang/test/OpenMP/flatten_codegen.cpp new file mode 100644 index 0000000000000..f77b2fb00ce9b --- /dev/null +++ b/clang/test/OpenMP/flatten_codegen.cpp @@ -0,0 +1,65 @@ +// Check code generation +// RUN: %clang_cc1 -verify -triple x86_64-pc-linux-gnu -std=c++20 -fopenmp -fopenmp-version=60 -emit-llvm %s -o - | FileCheck %s + +// expected-no-diagnostics + +#ifndef HEADER +#define HEADER + +extern "C" void body(int, int); + +// CHECK-LABEL: define {{.*}}void @foo( +// CHECK: %.flatten.iv = alloca i64 +// CHECK: %.flatten.iv.0 = alloca i32 +// CHECK: %.flatten.iv.1 = alloca i32 +// CHECK: store i64 0, ptr %.flatten.iv, +// CHECK: %[[COND_IV:.+]] = load i64, ptr %.flatten.iv, +// CHECK: %[[MUL:.+]] = mul nsw i64 %{{.+}}, %{{.+}} +// CHECK: icmp slt i64 %[[COND_IV]], %[[MUL]] +// CHECK: %[[DIV_IV:.+]] = load i64, ptr %.flatten.iv, +// CHECK: %[[DIV:.+]] = sdiv i64 %[[DIV_IV]], %{{.+}} +// CHECK: store i32 %{{.+}}, ptr %.flatten.iv.0 +// CHECK: %[[REM_IV:.+]] = load i64, ptr %.flatten.iv, +// CHECK: %[[REM:.+]] = srem i64 %[[REM_IV]], %{{.+}} +// CHECK: store i32 %{{.+}}, ptr %.flatten.iv.1 +// CHECK: %[[I_VAL:.+]] = load i32, ptr %i, +// CHECK: %[[J_VAL:.+]] = load i32, ptr %j, +// CHECK: call void @body(i32{{.*}} %[[I_VAL]], i32{{.*}} %[[J_VAL]]) +// CHECK: for.inc: +// CHECK: %[[INC_IV:.+]] = load i64, ptr %.flatten.iv, +// CHECK: %[[NEXT:.+]] = add nsw i64 %[[INC_IV]], 1 +// CHECK: store i64 %[[NEXT]], ptr %.flatten.iv, +// CHECK: br label %for.cond +extern "C" void foo(int n, int m) { +#pragma omp flatten + for (int i = 0; i < n; ++i) + for (int j = 0; j < m; ++j) + body(i, j); +} + +// CHECK-LABEL: define {{.*}}void @bar( +// CHECK: icmp slt i32 %{{.+}}, 63 +// CHECK: sdiv i32 %{{.+}}, 9 +// CHECK: srem i32 %{{.+}}, 9 +// CHECK: call void @body( +extern "C" void bar() { +#pragma omp flatten + for (int i = 0; i < 7; ++i) + for (int j = 0; j < 9; ++j) + body(i, j); +} + +// CHECK-LABEL: define {{.*}}void @baz( +// CHECK: %.flatten.iv = alloca i64 +// CHECK: icmp slt i64 %{{.+}}, 10000000000 +// CHECK: sdiv i64 %{{.+}}, 100000 +// CHECK: srem i64 %{{.+}}, 100000 +// CHECK: call void @body( +extern "C" void baz() { +#pragma omp flatten + for (int i = 0; i < 100000; ++i) + for (int j = 0; j < 100000; ++j) + body(i, j); +} + +#endif diff --git a/clang/test/OpenMP/flatten_depth_codegen.cpp b/clang/test/OpenMP/flatten_depth_codegen.cpp new file mode 100644 index 0000000000000..77408fbcbdcf1 --- /dev/null +++ b/clang/test/OpenMP/flatten_depth_codegen.cpp @@ -0,0 +1,154 @@ +// Check code generation for the 'depth' clause on '#pragma omp flatten'. +// RUN: %clang_cc1 -verify -triple x86_64-pc-linux-gnu -std=c++20 -fopenmp -fopenmp-version=61 -emit-llvm %s -o - | FileCheck %s + +// expected-no-diagnostics + +#ifndef HEADER +#define HEADER + +extern "C" void body(int, int, int); + +// CHECK-LABEL: define {{.*}}void @foo3( +// CHECK: %.flatten.iv = alloca i64 +// CHECK: %.flatten.iv.0 = alloca i32 +// CHECK: %.flatten.iv.1 = alloca i32 +// CHECK: %.flatten.iv.2 = alloca i32 +// CHECK: %[[CIV:.+]] = load i64, ptr %.flatten.iv, +// CHECK: %[[T01:.+]] = mul nsw i64 %{{.+}}, %{{.+}} +// CHECK: %[[TALL:.+]] = mul nsw i64 %[[T01]], %{{.+}} +// CHECK: icmp slt i64 %[[CIV]], %[[TALL]] +// CHECK: %[[D0IV:.+]] = load i64, ptr %.flatten.iv, +// CHECK: %[[D0M:.+]] = mul nsw i64 %{{.+}}, %{{.+}} +// CHECK: %[[D0:.+]] = sdiv i64 %[[D0IV]], %[[D0M]] +// CHECK: store i32 %{{.+}}, ptr %.flatten.iv.0 +// CHECK: %[[D1IV:.+]] = load i64, ptr %.flatten.iv, +// CHECK: %[[D1:.+]] = sdiv i64 %[[D1IV]], %{{.+}} +// CHECK: %[[R1:.+]] = srem i64 %[[D1]], %{{.+}} +// CHECK: store i32 %{{.+}}, ptr %.flatten.iv.1 +// CHECK: %[[R2IV:.+]] = load i64, ptr %.flatten.iv, +// CHECK: %[[R2:.+]] = srem i64 %[[R2IV]], %{{.+}} +// CHECK: store i32 %{{.+}}, ptr %.flatten.iv.2 +// CHECK: call void @body( +extern "C" void foo3(int n, int m, int p) { +#pragma omp flatten depth(3) + for (int i = 0; i < n; ++i) + for (int j = 0; j < m; ++j) + for (int k = 0; k < p; ++k) + body(i, j, k); +} + +// CHECK-LABEL: define {{.*}}void @foo2( +// CHECK: %.flatten.iv = alloca i64 +// CHECK: %.flatten.iv.0 = alloca i32 +// CHECK: %.flatten.iv.1 = alloca i32 +// CHECK: %[[CIV2:.+]] = load i64, ptr %.flatten.iv, +// CHECK: %[[M:.+]] = mul nsw i64 %{{.+}}, %{{.+}} +// CHECK: icmp slt i64 %[[CIV2]], %[[M]] +// CHECK: sdiv i64 %{{.+}}, %{{.+}} +// CHECK: store i32 %{{.+}}, ptr %.flatten.iv.0 +// CHECK: srem i64 %{{.+}}, %{{.+}} +// CHECK: store i32 %{{.+}}, ptr %.flatten.iv.1 +extern "C" void foo2(int n, int m) { +#pragma omp flatten depth(2) + for (int i = 0; i < n; ++i) + for (int j = 0; j < m; ++j) + body(i, j, 0); +} + +// CHECK-LABEL: define {{.*}}void @foo1( +// CHECK: %.flatten.iv = alloca i32 +// CHECK: %.flatten.iv.0 = alloca i32 +// CHECK: for.body: +// CHECK: %[[FV:.+]] = load i32, ptr %.flatten.iv, +// CHECK: store i32 %[[FV]], ptr %.flatten.iv.0, +// CHECK-NOT: sdiv +// CHECK-NOT: srem +// CHECK: call void @body( +extern "C" void foo1(int n) { +#pragma omp flatten depth(1) + for (int i = 0; i < n; ++i) + body(i, 0, 0); +} + +// CHECK-LABEL: define {{.*}}void @foo2partial( +// CHECK: %.flatten.iv = alloca i64 +// CHECK: %.flatten.iv.0 = alloca i32 +// CHECK: %.flatten.iv.1 = alloca i32 +// CHECK-NOT: %.flatten.iv.2 = alloca +// CHECK: %[[CIV:.+]] = load i64, ptr %.flatten.iv, +// CHECK: %[[M:.+]] = mul nsw i64 %{{.+}}, %{{.+}} +// CHECK: icmp slt i64 %[[CIV]], %[[M]] +// CHECK: sdiv i64 %{{.+}}, %{{.+}} +// CHECK: store i32 %{{.+}}, ptr %.flatten.iv.0 +// CHECK: srem i64 %{{.+}}, %{{.+}} +// CHECK: store i32 %{{.+}}, ptr %.flatten.iv.1 +// CHECK: br label %for.cond{{.*}} +// CHECK: for.cond{{.*}}: +// CHECK: icmp slt i32 %{{.+}}, 5 +extern "C" void foo2partial(int n, int m) { +#pragma omp flatten depth(2) + for (int i = 0; i < n; ++i) + for (int j = 0; j < m; ++j) + for (int k = 0; k < 5; ++k) + body(i, j, k); +} + +// A value-dependent 'depth' (the argument is a non-type template parameter) is +// deferred until instantiation, where the concrete depth drives the flattening. + +// CHECK-LABEL: define {{.*}}void @_Z11tmpl_depth3ILi3EEviii( +// CHECK: %.flatten.iv = alloca i64 +// CHECK: %.flatten.iv.0 = alloca i32 +// CHECK: %.flatten.iv.1 = alloca i32 +// CHECK: %.flatten.iv.2 = alloca i32 +// CHECK: %[[CIV:.+]] = load i64, ptr %.flatten.iv, +// CHECK: %[[T01:.+]] = mul nsw i64 %{{.+}}, %{{.+}} +// CHECK: %[[TALL:.+]] = mul nsw i64 %[[T01]], %{{.+}} +// CHECK: icmp slt i64 %[[CIV]], %[[TALL]] +// CHECK: %[[D0IV:.+]] = load i64, ptr %.flatten.iv, +// CHECK: %[[D0M:.+]] = mul nsw i64 %{{.+}}, %{{.+}} +// CHECK: %[[D0:.+]] = sdiv i64 %[[D0IV]], %[[D0M]] +// CHECK: store i32 %{{.+}}, ptr %.flatten.iv.0 +// CHECK: %[[D1IV:.+]] = load i64, ptr %.flatten.iv, +// CHECK: %[[D1:.+]] = sdiv i64 %[[D1IV]], %{{.+}} +// CHECK: %[[R1:.+]] = srem i64 %[[D1]], %{{.+}} +// CHECK: store i32 %{{.+}}, ptr %.flatten.iv.1 +// CHECK: %[[R2IV:.+]] = load i64, ptr %.flatten.iv, +// CHECK: %[[R2:.+]] = srem i64 %[[R2IV]], %{{.+}} +// CHECK: store i32 %{{.+}}, ptr %.flatten.iv.2 +// CHECK: call void @body( +template <int D> +void tmpl_depth3(int n, int m, int p) { +#pragma omp flatten depth(D) + for (int i = 0; i < n; ++i) + for (int j = 0; j < m; ++j) + for (int k = 0; k < p; ++k) + body(i, j, k); +} + +// A value-dependent 'depth' that instantiates to depth(1) on a single loop is +// well-formed: the shallower-than-default nest is accepted because validation is +// deferred to instantiation. The single loop needs no div/mod recovery. + +// CHECK-LABEL: define {{.*}}void @_Z11tmpl_depth1ILi1EEvi( +// CHECK: %.flatten.iv = alloca i32 +// CHECK: %.flatten.iv.0 = alloca i32 +// CHECK: for.body: +// CHECK: %[[FV:.+]] = load i32, ptr %.flatten.iv, +// CHECK: store i32 %[[FV]], ptr %.flatten.iv.0, +// CHECK-NOT: sdiv +// CHECK-NOT: srem +// CHECK: call void @body( +template <int D> +void tmpl_depth1(int n) { +#pragma omp flatten depth(D) + for (int i = 0; i < n; ++i) + body(i, 0, 0); +} + +void inst() { + tmpl_depth3<3>(1, 1, 1); + tmpl_depth1<1>(1); +} + +#endif diff --git a/openmp/runtime/test/transform/flatten/depth-intfor.c b/openmp/runtime/test/transform/flatten/depth-intfor.c new file mode 100644 index 0000000000000..7211e02e25313 --- /dev/null +++ b/openmp/runtime/test/transform/flatten/depth-intfor.c @@ -0,0 +1,40 @@ +// RUN: %libomp-compile-and-run | FileCheck %s --match-full-lines + +// 'depth(3)' fully flattens a three-deep perfectly nested loop into one loop. +// This verifies that the flattened loop preserves the exact row-major +// visitation order of the original nest, including non-unit steps and non-zero +// start values. + +#ifndef HEADER +#define HEADER + +#include <stdlib.h> +#include <stdio.h> + +int main() { + printf("do\n"); +#pragma omp flatten depth(3) + for (int i = 7; i < 11; i += 2) + for (int j = 1; j < 4; j += 1) + for (int k = 0; k < 2; ++k) + printf("i=%d j=%d k=%d\n", i, j, k); + printf("done\n"); + return EXIT_SUCCESS; +} + +#endif /* HEADER */ + +// CHECK: do +// CHECK-NEXT: i=7 j=1 k=0 +// CHECK-NEXT: i=7 j=1 k=1 +// CHECK-NEXT: i=7 j=2 k=0 +// CHECK-NEXT: i=7 j=2 k=1 +// CHECK-NEXT: i=7 j=3 k=0 +// CHECK-NEXT: i=7 j=3 k=1 +// CHECK-NEXT: i=9 j=1 k=0 +// CHECK-NEXT: i=9 j=1 k=1 +// CHECK-NEXT: i=9 j=2 k=0 +// CHECK-NEXT: i=9 j=2 k=1 +// CHECK-NEXT: i=9 j=3 k=0 +// CHECK-NEXT: i=9 j=3 k=1 +// CHECK-NEXT: done diff --git a/openmp/runtime/test/transform/flatten/intfor.c b/openmp/runtime/test/transform/flatten/intfor.c new file mode 100644 index 0000000000000..f937659fbebe1 --- /dev/null +++ b/openmp/runtime/test/transform/flatten/intfor.c @@ -0,0 +1,33 @@ +// RUN: %libomp-compile-and-run | FileCheck %s --match-full-lines + +#ifndef HEADER +#define HEADER + +#include <stdlib.h> +#include <stdio.h> + +int main() { + printf("do\n"); +#pragma omp flatten + for (int i = 5; i < 11; i += 2) + for (int j = 1; j < 7; j += 2) + printf("i=%d j=%d\n", i, j); + printf("done\n"); + return EXIT_SUCCESS; +} + +#endif /* HEADER */ + +// The flattened loop visits the original iteration space in the original +// (row-major) order: for each outer iteration the inner loop runs fully. +// CHECK: do +// CHECK-NEXT: i=5 j=1 +// CHECK-NEXT: i=5 j=3 +// CHECK-NEXT: i=5 j=5 +// CHECK-NEXT: i=7 j=1 +// CHECK-NEXT: i=7 j=3 +// CHECK-NEXT: i=7 j=5 +// CHECK-NEXT: i=9 j=1 +// CHECK-NEXT: i=9 j=3 +// CHECK-NEXT: i=9 j=5 +// CHECK-NEXT: done diff --git a/openmp/runtime/test/transform/flatten/lit.local.cfg b/openmp/runtime/test/transform/flatten/lit.local.cfg new file mode 100644 index 0000000000000..e05bd627df1e9 --- /dev/null +++ b/openmp/runtime/test/transform/flatten/lit.local.cfg @@ -0,0 +1,5 @@ +# The flatten directive's depth clause requires OpenMP 6.1. +for i, (pattern, replacement) in enumerate(config.substitutions): + if pattern == "%openmp_flags": + config.substitutions[i] = (pattern, replacement + " -fopenmp-version=61") + break >From 667fe837a39dbc711dcc5288b736aa52fbe893b6 Mon Sep 17 00:00:00 2001 From: amtiwari <[email protected]> Date: Wed, 5 Aug 2026 04:48:45 -0400 Subject: [PATCH 14/14] TBD --- clang/docs/OpenMPSupport.rst | 2 +- clang/include/clang/ASTMatchers/ASTMatchers.h | 14 ++ clang/lib/ASTMatchers/ASTMatchersInternal.cpp | 2 + clang/lib/ASTMatchers/Dynamic/Registry.cpp | 1 + clang/test/Index/openmp-flatten.c | 12 ++ clang/test/OpenMP/flatten_ast_print.cpp | 124 ++++++++++++++++++ clang/test/OpenMP/flatten_codegen_for.cpp | 67 ++++++++++ clang/test/OpenMP/flatten_depth_ast_print.cpp | 74 +++++++++++ clang/test/OpenMP/flatten_depth_messages.cpp | 48 +++++++ clang/test/OpenMP/flatten_messages.cpp | 89 +++++++++++++ .../test/OpenMP/flatten_serialize_module.cpp | 25 ++++ .../ASTMatchers/ASTMatchersNodeTest.cpp | 22 ++++ flatten-verify-tmp/01_default_depth2.cpp | 8 ++ flatten-verify-tmp/02_depth3_full.cpp | 9 ++ flatten-verify-tmp/03_depth2_partial.cpp | 10 ++ flatten-verify-tmp/04_depth1_identity.cpp | 8 ++ flatten-verify-tmp/05_rangefor_template.cpp | 20 +++ flatten-verify-tmp/README.md | 39 ++++++ flatten-verify-tmp/end_result_check.cpp | 82 ++++++++++++ .../test/transform/flatten/depth-partial.c | 45 +++++++ openmp/runtime/test/transform/flatten/empty.c | 42 ++++++ .../test/transform/flatten/foreach.cpp | 31 +++++ .../flatten/parallel-wsloop-foreach.cpp | 32 +++++ 23 files changed, 805 insertions(+), 1 deletion(-) create mode 100644 clang/test/Index/openmp-flatten.c create mode 100644 clang/test/OpenMP/flatten_ast_print.cpp create mode 100644 clang/test/OpenMP/flatten_codegen_for.cpp create mode 100644 clang/test/OpenMP/flatten_depth_ast_print.cpp create mode 100644 clang/test/OpenMP/flatten_depth_messages.cpp create mode 100644 clang/test/OpenMP/flatten_messages.cpp create mode 100644 clang/test/OpenMP/flatten_serialize_module.cpp create mode 100644 flatten-verify-tmp/01_default_depth2.cpp create mode 100644 flatten-verify-tmp/02_depth3_full.cpp create mode 100644 flatten-verify-tmp/03_depth2_partial.cpp create mode 100644 flatten-verify-tmp/04_depth1_identity.cpp create mode 100644 flatten-verify-tmp/05_rangefor_template.cpp create mode 100644 flatten-verify-tmp/README.md create mode 100644 flatten-verify-tmp/end_result_check.cpp create mode 100644 openmp/runtime/test/transform/flatten/depth-partial.c create mode 100644 openmp/runtime/test/transform/flatten/empty.c create mode 100644 openmp/runtime/test/transform/flatten/foreach.cpp create mode 100644 openmp/runtime/test/transform/flatten/parallel-wsloop-foreach.cpp diff --git a/clang/docs/OpenMPSupport.rst b/clang/docs/OpenMPSupport.rst index 0c21aa41ff269..c11a46c3bb6a6 100644 --- a/clang/docs/OpenMPSupport.rst +++ b/clang/docs/OpenMPSupport.rst @@ -666,7 +666,7 @@ implementation. +=============================================================+===========================+===========================+==========================================================================+ | dyn_groupprivate clause | :part:`partial` | :part:`In Progress` | C/C++: Host device support missing | +-------------------------------------------------------------+---------------------------+---------------------------+--------------------------------------------------------------------------+ -| loop flatten transformation | :none:`unclaimed` | :none:`unclaimed` | | +| loop flatten transformation | :part:`partial` | :none:`unclaimed` | Clang: depth clause supported (OpenMP 6.1); apply clause not yet supported| +-------------------------------------------------------------+---------------------------+---------------------------+--------------------------------------------------------------------------+ | loop grid/tile modifiers for sizes clause | :none:`unclaimed` | :none:`unclaimed` | | +-------------------------------------------------------------+---------------------------+---------------------------+--------------------------------------------------------------------------+ diff --git a/clang/include/clang/ASTMatchers/ASTMatchers.h b/clang/include/clang/ASTMatchers/ASTMatchers.h index bc0f35898a2c9..602bab9aa8cb8 100644 --- a/clang/include/clang/ASTMatchers/ASTMatchers.h +++ b/clang/include/clang/ASTMatchers/ASTMatchers.h @@ -8842,6 +8842,20 @@ extern const internal::VariadicDynCastAllOfMatcher<Stmt, extern const internal::VariadicDynCastAllOfMatcher<Stmt, OMPSplitDirective> ompSplitDirective; +/// Matches OpenMP ``flatten`` directive. +/// +/// Given +/// +/// \code +/// #pragma omp flatten +/// for (int i = 0; i < m; ++i) +/// for (int j = 0; j < n; ++j) {} +/// \endcode +/// +/// ``ompFlattenDirective()`` matches the flatten directive. +extern const internal::VariadicDynCastAllOfMatcher<Stmt, OMPFlattenDirective> + ompFlattenDirective; + /// Matches OpenMP ``counts`` clause used by ``#pragma omp split``. /// /// Given ``#pragma omp split counts(1, 2, omp_fill)``, ``ompCountsClause()`` diff --git a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp index 5cbf134620e34..e2c0c25518b70 100644 --- a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp +++ b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp @@ -1141,6 +1141,8 @@ const internal::VariadicDynCastAllOfMatcher<Stmt, OMPTargetUpdateDirective> ompTargetUpdateDirective; const internal::VariadicDynCastAllOfMatcher<Stmt, OMPSplitDirective> ompSplitDirective; +const internal::VariadicDynCastAllOfMatcher<Stmt, OMPFlattenDirective> + ompFlattenDirective; const internal::VariadicDynCastAllOfMatcher<OMPClause, OMPCountsClause> ompCountsClause; const internal::VariadicDynCastAllOfMatcher<OMPClause, OMPDefaultClause> diff --git a/clang/lib/ASTMatchers/Dynamic/Registry.cpp b/clang/lib/ASTMatchers/Dynamic/Registry.cpp index a04070971f0eb..a6252363e08ce 100644 --- a/clang/lib/ASTMatchers/Dynamic/Registry.cpp +++ b/clang/lib/ASTMatchers/Dynamic/Registry.cpp @@ -534,6 +534,7 @@ RegistryMaps::RegistryMaps() { REGISTER_MATCHER(ompFromClause); REGISTER_MATCHER(ompToClause); REGISTER_MATCHER(ompExecutableDirective); + REGISTER_MATCHER(ompFlattenDirective); REGISTER_MATCHER(ompSplitDirective); REGISTER_MATCHER(ompTargetUpdateDirective); REGISTER_MATCHER(on); diff --git a/clang/test/Index/openmp-flatten.c b/clang/test/Index/openmp-flatten.c new file mode 100644 index 0000000000000..74bc3cda3ef1a --- /dev/null +++ b/clang/test/Index/openmp-flatten.c @@ -0,0 +1,12 @@ +// RUN: c-index-test -test-load-source local %s -fopenmp=libomp -fopenmp-version=60 | FileCheck %s + +void test(void) { +#pragma omp flatten + for (int i = 0; i < 20; i += 1) + for (int j = 0; j < 30; j += 1) + ; +} + +// CHECK: openmp-flatten.c:4:1: OMPFlattenDirective= Extent=[4:1 - 4:20] +// CHECK: openmp-flatten.c:5:3: ForStmt= Extent=[5:3 - 7:8] +// CHECK: openmp-flatten.c:6:5: ForStmt= Extent=[6:5 - 7:8] diff --git a/clang/test/OpenMP/flatten_ast_print.cpp b/clang/test/OpenMP/flatten_ast_print.cpp new file mode 100644 index 0000000000000..e1115bf112c40 --- /dev/null +++ b/clang/test/OpenMP/flatten_ast_print.cpp @@ -0,0 +1,124 @@ +// Check no warnings/errors +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 -fsyntax-only -verify %s +// expected-no-diagnostics + +// Check AST and unparsing +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 -ast-dump %s | FileCheck %s --check-prefix=DUMP +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 -ast-print %s | FileCheck %s --check-prefix=PRINT + +// Check same results after serialization round-trip +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 -emit-pch -o %t %s +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 -include-pch %t -ast-dump-all %s | FileCheck %s --check-prefix=DUMP +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 -include-pch %t -ast-print %s | FileCheck %s --check-prefix=PRINT + +#ifndef HEADER +#define HEADER + +// placeholder for loop body code. +extern "C" void body(...); + +// PRINT-LABEL: void foo1( +// DUMP-LABEL: FunctionDecl {{.*}} foo1 +void foo1() { + // PRINT: #pragma omp flatten + // DUMP: OMPFlattenDirective + #pragma omp flatten + // PRINT: for (int i = 0; i < 7; i += 1) + // DUMP-NEXT: ForStmt + for (int i = 0; i < 7; i += 1) + // PRINT: for (int j = 0; j < 9; j += 1) + // DUMP: ForStmt + for (int j = 0; j < 9; j += 1) + // PRINT: body(i, j); + // DUMP: CallExpr + body(i, j); +} + +// PRINT-LABEL: void foo2( +// DUMP-LABEL: FunctionDecl {{.*}} foo2 +void foo2(int start1, int end1, int step1, int start2, int end2, int step2) { + // PRINT: #pragma omp flatten + // DUMP: OMPFlattenDirective + #pragma omp flatten + // PRINT: for (int i = start1; i < end1; i += step1) + // DUMP-NEXT: ForStmt + for (int i = start1; i < end1; i += step1) + // PRINT: for (int j = start2; j < end2; j += step2) + // DUMP: ForStmt + for (int j = start2; j < end2; j += step2) + // PRINT: body(i, j); + // DUMP: CallExpr + body(i, j); +} + +// PRINT-LABEL: void foo3( +// DUMP-LABEL: FunctionDecl {{.*}} foo3 +void foo3() { + // Verify that a flattened loop can itself be the operand of another loop + // transformation. The outer reverse consumes the loop generated by flatten. + // PRINT: #pragma omp reverse + // DUMP: OMPReverseDirective + #pragma omp reverse + // PRINT: #pragma omp flatten + // DUMP: OMPFlattenDirective + #pragma omp flatten + // PRINT: for (int i = 0; i < 13; i += 1) + // DUMP-NEXT: ForStmt + for (int i = 0; i < 13; i += 1) + // PRINT: for (int j = 0; j < 17; j += 1) + // DUMP: ForStmt + for (int j = 0; j < 17; j += 1) + // PRINT: body(i, j); + // DUMP: CallExpr + body(i, j); +} + +// Range-based for loops are supported as affected loops. +// PRINT-LABEL: void foo4( +// DUMP-LABEL: FunctionDecl {{.*}} foo4 +struct Range { + int *begin(); + int *end(); +}; +void foo4(Range r1, Range r2) { + // PRINT: #pragma omp flatten + // DUMP: OMPFlattenDirective + #pragma omp flatten + // PRINT: for (int i : r1) + // DUMP-NEXT: CXXForRangeStmt + for (int i : r1) + // PRINT: for (int j : r2) + // DUMP: CXXForRangeStmt + for (int j : r2) + // PRINT: body(i, j); + // DUMP: CallExpr + body(i, j); +} + +// The directive is instantiated together with its enclosing template. +// PRINT-LABEL: template <typename T, int LO, int HI> void foo5() +// DUMP-LABEL: FunctionTemplateDecl {{.*}} foo5 +template <typename T, int LO, int HI> +void foo5() { + // PRINT: #pragma omp flatten + // DUMP: OMPFlattenDirective + #pragma omp flatten + // PRINT: for (T i = LO; i < HI; ++i) + // DUMP-NEXT: ForStmt + for (T i = LO; i < HI; ++i) + // PRINT: for (T j = LO; j < HI; ++j) + // DUMP: ForStmt + for (T j = LO; j < HI; ++j) + // PRINT: body(i, j); + // DUMP: CallExpr + body(i, j); +} + +// PRINT-LABEL: template<> void foo5<int, 0, 8>() +// DUMP-LABEL: FunctionDecl {{.*}} foo5 'void ()' implicit_instantiation +// DUMP: OMPFlattenDirective +void inst() { + foo5<int, 0, 8>(); +} + +#endif diff --git a/clang/test/OpenMP/flatten_codegen_for.cpp b/clang/test/OpenMP/flatten_codegen_for.cpp new file mode 100644 index 0000000000000..d64128bd25d78 --- /dev/null +++ b/clang/test/OpenMP/flatten_codegen_for.cpp @@ -0,0 +1,67 @@ +// Check that the loop generated by 'flatten' can be consumed by a loop +// associated directive ('omp for' / 'omp parallel for'), and that signed and +// unsigned induction variables generate signed/unsigned division. + +// RUN: %clang_cc1 -verify -triple x86_64-pc-linux-gnu -std=c++20 -fopenmp -fopenmp-version=60 -emit-llvm %s -o - | FileCheck %s + +// expected-no-diagnostics + +#ifndef HEADER +#define HEADER + +extern "C" void body(int, int); +extern "C" void ubody(unsigned, unsigned); + +// 'omp for' worksharing over the flattened iteration space: the flattened loop +// uses a 64-bit induction variable for the variable-bound product trip count, +// so the worksharing loop is distributed with the 64-bit static-init call and +// the body recovers the logical counters via signed division/remainder. +// +// CHECK-LABEL: define {{.*}}void @wsfor( +// CHECK: mul nsw i64 +// CHECK: call void @__kmpc_for_static_init_8( +// CHECK: sdiv i64 +// CHECK: srem i64 +// CHECK: call void @body( +// CHECK: call void @__kmpc_for_static_fini( +extern "C" void wsfor(int n, int m) { +#pragma omp for +#pragma omp flatten + for (int i = 0; i < n; ++i) + for (int j = 0; j < m; ++j) + body(i, j); +} + +// 'omp parallel for' outlines the flattened loop and worksharing-distributes +// it. +// +// CHECK-LABEL: define {{.*}}void @pfor( +// CHECK: call {{.*}}@__kmpc_fork_call( +// CHECK-LABEL: define internal void @pfor.omp_outlined( +// CHECK: call void @__kmpc_for_static_init_8( +// CHECK: sdiv i64 +// CHECK: srem i64 +// CHECK: call void @body( +extern "C" void pfor(int n, int m) { +#pragma omp parallel for +#pragma omp flatten + for (int i = 0; i < n; ++i) + for (int j = 0; j < m; ++j) + body(i, j); +} + +// Unsigned induction variables produce unsigned division/remainder. +// +// CHECK-LABEL: define {{.*}}void @ufor( +// CHECK: icmp ult i32 %{{.+}}, 63 +// CHECK: udiv i32 %{{.+}}, 9 +// CHECK: urem i32 %{{.+}}, 9 +// CHECK: call void @ubody( +extern "C" void ufor() { +#pragma omp flatten + for (unsigned i = 0; i < 7u; ++i) + for (unsigned j = 0; j < 9u; ++j) + ubody(i, j); +} + +#endif diff --git a/clang/test/OpenMP/flatten_depth_ast_print.cpp b/clang/test/OpenMP/flatten_depth_ast_print.cpp new file mode 100644 index 0000000000000..ceaaa68e1f79a --- /dev/null +++ b/clang/test/OpenMP/flatten_depth_ast_print.cpp @@ -0,0 +1,74 @@ +// Check no warnings/errors +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=61 -fsyntax-only -verify %s +// expected-no-diagnostics + +// Check AST and unparsing +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=61 -ast-dump %s | FileCheck %s --check-prefix=DUMP +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=61 -ast-print %s | FileCheck %s --check-prefix=PRINT + +// Check same results after serialization round-trip +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=61 -emit-pch -o %t %s +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=61 -include-pch %t -ast-dump-all %s | FileCheck %s --check-prefix=DUMP +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=61 -include-pch %t -ast-print %s | FileCheck %s --check-prefix=PRINT + +#ifndef HEADER +#define HEADER + +// placeholder for loop body code. +extern "C" void body(...); + +// The depth clause round-trips through -ast-print and PCH serialization. +// PRINT-LABEL: void foo_depth3( +// DUMP-LABEL: FunctionDecl {{.*}} foo_depth3 +void foo_depth3(int n, int m, int p) { + // PRINT: #pragma omp flatten depth(3) + // DUMP: OMPFlattenDirective + // DUMP-NEXT: OMPDepthClause + #pragma omp flatten depth(3) + // PRINT: for (int i = 0; i < n; ++i) + // DUMP: ForStmt + for (int i = 0; i < n; ++i) + for (int j = 0; j < m; ++j) + for (int k = 0; k < p; ++k) + // PRINT: body(i, j, k); + // DUMP: CallExpr + body(i, j, k); +} + +// depth(2) is accepted and printed like any other depth argument. +// PRINT-LABEL: void foo_depth2( +// DUMP-LABEL: FunctionDecl {{.*}} foo_depth2 +void foo_depth2(int n, int m) { + // PRINT: #pragma omp flatten depth(2) + // DUMP: OMPFlattenDirective + // DUMP-NEXT: OMPDepthClause + #pragma omp flatten depth(2) + for (int i = 0; i < n; ++i) + for (int j = 0; j < m; ++j) + body(i, j); +} + +// The depth clause is instantiated together with its enclosing template. +// PRINT-LABEL: template <typename T> void foo_tmpl() +// DUMP-LABEL: FunctionTemplateDecl {{.*}} foo_tmpl +template <typename T> +void foo_tmpl() { + // PRINT: #pragma omp flatten depth(3) + // DUMP: OMPFlattenDirective + // DUMP-NEXT: OMPDepthClause + #pragma omp flatten depth(3) + for (T i = 0; i < 8; ++i) + for (T j = 0; j < 8; ++j) + for (T k = 0; k < 8; ++k) + body(i, j, k); +} + +// PRINT-LABEL: template<> void foo_tmpl<int>() +// DUMP-LABEL: FunctionDecl {{.*}} foo_tmpl 'void ()' implicit_instantiation +// DUMP: OMPFlattenDirective +// DUMP-NEXT: OMPDepthClause +void inst() { + foo_tmpl<int>(); +} + +#endif diff --git a/clang/test/OpenMP/flatten_depth_messages.cpp b/clang/test/OpenMP/flatten_depth_messages.cpp new file mode 100644 index 0000000000000..16f60318d0d06 --- /dev/null +++ b/clang/test/OpenMP/flatten_depth_messages.cpp @@ -0,0 +1,48 @@ +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -std=c++20 -fopenmp -fopenmp-version=61 -fsyntax-only -Wuninitialized -verify %s + +extern "C" void body(...); + +// expected-note@+1 {{declared here}} +void func(int n) { + + // The depth argument must be a positive integer constant expression. + // expected-error@+1 {{argument to 'depth' clause must be a strictly positive integer value}} + #pragma omp flatten depth(0) + for (int i = 0; i < n; ++i) + for (int j = 0; j < n; ++j) + body(i, j); + + // expected-error@+1 {{argument to 'depth' clause must be a strictly positive integer value}} + #pragma omp flatten depth(-1) + for (int i = 0; i < n; ++i) + for (int j = 0; j < n; ++j) + body(i, j); + + // A non-constant argument is rejected. + // expected-error@+2 {{expression is not an integral constant expression}} + // expected-note@+1 {{function parameter 'n' with unknown value cannot be used in a constant expression}} + #pragma omp flatten depth(n) + for (int i = 0; i < n; ++i) + for (int j = 0; j < n; ++j) + body(i, j); + + // At most one 'depth' clause may appear on the directive. + // expected-error@+1 {{directive '#pragma omp flatten' cannot contain more than one 'depth' clause}} + #pragma omp flatten depth(2) depth(2) + for (int i = 0; i < n; ++i) + for (int j = 0; j < n; ++j) + body(i, j); + + // The depth must be at most the loop nest depth: depth(3) requires three + // perfectly nested loops, but only two are present here. + #pragma omp flatten depth(3) + // expected-error@+1 {{expected 3 for loops after '#pragma omp flatten', but found only 2}} + for (int i = 0; i < n; ++i) + for (int j = 0; j < n; ++j) + body(i, j); + + // depth(1) flattens a single loop and is well-formed (no diagnostic). + #pragma omp flatten depth(1) + for (int i = 0; i < n; ++i) + body(i); +} diff --git a/clang/test/OpenMP/flatten_messages.cpp b/clang/test/OpenMP/flatten_messages.cpp new file mode 100644 index 0000000000000..a022e0e8999d2 --- /dev/null +++ b/clang/test/OpenMP/flatten_messages.cpp @@ -0,0 +1,89 @@ +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -std=c++20 -fopenmp -fopenmp-version=60 -fsyntax-only -Wuninitialized -verify %s + +extern "C" void body(...); + +void func(int n) { + + // The associated statement must be a for loop. + // expected-error@+2 {{statement after '#pragma omp flatten' must be a for loop}} + #pragma omp flatten + ; + + // A non-loop statement is rejected as well. + // expected-error@+2 {{statement after '#pragma omp flatten' must be a for loop}} + #pragma omp flatten + int b = 0; + + // A single loop is not enough: flatten combines two perfectly nested loops, + // so the body of the outer loop must itself be a for loop. + #pragma omp flatten + for (int i = 0; i < 7; ++i) + // expected-error@+1 {{statement after '#pragma omp flatten' must be a for loop}} + ; + + // The associated statement of a directive is not a for loop. + // expected-error@+2 {{statement after '#pragma omp flatten' must be a for loop}} + #pragma omp flatten + #pragma omp for + for (int i = 0; i < 7; ++i) + for (int j = 0; j < 7; ++j) + body(i, j); + + { + // expected-error@+2 {{expected statement}} + #pragma omp flatten + } + + // The loops must be perfectly nested: no code is allowed between them. + #pragma omp flatten + for (int i = 0; i < n; ++i) { + int x = 0; + // expected-error@-2 {{statement after '#pragma omp flatten' must be a for loop}} + for (int j = 0; j < n; ++j) + body(i, j, x); + } + + // Each affected loop must be in OpenMP canonical form. + #pragma omp flatten + for (int i = 0; i < n; ++i) + // expected-error@+1 {{condition of OpenMP for loop must be a relational comparison ('<', '<=', '>', '>=', or '!=') of loop variable 'j'}} + for (int j = 0; j / 3 < n; ++j) + body(i, j); + + // The affected loops must be rectangular: an inner bound may not depend on an + // outer loop counter. + #pragma omp flatten + for (int i = 0; i < n; ++i) + // expected-error@+1 {{expected loop invariant expression}} + for (int j = i; j < n; ++j) + body(i, j); + + // The 'sizes' clause is not allowed on 'flatten'. + // expected-error@+1 {{unexpected OpenMP clause 'sizes' in directive '#pragma omp flatten'}} + #pragma omp flatten sizes(2) + for (int i = 0; i < 7; ++i) + for (int j = 0; j < 9; ++j) + body(i, j); + + // The 'permutation' clause is not allowed on 'flatten'. + // expected-error@+1 {{unexpected OpenMP clause 'permutation' in directive '#pragma omp flatten'}} + #pragma omp flatten permutation(2, 1) + for (int i = 0; i < 7; ++i) + for (int j = 0; j < 9; ++j) + body(i, j); + + // Tokens after the directive name are ignored with a warning. + // expected-warning@+1 {{extra tokens at the end of '#pragma omp flatten' are ignored}} + #pragma omp flatten foo + for (int i = 0; i < 7; ++i) + for (int j = 0; j < 9; ++j) + body(i, j); + + // The 'depth' clause is only available from OpenMP 6.1; it is rejected here + // under -fopenmp-version=60. + // expected-error@+1 {{unexpected OpenMP clause 'depth' in directive '#pragma omp flatten'}} + #pragma omp flatten depth(2) + for (int i = 0; i < 7; ++i) + for (int j = 0; j < 9; ++j) + body(i, j); +} diff --git a/clang/test/OpenMP/flatten_serialize_module.cpp b/clang/test/OpenMP/flatten_serialize_module.cpp new file mode 100644 index 0000000000000..5d3420500b282 --- /dev/null +++ b/clang/test/OpenMP/flatten_serialize_module.cpp @@ -0,0 +1,25 @@ +// C++20 module interface with `#pragma omp flatten` — emit BMI + import; AST retains directive. +// +// RUN: rm -rf %t && split-file %s %t && cd %t +// RUN: %clang_cc1 -std=c++20 -fopenmp -fopenmp-version=60 -triple x86_64-unknown-linux-gnu %t/FlattenMod.cppm -emit-module-interface -o %t/FlattenMod.pcm +// RUN: %clang_cc1 -std=c++20 -fopenmp -fopenmp-version=60 -triple x86_64-unknown-linux-gnu %t/UseFlattenMod.cpp -fmodule-file=FlattenMod=%t/FlattenMod.pcm -ast-dump-all | FileCheck %t/FlattenMod.cppm + +// expected-no-diagnostics + +//--- FlattenMod.cppm +module; +export module FlattenMod; + +export void flattenfoo(int n, int m) { +// CHECK: OMPFlattenDirective +// CHECK: ForStmt +#pragma omp flatten + for (int i = 0; i < n; ++i) + for (int j = 0; j < m; ++j) { + } +} + +//--- UseFlattenMod.cpp +import FlattenMod; + +void g(void) { flattenfoo(10, 20); } diff --git a/clang/unittests/ASTMatchers/ASTMatchersNodeTest.cpp b/clang/unittests/ASTMatchers/ASTMatchersNodeTest.cpp index 4190d4703e37d..34c0fd27821d1 100644 --- a/clang/unittests/ASTMatchers/ASTMatchersNodeTest.cpp +++ b/clang/unittests/ASTMatchers/ASTMatchersNodeTest.cpp @@ -3126,6 +3126,28 @@ void f() { EXPECT_TRUE(notMatchesWithOpenMP60(ParallelOnly, Matcher)); } +// OpenMP 6 flatten directive +TEST(ASTMatchersTestOpenMP, OMPFlattenDirective) { + auto Matcher = stmt(ompFlattenDirective()); + + StringRef FlattenOk = R"( +void f() { +#pragma omp flatten + for (int i = 0; i < 10; ++i) + for (int j = 0; j < 10; ++j) {} +} +)"; + EXPECT_TRUE(matchesWithOpenMP60(FlattenOk, Matcher)); + + StringRef ParallelOnly = R"( +void f() { +#pragma omp parallel + ; +} +)"; + EXPECT_TRUE(notMatchesWithOpenMP60(ParallelOnly, Matcher)); +} + TEST(ASTMatchersTestOpenMP, OMPSplitDirective_HasCountsClause) { auto Matcher = stmt(ompSplitDirective(hasAnyClause(ompCountsClause()))); diff --git a/flatten-verify-tmp/01_default_depth2.cpp b/flatten-verify-tmp/01_default_depth2.cpp new file mode 100644 index 0000000000000..900d0263622cd --- /dev/null +++ b/flatten-verify-tmp/01_default_depth2.cpp @@ -0,0 +1,8 @@ +// Default flatten: combine 2 perfectly nested loops (depth defaults to 2). +extern "C" void body(int, int); +void t(int n, int m) { +#pragma omp flatten + for (int i = 0; i < n; ++i) + for (int j = 0; j < m; ++j) + body(i, j); +} diff --git a/flatten-verify-tmp/02_depth3_full.cpp b/flatten-verify-tmp/02_depth3_full.cpp new file mode 100644 index 0000000000000..ba9f0b7ed8808 --- /dev/null +++ b/flatten-verify-tmp/02_depth3_full.cpp @@ -0,0 +1,9 @@ +// depth(3): fully flatten a three-deep perfect nest (OpenMP 6.1). +extern "C" void body(int, int, int); +void t(int n, int m, int p) { +#pragma omp flatten depth(3) + for (int i = 0; i < n; ++i) + for (int j = 0; j < m; ++j) + for (int k = 0; k < p; ++k) + body(i, j, k); +} diff --git a/flatten-verify-tmp/03_depth2_partial.cpp b/flatten-verify-tmp/03_depth2_partial.cpp new file mode 100644 index 0000000000000..35b9dc83834fd --- /dev/null +++ b/flatten-verify-tmp/03_depth2_partial.cpp @@ -0,0 +1,10 @@ +// depth(2) on a three-deep nest: only the outer two loops are combined; the +// innermost loop must remain as ordinary body of the flattened loop. +extern "C" void body(int, int, int); +void t(int n, int m, int p) { +#pragma omp flatten depth(2) + for (int i = 0; i < n; ++i) + for (int j = 0; j < m; ++j) + for (int k = 0; k < p; ++k) + body(i, j, k); +} diff --git a/flatten-verify-tmp/04_depth1_identity.cpp b/flatten-verify-tmp/04_depth1_identity.cpp new file mode 100644 index 0000000000000..ae62a11312fe8 --- /dev/null +++ b/flatten-verify-tmp/04_depth1_identity.cpp @@ -0,0 +1,8 @@ +// depth(1): identity transform. A single loop is "combined"; no mixed-radix +// decomposition (no srem), the flattened counter maps straight to the loop var. +extern "C" void body(int); +void t(int n) { +#pragma omp flatten depth(1) + for (int i = 0; i < n; ++i) + body(i); +} diff --git a/flatten-verify-tmp/05_rangefor_template.cpp b/flatten-verify-tmp/05_rangefor_template.cpp new file mode 100644 index 0000000000000..89928c34a8a15 --- /dev/null +++ b/flatten-verify-tmp/05_rangefor_template.cpp @@ -0,0 +1,20 @@ +// Range-for (CXXForRangeStmt) nest + a function template instantiated with a +// dependent depth(K). Exercises the __begin/__end pre-inits and the deferred +// (dependent-context) desugaring path. +#include <stdlibc++.h> +extern "C" void body(int, int); + +void ranges(std::vector<int> &a, std::vector<int> &b) { +#pragma omp flatten + for (int i : a) + for (int j : b) + body(i, j); +} + +template <int K> void tmpl(int n, int m) { +#pragma omp flatten depth(K) + for (int i = 0; i < n; ++i) + for (int j = 0; j < m; ++j) + body(i, j); +} +template void tmpl<2>(int, int); diff --git a/flatten-verify-tmp/README.md b/flatten-verify-tmp/README.md new file mode 100644 index 0000000000000..f48f4d06cfec0 --- /dev/null +++ b/flatten-verify-tmp/README.md @@ -0,0 +1,39 @@ +# Temporary flatten + depth verification cases + +Throwaway sandbox for eyeballing the flatten desugaring (AST / IR / runtime). +Not part of any PR — delete freely. The committed regression tests live under +`clang/test/OpenMP/flatten*` and `openmp/runtime/test/transform/flatten/*`. + +`CLANG=build/bin/clang`, `CLANGXX=build/bin/clang++`, run from the repo root. + +| File | Case | What to look for | +|------|------|------------------| +| `01_default_depth2.cpp` | default (no clause) = depth 2 | i64 `.flatten.iv`, `mul nsw`, `sdiv`+`srem` | +| `02_depth3_full.cpp` | `depth(3)` full flatten | 3 `.flatten.iv.N`, two-level mixed radix | +| `03_depth2_partial.cpp` | `depth(2)` on 3-deep nest | 2 flatten IVs, **inner `%k` loop stays** | +| `04_depth1_identity.cpp` | `depth(1)` identity | i32 IV, **no `srem`**, direct store | +| `05_rangefor_template.cpp` | range-for + template `depth(K)` | ast-print round-trips; instantiation | +| `end_result_check.cpp` | runtime semantic check | prints `OK`, exit 0 (order+set preserved) | + +## Commands + +AST (round-trip original loops) and the desugared node: +``` +$CLANG -cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=61 -ast-print FILE +$CLANG -cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=61 -ast-dump FILE +``` + +IR (the div/mod arithmetic that implements the transform): +``` +$CLANG -cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=61 -emit-llvm -o - FILE +``` + +Range-for / template need the driver (libstdc++ + template instantiation): +``` +$CLANGXX -fopenmp -fopenmp-version=61 -Xclang -ast-print -fsyntax-only 05_rangefor_template.cpp +``` + +Runtime end-result (must print OK, exit 0): +``` +$CLANGXX -fopenmp -fopenmp-version=61 -O0 end_result_check.cpp -o /tmp/erc && /tmp/erc +``` diff --git a/flatten-verify-tmp/end_result_check.cpp b/flatten-verify-tmp/end_result_check.cpp new file mode 100644 index 0000000000000..3aa8850829058 --- /dev/null +++ b/flatten-verify-tmp/end_result_check.cpp @@ -0,0 +1,82 @@ +// End-result (semantic) verification: flatten must preserve BOTH the set of +// iterations and their row-major visitation ORDER. We record the sequence of +// visited tuples from a flattened nest and compare it against the sequence from +// an identical non-flattened reference nest. Exit code 0 == identical. +// +// flatten is a pure frontend AST desugaring to an ordinary loop, so this runs +// as a normal program (build with: clang++ -fopenmp -fopenmp-version=61). +#include <cassert> +#include <cstdio> +#include <vector> + +using Seq = std::vector<long>; + +static void rec(Seq &s, int i, int j, int k = -1) { + s.push_back(i); + s.push_back(j); + if (k >= 0) + s.push_back(k); +} + +int main() { + const int n = 3, m = 4, p = 2; + + // --- depth(2) default over 2 loops --- + { + Seq flat, ref; +#pragma omp flatten + for (int i = 0; i < n; ++i) + for (int j = 0; j < m; ++j) + rec(flat, i, j); + for (int i = 0; i < n; ++i) + for (int j = 0; j < m; ++j) + rec(ref, i, j); + assert(flat == ref && "depth(2) order/set mismatch"); + } + + // --- depth(3) full flatten over 3 loops --- + { + Seq flat, ref; +#pragma omp flatten depth(3) + for (int i = 0; i < n; ++i) + for (int j = 0; j < m; ++j) + for (int k = 0; k < p; ++k) + rec(flat, i, j, k); + for (int i = 0; i < n; ++i) + for (int j = 0; j < m; ++j) + for (int k = 0; k < p; ++k) + rec(ref, i, j, k); + assert(flat == ref && "depth(3) order/set mismatch"); + } + + // --- depth(2) partial: inner k-loop stays intact --- + { + Seq flat, ref; +#pragma omp flatten depth(2) + for (int i = 0; i < n; ++i) + for (int j = 0; j < m; ++j) + for (int k = 0; k < p; ++k) + rec(flat, i, j, k); + for (int i = 0; i < n; ++i) + for (int j = 0; j < m; ++j) + for (int k = 0; k < p; ++k) + rec(ref, i, j, k); + assert(flat == ref && "depth(2)-partial order/set mismatch"); + } + + // --- non-unit step / non-zero start must survive --- + { + Seq flat, ref; +#pragma omp flatten + for (int i = 5; i < 12; i += 2) + for (int j = 1; j < 7; j += 3) + rec(flat, i, j); + for (int i = 5; i < 12; i += 2) + for (int j = 1; j < 7; j += 3) + rec(ref, i, j); + assert(flat == ref && "stride/offset mismatch"); + } + + std::puts("flatten end-result preserved: OK"); + return 0; +} diff --git a/openmp/runtime/test/transform/flatten/depth-partial.c b/openmp/runtime/test/transform/flatten/depth-partial.c new file mode 100644 index 0000000000000..2fda3a04cbd84 --- /dev/null +++ b/openmp/runtime/test/transform/flatten/depth-partial.c @@ -0,0 +1,45 @@ +// RUN: %libomp-compile-and-run | FileCheck %s --match-full-lines + +// A depth(2) clause on a three-deep loop nest flattens only the outermost two +// loops; the innermost loop is left untouched and executes normally inside the +// flattened loop body. + +#ifndef HEADER +#define HEADER + +#include <stdlib.h> +#include <stdio.h> + +int main() { + printf("do\n"); +#pragma omp flatten depth(2) + for (int i = 5; i < 11; i += 2) + for (int j = 1; j < 7; j += 2) + for (int k = 0; k < 2; ++k) + printf("i=%d j=%d k=%d\n", i, j, k); + printf("done\n"); + return EXIT_SUCCESS; +} + +#endif /* HEADER */ + +// CHECK: do +// CHECK-NEXT: i=5 j=1 k=0 +// CHECK-NEXT: i=5 j=1 k=1 +// CHECK-NEXT: i=5 j=3 k=0 +// CHECK-NEXT: i=5 j=3 k=1 +// CHECK-NEXT: i=5 j=5 k=0 +// CHECK-NEXT: i=5 j=5 k=1 +// CHECK-NEXT: i=7 j=1 k=0 +// CHECK-NEXT: i=7 j=1 k=1 +// CHECK-NEXT: i=7 j=3 k=0 +// CHECK-NEXT: i=7 j=3 k=1 +// CHECK-NEXT: i=7 j=5 k=0 +// CHECK-NEXT: i=7 j=5 k=1 +// CHECK-NEXT: i=9 j=1 k=0 +// CHECK-NEXT: i=9 j=1 k=1 +// CHECK-NEXT: i=9 j=3 k=0 +// CHECK-NEXT: i=9 j=3 k=1 +// CHECK-NEXT: i=9 j=5 k=0 +// CHECK-NEXT: i=9 j=5 k=1 +// CHECK-NEXT: done diff --git a/openmp/runtime/test/transform/flatten/empty.c b/openmp/runtime/test/transform/flatten/empty.c new file mode 100644 index 0000000000000..ecf1e8ef622f2 --- /dev/null +++ b/openmp/runtime/test/transform/flatten/empty.c @@ -0,0 +1,42 @@ +// RUN: %libomp-compile-and-run | FileCheck %s --match-full-lines + +#ifndef HEADER +#define HEADER + +#include <stdlib.h> +#include <stdio.h> + +// The bounds are runtime values so that an empty iteration space does not +// trigger a compile-time division-by-zero diagnostic for the (never executed) +// induction-variable recovery. +static void flatten(int n, int m) { +#pragma omp flatten + for (int i = 0; i < n; ++i) + for (int j = 0; j < m; ++j) + printf("i=%d j=%d\n", i, j); +} + +int main() { + printf("empty-inner-begin\n"); + flatten(3, 0); + printf("empty-inner-end\n"); + + printf("empty-outer-begin\n"); + flatten(0, 3); + printf("empty-outer-end\n"); + + printf("single-begin\n"); + flatten(1, 1); + printf("single-end\n"); + return EXIT_SUCCESS; +} + +#endif /* HEADER */ + +// CHECK: empty-inner-begin +// CHECK-NEXT: empty-inner-end +// CHECK-NEXT: empty-outer-begin +// CHECK-NEXT: empty-outer-end +// CHECK-NEXT: single-begin +// CHECK-NEXT: i=0 j=0 +// CHECK-NEXT: single-end diff --git a/openmp/runtime/test/transform/flatten/foreach.cpp b/openmp/runtime/test/transform/flatten/foreach.cpp new file mode 100644 index 0000000000000..14997f7c4051d --- /dev/null +++ b/openmp/runtime/test/transform/flatten/foreach.cpp @@ -0,0 +1,31 @@ +// RUN: %libomp-cxx-compile-and-run | FileCheck %s --match-full-lines + +#ifndef HEADER +#define HEADER + +#include <cstdlib> +#include <cstdio> +#include <vector> + +int main() { + printf("do\n"); + std::vector<int> outer{10, 20}; + std::vector<int> inner{1, 2, 3}; +#pragma omp flatten + for (int a : outer) + for (int b : inner) + printf("a=%d b=%d\n", a, b); + printf("done\n"); + return EXIT_SUCCESS; +} + +#endif /* HEADER */ + +// CHECK: do +// CHECK-NEXT: a=10 b=1 +// CHECK-NEXT: a=10 b=2 +// CHECK-NEXT: a=10 b=3 +// CHECK-NEXT: a=20 b=1 +// CHECK-NEXT: a=20 b=2 +// CHECK-NEXT: a=20 b=3 +// CHECK-NEXT: done diff --git a/openmp/runtime/test/transform/flatten/parallel-wsloop-foreach.cpp b/openmp/runtime/test/transform/flatten/parallel-wsloop-foreach.cpp new file mode 100644 index 0000000000000..7ddcde2fd622c --- /dev/null +++ b/openmp/runtime/test/transform/flatten/parallel-wsloop-foreach.cpp @@ -0,0 +1,32 @@ +// RUN: %libomp-cxx-compile-and-run | FileCheck %s --match-full-lines + +#ifndef HEADER +#define HEADER + +#include <cstdlib> +#include <cstdio> + +int main() { + printf("do\n"); + // The loop generated by 'flatten' is the canonical loop associated with the + // worksharing 'omp for'. With a single thread the iteration order is the + // original row-major order. +#pragma omp parallel for num_threads(1) +#pragma omp flatten + for (int i = 0; i < 2; ++i) + for (int j = 0; j < 3; ++j) + printf("i=%d j=%d\n", i, j); + printf("done\n"); + return EXIT_SUCCESS; +} + +#endif /* HEADER */ + +// CHECK: do +// CHECK-NEXT: i=0 j=0 +// CHECK-NEXT: i=0 j=1 +// CHECK-NEXT: i=0 j=2 +// CHECK-NEXT: i=1 j=0 +// CHECK-NEXT: i=1 j=1 +// CHECK-NEXT: i=1 j=2 +// CHECK-NEXT: done _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
