[Clang][OpenMP][LoopTransformations] Implement "#pragma omp fuse" loop transformation directive and "looprange" clause (#139293)

This change implements the fuse directive, `#pragma omp fuse`, as specified in the OpenMP 6.0, along with the `looprange` clause in clang.

This change also adds minimal stubs so flang keeps compiling (a full implementation in flang of this directive is still pending).

---------

Co-authored-by: Roger Ferrer Ibanez <roger.ferrer@bsc.es>
This commit is contained in:
Walter J.T.V 2025-09-29 07:48:18 +02:00 committed by GitHub
parent 98563d850d
commit cd4c5280c7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
49 changed files with 5198 additions and 54 deletions

View File

@ -1446,6 +1446,9 @@ class CursorKind(BaseEnumeration):
# OpenMP stripe directive.
OMP_STRIPE_DIRECTIVE = 310
# OpenMP fuse directive.
OMP_FUSE_DIRECTIVE = 311
# OpenACC Compute Construct.
OPEN_ACC_COMPUTE_DIRECTIVE = 320

View File

@ -482,6 +482,8 @@ implementation.
+-------------------------------------------------------------+---------------------------+---------------------------+--------------------------------------------------------------------------+
| loop transformation apply clause | :none:`unclaimed` | :none:`unclaimed` | |
+-------------------------------------------------------------+---------------------------+---------------------------+--------------------------------------------------------------------------+
| loop fuse transformation | :good:`done` | :none:`unclaimed` | |
+-------------------------------------------------------------+---------------------------+---------------------------+--------------------------------------------------------------------------+
| workdistribute construct | | :none:`in progress` | @skc7, @mjklemm |
+-------------------------------------------------------------+---------------------------+---------------------------+--------------------------------------------------------------------------+
| task_iteration | :none:`unclaimed` | :none:`unclaimed` | |

View File

@ -599,6 +599,7 @@ OpenMP Support
- Added support for ``defaultmap`` directive implicit-behavior ``storage``.
- Added support for ``defaultmap`` directive implicit-behavior ``private``.
- Added parsing and semantic analysis support for ``groupprivate`` directive.
- Added support for 'omp fuse' directive.
Improvements
^^^^^^^^^^^^

View File

@ -2162,6 +2162,10 @@ enum CXCursorKind {
*/
CXCursor_OMPStripeDirective = 310,
/** OpenMP fuse directive
*/
CXCursor_OMPFuseDirective = 311,
/** OpenACC Compute Construct.
*/
CXCursor_OpenACCComputeConstruct = 320,

View File

@ -1149,6 +1149,80 @@ public:
static OMPFullClause *CreateEmpty(const ASTContext &C);
};
/// This class represents the 'looprange' clause in the
/// '#pragma omp fuse' directive
///
/// \code {c}
/// #pragma omp fuse looprange(1,2)
/// {
/// for(int i = 0; i < 64; ++i)
/// for(int j = 0; j < 256; j+=2)
/// for(int k = 127; k >= 0; --k)
/// \endcode
class OMPLoopRangeClause final : public OMPClause {
friend class OMPClauseReader;
/// Location of '('
SourceLocation LParenLoc;
/// Location of first and count expressions
SourceLocation FirstLoc, CountLoc;
/// Number of looprange arguments (always 2: first, count)
enum { FirstExpr, CountExpr, NumArgs };
Stmt *Args[NumArgs] = {nullptr, nullptr};
/// Set looprange 'first' expression
void setFirst(Expr *E) { Args[FirstExpr] = E; }
/// Set looprange 'count' expression
void setCount(Expr *E) { Args[CountExpr] = E; }
/// Build an empty clause for deserialization.
explicit OMPLoopRangeClause()
: OMPClause(llvm::omp::OMPC_looprange, {}, {}) {}
public:
/// Build a 'looprange' clause AST node.
static OMPLoopRangeClause *
Create(const ASTContext &C, SourceLocation StartLoc, SourceLocation LParenLoc,
SourceLocation FirstLoc, SourceLocation CountLoc,
SourceLocation EndLoc, Expr *First, Expr *Count);
/// Build an empty 'looprange' clause node.
static OMPLoopRangeClause *CreateEmpty(const ASTContext &C);
// Location getters/setters
SourceLocation getLParenLoc() const { return LParenLoc; }
SourceLocation getFirstLoc() const { return FirstLoc; }
SourceLocation getCountLoc() const { return CountLoc; }
void setLParenLoc(SourceLocation Loc) { LParenLoc = Loc; }
void setFirstLoc(SourceLocation Loc) { FirstLoc = Loc; }
void setCountLoc(SourceLocation Loc) { CountLoc = Loc; }
/// Get looprange 'first' expression
Expr *getFirst() const { return cast_or_null<Expr>(Args[FirstExpr]); }
/// Get looprange 'count' expression
Expr *getCount() const { return cast_or_null<Expr>(Args[CountExpr]); }
child_range children() { return child_range(Args, Args + NumArgs); }
const_child_range children() const {
return const_child_range(Args, Args + NumArgs);
}
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_looprange;
}
};
/// Representation of the 'partial' clause of the '#pragma omp unroll'
/// directive.
///

View File

@ -3177,6 +3177,9 @@ DEF_TRAVERSE_STMT(OMPUnrollDirective,
DEF_TRAVERSE_STMT(OMPReverseDirective,
{ TRY_TO(TraverseOMPExecutableDirective(S)); })
DEF_TRAVERSE_STMT(OMPFuseDirective,
{ TRY_TO(TraverseOMPExecutableDirective(S)); })
DEF_TRAVERSE_STMT(OMPInterchangeDirective,
{ TRY_TO(TraverseOMPExecutableDirective(S)); })
@ -3494,6 +3497,14 @@ bool RecursiveASTVisitor<Derived>::VisitOMPFullClause(OMPFullClause *C) {
return true;
}
template <typename Derived>
bool RecursiveASTVisitor<Derived>::VisitOMPLoopRangeClause(
OMPLoopRangeClause *C) {
TRY_TO(TraverseStmt(C->getFirst()));
TRY_TO(TraverseStmt(C->getCount()));
return true;
}
template <typename Derived>
bool RecursiveASTVisitor<Derived>::VisitOMPPartialClause(OMPPartialClause *C) {
TRY_TO(TraverseStmt(C->getFactor()));

View File

@ -21,6 +21,7 @@
#include "clang/AST/StmtCXX.h"
#include "clang/Basic/OpenMPKinds.h"
#include "clang/Basic/SourceLocation.h"
#include "llvm/Support/Casting.h"
namespace clang {
@ -677,6 +678,10 @@ public:
}
};
// Forward declaration of a generic loop transformation. Used in the declaration
// of OMPLoopBasedDirective.
class OMPLoopTransformationDirective;
/// The base class for all loop-based directives, including loop transformation
/// directives.
class OMPLoopBasedDirective : public OMPExecutableDirective {
@ -889,24 +894,23 @@ public:
/// Calls the specified callback function for all the loops in \p CurStmt,
/// from the outermost to the innermost.
static bool doForAllLoops(
Stmt *CurStmt, bool TryImperfectlyNestedLoops, unsigned NumLoops,
llvm::function_ref<bool(unsigned, Stmt *)> Callback,
llvm::function_ref<void(OMPCanonicalLoopNestTransformationDirective *)>
OnTransformationCallback);
static bool
doForAllLoops(Stmt *CurStmt, bool TryImperfectlyNestedLoops,
unsigned NumLoops,
llvm::function_ref<bool(unsigned, Stmt *)> Callback,
llvm::function_ref<void(OMPLoopTransformationDirective *)>
OnTransformationCallback);
static bool
doForAllLoops(const Stmt *CurStmt, bool TryImperfectlyNestedLoops,
unsigned NumLoops,
llvm::function_ref<bool(unsigned, const Stmt *)> Callback,
llvm::function_ref<
void(const OMPCanonicalLoopNestTransformationDirective *)>
llvm::function_ref<void(const OMPLoopTransformationDirective *)>
OnTransformationCallback) {
auto &&NewCallback = [Callback](unsigned Cnt, Stmt *CurStmt) {
return Callback(Cnt, CurStmt);
};
auto &&NewTransformCb =
[OnTransformationCallback](
OMPCanonicalLoopNestTransformationDirective *A) {
[OnTransformationCallback](OMPLoopTransformationDirective *A) {
OnTransformationCallback(A);
};
return doForAllLoops(const_cast<Stmt *>(CurStmt), TryImperfectlyNestedLoops,
@ -919,7 +923,7 @@ public:
doForAllLoops(Stmt *CurStmt, bool TryImperfectlyNestedLoops,
unsigned NumLoops,
llvm::function_ref<bool(unsigned, Stmt *)> Callback) {
auto &&TransformCb = [](OMPCanonicalLoopNestTransformationDirective *) {};
auto &&TransformCb = [](OMPLoopTransformationDirective *) {};
return doForAllLoops(CurStmt, TryImperfectlyNestedLoops, NumLoops, Callback,
TransformCb);
}
@ -957,9 +961,11 @@ public:
};
/// Common class of data shared between
/// OMPCanonicalLoopNestTransformationDirective and transformations over
/// canonical loop sequences.
/// OMPCanonicalLoopNestTransformationDirective and
/// OMPCanonicalLoopSequenceTransformationDirective
class OMPLoopTransformationDirective {
friend class ASTStmtReader;
/// Number of (top-level) generated loops.
/// This value is 1 for most transformations as they only map one loop nest
/// into another.
@ -969,15 +975,39 @@ class OMPLoopTransformationDirective {
/// generate more than one loop nest, so the value would be >= 1.
unsigned NumGeneratedTopLevelLoops = 1;
/// We need this because we cannot easily make OMPLoopTransformationDirective
/// a proper Stmt.
Stmt *S = nullptr;
protected:
void setNumGeneratedTopLevelLoops(unsigned N) {
NumGeneratedTopLevelLoops = N;
}
explicit OMPLoopTransformationDirective(Stmt *S) : S(S) {}
public:
unsigned getNumGeneratedTopLevelLoops() const {
return NumGeneratedTopLevelLoops;
}
/// Returns the specific directive related to this loop transformation.
Stmt *getDirective() const { return S; }
/// Get the de-sugared statements after the loop transformation.
///
/// Might be nullptr if either the directive generates no loops and is handled
/// directly in CodeGen, or resolving a template-dependence context is
/// required.
Stmt *getTransformedStmt() const;
/// Return preinits statement.
Stmt *getPreInits() const;
static bool classof(const Stmt *T) {
return isa<OMPCanonicalLoopNestTransformationDirective,
OMPCanonicalLoopSequenceTransformationDirective>(T);
}
};
/// The base class for all transformation directives of canonical loop nests.
@ -990,7 +1020,8 @@ protected:
explicit OMPCanonicalLoopNestTransformationDirective(
StmtClass SC, OpenMPDirectiveKind Kind, SourceLocation StartLoc,
SourceLocation EndLoc, unsigned NumAssociatedLoops)
: OMPLoopBasedDirective(SC, Kind, StartLoc, EndLoc, NumAssociatedLoops) {}
: OMPLoopBasedDirective(SC, Kind, StartLoc, EndLoc, NumAssociatedLoops),
OMPLoopTransformationDirective(this) {}
public:
/// Return the number of associated (consumed) loops.
@ -5928,6 +5959,112 @@ public:
}
};
/// The base class for all transformation directives of canonical loop
/// sequences (currently only 'fuse')
class OMPCanonicalLoopSequenceTransformationDirective
: public OMPExecutableDirective,
public OMPLoopTransformationDirective {
friend class ASTStmtReader;
protected:
explicit OMPCanonicalLoopSequenceTransformationDirective(
StmtClass SC, OpenMPDirectiveKind Kind, SourceLocation StartLoc,
SourceLocation EndLoc)
: OMPExecutableDirective(SC, Kind, StartLoc, EndLoc),
OMPLoopTransformationDirective(this) {}
public:
/// Get the de-sugared statements after the loop transformation.
///
/// Might be nullptr if either the directive generates no loops and is handled
/// directly in CodeGen, or resolving a template-dependence context is
/// required.
Stmt *getTransformedStmt() const;
/// Return preinits statement.
Stmt *getPreInits() const;
static bool classof(const Stmt *T) {
Stmt::StmtClass C = T->getStmtClass();
return C == OMPFuseDirectiveClass;
}
};
/// Represents the '#pragma omp fuse' loop transformation directive
///
/// \code{c}
/// #pragma omp fuse
/// {
/// for(int i = 0; i < m1; ++i) {...}
/// for(int j = 0; j < m2; ++j) {...}
/// ...
/// }
/// \endcode
class OMPFuseDirective final
: public OMPCanonicalLoopSequenceTransformationDirective {
friend class ASTStmtReader;
friend class OMPExecutableDirective;
// Offsets of child members.
enum {
PreInitsOffset = 0,
TransformedStmtOffset,
};
explicit OMPFuseDirective(SourceLocation StartLoc, SourceLocation EndLoc)
: OMPCanonicalLoopSequenceTransformationDirective(
OMPFuseDirectiveClass, llvm::omp::OMPD_fuse, StartLoc, EndLoc) {}
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 fuse'
///
/// \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 Total number of loops in the canonical loop sequence.
/// \param NumGeneratedTopLevelLoops Number of top-level generated loops.
// Typically 1 but looprange clause can
// change this.
/// \param AssociatedStmt The outermost associated loop
/// \param TransformedStmt The loop nest after fusion, or nullptr in
/// dependent
/// \param PreInits Helper preinits statements for the loop nest
static OMPFuseDirective *
Create(const ASTContext &C, SourceLocation StartLoc, SourceLocation EndLoc,
ArrayRef<OMPClause *> Clauses, unsigned NumGeneratedTopLevelLoops,
Stmt *AssociatedStmt, Stmt *TransformedStmt, Stmt *PreInits);
/// Build an empty '#pragma omp fuse' AST node for deserialization
///
/// \param C Context of the AST
/// \param NumClauses Number of clauses to allocate
/// \param NumLoops Number of top level loops to allocate
static OMPFuseDirective *CreateEmpty(const ASTContext &C,
unsigned NumClauses);
/// Gets the associated loops after the transformation. This is the de-sugared
/// replacement or nulltpr 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() == OMPFuseDirectiveClass;
}
};
/// This represents '#pragma omp scan' directive.
///
/// \code
@ -6596,4 +6733,37 @@ public:
} // end namespace clang
namespace llvm {
// Allow a Stmt* be casted correctly to an OMPLoopTransformationDirective*.
// The default routines would just use a C-style cast which won't work well
// for the multiple inheritance here. We have to use a static cast from the
// corresponding subclass.
template <>
struct CastInfo<clang::OMPLoopTransformationDirective, clang::Stmt *>
: public NullableValueCastFailed<clang::OMPLoopTransformationDirective *>,
public DefaultDoCastIfPossible<
clang::OMPLoopTransformationDirective *, clang::Stmt *,
CastInfo<clang::OMPLoopTransformationDirective, clang::Stmt *>> {
static bool isPossible(const clang::Stmt *T) {
return clang::OMPLoopTransformationDirective::classof(T);
}
static clang::OMPLoopTransformationDirective *doCast(clang::Stmt *T) {
if (auto *D =
dyn_cast<clang::OMPCanonicalLoopNestTransformationDirective>(T))
return static_cast<clang::OMPLoopTransformationDirective *>(D);
if (auto *D =
dyn_cast<clang::OMPCanonicalLoopSequenceTransformationDirective>(T))
return static_cast<clang::OMPLoopTransformationDirective *>(D);
llvm_unreachable("unexpected type");
}
};
template <>
struct CastInfo<clang::OMPLoopTransformationDirective, const clang::Stmt *>
: public ConstStrippingForwardingCast<
clang::OMPLoopTransformationDirective, const clang::Stmt *,
CastInfo<clang::OMPLoopTransformationDirective, clang::Stmt *>> {};
} // namespace llvm
#endif

View File

@ -11761,6 +11761,18 @@ def note_omp_implicit_dsa : Note<
"implicitly determined as %0">;
def err_omp_loop_var_dsa : Error<
"loop iteration variable in the associated loop of 'omp %1' directive may not be %0, predetermined as %2">;
def err_omp_not_a_loop_sequence
: Error<"statement after '#pragma omp %0' must be a loop sequence "
"containing canonical loops or loop-generating constructs">;
def err_omp_empty_loop_sequence
: Error<"loop sequence after '#pragma omp %0' must contain at least 1 "
"canonical loop or loop-generating construct">;
def err_omp_invalid_looprange
: Error<"looprange clause selects loops from %1 to %2 but this exceeds the "
"number of loops (%3) in the loop sequence">;
def warn_omp_redundant_fusion : Warning<"looprange clause selects a single "
"loop, resulting in redundant fusion">,
InGroup<OpenMPClauses>;
def err_omp_not_for : Error<
"%select{statement after '#pragma omp %1' must be a for loop|"
"expected %2 for loops after '#pragma omp %1'%select{|, but found only %4}3}0">;

View File

@ -391,6 +391,13 @@ bool isOpenMPLoopBoundSharingDirective(OpenMPDirectiveKind Kind);
bool isOpenMPCanonicalLoopNestTransformationDirective(
OpenMPDirectiveKind DKind);
/// Checks if the specified directive is a loop transformation directive that
/// applies to a canonical loop sequence.
/// \param DKind Specified directive.
/// \return True iff the directive is a loop transformation.
bool isOpenMPCanonicalLoopSequenceTransformationDirective(
OpenMPDirectiveKind DKind);
/// Checks if the specified directive is a loop transformation directive.
/// \param DKind Specified directive.
/// \return True iff the directive is a loop transformation.

View File

@ -238,6 +238,10 @@ def OMPUnrollDirective : StmtNode<OMPCanonicalLoopNestTransformationDirective>;
def OMPReverseDirective : StmtNode<OMPCanonicalLoopNestTransformationDirective>;
def OMPInterchangeDirective
: StmtNode<OMPCanonicalLoopNestTransformationDirective>;
def OMPCanonicalLoopSequenceTransformationDirective
: StmtNode<OMPExecutableDirective, 1>;
def OMPFuseDirective
: StmtNode<OMPCanonicalLoopSequenceTransformationDirective>;
def OMPForDirective : StmtNode<OMPLoopDirective>;
def OMPForSimdDirective : StmtNode<OMPLoopDirective>;
def OMPSectionsDirective : StmtNode<OMPExecutableDirective>;

View File

@ -6767,6 +6767,9 @@ private:
OpenMPClauseKind Kind,
bool ParseOnly);
/// Parses the 'looprange' clause of a '#pragma omp fuse' directive.
OMPClause *ParseOpenMPLoopRangeClause();
/// Parses the 'sizes' clause of a '#pragma omp tile' directive.
OMPClause *ParseOpenMPSizesClause();

View File

@ -463,6 +463,13 @@ public:
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,
Stmt *AStmt, SourceLocation StartLoc,
SourceLocation EndLoc);
/// Called on well-formed '\#pragma omp for' after parsing
/// of the associated statement.
StmtResult
@ -921,6 +928,12 @@ public:
SourceLocation StartLoc,
SourceLocation LParenLoc,
SourceLocation EndLoc);
/// Called on well-form 'looprange' clause after parsing its arguments.
OMPClause *
ActOnOpenMPLoopRangeClause(Expr *First, Expr *Count, SourceLocation StartLoc,
SourceLocation LParenLoc, SourceLocation FirstLoc,
SourceLocation CountLoc, SourceLocation EndLoc);
/// Called on well-formed 'ordered' clause.
OMPClause *
ActOnOpenMPOrderedClause(SourceLocation StartLoc, SourceLocation EndLoc,
@ -1485,7 +1498,81 @@ private:
bool checkTransformableLoopNest(
OpenMPDirectiveKind Kind, Stmt *AStmt, int NumLoops,
SmallVectorImpl<OMPLoopBasedDirective::HelperExprs> &LoopHelpers,
Stmt *&Body, SmallVectorImpl<SmallVector<Stmt *, 0>> &OriginalInits);
Stmt *&Body, SmallVectorImpl<SmallVector<Stmt *>> &OriginalInits);
/// Holds the result of the analysis of a (possibly canonical) loop.
struct LoopAnalysis {
/// The analyzed loop or loop transformation.
Stmt *AStmt = nullptr;
/// Loop analyses results.
OMPLoopBasedDirective::HelperExprs HelperExprs;
/// The for-statement of the loop. TheForStmt equals AStmt only when the
/// latter is a canonical loop (i.e. not a loop transformation).
Stmt *TheForStmt = nullptr;
/// Initialization statements before transformations.
SmallVector<Stmt *> OriginalInits;
/// Initialization statements required after transformation of this loop.
SmallVector<Stmt *> TransformsPreInits;
explicit LoopAnalysis(Stmt *S) : AStmt(S) {}
bool isRegularLoop() const { return isRegularLoop(AStmt); }
bool isLoopTransformation() const { return isLoopTransformation(AStmt); }
// Convenience functions used when building LoopSequenceAnalysis.
static bool isRegularLoop(Stmt *S) {
return isa<ForStmt, CXXForRangeStmt>(S);
}
static bool isLoopTransformation(Stmt *S) {
return isa<OMPLoopTransformationDirective>(S);
}
};
/// Holds the result of the analysis of a (possibly canonical) loop sequence.
struct LoopSequenceAnalysis {
/// Number of top level canonical loops.
unsigned LoopSeqSize = 0;
/// For each loop results of the analysis.
SmallVector<LoopAnalysis, 2> Loops;
/// Additional code required before entering the transformed loop sequence.
SmallVector<Stmt *> LoopSequencePreInits;
// Convenience function used when building the LoopSequenceAnalysis.
static bool isLoopSequenceDerivation(Stmt *S) {
return LoopAnalysis::isRegularLoop(S) ||
LoopAnalysis::isLoopTransformation(S);
}
};
/// The main recursive process of `checkTransformableLoopSequence` that
/// performs grammatical parsing of a canonical loop sequence. It extracts
/// key information, such as the number of top-level loops, loop statements,
/// helper expressions, and other relevant loop-related data, all in a single
/// execution to avoid redundant traversals. This analysis flattens inner
/// Loop Sequences
///
/// \param LoopSeqStmt The AST of the original statement.
/// \param SeqAnalysis [out] Result of the analysis of \p LoopSeqStmt
/// \param Context
/// \param Kind The loop transformation directive kind.
/// \return Whether the original statement is both syntactically and
/// semantically correct according to OpenMP 6.0 canonical loop
/// sequence definition.
bool analyzeLoopSequence(Stmt *LoopSeqStmt, LoopSequenceAnalysis &SeqAnalysis,
ASTContext &Context, OpenMPDirectiveKind Kind);
/// Validates and checks whether a loop sequence can be transformed according
/// to the given directive, providing necessary setup and initialization
/// (Driver function) before recursion using `analyzeLoopSequence`.
///
/// \param Kind The loop transformation directive kind.
/// \param AStmt The AST of the original statement
/// \param SeqAnalysis [out] Result of the analysis of \p LoopSeqStmt
/// \param Context
/// \return Whether there was an absence of errors or not
bool checkTransformableLoopSequence(OpenMPDirectiveKind Kind, Stmt *AStmt,
LoopSequenceAnalysis &SeqAnalysis,
ASTContext &Context);
/// Helper to keep information about the current `omp begin/end declare
/// variant` nesting.

View File

@ -1951,6 +1951,7 @@ enum StmtCode {
STMT_OMP_UNROLL_DIRECTIVE,
STMT_OMP_REVERSE_DIRECTIVE,
STMT_OMP_INTERCHANGE_DIRECTIVE,
STMT_OMP_FUSE_DIRECTIVE,
STMT_OMP_FOR_DIRECTIVE,
STMT_OMP_FOR_SIMD_DIRECTIVE,
STMT_OMP_SECTIONS_DIRECTIVE,

View File

@ -1024,6 +1024,26 @@ OMPPartialClause *OMPPartialClause::CreateEmpty(const ASTContext &C) {
return new (C) OMPPartialClause();
}
OMPLoopRangeClause *
OMPLoopRangeClause::Create(const ASTContext &C, SourceLocation StartLoc,
SourceLocation LParenLoc, SourceLocation FirstLoc,
SourceLocation CountLoc, SourceLocation EndLoc,
Expr *First, Expr *Count) {
OMPLoopRangeClause *Clause = CreateEmpty(C);
Clause->setLocStart(StartLoc);
Clause->setLParenLoc(LParenLoc);
Clause->setFirstLoc(FirstLoc);
Clause->setCountLoc(CountLoc);
Clause->setLocEnd(EndLoc);
Clause->setFirst(First);
Clause->setCount(Count);
return Clause;
}
OMPLoopRangeClause *OMPLoopRangeClause::CreateEmpty(const ASTContext &C) {
return new (C) OMPLoopRangeClause();
}
OMPAllocateClause *OMPAllocateClause::Create(
const ASTContext &C, SourceLocation StartLoc, SourceLocation LParenLoc,
Expr *Allocator, Expr *Alignment, SourceLocation ColonLoc,
@ -1964,6 +1984,21 @@ void OMPClausePrinter::VisitOMPPartialClause(OMPPartialClause *Node) {
}
}
void OMPClausePrinter::VisitOMPLoopRangeClause(OMPLoopRangeClause *Node) {
OS << "looprange";
Expr *First = Node->getFirst();
Expr *Count = Node->getCount();
if (First && Count) {
OS << "(";
First->printPretty(OS, nullptr, Policy, 0);
OS << ",";
Count->printPretty(OS, nullptr, Policy, 0);
OS << ")";
}
}
void OMPClausePrinter::VisitOMPAllocatorClause(OMPAllocatorClause *Node) {
OS << "allocator(";
Node->getAllocator()->printPretty(OS, nullptr, Policy, 0);

View File

@ -125,13 +125,12 @@ OMPLoopBasedDirective::tryToFindNextInnerLoop(Stmt *CurStmt,
bool OMPLoopBasedDirective::doForAllLoops(
Stmt *CurStmt, bool TryImperfectlyNestedLoops, unsigned NumLoops,
llvm::function_ref<bool(unsigned, Stmt *)> Callback,
llvm::function_ref<void(OMPCanonicalLoopNestTransformationDirective *)>
llvm::function_ref<void(OMPLoopTransformationDirective *)>
OnTransformationCallback) {
CurStmt = CurStmt->IgnoreContainers();
for (unsigned Cnt = 0; Cnt < NumLoops; ++Cnt) {
while (true) {
auto *Dir =
dyn_cast<OMPCanonicalLoopNestTransformationDirective>(CurStmt);
auto *Dir = dyn_cast<OMPLoopTransformationDirective>(CurStmt);
if (!Dir)
break;
@ -371,6 +370,22 @@ OMPForDirective *OMPForDirective::Create(
return Dir;
}
Stmt *OMPLoopTransformationDirective::getTransformedStmt() const {
if (auto *D = dyn_cast<OMPCanonicalLoopNestTransformationDirective>(S))
return D->getTransformedStmt();
if (auto *D = dyn_cast<OMPCanonicalLoopSequenceTransformationDirective>(S))
return D->getTransformedStmt();
llvm_unreachable("unexpected object type");
}
Stmt *OMPLoopTransformationDirective::getPreInits() const {
if (auto *D = dyn_cast<OMPCanonicalLoopNestTransformationDirective>(S))
return D->getPreInits();
if (auto *D = dyn_cast<OMPCanonicalLoopSequenceTransformationDirective>(S))
return D->getPreInits();
llvm_unreachable("unexpected object type");
}
Stmt *OMPCanonicalLoopNestTransformationDirective::getTransformedStmt() const {
switch (getStmtClass()) {
#define STMT(CLASS, PARENT)
@ -380,7 +395,7 @@ Stmt *OMPCanonicalLoopNestTransformationDirective::getTransformedStmt() const {
return static_cast<const CLASS *>(this)->getTransformedStmt();
#include "clang/AST/StmtNodes.inc"
default:
llvm_unreachable("Not a loop transformation");
llvm_unreachable("Not a loop transformation for canonical loop nests");
}
}
@ -393,7 +408,34 @@ Stmt *OMPCanonicalLoopNestTransformationDirective::getPreInits() const {
return static_cast<const CLASS *>(this)->getPreInits();
#include "clang/AST/StmtNodes.inc"
default:
llvm_unreachable("Not a loop transformation");
llvm_unreachable("Not a loop transformation for canonical loop nests");
}
}
Stmt *
OMPCanonicalLoopSequenceTransformationDirective::getTransformedStmt() const {
switch (getStmtClass()) {
#define STMT(CLASS, PARENT)
#define ABSTRACT_STMT(CLASS)
#define OMPCANONICALLOOPSEQUENCETRANSFORMATIONDIRECTIVE(CLASS, PARENT) \
case Stmt::CLASS##Class: \
return static_cast<const CLASS *>(this)->getTransformedStmt();
#include "clang/AST/StmtNodes.inc"
default:
llvm_unreachable("Not a loop transformation for canonical loop sequences");
}
}
Stmt *OMPCanonicalLoopSequenceTransformationDirective::getPreInits() const {
switch (getStmtClass()) {
#define STMT(CLASS, PARENT)
#define ABSTRACT_STMT(CLASS)
#define OMPCANONICALLOOPSEQUENCETRANSFORMATIONDIRECTIVE(CLASS, PARENT) \
case Stmt::CLASS##Class: \
return static_cast<const CLASS *>(this)->getPreInits();
#include "clang/AST/StmtNodes.inc"
default:
llvm_unreachable("Not a loop transformation for canonical loop sequences");
}
}
@ -510,6 +552,27 @@ OMPInterchangeDirective::CreateEmpty(const ASTContext &C, unsigned NumClauses,
SourceLocation(), SourceLocation(), NumLoops);
}
OMPFuseDirective *OMPFuseDirective::Create(
const ASTContext &C, SourceLocation StartLoc, SourceLocation EndLoc,
ArrayRef<OMPClause *> Clauses, unsigned NumGeneratedTopLevelLoops,
Stmt *AssociatedStmt, Stmt *TransformedStmt, Stmt *PreInits) {
OMPFuseDirective *Dir = createDirective<OMPFuseDirective>(
C, Clauses, AssociatedStmt, TransformedStmtOffset + 1, StartLoc, EndLoc);
Dir->setTransformedStmt(TransformedStmt);
Dir->setPreInits(PreInits);
Dir->setNumGeneratedTopLevelLoops(NumGeneratedTopLevelLoops);
return Dir;
}
OMPFuseDirective *OMPFuseDirective::CreateEmpty(const ASTContext &C,
unsigned NumClauses) {
OMPFuseDirective *Dir = createEmptyDirective<OMPFuseDirective>(
C, NumClauses, /*HasAssociatedStmt=*/true, TransformedStmtOffset + 1,
SourceLocation(), SourceLocation());
return Dir;
}
OMPForSimdDirective *
OMPForSimdDirective::Create(const ASTContext &C, SourceLocation StartLoc,
SourceLocation EndLoc, unsigned CollapsedNum,

View File

@ -795,6 +795,11 @@ void StmtPrinter::VisitOMPInterchangeDirective(OMPInterchangeDirective *Node) {
PrintOMPExecutableDirective(Node);
}
void StmtPrinter::VisitOMPFuseDirective(OMPFuseDirective *Node) {
Indent() << "#pragma omp fuse";
PrintOMPExecutableDirective(Node);
}
void StmtPrinter::VisitOMPForDirective(OMPForDirective *Node) {
Indent() << "#pragma omp for";
PrintOMPExecutableDirective(Node);

View File

@ -510,6 +510,13 @@ void OMPClauseProfiler::VisitOMPPartialClause(const OMPPartialClause *C) {
Profiler->VisitExpr(Factor);
}
void OMPClauseProfiler::VisitOMPLoopRangeClause(const OMPLoopRangeClause *C) {
if (const Expr *First = C->getFirst())
Profiler->VisitExpr(First);
if (const Expr *Count = C->getCount())
Profiler->VisitExpr(Count);
}
void OMPClauseProfiler::VisitOMPAllocatorClause(const OMPAllocatorClause *C) {
if (C->getAllocator())
Profiler->VisitStmt(C->getAllocator());
@ -1025,6 +1032,15 @@ void StmtProfiler::VisitOMPInterchangeDirective(
VisitOMPCanonicalLoopNestTransformationDirective(S);
}
void StmtProfiler::VisitOMPCanonicalLoopSequenceTransformationDirective(
const OMPCanonicalLoopSequenceTransformationDirective *S) {
VisitOMPExecutableDirective(S);
}
void StmtProfiler::VisitOMPFuseDirective(const OMPFuseDirective *S) {
VisitOMPCanonicalLoopSequenceTransformationDirective(S);
}
void StmtProfiler::VisitOMPForDirective(const OMPForDirective *S) {
VisitOMPLoopDirective(S);
}

View File

@ -282,6 +282,7 @@ unsigned clang::getOpenMPSimpleClauseType(OpenMPClauseKind Kind, StringRef Str,
case OMPC_affinity:
case OMPC_when:
case OMPC_append_args:
case OMPC_looprange:
break;
default:
break;
@ -627,6 +628,7 @@ const char *clang::getOpenMPSimpleClauseTypeName(OpenMPClauseKind Kind,
case OMPC_affinity:
case OMPC_when:
case OMPC_append_args:
case OMPC_looprange:
break;
default:
break;
@ -755,9 +757,14 @@ bool clang::isOpenMPCanonicalLoopNestTransformationDirective(
DKind == OMPD_interchange || DKind == OMPD_stripe;
}
bool clang::isOpenMPCanonicalLoopSequenceTransformationDirective(
OpenMPDirectiveKind DKind) {
return DKind == OMPD_fuse;
}
bool clang::isOpenMPLoopTransformationDirective(OpenMPDirectiveKind DKind) {
// FIXME: There will be more cases when we implement 'fuse'.
return isOpenMPCanonicalLoopNestTransformationDirective(DKind);
return isOpenMPCanonicalLoopNestTransformationDirective(DKind) ||
isOpenMPCanonicalLoopSequenceTransformationDirective(DKind);
}
bool clang::isOpenMPCombinedParallelADirective(OpenMPDirectiveKind DKind) {

View File

@ -234,6 +234,9 @@ void CodeGenFunction::EmitStmt(const Stmt *S, ArrayRef<const Attr *> Attrs) {
case Stmt::OMPInterchangeDirectiveClass:
EmitOMPInterchangeDirective(cast<OMPInterchangeDirective>(*S));
break;
case Stmt::OMPFuseDirectiveClass:
EmitOMPFuseDirective(cast<OMPFuseDirective>(*S));
break;
case Stmt::OMPForDirectiveClass:
EmitOMPForDirective(cast<OMPForDirective>(*S));
break;

View File

@ -201,6 +201,24 @@ class OMPLoopScope : public CodeGenFunction::RunCleanupsScope {
} else {
llvm_unreachable("Unknown loop-based directive kind.");
}
doEmitPreinits(PreInits);
PreCondVars.restore(CGF);
}
void
emitPreInitStmt(CodeGenFunction &CGF,
const OMPCanonicalLoopSequenceTransformationDirective &S) {
const Stmt *PreInits;
if (const auto *Fuse = dyn_cast<OMPFuseDirective>(&S)) {
PreInits = Fuse->getPreInits();
} else {
llvm_unreachable(
"Unknown canonical loop sequence transform directive kind.");
}
doEmitPreinits(PreInits);
}
void doEmitPreinits(const Stmt *PreInits) {
if (PreInits) {
// CompoundStmts and DeclStmts are used as lists of PreInit statements and
// declarations. Since declarations must be visible in the the following
@ -222,7 +240,6 @@ class OMPLoopScope : public CodeGenFunction::RunCleanupsScope {
CGF.EmitStmt(S);
}
}
PreCondVars.restore(CGF);
}
public:
@ -230,6 +247,11 @@ public:
: CodeGenFunction::RunCleanupsScope(CGF) {
emitPreInitStmt(CGF, S);
}
OMPLoopScope(CodeGenFunction &CGF,
const OMPCanonicalLoopSequenceTransformationDirective &S)
: CodeGenFunction::RunCleanupsScope(CGF) {
emitPreInitStmt(CGF, S);
}
};
class OMPSimdLexicalScope : public CodeGenFunction::LexicalScope {
@ -1929,6 +1951,15 @@ public:
CGSI = new CodeGenFunction::CGCapturedStmtInfo(CR_OpenMP);
CapInfoRAII = new CodeGenFunction::CGCapturedStmtRAII(CGF, CGSI);
}
if (const auto *Dir =
dyn_cast<OMPCanonicalLoopSequenceTransformationDirective>(S)) {
// For simplicity we reuse the loop scope similarly to what we do with
// OMPCanonicalLoopNestTransformationDirective do by being a subclass
// of OMPLoopBasedDirective.
Scope = new OMPLoopScope(CGF, *Dir);
CGSI = new CodeGenFunction::CGCapturedStmtInfo(CR_OpenMP);
CapInfoRAII = new CodeGenFunction::CGCapturedStmtRAII(CGF, CGSI);
}
}
~OMPTransformDirectiveScopeRAII() {
if (!Scope)
@ -1956,8 +1987,7 @@ static void emitBody(CodeGenFunction &CGF, const Stmt *S, const Stmt *NextLoop,
return;
}
if (SimplifiedS == NextLoop) {
if (auto *Dir =
dyn_cast<OMPCanonicalLoopNestTransformationDirective>(SimplifiedS))
if (auto *Dir = dyn_cast<OMPLoopTransformationDirective>(SimplifiedS))
SimplifiedS = Dir->getTransformedStmt();
if (const auto *CanonLoop = dyn_cast<OMPCanonicalLoop>(SimplifiedS))
SimplifiedS = CanonLoop->getLoopStmt();
@ -2952,6 +2982,12 @@ void CodeGenFunction::EmitOMPInterchangeDirective(
EmitStmt(S.getTransformedStmt());
}
void CodeGenFunction::EmitOMPFuseDirective(const OMPFuseDirective &S) {
// Emit the de-sugared statement
OMPTransformDirectiveScopeRAII FuseScope(*this, &S);
EmitStmt(S.getTransformedStmt());
}
void CodeGenFunction::EmitOMPUnrollDirective(const OMPUnrollDirective &S) {
bool UseOMPIRBuilder = CGM.getLangOpts().OpenMPIRBuilder;

View File

@ -3861,6 +3861,7 @@ public:
void EmitOMPUnrollDirective(const OMPUnrollDirective &S);
void EmitOMPReverseDirective(const OMPReverseDirective &S);
void EmitOMPInterchangeDirective(const OMPInterchangeDirective &S);
void EmitOMPFuseDirective(const OMPFuseDirective &S);
void EmitOMPForDirective(const OMPForDirective &S);
void EmitOMPForSimdDirective(const OMPForSimdDirective &S);
void EmitOMPScopeDirective(const OMPScopeDirective &S);

View File

@ -2968,6 +2968,39 @@ OMPClause *Parser::ParseOpenMPSizesClause() {
OpenLoc, CloseLoc);
}
OMPClause *Parser::ParseOpenMPLoopRangeClause() {
SourceLocation ClauseNameLoc = ConsumeToken();
SourceLocation FirstLoc, CountLoc;
BalancedDelimiterTracker T(*this, tok::l_paren, tok::annot_pragma_openmp_end);
if (T.consumeOpen()) {
Diag(Tok, diag::err_expected) << tok::l_paren;
return nullptr;
}
FirstLoc = Tok.getLocation();
ExprResult FirstVal = ParseConstantExpression();
if (!FirstVal.isUsable()) {
T.skipToEnd();
return nullptr;
}
ExpectAndConsume(tok::comma);
CountLoc = Tok.getLocation();
ExprResult CountVal = ParseConstantExpression();
if (!CountVal.isUsable()) {
T.skipToEnd();
return nullptr;
}
T.consumeClose();
return Actions.OpenMP().ActOnOpenMPLoopRangeClause(
FirstVal.get(), CountVal.get(), ClauseNameLoc, T.getOpenLocation(),
FirstLoc, CountLoc, T.getCloseLocation());
}
OMPClause *Parser::ParseOpenMPPermutationClause() {
SourceLocation ClauseNameLoc, OpenLoc, CloseLoc;
SmallVector<Expr *> ArgExprs;
@ -3473,6 +3506,9 @@ OMPClause *Parser::ParseOpenMPClause(OpenMPDirectiveKind DKind,
}
Clause = ParseOpenMPClause(CKind, WrongDirective);
break;
case OMPC_looprange:
Clause = ParseOpenMPLoopRangeClause();
break;
default:
break;
}

View File

@ -1493,6 +1493,7 @@ CanThrowResult Sema::canThrow(const Stmt *S) {
case Stmt::OMPUnrollDirectiveClass:
case Stmt::OMPReverseDirectiveClass:
case Stmt::OMPInterchangeDirectiveClass:
case Stmt::OMPFuseDirectiveClass:
case Stmt::OMPSingleDirectiveClass:
case Stmt::OMPTargetDataDirectiveClass:
case Stmt::OMPTargetDirectiveClass:

View File

@ -4569,6 +4569,7 @@ void SemaOpenMP::ActOnOpenMPRegionStart(OpenMPDirectiveKind DKind,
case OMPD_unroll:
case OMPD_reverse:
case OMPD_interchange:
case OMPD_fuse:
case OMPD_assume:
break;
default:
@ -6410,6 +6411,10 @@ StmtResult SemaOpenMP::ActOnOpenMPExecutableDirective(
Res = ActOnOpenMPInterchangeDirective(ClausesWithImplicit, AStmt, StartLoc,
EndLoc);
break;
case OMPD_fuse:
Res =
ActOnOpenMPFuseDirective(ClausesWithImplicit, AStmt, StartLoc, EndLoc);
break;
case OMPD_for:
Res = ActOnOpenMPForDirective(ClausesWithImplicit, AStmt, StartLoc, EndLoc,
VarsWithInheritedDSA);
@ -9488,7 +9493,9 @@ static bool checkOpenMPIterationSpace(
// sharing attributes.
VarsWithImplicitDSA.erase(LCDecl);
assert(isOpenMPLoopDirective(DKind) && "DSA for non-loop vars");
assert((isOpenMPLoopDirective(DKind) ||
isOpenMPCanonicalLoopSequenceTransformationDirective(DKind)) &&
"DSA for non-loop vars");
// Check test-expr.
HasErrors |= ISC.checkAndSetCond(For ? For->getCond() : CXXFor->getCond());
@ -9916,7 +9923,8 @@ checkOpenMPLoop(OpenMPDirectiveKind DKind, Expr *CollapseLoopCountExpr,
unsigned NumLoops = std::max(OrderedLoopCount, NestedLoopCount);
SmallVector<LoopIterationSpace, 4> IterSpaces(NumLoops);
if (!OMPLoopBasedDirective::doForAllLoops(
AStmt->IgnoreContainers(!isOpenMPLoopTransformationDirective(DKind)),
AStmt->IgnoreContainers(
!isOpenMPCanonicalLoopNestTransformationDirective(DKind)),
SupportsNonPerfectlyNested, NumLoops,
[DKind, &SemaRef, &DSA, NumLoops, NestedLoopCount,
CollapseLoopCountExpr, OrderedLoopCountExpr, &VarsWithImplicitDSA,
@ -9938,8 +9946,7 @@ checkOpenMPLoop(OpenMPDirectiveKind DKind, Expr *CollapseLoopCountExpr,
}
return false;
},
[&SemaRef,
&Captures](OMPCanonicalLoopNestTransformationDirective *Transform) {
[&SemaRef, &Captures](OMPLoopTransformationDirective *Transform) {
Stmt *DependentPreInits = Transform->getPreInits();
if (!DependentPreInits)
return;
@ -9954,7 +9961,8 @@ checkOpenMPLoop(OpenMPDirectiveKind DKind, Expr *CollapseLoopCountExpr,
auto *D = cast<VarDecl>(C);
DeclRefExpr *Ref = buildDeclRefExpr(
SemaRef, D, D->getType().getNonReferenceType(),
Transform->getBeginLoc());
cast<OMPExecutableDirective>(Transform->getDirective())
->getBeginLoc());
Captures[Ref] = Ref;
}
}
@ -14404,10 +14412,34 @@ StmtResult SemaOpenMP::ActOnOpenMPTargetTeamsDistributeSimdDirective(
getASTContext(), StartLoc, EndLoc, NestedLoopCount, Clauses, AStmt, B);
}
/// Updates OriginalInits by checking Transform against loop transformation
/// directives and appending their pre-inits if a match is found.
static void updatePreInits(OMPLoopTransformationDirective *Transform,
SmallVectorImpl<Stmt *> &PreInits) {
Stmt *Dir = Transform->getDirective();
switch (Dir->getStmtClass()) {
#define STMT(CLASS, PARENT)
#define ABSTRACT_STMT(CLASS)
#define COMMON_OMP_LOOP_TRANSFORMATION(CLASS, PARENT) \
case Stmt::CLASS##Class: \
appendFlattenedStmtList(PreInits, \
static_cast<const CLASS *>(Dir)->getPreInits()); \
break;
#define OMPCANONICALLOOPNESTTRANSFORMATIONDIRECTIVE(CLASS, PARENT) \
COMMON_OMP_LOOP_TRANSFORMATION(CLASS, PARENT)
#define OMPCANONICALLOOPSEQUENCETRANSFORMATIONDIRECTIVE(CLASS, PARENT) \
COMMON_OMP_LOOP_TRANSFORMATION(CLASS, PARENT)
#include "clang/AST/StmtNodes.inc"
#undef COMMON_OMP_LOOP_TRANSFORMATION
default:
llvm_unreachable("Not a loop transformation");
}
}
bool SemaOpenMP::checkTransformableLoopNest(
OpenMPDirectiveKind Kind, Stmt *AStmt, int NumLoops,
SmallVectorImpl<OMPLoopBasedDirective::HelperExprs> &LoopHelpers,
Stmt *&Body, SmallVectorImpl<SmallVector<Stmt *, 0>> &OriginalInits) {
Stmt *&Body, SmallVectorImpl<SmallVector<Stmt *>> &OriginalInits) {
OriginalInits.emplace_back();
bool Result = OMPLoopBasedDirective::doForAllLoops(
AStmt->IgnoreContainers(), /*TryImperfectlyNestedLoops=*/false, NumLoops,
@ -14433,29 +14465,268 @@ bool SemaOpenMP::checkTransformableLoopNest(
OriginalInits.emplace_back();
return false;
},
[&OriginalInits](OMPLoopBasedDirective *Transform) {
Stmt *DependentPreInits;
if (auto *Dir = dyn_cast<OMPTileDirective>(Transform))
DependentPreInits = Dir->getPreInits();
else if (auto *Dir = dyn_cast<OMPStripeDirective>(Transform))
DependentPreInits = Dir->getPreInits();
else if (auto *Dir = dyn_cast<OMPUnrollDirective>(Transform))
DependentPreInits = Dir->getPreInits();
else if (auto *Dir = dyn_cast<OMPReverseDirective>(Transform))
DependentPreInits = Dir->getPreInits();
else if (auto *Dir = dyn_cast<OMPInterchangeDirective>(Transform))
DependentPreInits = Dir->getPreInits();
else
llvm_unreachable("Unhandled loop transformation");
appendFlattenedStmtList(OriginalInits.back(), DependentPreInits);
[&OriginalInits](OMPLoopTransformationDirective *Transform) {
updatePreInits(Transform, OriginalInits.back());
});
assert(OriginalInits.back().empty() && "No preinit after innermost loop");
OriginalInits.pop_back();
return Result;
}
/// Add preinit statements that need to be propageted from the selected loop.
/// Counts the total number of OpenMP canonical nested loops, including the
/// outermost loop (the original loop). PRECONDITION of this visitor is that it
/// must be invoked from the original loop to be analyzed. The traversal stops
/// for Decl's and Expr's given that they may contain inner loops that must not
/// be counted.
///
/// Example AST structure for the code:
///
/// int main() {
/// #pragma omp fuse
/// {
/// for (int i = 0; i < 100; i++) { <-- Outer loop
/// []() {
/// for(int j = 0; j < 100; j++) {} <-- NOT A LOOP (1)
/// };
/// for(int j = 0; j < 5; ++j) {} <-- Inner loop
/// }
/// for (int r = 0; i < 100; i++) { <-- Outer loop
/// struct LocalClass {
/// void bar() {
/// for(int j = 0; j < 100; j++) {} <-- NOT A LOOP (2)
/// }
/// };
/// for(int k = 0; k < 10; ++k) {} <-- Inner loop
/// {x = 5; for(k = 0; k < 10; ++k) x += k; x}; <-- NOT A LOOP (3)
/// }
/// }
/// }
/// (1) because in a different function (here: a lambda)
/// (2) because in a different function (here: class method)
/// (3) because considered to be intervening-code of non-perfectly nested loop
/// Result: Loop 'i' contains 2 loops, Loop 'r' also contains 2 loops.
class NestedLoopCounterVisitor final : public DynamicRecursiveASTVisitor {
private:
unsigned NestedLoopCount = 0;
public:
explicit NestedLoopCounterVisitor() = default;
unsigned getNestedLoopCount() const { return NestedLoopCount; }
bool VisitForStmt(ForStmt *FS) override {
++NestedLoopCount;
return true;
}
bool VisitCXXForRangeStmt(CXXForRangeStmt *FRS) override {
++NestedLoopCount;
return true;
}
bool TraverseStmt(Stmt *S) override {
if (!S)
return true;
// Skip traversal of all expressions, including special cases like
// LambdaExpr, StmtExpr, BlockExpr, and RequiresExpr. These expressions
// may contain inner statements (and even loops), but they are not part
// of the syntactic body of the surrounding loop structure.
// Therefore must not be counted.
if (isa<Expr>(S))
return true;
// Only recurse into CompoundStmt (block {}) and loop bodies.
if (isa<CompoundStmt, ForStmt, CXXForRangeStmt>(S)) {
return DynamicRecursiveASTVisitor::TraverseStmt(S);
}
// Stop traversal of the rest of statements, that break perfect
// loop nesting, such as control flow (IfStmt, SwitchStmt...).
return true;
}
bool TraverseDecl(Decl *D) override {
// Stop in the case of finding a declaration, it is not important
// in order to find nested loops (Possible CXXRecordDecl, RecordDecl,
// FunctionDecl...).
return true;
}
};
bool SemaOpenMP::analyzeLoopSequence(Stmt *LoopSeqStmt,
LoopSequenceAnalysis &SeqAnalysis,
ASTContext &Context,
OpenMPDirectiveKind Kind) {
VarsWithInheritedDSAType TmpDSA;
// Helper Lambda to handle storing initialization and body statements for
// both ForStmt and CXXForRangeStmt.
auto StoreLoopStatements = [](LoopAnalysis &Analysis, Stmt *LoopStmt) {
if (auto *For = dyn_cast<ForStmt>(LoopStmt)) {
Analysis.OriginalInits.push_back(For->getInit());
Analysis.TheForStmt = For;
} else {
auto *CXXFor = cast<CXXForRangeStmt>(LoopStmt);
Analysis.OriginalInits.push_back(CXXFor->getBeginStmt());
Analysis.TheForStmt = CXXFor;
}
};
// Helper lambda functions to encapsulate the processing of different
// derivations of the canonical loop sequence grammar
// Modularized code for handling loop generation and transformations.
auto AnalyzeLoopGeneration = [&](Stmt *Child) {
auto *LoopTransform = cast<OMPLoopTransformationDirective>(Child);
Stmt *TransformedStmt = LoopTransform->getTransformedStmt();
unsigned NumGeneratedTopLevelLoops =
LoopTransform->getNumGeneratedTopLevelLoops();
// Handle the case where transformed statement is not available due to
// dependent contexts
if (!TransformedStmt) {
if (NumGeneratedTopLevelLoops > 0) {
SeqAnalysis.LoopSeqSize += NumGeneratedTopLevelLoops;
return true;
}
// Unroll full (0 loops produced)
Diag(Child->getBeginLoc(), diag::err_omp_not_for)
<< 0 << getOpenMPDirectiveName(Kind);
return false;
}
// Handle loop transformations with multiple loop nests
// Unroll full
if (!NumGeneratedTopLevelLoops) {
Diag(Child->getBeginLoc(), diag::err_omp_not_for)
<< 0 << getOpenMPDirectiveName(Kind);
return false;
}
// Loop transformatons such as split or loopranged fuse
if (NumGeneratedTopLevelLoops > 1) {
// Get the preinits related to this loop sequence generating
// loop transformation (i.e loopranged fuse, split...)
// These preinits differ slightly from regular inits/pre-inits related
// to single loop generating loop transformations (interchange, unroll)
// given that they are not bounded to a particular loop nest
// so they need to be treated independently
updatePreInits(LoopTransform, SeqAnalysis.LoopSequencePreInits);
return analyzeLoopSequence(TransformedStmt, SeqAnalysis, Context, Kind);
}
// Vast majority: (Tile, Unroll, Stripe, Reverse, Interchange, Fuse all)
// Process the transformed loop statement
LoopAnalysis &NewTransformedSingleLoop =
SeqAnalysis.Loops.emplace_back(Child);
unsigned IsCanonical = checkOpenMPLoop(
Kind, nullptr, nullptr, TransformedStmt, SemaRef, *DSAStack, TmpDSA,
NewTransformedSingleLoop.HelperExprs);
if (!IsCanonical)
return false;
StoreLoopStatements(NewTransformedSingleLoop, TransformedStmt);
updatePreInits(LoopTransform, NewTransformedSingleLoop.TransformsPreInits);
SeqAnalysis.LoopSeqSize++;
return true;
};
// Modularized code for handling regular canonical loops.
auto AnalyzeRegularLoop = [&](Stmt *Child) {
LoopAnalysis &NewRegularLoop = SeqAnalysis.Loops.emplace_back(Child);
unsigned IsCanonical =
checkOpenMPLoop(Kind, nullptr, nullptr, Child, SemaRef, *DSAStack,
TmpDSA, NewRegularLoop.HelperExprs);
if (!IsCanonical)
return false;
StoreLoopStatements(NewRegularLoop, Child);
NestedLoopCounterVisitor NLCV;
NLCV.TraverseStmt(Child);
return true;
};
// High level grammar validation.
for (Stmt *Child : LoopSeqStmt->children()) {
if (!Child)
continue;
// Skip over non-loop-sequence statements.
if (!LoopSequenceAnalysis::isLoopSequenceDerivation(Child)) {
Child = Child->IgnoreContainers();
// Ignore empty compound statement.
if (!Child)
continue;
// In the case of a nested loop sequence ignoring containers would not
// be enough, a recurisve transversal of the loop sequence is required.
if (isa<CompoundStmt>(Child)) {
if (!analyzeLoopSequence(Child, SeqAnalysis, Context, Kind))
return false;
// Already been treated, skip this children
continue;
}
}
// Regular loop sequence handling.
if (LoopSequenceAnalysis::isLoopSequenceDerivation(Child)) {
if (LoopAnalysis::isLoopTransformation(Child)) {
if (!AnalyzeLoopGeneration(Child))
return false;
// AnalyzeLoopGeneration updates SeqAnalysis.LoopSeqSize accordingly.
} else {
if (!AnalyzeRegularLoop(Child))
return false;
SeqAnalysis.LoopSeqSize++;
}
} else {
// Report error for invalid statement inside canonical loop sequence.
Diag(Child->getBeginLoc(), diag::err_omp_not_for)
<< 0 << getOpenMPDirectiveName(Kind);
return false;
}
}
return true;
}
bool SemaOpenMP::checkTransformableLoopSequence(
OpenMPDirectiveKind Kind, Stmt *AStmt, LoopSequenceAnalysis &SeqAnalysis,
ASTContext &Context) {
// Following OpenMP 6.0 API Specification, a Canonical Loop Sequence follows
// the grammar:
//
// canonical-loop-sequence:
// {
// loop-sequence+
// }
// where loop-sequence can be any of the following:
// 1. canonical-loop-sequence
// 2. loop-nest
// 3. loop-sequence-generating-construct (i.e OMPLoopTransformationDirective)
//
// To recognise and traverse this structure the helper function
// analyzeLoopSequence serves as the recurisve entry point
// and tries to match the input AST to the canonical loop sequence grammar
// structure. This function will perform both a semantic and syntactical
// analysis of the given statement according to OpenMP 6.0 definition of
// the aforementioned canonical loop sequence.
// We expect an outer compound statement.
if (!isa<CompoundStmt>(AStmt)) {
Diag(AStmt->getBeginLoc(), diag::err_omp_not_a_loop_sequence)
<< getOpenMPDirectiveName(Kind);
return false;
}
// Recursive entry point to process the main loop sequence
if (!analyzeLoopSequence(AStmt, SeqAnalysis, Context, Kind))
return false;
// Diagnose an empty loop sequence.
if (!SeqAnalysis.LoopSeqSize) {
Diag(AStmt->getBeginLoc(), diag::err_omp_empty_loop_sequence)
<< getOpenMPDirectiveName(Kind);
return false;
}
return true;
}
/// Add preinit statements that need to be propagated from the selected loop.
static void addLoopPreInits(ASTContext &Context,
OMPLoopBasedDirective::HelperExprs &LoopHelper,
Stmt *LoopStmt, ArrayRef<Stmt *> OriginalInit,
@ -14540,7 +14811,7 @@ StmtResult SemaOpenMP::ActOnOpenMPTileDirective(ArrayRef<OMPClause *> Clauses,
// Verify and diagnose loop nest.
SmallVector<OMPLoopBasedDirective::HelperExprs, 4> LoopHelpers(NumLoops);
Stmt *Body = nullptr;
SmallVector<SmallVector<Stmt *, 0>, 4> OriginalInits;
SmallVector<SmallVector<Stmt *>, 4> OriginalInits;
if (!checkTransformableLoopNest(OMPD_tile, AStmt, NumLoops, LoopHelpers, Body,
OriginalInits))
return StmtError();
@ -14817,7 +15088,7 @@ StmtResult SemaOpenMP::ActOnOpenMPStripeDirective(ArrayRef<OMPClause *> Clauses,
// Verify and diagnose loop nest.
SmallVector<OMPLoopBasedDirective::HelperExprs, 4> LoopHelpers(NumLoops);
Stmt *Body = nullptr;
SmallVector<SmallVector<Stmt *, 0>, 4> OriginalInits;
SmallVector<SmallVector<Stmt *>, 4> OriginalInits;
if (!checkTransformableLoopNest(OMPD_stripe, AStmt, NumLoops, LoopHelpers,
Body, OriginalInits))
return StmtError();
@ -15078,7 +15349,7 @@ StmtResult SemaOpenMP::ActOnOpenMPUnrollDirective(ArrayRef<OMPClause *> Clauses,
Stmt *Body = nullptr;
SmallVector<OMPLoopBasedDirective::HelperExprs, NumLoops> LoopHelpers(
NumLoops);
SmallVector<SmallVector<Stmt *, 0>, NumLoops + 1> OriginalInits;
SmallVector<SmallVector<Stmt *>, NumLoops + 1> OriginalInits;
if (!checkTransformableLoopNest(OMPD_unroll, AStmt, NumLoops, LoopHelpers,
Body, OriginalInits))
return StmtError();
@ -15348,7 +15619,7 @@ StmtResult SemaOpenMP::ActOnOpenMPReverseDirective(Stmt *AStmt,
Stmt *Body = nullptr;
SmallVector<OMPLoopBasedDirective::HelperExprs, NumLoops> LoopHelpers(
NumLoops);
SmallVector<SmallVector<Stmt *, 0>, NumLoops + 1> OriginalInits;
SmallVector<SmallVector<Stmt *>, NumLoops + 1> OriginalInits;
if (!checkTransformableLoopNest(OMPD_reverse, AStmt, NumLoops, LoopHelpers,
Body, OriginalInits))
return StmtError();
@ -15540,7 +15811,7 @@ StmtResult SemaOpenMP::ActOnOpenMPInterchangeDirective(
// Verify and diagnose loop nest.
SmallVector<OMPLoopBasedDirective::HelperExprs, 4> LoopHelpers(NumLoops);
Stmt *Body = nullptr;
SmallVector<SmallVector<Stmt *, 0>, 2> OriginalInits;
SmallVector<SmallVector<Stmt *>, 2> OriginalInits;
if (!checkTransformableLoopNest(OMPD_interchange, AStmt, NumLoops,
LoopHelpers, Body, OriginalInits))
return StmtError();
@ -15716,6 +15987,484 @@ StmtResult SemaOpenMP::ActOnOpenMPInterchangeDirective(
buildPreInits(Context, PreInits));
}
StmtResult SemaOpenMP::ActOnOpenMPFuseDirective(ArrayRef<OMPClause *> Clauses,
Stmt *AStmt,
SourceLocation StartLoc,
SourceLocation EndLoc) {
ASTContext &Context = getASTContext();
DeclContext *CurrContext = SemaRef.CurContext;
Scope *CurScope = SemaRef.getCurScope();
CaptureVars CopyTransformer(SemaRef);
// Ensure the structured block is not empty
if (!AStmt)
return StmtError();
// Defer transformation in dependent contexts
// The NumLoopNests argument is set to a placeholder 1 (even though
// using looprange fuse could yield up to 3 top level loop nests)
// because a dependent context could prevent determining its true value
if (CurrContext->isDependentContext())
return OMPFuseDirective::Create(Context, StartLoc, EndLoc, Clauses,
/* NumLoops */ 1, AStmt, nullptr, nullptr);
// Validate that the potential loop sequence is transformable for fusion
// Also collect the HelperExprs, Loop Stmts, Inits, and Number of loops
LoopSequenceAnalysis SeqAnalysis;
if (!checkTransformableLoopSequence(OMPD_fuse, AStmt, SeqAnalysis, Context))
return StmtError();
// SeqAnalysis.LoopSeqSize exists mostly to handle dependent contexts,
// otherwise it must be the same as SeqAnalysis.Loops.size().
assert(SeqAnalysis.LoopSeqSize == SeqAnalysis.Loops.size() &&
"Inconsistent size of the loop sequence and the number of loops "
"found in the sequence");
// Handle clauses, which can be any of the following: [looprange, apply]
const auto *LRC =
OMPExecutableDirective::getSingleClause<OMPLoopRangeClause>(Clauses);
// The clause arguments are invalidated if any error arises
// such as non-constant or non-positive arguments
if (LRC && (!LRC->getFirst() || !LRC->getCount()))
return StmtError();
// Delayed semantic check of LoopRange constraint
// Evaluates the loop range arguments and returns the first and count values
auto EvaluateLoopRangeArguments = [&Context](Expr *First, Expr *Count,
uint64_t &FirstVal,
uint64_t &CountVal) {
llvm::APSInt FirstInt = First->EvaluateKnownConstInt(Context);
llvm::APSInt CountInt = Count->EvaluateKnownConstInt(Context);
FirstVal = FirstInt.getZExtValue();
CountVal = CountInt.getZExtValue();
};
// OpenMP [6.0, Restrictions]
// first + count - 1 must not evaluate to a value greater than the
// loop sequence length of the associated canonical loop sequence.
auto ValidLoopRange = [](uint64_t FirstVal, uint64_t CountVal,
unsigned NumLoops) -> bool {
return FirstVal + CountVal - 1 <= NumLoops;
};
uint64_t FirstVal = 1, CountVal = 0, LastVal = SeqAnalysis.LoopSeqSize;
// Validates the loop range after evaluating the semantic information
// and ensures that the range is valid for the given loop sequence size.
// Expressions are evaluated at compile time to obtain constant values.
if (LRC) {
EvaluateLoopRangeArguments(LRC->getFirst(), LRC->getCount(), FirstVal,
CountVal);
if (CountVal == 1)
SemaRef.Diag(LRC->getCountLoc(), diag::warn_omp_redundant_fusion)
<< getOpenMPDirectiveName(OMPD_fuse);
if (!ValidLoopRange(FirstVal, CountVal, SeqAnalysis.LoopSeqSize)) {
SemaRef.Diag(LRC->getFirstLoc(), diag::err_omp_invalid_looprange)
<< getOpenMPDirectiveName(OMPD_fuse) << FirstVal
<< (FirstVal + CountVal - 1) << SeqAnalysis.LoopSeqSize;
return StmtError();
}
LastVal = FirstVal + CountVal - 1;
}
// Complete fusion generates a single canonical loop nest
// However looprange clause may generate several loop nests
unsigned NumGeneratedTopLevelLoops =
LRC ? SeqAnalysis.LoopSeqSize - CountVal + 1 : 1;
// Emit a warning for redundant loop fusion when the sequence contains only
// one loop.
if (SeqAnalysis.LoopSeqSize == 1)
SemaRef.Diag(AStmt->getBeginLoc(), diag::warn_omp_redundant_fusion)
<< getOpenMPDirectiveName(OMPD_fuse);
// Select the type with the largest bit width among all induction variables
QualType IVType =
SeqAnalysis.Loops[FirstVal - 1].HelperExprs.IterationVarRef->getType();
for (unsigned I : llvm::seq<unsigned>(FirstVal, LastVal)) {
QualType CurrentIVType =
SeqAnalysis.Loops[I].HelperExprs.IterationVarRef->getType();
if (Context.getTypeSize(CurrentIVType) > Context.getTypeSize(IVType)) {
IVType = CurrentIVType;
}
}
uint64_t IVBitWidth = Context.getIntWidth(IVType);
// Create pre-init declarations for all loops lower bounds, upper bounds,
// strides and num-iterations for every top level loop in the fusion
SmallVector<VarDecl *, 4> LBVarDecls;
SmallVector<VarDecl *, 4> STVarDecls;
SmallVector<VarDecl *, 4> NIVarDecls;
SmallVector<VarDecl *, 4> UBVarDecls;
SmallVector<VarDecl *, 4> IVVarDecls;
// Helper lambda to create variables for bounds, strides, and other
// expressions. Generates both the variable declaration and the corresponding
// initialization statement.
auto CreateHelperVarAndStmt =
[&, &SemaRef = SemaRef](Expr *ExprToCopy, const std::string &BaseName,
unsigned I, bool NeedsNewVD = false) {
Expr *TransformedExpr =
AssertSuccess(CopyTransformer.TransformExpr(ExprToCopy));
if (!TransformedExpr)
return std::pair<VarDecl *, StmtResult>(nullptr, StmtError());
auto Name = (Twine(".omp.") + BaseName + std::to_string(I)).str();
VarDecl *VD;
if (NeedsNewVD) {
VD = buildVarDecl(SemaRef, SourceLocation(), IVType, Name);
SemaRef.AddInitializerToDecl(VD, TransformedExpr, false);
} else {
// Create a unique variable name
DeclRefExpr *DRE = cast<DeclRefExpr>(TransformedExpr);
VD = cast<VarDecl>(DRE->getDecl());
VD->setDeclName(&SemaRef.PP.getIdentifierTable().get(Name));
}
// Create the corresponding declaration statement
StmtResult DeclStmt = new (Context) class DeclStmt(
DeclGroupRef(VD), SourceLocation(), SourceLocation());
return std::make_pair(VD, DeclStmt);
};
// PreInits hold a sequence of variable declarations that must be executed
// before the fused loop begins. These include bounds, strides, and other
// helper variables required for the transformation. Other loop transforms
// also contain their own preinits
SmallVector<Stmt *> PreInits;
// Update the general preinits using the preinits generated by loop sequence
// generating loop transformations. These preinits differ slightly from
// single-loop transformation preinits, as they can be detached from a
// specific loop inside multiple generated loop nests. This happens
// because certain helper variables, like '.omp.fuse.max', are introduced to
// handle fused iteration spaces and may not be directly tied to a single
// original loop. The preinit structure must ensure that hidden variables
// like '.omp.fuse.max' are still properly handled.
// Transformations that apply this concept: Loopranged Fuse, Split
llvm::append_range(PreInits, SeqAnalysis.LoopSequencePreInits);
// Process each single loop to generate and collect declarations
// and statements for all helper expressions related to
// particular single loop nests
// Also In the case of the fused loops, we keep track of their original
// inits by appending them to their preinits statement, and in the case of
// transformations, also append their preinits (which contain the original
// loop initialization statement or other statements)
// Firstly we need to set TransformIndex to match the begining of the
// looprange section
unsigned int TransformIndex = 0;
for (unsigned I : llvm::seq<unsigned>(FirstVal - 1)) {
if (SeqAnalysis.Loops[I].isLoopTransformation())
++TransformIndex;
}
for (unsigned int I = FirstVal - 1, J = 0; I < LastVal; ++I, ++J) {
if (SeqAnalysis.Loops[I].isRegularLoop()) {
addLoopPreInits(Context, SeqAnalysis.Loops[I].HelperExprs,
SeqAnalysis.Loops[I].TheForStmt,
SeqAnalysis.Loops[I].OriginalInits, PreInits);
} else if (SeqAnalysis.Loops[I].isLoopTransformation()) {
// For transformed loops, insert both pre-inits and original inits.
// Order matters: pre-inits may define variables used in the original
// inits such as upper bounds...
SmallVector<Stmt *> &TransformPreInit =
SeqAnalysis.Loops[TransformIndex++].TransformsPreInits;
llvm::append_range(PreInits, TransformPreInit);
addLoopPreInits(Context, SeqAnalysis.Loops[I].HelperExprs,
SeqAnalysis.Loops[I].TheForStmt,
SeqAnalysis.Loops[I].OriginalInits, PreInits);
}
auto [UBVD, UBDStmt] =
CreateHelperVarAndStmt(SeqAnalysis.Loops[I].HelperExprs.UB, "ub", J);
auto [LBVD, LBDStmt] =
CreateHelperVarAndStmt(SeqAnalysis.Loops[I].HelperExprs.LB, "lb", J);
auto [STVD, STDStmt] =
CreateHelperVarAndStmt(SeqAnalysis.Loops[I].HelperExprs.ST, "st", J);
auto [NIVD, NIDStmt] = CreateHelperVarAndStmt(
SeqAnalysis.Loops[I].HelperExprs.NumIterations, "ni", J, true);
auto [IVVD, IVDStmt] = CreateHelperVarAndStmt(
SeqAnalysis.Loops[I].HelperExprs.IterationVarRef, "iv", J);
assert(LBVD && STVD && NIVD && IVVD &&
"OpenMP Fuse Helper variables creation failed");
UBVarDecls.push_back(UBVD);
LBVarDecls.push_back(LBVD);
STVarDecls.push_back(STVD);
NIVarDecls.push_back(NIVD);
IVVarDecls.push_back(IVVD);
PreInits.push_back(LBDStmt.get());
PreInits.push_back(STDStmt.get());
PreInits.push_back(NIDStmt.get());
PreInits.push_back(IVDStmt.get());
}
auto MakeVarDeclRef = [&SemaRef = this->SemaRef](VarDecl *VD) {
return buildDeclRefExpr(SemaRef, VD, VD->getType(), VD->getLocation(),
false);
};
// Following up the creation of the final fused loop will be performed
// which has the following shape (considering the selected loops):
//
// for (fuse.index = 0; fuse.index < max(ni0, ni1..., nik); ++fuse.index) {
// if (fuse.index < ni0){
// iv0 = lb0 + st0 * fuse.index;
// original.index0 = iv0
// body(0);
// }
// if (fuse.index < ni1){
// iv1 = lb1 + st1 * fuse.index;
// original.index1 = iv1
// body(1);
// }
//
// ...
//
// if (fuse.index < nik){
// ivk = lbk + stk * fuse.index;
// original.indexk = ivk
// body(k); Expr *InitVal = IntegerLiteral::Create(Context,
// llvm::APInt(IVWidth, 0),
// }
// 1. Create the initialized fuse index
StringRef IndexName = ".omp.fuse.index";
Expr *InitVal = IntegerLiteral::Create(Context, llvm::APInt(IVBitWidth, 0),
IVType, SourceLocation());
VarDecl *IndexDecl =
buildVarDecl(SemaRef, {}, IVType, IndexName, nullptr, nullptr);
SemaRef.AddInitializerToDecl(IndexDecl, InitVal, false);
StmtResult InitStmt = new (Context)
DeclStmt(DeclGroupRef(IndexDecl), SourceLocation(), SourceLocation());
if (!InitStmt.isUsable())
return StmtError();
auto MakeIVRef = [&SemaRef = this->SemaRef, IndexDecl, IVType,
Loc = InitVal->getExprLoc()]() {
return buildDeclRefExpr(SemaRef, IndexDecl, IVType, Loc, false);
};
// 2. Iteratively compute the max number of logical iterations Max(NI_1, NI_2,
// ..., NI_k)
//
// This loop accumulates the maximum value across multiple expressions,
// ensuring each step constructs a unique AST node for correctness. By using
// intermediate temporary variables and conditional operators, we maintain
// distinct nodes and avoid duplicating subtrees, For instance, max(a,b,c):
// omp.temp0 = max(a, b)
// omp.temp1 = max(omp.temp0, c)
// omp.fuse.max = max(omp.temp1, omp.temp0)
ExprResult MaxExpr;
// I is the range of loops in the sequence that we fuse.
for (unsigned I = FirstVal - 1, J = 0; I < LastVal; ++I, ++J) {
DeclRefExpr *NIRef = MakeVarDeclRef(NIVarDecls[J]);
QualType NITy = NIRef->getType();
if (MaxExpr.isUnset()) {
// Initialize MaxExpr with the first NI expression
MaxExpr = NIRef;
} else {
// Create a new acummulator variable t_i = MaxExpr
std::string TempName = (Twine(".omp.temp.") + Twine(J)).str();
VarDecl *TempDecl =
buildVarDecl(SemaRef, {}, NITy, TempName, nullptr, nullptr);
TempDecl->setInit(MaxExpr.get());
DeclRefExpr *TempRef =
buildDeclRefExpr(SemaRef, TempDecl, NITy, SourceLocation(), false);
DeclRefExpr *TempRef2 =
buildDeclRefExpr(SemaRef, TempDecl, NITy, SourceLocation(), false);
// Add a DeclStmt to PreInits to ensure the variable is declared.
StmtResult TempStmt = new (Context)
DeclStmt(DeclGroupRef(TempDecl), SourceLocation(), SourceLocation());
if (!TempStmt.isUsable())
return StmtError();
PreInits.push_back(TempStmt.get());
// Build MaxExpr <-(MaxExpr > NIRef ? MaxExpr : NIRef)
ExprResult Comparison =
SemaRef.BuildBinOp(nullptr, SourceLocation(), BO_GT, TempRef, NIRef);
// Handle any errors in Comparison creation
if (!Comparison.isUsable())
return StmtError();
DeclRefExpr *NIRef2 = MakeVarDeclRef(NIVarDecls[J]);
// Update MaxExpr using a conditional expression to hold the max value
MaxExpr = new (Context) ConditionalOperator(
Comparison.get(), SourceLocation(), TempRef2, SourceLocation(),
NIRef2->getExprStmt(), NITy, VK_LValue, OK_Ordinary);
if (!MaxExpr.isUsable())
return StmtError();
}
}
if (!MaxExpr.isUsable())
return StmtError();
// 3. Declare the max variable
const std::string MaxName = Twine(".omp.fuse.max").str();
VarDecl *MaxDecl =
buildVarDecl(SemaRef, {}, IVType, MaxName, nullptr, nullptr);
MaxDecl->setInit(MaxExpr.get());
DeclRefExpr *MaxRef = buildDeclRefExpr(SemaRef, MaxDecl, IVType, {}, false);
StmtResult MaxStmt = new (Context)
DeclStmt(DeclGroupRef(MaxDecl), SourceLocation(), SourceLocation());
if (MaxStmt.isInvalid())
return StmtError();
PreInits.push_back(MaxStmt.get());
// 4. Create condition Expr: index < n_max
ExprResult CondExpr = SemaRef.BuildBinOp(CurScope, SourceLocation(), BO_LT,
MakeIVRef(), MaxRef);
if (!CondExpr.isUsable())
return StmtError();
// 5. Increment Expr: ++index
ExprResult IncrExpr =
SemaRef.BuildUnaryOp(CurScope, SourceLocation(), UO_PreInc, MakeIVRef());
if (!IncrExpr.isUsable())
return StmtError();
// 6. Build the Fused Loop Body
// The final fused loop iterates over the maximum logical range. Inside the
// loop, each original loop's index is calculated dynamically, and its body
// is executed conditionally.
//
// Each sub-loop's body is guarded by a conditional statement to ensure
// it executes only within its logical iteration range:
//
// if (fuse.index < ni_k){
// iv_k = lb_k + st_k * fuse.index;
// original.index = iv_k
// body(k);
// }
CompoundStmt *FusedBody = nullptr;
SmallVector<Stmt *, 4> FusedBodyStmts;
for (unsigned I = FirstVal - 1, J = 0; I < LastVal; ++I, ++J) {
// Assingment of the original sub-loop index to compute the logical index
// IV_k = LB_k + omp.fuse.index * ST_k
ExprResult IdxExpr =
SemaRef.BuildBinOp(CurScope, SourceLocation(), BO_Mul,
MakeVarDeclRef(STVarDecls[J]), MakeIVRef());
if (!IdxExpr.isUsable())
return StmtError();
IdxExpr = SemaRef.BuildBinOp(CurScope, SourceLocation(), BO_Add,
MakeVarDeclRef(LBVarDecls[J]), IdxExpr.get());
if (!IdxExpr.isUsable())
return StmtError();
IdxExpr = SemaRef.BuildBinOp(CurScope, SourceLocation(), BO_Assign,
MakeVarDeclRef(IVVarDecls[J]), IdxExpr.get());
if (!IdxExpr.isUsable())
return StmtError();
// Update the original i_k = IV_k
SmallVector<Stmt *, 4> BodyStmts;
BodyStmts.push_back(IdxExpr.get());
llvm::append_range(BodyStmts, SeqAnalysis.Loops[I].HelperExprs.Updates);
// If the loop is a CXXForRangeStmt then the iterator variable is needed
if (auto *SourceCXXFor =
dyn_cast<CXXForRangeStmt>(SeqAnalysis.Loops[I].TheForStmt))
BodyStmts.push_back(SourceCXXFor->getLoopVarStmt());
Stmt *Body =
(isa<ForStmt>(SeqAnalysis.Loops[I].TheForStmt))
? cast<ForStmt>(SeqAnalysis.Loops[I].TheForStmt)->getBody()
: cast<CXXForRangeStmt>(SeqAnalysis.Loops[I].TheForStmt)->getBody();
BodyStmts.push_back(Body);
CompoundStmt *CombinedBody =
CompoundStmt::Create(Context, BodyStmts, FPOptionsOverride(),
SourceLocation(), SourceLocation());
ExprResult Condition =
SemaRef.BuildBinOp(CurScope, SourceLocation(), BO_LT, MakeIVRef(),
MakeVarDeclRef(NIVarDecls[J]));
if (!Condition.isUsable())
return StmtError();
IfStmt *IfStatement = IfStmt::Create(
Context, SourceLocation(), IfStatementKind::Ordinary, nullptr, nullptr,
Condition.get(), SourceLocation(), SourceLocation(), CombinedBody,
SourceLocation(), nullptr);
FusedBodyStmts.push_back(IfStatement);
}
FusedBody = CompoundStmt::Create(Context, FusedBodyStmts, FPOptionsOverride(),
SourceLocation(), SourceLocation());
// 7. Construct the final fused loop
ForStmt *FusedForStmt = new (Context)
ForStmt(Context, InitStmt.get(), CondExpr.get(), nullptr, IncrExpr.get(),
FusedBody, InitStmt.get()->getBeginLoc(), SourceLocation(),
IncrExpr.get()->getEndLoc());
// In the case of looprange, the result of fuse won't simply
// be a single loop (ForStmt), but rather a loop sequence
// (CompoundStmt) of 3 parts: the pre-fusion loops, the fused loop
// and the post-fusion loops, preserving its original order.
//
// Note: If looprange clause produces a single fused loop nest then
// this compound statement wrapper is unnecessary (Therefore this
// treatment is skipped)
Stmt *FusionStmt = FusedForStmt;
if (LRC && CountVal != SeqAnalysis.LoopSeqSize) {
SmallVector<Stmt *, 4> FinalLoops;
// Reset the transform index
TransformIndex = 0;
// Collect all non-fused loops before and after the fused region.
// Pre-fusion and post-fusion loops are inserted in order exploiting their
// symmetry, along with their corresponding transformation pre-inits if
// needed. The fused loop is added between the two regions.
for (unsigned I : llvm::seq<unsigned>(SeqAnalysis.LoopSeqSize)) {
if (I >= FirstVal - 1 && I < FirstVal + CountVal - 1) {
// Update the Transformation counter to skip already treated
// loop transformations
if (!SeqAnalysis.Loops[I].isLoopTransformation())
++TransformIndex;
continue;
}
// No need to handle:
// Regular loops: they are kept intact as-is.
// Loop-sequence-generating transformations: already handled earlier.
// Only TransformSingleLoop requires inserting pre-inits here
if (SeqAnalysis.Loops[I].isRegularLoop()) {
const auto &TransformPreInit =
SeqAnalysis.Loops[TransformIndex++].TransformsPreInits;
if (!TransformPreInit.empty())
llvm::append_range(PreInits, TransformPreInit);
}
FinalLoops.push_back(SeqAnalysis.Loops[I].TheForStmt);
}
FinalLoops.insert(FinalLoops.begin() + (FirstVal - 1), FusedForStmt);
FusionStmt = CompoundStmt::Create(Context, FinalLoops, FPOptionsOverride(),
SourceLocation(), SourceLocation());
}
return OMPFuseDirective::Create(Context, StartLoc, EndLoc, Clauses,
NumGeneratedTopLevelLoops, AStmt, FusionStmt,
buildPreInits(Context, PreInits));
}
OMPClause *SemaOpenMP::ActOnOpenMPSingleExprClause(OpenMPClauseKind Kind,
Expr *Expr,
SourceLocation StartLoc,
@ -16887,6 +17636,31 @@ OMPClause *SemaOpenMP::ActOnOpenMPPartialClause(Expr *FactorExpr,
FactorExpr);
}
OMPClause *SemaOpenMP::ActOnOpenMPLoopRangeClause(
Expr *First, Expr *Count, SourceLocation StartLoc, SourceLocation LParenLoc,
SourceLocation FirstLoc, SourceLocation CountLoc, SourceLocation EndLoc) {
// OpenMP [6.0, Restrictions]
// First and Count must be integer expressions with positive value
ExprResult FirstVal =
VerifyPositiveIntegerConstantInClause(First, OMPC_looprange);
if (FirstVal.isInvalid())
First = nullptr;
ExprResult CountVal =
VerifyPositiveIntegerConstantInClause(Count, OMPC_looprange);
if (CountVal.isInvalid())
Count = nullptr;
// OpenMP [6.0, Restrictions]
// first + count - 1 must not evaluate to a value greater than the
// loop sequence length of the associated canonical loop sequence.
// This check must be performed afterwards due to the delayed
// parsing and computation of the associated loop sequence
return OMPLoopRangeClause::Create(getASTContext(), StartLoc, LParenLoc,
FirstLoc, CountLoc, EndLoc, First, Count);
}
OMPClause *SemaOpenMP::ActOnOpenMPAlignClause(Expr *A, SourceLocation StartLoc,
SourceLocation LParenLoc,
SourceLocation EndLoc) {

View File

@ -1783,6 +1783,14 @@ public:
LParenLoc, EndLoc);
}
OMPClause *
RebuildOMPLoopRangeClause(Expr *First, Expr *Count, SourceLocation StartLoc,
SourceLocation LParenLoc, SourceLocation FirstLoc,
SourceLocation CountLoc, SourceLocation EndLoc) {
return getSema().OpenMP().ActOnOpenMPLoopRangeClause(
First, Count, StartLoc, LParenLoc, FirstLoc, CountLoc, EndLoc);
}
/// Build a new OpenMP 'allocator' clause.
///
/// By default, performs semantic analysis to build the new OpenMP clause.
@ -9607,6 +9615,17 @@ StmtResult TreeTransform<Derived>::TransformOMPInterchangeDirective(
return Res;
}
template <typename Derived>
StmtResult
TreeTransform<Derived>::TransformOMPFuseDirective(OMPFuseDirective *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>::TransformOMPForDirective(OMPForDirective *D) {
@ -10500,6 +10519,31 @@ TreeTransform<Derived>::TransformOMPPartialClause(OMPPartialClause *C) {
C->getEndLoc());
}
template <typename Derived>
OMPClause *
TreeTransform<Derived>::TransformOMPLoopRangeClause(OMPLoopRangeClause *C) {
ExprResult F = getDerived().TransformExpr(C->getFirst());
if (F.isInvalid())
return nullptr;
ExprResult Cn = getDerived().TransformExpr(C->getCount());
if (Cn.isInvalid())
return nullptr;
Expr *First = F.get();
Expr *Count = Cn.get();
bool Changed = (First != C->getFirst()) || (Count != C->getCount());
// If no changes and AlwaysRebuild() is false, return the original clause
if (!Changed && !getDerived().AlwaysRebuild())
return C;
return RebuildOMPLoopRangeClause(First, Count, C->getBeginLoc(),
C->getLParenLoc(), C->getFirstLoc(),
C->getCountLoc(), C->getEndLoc());
}
template <typename Derived>
OMPClause *
TreeTransform<Derived>::TransformOMPCollapseClause(OMPCollapseClause *C) {

View File

@ -11215,6 +11215,9 @@ OMPClause *OMPClauseReader::readClause() {
case llvm::omp::OMPC_partial:
C = OMPPartialClause::CreateEmpty(Context);
break;
case llvm::omp::OMPC_looprange:
C = OMPLoopRangeClause::CreateEmpty(Context);
break;
case llvm::omp::OMPC_allocator:
C = new (Context) OMPAllocatorClause();
break;
@ -11618,6 +11621,14 @@ void OMPClauseReader::VisitOMPPartialClause(OMPPartialClause *C) {
C->setLParenLoc(Record.readSourceLocation());
}
void OMPClauseReader::VisitOMPLoopRangeClause(OMPLoopRangeClause *C) {
C->setFirst(Record.readSubExpr());
C->setCount(Record.readSubExpr());
C->setLParenLoc(Record.readSourceLocation());
C->setFirstLoc(Record.readSourceLocation());
C->setCountLoc(Record.readSourceLocation());
}
void OMPClauseReader::VisitOMPAllocatorClause(OMPAllocatorClause *C) {
C->setAllocator(Record.readExpr());
C->setLParenLoc(Record.readSourceLocation());

View File

@ -2469,10 +2469,21 @@ void ASTStmtReader::VisitOMPReverseDirective(OMPReverseDirective *D) {
VisitOMPCanonicalLoopNestTransformationDirective(D);
}
void ASTStmtReader::VisitOMPCanonicalLoopSequenceTransformationDirective(
OMPCanonicalLoopSequenceTransformationDirective *D) {
VisitStmt(D);
VisitOMPExecutableDirective(D);
D->setNumGeneratedTopLevelLoops(Record.readUInt32());
}
void ASTStmtReader::VisitOMPInterchangeDirective(OMPInterchangeDirective *D) {
VisitOMPCanonicalLoopNestTransformationDirective(D);
}
void ASTStmtReader::VisitOMPFuseDirective(OMPFuseDirective *D) {
VisitOMPCanonicalLoopSequenceTransformationDirective(D);
}
void ASTStmtReader::VisitOMPForDirective(OMPForDirective *D) {
VisitOMPLoopDirective(D);
D->setHasCancel(Record.readBool());
@ -3615,6 +3626,12 @@ Stmt *ASTReader::ReadStmtFromStream(ModuleFile &F) {
break;
}
case STMT_OMP_FUSE_DIRECTIVE: {
unsigned NumClauses = Record[ASTStmtReader::NumStmtFields];
S = OMPFuseDirective::CreateEmpty(Context, NumClauses);
break;
}
case STMT_OMP_INTERCHANGE_DIRECTIVE: {
unsigned NumLoops = Record[ASTStmtReader::NumStmtFields];
unsigned NumClauses = Record[ASTStmtReader::NumStmtFields + 1];

View File

@ -7882,6 +7882,14 @@ void OMPClauseWriter::VisitOMPPartialClause(OMPPartialClause *C) {
Record.AddSourceLocation(C->getLParenLoc());
}
void OMPClauseWriter::VisitOMPLoopRangeClause(OMPLoopRangeClause *C) {
Record.AddStmt(C->getFirst());
Record.AddStmt(C->getCount());
Record.AddSourceLocation(C->getLParenLoc());
Record.AddSourceLocation(C->getFirstLoc());
Record.AddSourceLocation(C->getCountLoc());
}
void OMPClauseWriter::VisitOMPAllocatorClause(OMPAllocatorClause *C) {
Record.AddStmt(C->getAllocator());
Record.AddSourceLocation(C->getLParenLoc());

View File

@ -2487,6 +2487,18 @@ void ASTStmtWriter::VisitOMPInterchangeDirective(OMPInterchangeDirective *D) {
Code = serialization::STMT_OMP_INTERCHANGE_DIRECTIVE;
}
void ASTStmtWriter::VisitOMPCanonicalLoopSequenceTransformationDirective(
OMPCanonicalLoopSequenceTransformationDirective *D) {
VisitStmt(D);
VisitOMPExecutableDirective(D);
Record.writeUInt32(D->getNumGeneratedTopLevelLoops());
}
void ASTStmtWriter::VisitOMPFuseDirective(OMPFuseDirective *D) {
VisitOMPCanonicalLoopSequenceTransformationDirective(D);
Code = serialization::STMT_OMP_FUSE_DIRECTIVE;
}
void ASTStmtWriter::VisitOMPForDirective(OMPForDirective *D) {
VisitOMPLoopDirective(D);
Record.writeBool(D->hasCancel());

View File

@ -1814,6 +1814,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
case Stmt::OMPStripeDirectiveClass:
case Stmt::OMPTileDirectiveClass:
case Stmt::OMPInterchangeDirectiveClass:
case Stmt::OMPFuseDirectiveClass:
case Stmt::OMPInteropDirectiveClass:
case Stmt::OMPDispatchDirectiveClass:
case Stmt::OMPMaskedDirectiveClass:

View File

@ -0,0 +1,397 @@
// Check no warnings/errors
// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -std=c++20 -fopenmp-version=60 -fsyntax-only -verify %s
// expected-no-diagnostics
// Check AST and unparsing
// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -std=c++20 -fopenmp-version=60 -ast-dump %s | FileCheck %s --check-prefix=DUMP
// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -std=c++20 -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 -std=c++20 -fopenmp-version=60 -emit-pch -o %t %s
// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -std=c++20 -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 -std=c++20 -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 fuse
// DUMP: OMPFuseDirective
#pragma omp fuse
// PRINT: {
// DUMP: CompoundStmt
{
// PRINT: for (int i = 0; i < 10; i += 2)
// DUMP: ForStmt
for (int i = 0; i < 10; i += 2)
// PRINT: body(i)
// DUMP: CallExpr
body(i);
// PRINT: for (int j = 10; j > 0; --j)
// DUMP: ForStmt
for (int j = 10; j > 0; --j)
// PRINT: body(j)
// DUMP: CallExpr
body(j);
// PRINT: for (int k = 0; k <= 10; ++k)
// DUMP: ForStmt
for (int k = 0; k <= 10; ++k)
// PRINT: body(k)
// DUMP: CallExpr
body(k);
}
}
// PRINT-LABEL: void foo2(
// DUMP-LABEL: FunctionDecl {{.*}} foo2
void foo2() {
// PRINT: #pragma omp unroll partial(4)
// DUMP: OMPUnrollDirective
// DUMP-NEXT: OMPPartialClause
// DUMP-NEXT: ConstantExpr
// DUMP-NEXT: value: Int 4
// DUMP-NEXT: IntegerLiteral {{.*}} 4
#pragma omp unroll partial(4)
// PRINT: #pragma omp fuse
// DUMP-NEXT: OMPFuseDirective
#pragma omp fuse
// PRINT: {
// DUMP: CompoundStmt
{
// PRINT: for (int i = 0; i < 10; i += 2)
// DUMP: ForStmt
for (int i = 0; i < 10; i += 2)
// PRINT: body(i)
// DUMP: CallExpr
body(i);
// PRINT: for (int j = 10; j > 0; --j)
// DUMP: ForStmt
for (int j = 10; j > 0; --j)
// PRINT: body(j)
// DUMP: CallExpr
body(j);
}
}
//PRINT-LABEL: void foo3(
//DUMP-LABEL: FunctionTemplateDecl {{.*}} foo3
template<int Factor1, int Factor2>
void foo3() {
// PRINT: #pragma omp fuse
// DUMP: OMPFuseDirective
#pragma omp fuse
// PRINT: {
// DUMP: CompoundStmt
{
// PRINT: #pragma omp unroll partial(Factor1)
// DUMP: OMPUnrollDirective
#pragma omp unroll partial(Factor1)
// PRINT: for (int i = 0; i < 12; i += 1)
// DUMP: ForStmt
for (int i = 0; i < 12; i += 1)
// PRINT: body(i)
// DUMP: CallExpr
body(i);
// PRINT: #pragma omp unroll partial(Factor2)
// DUMP: OMPUnrollDirective
#pragma omp unroll partial(Factor2)
// PRINT: for (int k = 0; k <= 10; ++k)
// DUMP: ForStmt
for (int k = 0; k <= 10; ++k)
// PRINT: body(k)
// DUMP: CallExpr
body(k);
}
}
// Also test instantiating the template.
void tfoo3() {
foo3<4,2>();
}
//PRINT-LABEL: void foo4(
//DUMP-LABEL: FunctionTemplateDecl {{.*}} foo4
template<typename T, T Step>
void foo4(int start, int end) {
// PRINT: #pragma omp fuse
// DUMP: OMPFuseDirective
#pragma omp fuse
// PRINT: {
// DUMP: CompoundStmt
{
// PRINT: for (T i = start; i < end; i += Step)
// DUMP: ForStmt
for (T i = start; i < end; i += Step)
// PRINT: body(i)
// DUMP: CallExpr
body(i);
// PRINT: for (T j = end; j > start; j -= Step)
// DUMP: ForStmt
for (T j = end; j > start; j -= Step) {
// PRINT: body(j)
// DUMP: CallExpr
body(j);
}
}
}
// Also test instantiating the template.
void tfoo4() {
foo4<int, 4>(0, 64);
}
// PRINT-LABEL: void foo5(
// DUMP-LABEL: FunctionDecl {{.*}} foo5
void foo5() {
double arr[128], arr2[128];
// PRINT: #pragma omp fuse
// DUMP: OMPFuseDirective
#pragma omp fuse
// PRINT: {
// DUMP: CompoundStmt
{
// PRINT-NEXT: for (auto &&a : arr)
// DUMP-NEXT: CXXForRangeStmt
for (auto &&a: arr)
// PRINT: body(a)
// DUMP: CallExpr
body(a);
// PRINT: for (double v = 42; auto &&b : arr)
// DUMP: CXXForRangeStmt
for (double v = 42; auto &&b: arr)
// PRINT: body(b, v);
// DUMP: CallExpr
body(b, v);
// PRINT: for (auto &&c : arr2)
// DUMP: CXXForRangeStmt
for (auto &&c: arr2)
// PRINT: body(c)
// DUMP: CallExpr
body(c);
}
}
// PRINT-LABEL: void foo6(
// DUMP-LABEL: FunctionDecl {{.*}} foo6
void foo6() {
// PRINT: #pragma omp fuse
// DUMP: OMPFuseDirective
#pragma omp fuse
// PRINT: {
// DUMP: CompoundStmt
{
// PRINT: #pragma omp fuse
// DUMP: OMPFuseDirective
#pragma omp fuse
// PRINT: {
// DUMP: CompoundStmt
{
// PRINT: for (int i = 0; i <= 10; ++i)
// DUMP: ForStmt
for (int i = 0; i <= 10; ++i)
body(i);
// PRINT: for (int j = 0; j < 100; ++j)
// DUMP: ForStmt
for(int j = 0; j < 100; ++j)
body(j);
}
// PRINT: #pragma omp unroll partial(4)
// DUMP: OMPUnrollDirective
#pragma omp unroll partial(4)
// PRINT: for (int k = 0; k < 250; ++k)
// DUMP: ForStmt
for (int k = 0; k < 250; ++k)
body(k);
}
}
// PRINT-LABEL: void foo7(
// DUMP-LABEL: FunctionDecl {{.*}} foo7
void foo7() {
// PRINT: #pragma omp fuse
// DUMP: OMPFuseDirective
#pragma omp fuse
// PRINT: {
// DUMP: CompoundStmt
{
// PRINT: {
// DUMP: CompoundStmt
{
// PRINT: {
// DUMP: CompoundStmt
{
// PRINT: for (int i = 0; i < 10; i += 2)
// DUMP: ForStmt
for (int i = 0; i < 10; i += 2)
// PRINT: body(i)
// DUMP: CallExpr
body(i);
// PRINT: for (int j = 10; j > 0; --j)
// DUMP: ForStmt
for (int j = 10; j > 0; --j)
// PRINT: body(j)
// DUMP: CallExpr
body(j);
}
}
// PRINT: {
// DUMP: CompoundStmt
{
// PRINT: {
// DUMP: CompoundStmt
{
// PRINT: {
// DUMP: CompoundStmt
{
// PRINT: for (int k = 0; k <= 10; ++k)
// DUMP: ForStmt
for (int k = 0; k <= 10; ++k)
// PRINT: body(k)
// DUMP: CallExpr
body(k);
}
}
}
}
}
// PRINT-LABEL: void foo8(
// DUMP-LABEL: FunctionDecl {{.*}} foo8
void foo8() {
// PRINT: #pragma omp fuse looprange(2,2)
// DUMP: OMPFuseDirective
// DUMP: OMPLooprangeClause
#pragma omp fuse looprange(2,2)
// PRINT: {
// DUMP: CompoundStmt
{
// PRINT: for (int i = 0; i < 10; i += 2)
// DUMP: ForStmt
for (int i = 0; i < 10; i += 2)
// PRINT: body(i)
// DUMP: CallExpr
body(i);
// PRINT: for (int j = 10; j > 0; --j)
// DUMP: ForStmt
for (int j = 10; j > 0; --j)
// PRINT: body(j)
// DUMP: CallExpr
body(j);
// PRINT: for (int k = 0; k <= 10; ++k)
// DUMP: ForStmt
for (int k = 0; k <= 10; ++k)
// PRINT: body(k)
// DUMP: CallExpr
body(k);
}
}
//PRINT-LABEL: void foo9(
//DUMP-LABEL: FunctionTemplateDecl {{.*}} foo9
//DUMP-LABEL: NonTypeTemplateParmDecl {{.*}} F
//DUMP-LABEL: NonTypeTemplateParmDecl {{.*}} C
template<int F, int C>
void foo9() {
// PRINT: #pragma omp fuse looprange(F,C)
// DUMP: OMPFuseDirective
// DUMP: OMPLooprangeClause
#pragma omp fuse looprange(F,C)
// PRINT: {
// DUMP: CompoundStmt
{
// PRINT: for (int i = 0; i < 10; i += 2)
// DUMP: ForStmt
for (int i = 0; i < 10; i += 2)
// PRINT: body(i)
// DUMP: CallExpr
body(i);
// PRINT: for (int j = 10; j > 0; --j)
// DUMP: ForStmt
for (int j = 10; j > 0; --j)
// PRINT: body(j)
// DUMP: CallExpr
body(j);
}
}
// Also test instantiating the template.
void tfoo9() {
foo9<1, 2>();
}
// PRINT-LABEL: void foo10(
// DUMP-LABEL: FunctionDecl {{.*}} foo10
void foo10() {
// PRINT: #pragma omp fuse looprange(2,2)
// DUMP: OMPFuseDirective
// DUMP: OMPLooprangeClause
#pragma omp fuse looprange(2,2)
// PRINT: {
// DUMP: CompoundStmt
{
// PRINT: for (int i = 0; i < 10; i += 2)
// DUMP: ForStmt
for (int i = 0; i < 10; i += 2)
// PRINT: body(i)
// DUMP: CallExpr
body(i);
// PRINT: for (int ii = 0; ii < 10; ii += 2)
// DUMP: ForStmt
for (int ii = 0; ii < 10; ii += 2)
// PRINT: body(ii)
// DUMP: CallExpr
body(ii);
// PRINT: #pragma omp fuse looprange(2,2)
// DUMP: OMPFuseDirective
// DUMP: OMPLooprangeClause
#pragma omp fuse looprange(2,2)
{
// PRINT: for (int j = 10; j > 0; --j)
// DUMP: ForStmt
for (int j = 10; j > 0; --j)
// PRINT: body(j)
// DUMP: CallExpr
body(j);
// PRINT: for (int jj = 10; jj > 0; --jj)
// DUMP: ForStmt
for (int jj = 10; jj > 0; --jj)
// PRINT: body(jj)
// DUMP: CallExpr
body(jj);
// PRINT: for (int k = 0; k <= 10; ++k)
// DUMP: ForStmt
for (int k = 0; k <= 10; ++k)
// PRINT: body(k)
// DUMP: CallExpr
body(k);
// PRINT: for (int kk = 0; kk <= 10; ++kk)
// DUMP: ForStmt
for (int kk = 0; kk <= 10; ++kk)
// PRINT: body(kk)
// DUMP: CallExpr
body(kk);
}
}
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,209 @@
// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -std=c++20 -fopenmp -fopenmp-version=60 -fsyntax-only -Wuninitialized -verify %s
void func() {
// expected-error@+2 {{statement after '#pragma omp fuse' must be a loop sequence containing canonical loops or loop-generating constructs}}
#pragma omp fuse
;
// expected-error@+2 {{statement after '#pragma omp fuse' must be a for loop}}
#pragma omp fuse
{int bar = 0;}
// expected-error@+4 {{statement after '#pragma omp fuse' must be a for loop}}
#pragma omp fuse
{
for(int i = 0; i < 10; ++i);
int x = 2;
}
// expected-error@+2 {{statement after '#pragma omp fuse' must be a loop sequence containing canonical loops or loop-generating constructs}}
#pragma omp fuse
#pragma omp for
for (int i = 0; i < 7; ++i)
;
{
// expected-error@+2 {{expected statement}}
#pragma omp fuse
}
// expected-warning@+1 {{extra tokens at the end of '#pragma omp fuse' are ignored}}
#pragma omp fuse foo
{
for (int i = 0; i < 7; ++i)
;
for(int j = 0; j < 100; ++j);
}
// expected-error@+1 {{unexpected OpenMP clause 'final' in directive '#pragma omp fuse'}}
#pragma omp fuse final(0)
{
for (int i = 0; i < 7; ++i)
;
for(int j = 0; j < 100; ++j);
}
//expected-error@+3 {{increment clause of OpenMP for loop must perform simple addition or subtraction on loop variable 'i'}}
#pragma omp fuse
{
for(int i = 0; i < 10; i*=2) {
;
}
for(int j = 0; j < 100; ++j);
}
//expected-error@+2 {{loop sequence after '#pragma omp fuse' must contain at least 1 canonical loop or loop-generating construct}}
#pragma omp fuse
{}
//expected-error@+3 {{statement after '#pragma omp fuse' must be a for loop}}
#pragma omp fuse
{
#pragma omp unroll full
for(int i = 0; i < 10; ++i);
for(int j = 0; j < 10; ++j);
}
//expected-warning@+2 {{looprange clause selects a single loop, resulting in redundant fusion}}
#pragma omp fuse
{
for(int i = 0; i < 10; ++i);
}
//expected-warning@+1 {{looprange clause selects a single loop, resulting in redundant fusion}}
#pragma omp fuse looprange(1, 1)
{
for(int i = 0; i < 10; ++i);
for(int j = 0; j < 100; ++j);
}
//expected-error@+1 {{argument to 'looprange' clause must be a strictly positive integer value}}
#pragma omp fuse looprange(1, -1)
{
for(int i = 0; i < 10; ++i);
for(int j = 0; j < 100; ++j);
}
//expected-error@+1 {{argument to 'looprange' clause must be a strictly positive integer value}}
#pragma omp fuse looprange(1, 0)
{
for(int i = 0; i < 10; ++i);
for(int j = 0; j < 100; ++j);
}
const int x = 1;
constexpr int y = 4;
//expected-error@+1 {{looprange clause selects loops from 1 to 4 but this exceeds the number of loops (3) in the loop sequence}}
#pragma omp fuse looprange(x,y)
{
for(int i = 0; i < 10; ++i);
for(int j = 0; j < 100; ++j);
for(int k = 0; k < 50; ++k);
}
//expected-error@+1 {{looprange clause selects loops from 1 to 420 but this exceeds the number of loops (3) in the loop sequence}}
#pragma omp fuse looprange(1,420)
{
for(int i = 0; i < 10; ++i);
for(int j = 0; j < 100; ++j);
for(int k = 0; k < 50; ++k);
}
//expected-error@+1 {{looprange clause selects loops from 1 to 6 but this exceeds the number of loops (5) in the loop sequence}}
#pragma omp fuse looprange(1,6)
{
for(int i = 0; i < 10; ++i);
for(int j = 0; j < 100; ++j);
for(int k = 0; k < 50; ++k);
// This fusion results in 2 loops
#pragma omp fuse looprange(1,2)
{
for(int i = 0; i < 10; ++i);
for(int j = 0; j < 100; ++j);
for(int k = 0; k < 50; ++k);
}
}
//expected-error@+1 {{looprange clause selects loops from 2 to 4 but this exceeds the number of loops (3) in the loop sequence}}
#pragma omp fuse looprange(2,3)
{
#pragma omp unroll partial(2)
for(int i = 0; i < 10; ++i);
#pragma omp reverse
for(int j = 0; j < 10; ++j);
#pragma omp fuse
{
{
#pragma omp reverse
for(int j = 0; j < 10; ++j);
}
for(int k = 0; k < 50; ++k);
}
}
}
// In a template context, but expression itself not instantiation-dependent
template <typename T>
static void templated_func() {
//expected-warning@+1 {{looprange clause selects a single loop, resulting in redundant fusion}}
#pragma omp fuse looprange(2,1)
{
for(int i = 0; i < 10; ++i);
for(int j = 0; j < 100; ++j);
for(int k = 0; k < 50; ++k);
}
//expected-error@+1 {{looprange clause selects loops from 3 to 5 but this exceeds the number of loops (3) in the loop sequence}}
#pragma omp fuse looprange(3,3)
{
for(int i = 0; i < 10; ++i);
for(int j = 0; j < 100; ++j);
for(int k = 0; k < 50; ++k);
}
}
template <int V>
static void templated_func_value_dependent() {
//expected-warning@+1 {{looprange clause selects a single loop, resulting in redundant fusion}}
#pragma omp fuse looprange(V,1)
{
for(int i = 0; i < 10; ++i);
for(int j = 0; j < 100; ++j);
for(int k = 0; k < 50; ++k);
}
}
template <typename T>
static void templated_func_type_dependent() {
constexpr T s = 1;
//expected-error@+1 {{argument to 'looprange' clause must be a strictly positive integer value}}
#pragma omp fuse looprange(s,s-1)
{
for(int i = 0; i < 10; ++i);
for(int j = 0; j < 100; ++j);
for(int k = 0; k < 50; ++k);
}
}
void template_inst() {
// expected-note@+1 {{in instantiation of function template specialization 'templated_func<int>' requested here}}
templated_func<int>();
// expected-note@+1 {{in instantiation of function template specialization 'templated_func_value_dependent<1>' requested here}}
templated_func_value_dependent<1>();
// expected-note@+1 {{in instantiation of function template specialization 'templated_func_type_dependent<int>' requested here}}
templated_func_type_dependent<int>();
}

View File

@ -2148,6 +2148,9 @@ public:
void VisitOMPUnrollDirective(const OMPUnrollDirective *D);
void VisitOMPReverseDirective(const OMPReverseDirective *D);
void VisitOMPInterchangeDirective(const OMPInterchangeDirective *D);
void VisitOMPCanonicalLoopSequenceTransformationDirective(
const OMPCanonicalLoopSequenceTransformationDirective *D);
void VisitOMPFuseDirective(const OMPFuseDirective *D);
void VisitOMPForDirective(const OMPForDirective *D);
void VisitOMPForSimdDirective(const OMPForSimdDirective *D);
void VisitOMPSectionsDirective(const OMPSectionsDirective *D);
@ -2353,6 +2356,11 @@ void OMPClauseEnqueue::VisitOMPPartialClause(const OMPPartialClause *C) {
Visitor->AddStmt(C->getFactor());
}
void OMPClauseEnqueue::VisitOMPLoopRangeClause(const OMPLoopRangeClause *C) {
Visitor->AddStmt(C->getFirst());
Visitor->AddStmt(C->getCount());
}
void OMPClauseEnqueue::VisitOMPAllocatorClause(const OMPAllocatorClause *C) {
Visitor->AddStmt(C->getAllocator());
}
@ -3317,6 +3325,15 @@ void EnqueueVisitor::VisitOMPInterchangeDirective(
VisitOMPCanonicalLoopNestTransformationDirective(D);
}
void EnqueueVisitor::VisitOMPCanonicalLoopSequenceTransformationDirective(
const OMPCanonicalLoopSequenceTransformationDirective *D) {
VisitOMPExecutableDirective(D);
}
void EnqueueVisitor::VisitOMPFuseDirective(const OMPFuseDirective *D) {
VisitOMPCanonicalLoopSequenceTransformationDirective(D);
}
void EnqueueVisitor::VisitOMPForDirective(const OMPForDirective *D) {
VisitOMPLoopDirective(D);
}
@ -6275,6 +6292,8 @@ CXString clang_getCursorKindSpelling(enum CXCursorKind Kind) {
return cxstring::createRef("OMPReverseDirective");
case CXCursor_OMPInterchangeDirective:
return cxstring::createRef("OMPInterchangeDirective");
case CXCursor_OMPFuseDirective:
return cxstring::createRef("OMPFuseDirective");
case CXCursor_OMPForDirective:
return cxstring::createRef("OMPForDirective");
case CXCursor_OMPForSimdDirective:

View File

@ -687,6 +687,9 @@ CXCursor cxcursor::MakeCXCursor(const Stmt *S, const Decl *Parent,
case Stmt::OMPInterchangeDirectiveClass:
K = CXCursor_OMPInterchangeDirective;
break;
case Stmt::OMPFuseDirectiveClass:
K = CXCursor_OMPFuseDirective;
break;
case Stmt::OMPForDirectiveClass:
K = CXCursor_OMPForDirective;
break;

View File

@ -243,6 +243,7 @@ using Initializer = tomp::clause::InitializerT<TypeTy, IdTy, ExprTy>;
using InReduction = tomp::clause::InReductionT<TypeTy, IdTy, ExprTy>;
using IsDevicePtr = tomp::clause::IsDevicePtrT<TypeTy, IdTy, ExprTy>;
using Lastprivate = tomp::clause::LastprivateT<TypeTy, IdTy, ExprTy>;
using LoopRange = tomp::clause::LoopRangeT<TypeTy, IdTy, ExprTy>;
using Linear = tomp::clause::LinearT<TypeTy, IdTy, ExprTy>;
using Link = tomp::clause::LinkT<TypeTy, IdTy, ExprTy>;
using Map = tomp::clause::MapT<TypeTy, IdTy, ExprTy>;

View File

@ -613,6 +613,7 @@ public:
NODE_ENUM(OmpLinearModifier, Value)
NODE(parser, OmpLocator)
NODE(parser, OmpLocatorList)
NODE(parser, OmpLoopRangeClause)
NODE(parser, OmpMapClause)
NODE(OmpMapClause, Modifier)
NODE(parser, OmpMapper)

View File

@ -4546,6 +4546,15 @@ struct OmpLinearClause {
std::tuple<OmpObjectList, MODIFIERS(), /*PostModified=*/bool> t;
};
// Ref: [6.0:207-208]
//
// loop-range-clause ->
// LOOPRANGE(first, count) // since 6.0
struct OmpLoopRangeClause {
TUPLE_CLASS_BOILERPLATE(OmpLoopRangeClause);
std::tuple<ScalarIntConstantExpr, ScalarIntConstantExpr> t;
};
// Ref: [4.5:216-219], [5.0:315-324], [5.1:347-355], [5.2:150-158]
//
// map-clause ->

View File

@ -1036,6 +1036,11 @@ Link make(const parser::OmpClause::Link &inp,
return Link{/*List=*/makeObjects(inp.v, semaCtx)};
}
LoopRange make(const parser::OmpClause::Looprange &inp,
semantics::SemanticsContext &semaCtx) {
llvm_unreachable("Unimplemented: looprange");
}
Map make(const parser::OmpClause::Map &inp,
semantics::SemanticsContext &semaCtx) {
// inp.v -> parser::OmpMapClause

View File

@ -1023,6 +1023,9 @@ TYPE_PARSER(
maybe(":"_tok >> nonemptyList(Parser<OmpLinearClause::Modifier>{})),
/*PostModified=*/pure(true)))
TYPE_PARSER(construct<OmpLoopRangeClause>(
scalarIntConstantExpr, "," >> scalarIntConstantExpr))
// OpenMPv5.2 12.5.2 detach-clause -> DETACH (event-handle)
TYPE_PARSER(construct<OmpDetachClause>(Parser<OmpObject>{}))
@ -1207,6 +1210,8 @@ TYPE_PARSER( //
parenthesized(Parser<OmpLinearClause>{}))) ||
"LINK" >> construct<OmpClause>(construct<OmpClause::Link>(
parenthesized(Parser<OmpObjectList>{}))) ||
"LOOPRANGE" >> construct<OmpClause>(construct<OmpClause::Looprange>(
parenthesized(Parser<OmpLoopRangeClause>{}))) ||
"MAP" >> construct<OmpClause>(construct<OmpClause::Map>(
parenthesized(Parser<OmpMapClause>{}))) ||
"MATCH" >> construct<OmpClause>(construct<OmpClause::Match>(

View File

@ -2345,6 +2345,13 @@ public:
}
}
}
void Unparse(const OmpLoopRangeClause &x) {
Word("LOOPRANGE(");
Walk(std::get<0>(x.t));
Put(", ");
Walk(std::get<1>(x.t));
Put(")");
}
void Unparse(const OmpReductionClause &x) {
using Modifier = OmpReductionClause::Modifier;
Walk(std::get<std::optional<std::list<Modifier>>>(x.t), ": ");

View File

@ -3106,6 +3106,12 @@ CHECK_REQ_CONSTANT_SCALAR_INT_CLAUSE(Collapse, OMPC_collapse)
CHECK_REQ_CONSTANT_SCALAR_INT_CLAUSE(Safelen, OMPC_safelen)
CHECK_REQ_CONSTANT_SCALAR_INT_CLAUSE(Simdlen, OMPC_simdlen)
void OmpStructureChecker::Enter(const parser::OmpClause::Looprange &x) {
context_.Say(GetContext().clauseSource,
"LOOPRANGE clause is not implemented yet"_err_en_US,
ContextDirectiveAsFortran());
}
// Restrictions specific to each clause are implemented apart from the
// generalized restrictions.

View File

@ -1268,6 +1268,15 @@ struct WriteT {
using EmptyTrait = std::true_type;
};
// V6: [6.4.7] Looprange clause
template <typename T, typename I, typename E> struct LoopRangeT {
using Begin = E;
using End = E;
using TupleTrait = std::true_type;
std::tuple<Begin, End> t;
};
// ---
template <typename T, typename I, typename E>
@ -1300,8 +1309,8 @@ using TupleClausesT =
DoacrossT<T, I, E>, DynGroupprivateT<T, I, E>, FromT<T, I, E>,
GrainsizeT<T, I, E>, IfT<T, I, E>, InitT<T, I, E>,
InReductionT<T, I, E>, LastprivateT<T, I, E>, LinearT<T, I, E>,
MapT<T, I, E>, NumTasksT<T, I, E>, OrderT<T, I, E>,
ReductionT<T, I, E>, ScheduleT<T, I, E>,
LoopRangeT<T, I, E>, MapT<T, I, E>, NumTasksT<T, I, E>,
OrderT<T, I, E>, ReductionT<T, I, E>, ScheduleT<T, I, E>,
TaskReductionT<T, I, E>, ToT<T, I, E>>;
template <typename T, typename I, typename E>

View File

@ -284,6 +284,10 @@ def OMPC_Linear : Clause<[Spelling<"linear">]> {
def OMPC_Link : Clause<[Spelling<"link">]> {
let flangClass = "OmpObjectList";
}
def OMPC_LoopRange : Clause<[Spelling<"looprange">]> {
let clangClass = "OMPLoopRangeClause";
let flangClass = "OmpLoopRangeClause";
}
def OMPC_Map : Clause<[Spelling<"map">]> {
let clangClass = "OMPMapClause";
let flangClass = "OmpMapClause";
@ -902,6 +906,11 @@ def OMP_Groupprivate : Directive<[Spelling<"groupprivate">]> {
let category = CA_Declarative;
let languages = [L_C, L_Fortran];
}
def OMP_Fuse : Directive<[Spelling<"fuse">]> {
let allowedOnceClauses = [VersionedClause<OMPC_LoopRange, 60>];
let association = AS_Block;
let category = CA_Executable;
}
def OMP_Interchange : Directive<[Spelling<"interchange">]> {
let allowedOnceClauses = [
VersionedClause<OMPC_Permutation>,

View File

@ -0,0 +1,191 @@
// RUN: %libomp-cxx20-compile-and-run | FileCheck %s --match-full-lines
#ifndef HEADER
#define HEADER
#include <cstdlib>
#include <cstdarg>
#include <cstdio>
#include <vector>
struct Reporter {
const char *name;
Reporter(const char *name) : name(name) { print("ctor"); }
Reporter() : name("<anon>") { print("ctor"); }
Reporter(const Reporter &that) : name(that.name) { print("copy ctor"); }
Reporter(Reporter &&that) : name(that.name) { print("move ctor"); }
~Reporter() { print("dtor"); }
const Reporter &operator=(const Reporter &that) {
print("copy assign");
this->name = that.name;
return *this;
}
const Reporter &operator=(Reporter &&that) {
print("move assign");
this->name = that.name;
return *this;
}
struct Iterator {
const Reporter *owner;
int pos;
Iterator(const Reporter *owner, int pos) : owner(owner), pos(pos) {}
Iterator(const Iterator &that) : owner(that.owner), pos(that.pos) {
owner->print("iterator copy ctor");
}
Iterator(Iterator &&that) : owner(that.owner), pos(that.pos) {
owner->print("iterator move ctor");
}
~Iterator() { owner->print("iterator dtor"); }
const Iterator &operator=(const Iterator &that) {
owner->print("iterator copy assign");
this->owner = that.owner;
this->pos = that.pos;
return *this;
}
const Iterator &operator=(Iterator &&that) {
owner->print("iterator move assign");
this->owner = that.owner;
this->pos = that.pos;
return *this;
}
bool operator==(const Iterator &that) const {
owner->print("iterator %d == %d", 2 - this->pos, 2 - that.pos);
return this->pos == that.pos;
}
Iterator &operator++() {
owner->print("iterator prefix ++");
pos -= 1;
return *this;
}
Iterator operator++(int) {
owner->print("iterator postfix ++");
auto result = *this;
pos -= 1;
return result;
}
int operator*() const {
int result = 2 - pos;
owner->print("iterator deref: %i", result);
return result;
}
size_t operator-(const Iterator &that) const {
int result = (2 - this->pos) - (2 - that.pos);
owner->print("iterator distance: %d", result);
return result;
}
Iterator operator+(int steps) const {
owner->print("iterator advance: %i += %i", 2 - this->pos, steps);
return Iterator(owner, pos - steps);
}
void print(const char *msg) const { owner->print(msg); }
};
Iterator begin() const {
print("begin()");
return Iterator(this, 2);
}
Iterator end() const {
print("end()");
return Iterator(this, -1);
}
void print(const char *msg, ...) const {
va_list args;
va_start(args, msg);
printf("[%s] ", name);
vprintf(msg, args);
printf("\n");
va_end(args);
}
};
int main() {
printf("do\n");
#pragma omp fuse
{
for (Reporter a{"C"}; auto &&v : Reporter("A"))
printf("v=%d\n", v);
for (Reporter aa{"D"}; auto &&vv : Reporter("B"))
printf("vv=%d\n", vv);
}
printf("done\n");
return EXIT_SUCCESS;
}
// CHECK: [C] ctor
// CHECK-NEXT: [A] ctor
// CHECK-NEXT: [A] end()
// CHECK-NEXT: [A] begin()
// CHECK-NEXT: [A] begin()
// CHECK-NEXT: [A] iterator distance: 3
// CHECK-NEXT: [D] ctor
// CHECK-NEXT: [B] ctor
// CHECK-NEXT: [B] end()
// CHECK-NEXT: [B] begin()
// CHECK-NEXT: [B] begin()
// CHECK-NEXT: [B] iterator distance: 3
// CHECK-NEXT: [A] iterator advance: 0 += 0
// CHECK-NEXT: [A] iterator move assign
// CHECK-NEXT: [A] iterator deref: 0
// CHECK-NEXT: v=0
// CHECK-NEXT: [A] iterator dtor
// CHECK-NEXT: [B] iterator advance: 0 += 0
// CHECK-NEXT: [B] iterator move assign
// CHECK-NEXT: [B] iterator deref: 0
// CHECK-NEXT: vv=0
// CHECK-NEXT: [B] iterator dtor
// CHECK-NEXT: [A] iterator advance: 0 += 1
// CHECK-NEXT: [A] iterator move assign
// CHECK-NEXT: [A] iterator deref: 1
// CHECK-NEXT: v=1
// CHECK-NEXT: [A] iterator dtor
// CHECK-NEXT: [B] iterator advance: 0 += 1
// CHECK-NEXT: [B] iterator move assign
// CHECK-NEXT: [B] iterator deref: 1
// CHECK-NEXT: vv=1
// CHECK-NEXT: [B] iterator dtor
// CHECK-NEXT: [A] iterator advance: 0 += 2
// CHECK-NEXT: [A] iterator move assign
// CHECK-NEXT: [A] iterator deref: 2
// CHECK-NEXT: v=2
// CHECK-NEXT: [A] iterator dtor
// CHECK-NEXT: [B] iterator advance: 0 += 2
// CHECK-NEXT: [B] iterator move assign
// CHECK-NEXT: [B] iterator deref: 2
// CHECK-NEXT: vv=2
// CHECK-NEXT: [B] iterator dtor
// CHECK-NEXT: [B] iterator dtor
// CHECK-NEXT: [B] iterator dtor
// CHECK-NEXT: [B] iterator dtor
// CHECK-NEXT: [B] dtor
// CHECK-NEXT: [D] dtor
// CHECK-NEXT: [A] iterator dtor
// CHECK-NEXT: [A] iterator dtor
// CHECK-NEXT: [A] iterator dtor
// CHECK-NEXT: [A] dtor
// CHECK-NEXT: [C] dtor
// CHECK-NEXT: done
#endif

View File

@ -0,0 +1,50 @@
// 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 fuse
{
for (int i = 5; i <= 25; i += 5)
printf("i=%d\n", i);
for (int j = 10; j < 100; j += 10)
printf("j=%d\n", j);
for (int k = 10; k > 0; --k)
printf("k=%d\n", k);
}
printf("done\n");
return EXIT_SUCCESS;
}
#endif /* HEADER */
// CHECK: do
// CHECK-NEXT: i=5
// CHECK-NEXT: j=10
// CHECK-NEXT: k=10
// CHECK-NEXT: i=10
// CHECK-NEXT: j=20
// CHECK-NEXT: k=9
// CHECK-NEXT: i=15
// CHECK-NEXT: j=30
// CHECK-NEXT: k=8
// CHECK-NEXT: i=20
// CHECK-NEXT: j=40
// CHECK-NEXT: k=7
// CHECK-NEXT: i=25
// CHECK-NEXT: j=50
// CHECK-NEXT: k=6
// CHECK-NEXT: j=60
// CHECK-NEXT: k=5
// CHECK-NEXT: j=70
// CHECK-NEXT: k=4
// CHECK-NEXT: j=80
// CHECK-NEXT: k=3
// CHECK-NEXT: j=90
// CHECK-NEXT: k=2
// CHECK-NEXT: k=1
// CHECK-NEXT: done

View File

@ -0,0 +1,194 @@
// RUN: %libomp-cxx20-compile-and-run | FileCheck %s --match-full-lines
#ifndef HEADER
#define HEADER
#include <cstdlib>
#include <cstdarg>
#include <cstdio>
#include <vector>
struct Reporter {
const char *name;
Reporter(const char *name) : name(name) { print("ctor"); }
Reporter() : name("<anon>") { print("ctor"); }
Reporter(const Reporter &that) : name(that.name) { print("copy ctor"); }
Reporter(Reporter &&that) : name(that.name) { print("move ctor"); }
~Reporter() { print("dtor"); }
const Reporter &operator=(const Reporter &that) {
print("copy assign");
this->name = that.name;
return *this;
}
const Reporter &operator=(Reporter &&that) {
print("move assign");
this->name = that.name;
return *this;
}
struct Iterator {
const Reporter *owner;
int pos;
Iterator(const Reporter *owner, int pos) : owner(owner), pos(pos) {}
Iterator(const Iterator &that) : owner(that.owner), pos(that.pos) {
owner->print("iterator copy ctor");
}
Iterator(Iterator &&that) : owner(that.owner), pos(that.pos) {
owner->print("iterator move ctor");
}
~Iterator() { owner->print("iterator dtor"); }
const Iterator &operator=(const Iterator &that) {
owner->print("iterator copy assign");
this->owner = that.owner;
this->pos = that.pos;
return *this;
}
const Iterator &operator=(Iterator &&that) {
owner->print("iterator move assign");
this->owner = that.owner;
this->pos = that.pos;
return *this;
}
bool operator==(const Iterator &that) const {
owner->print("iterator %d == %d", 2 - this->pos, 2 - that.pos);
return this->pos == that.pos;
}
bool operator!=(const Iterator &that) const {
owner->print("iterator %d != %d", 2 - this->pos, 2 - that.pos);
return this->pos != that.pos;
}
Iterator &operator++() {
owner->print("iterator prefix ++");
pos -= 1;
return *this;
}
Iterator operator++(int) {
owner->print("iterator postfix ++");
auto result = *this;
pos -= 1;
return result;
}
int operator*() const {
int result = 2 - pos;
owner->print("iterator deref: %i", result);
return result;
}
size_t operator-(const Iterator &that) const {
int result = (2 - this->pos) - (2 - that.pos);
owner->print("iterator distance: %d", result);
return result;
}
Iterator operator+(int steps) const {
owner->print("iterator advance: %i += %i", 2 - this->pos, steps);
return Iterator(owner, pos - steps);
}
};
Iterator begin() const {
print("begin()");
return Iterator(this, 2);
}
Iterator end() const {
print("end()");
return Iterator(this, -1);
}
void print(const char *msg, ...) const {
va_list args;
va_start(args, msg);
printf("[%s] ", name);
vprintf(msg, args);
printf("\n");
va_end(args);
}
};
int main() {
printf("do\n");
Reporter C("C");
Reporter D("D");
#pragma omp fuse
{
for (auto it = C.begin(); it != C.end(); ++it)
printf("v=%d\n", *it);
for (auto it = D.begin(); it != D.end(); ++it)
printf("vv=%d\n", *it);
}
printf("done\n");
return EXIT_SUCCESS;
}
#endif /* HEADER */
// CHECK: do
// CHECK: [C] ctor
// CHECK-NEXT: [D] ctor
// CHECK-NEXT: [C] begin()
// CHECK-NEXT: [C] begin()
// CHECK-NEXT: [C] end()
// CHECK-NEXT: [C] iterator distance: 3
// CHECK-NEXT: [D] begin()
// CHECK-NEXT: [D] begin()
// CHECK-NEXT: [D] end()
// CHECK-NEXT: [D] iterator distance: 3
// CHECK-NEXT: [C] iterator advance: 0 += 0
// CHECK-NEXT: [C] iterator move assign
// CHECK-NEXT: [C] iterator deref: 0
// CHECK-NEXT: v=0
// CHECK-NEXT: [C] iterator dtor
// CHECK-NEXT: [D] iterator advance: 0 += 0
// CHECK-NEXT: [D] iterator move assign
// CHECK-NEXT: [D] iterator deref: 0
// CHECK-NEXT: vv=0
// CHECK-NEXT: [D] iterator dtor
// CHECK-NEXT: [C] iterator advance: 0 += 1
// CHECK-NEXT: [C] iterator move assign
// CHECK-NEXT: [C] iterator deref: 1
// CHECK-NEXT: v=1
// CHECK-NEXT: [C] iterator dtor
// CHECK-NEXT: [D] iterator advance: 0 += 1
// CHECK-NEXT: [D] iterator move assign
// CHECK-NEXT: [D] iterator deref: 1
// CHECK-NEXT: vv=1
// CHECK-NEXT: [D] iterator dtor
// CHECK-NEXT: [C] iterator advance: 0 += 2
// CHECK-NEXT: [C] iterator move assign
// CHECK-NEXT: [C] iterator deref: 2
// CHECK-NEXT: v=2
// CHECK-NEXT: [C] iterator dtor
// CHECK-NEXT: [D] iterator advance: 0 += 2
// CHECK-NEXT: [D] iterator move assign
// CHECK-NEXT: [D] iterator deref: 2
// CHECK-NEXT: vv=2
// CHECK-NEXT: [D] iterator dtor
// CHECK-NEXT: [D] iterator dtor
// CHECK-NEXT: [D] iterator dtor
// CHECK-NEXT: [C] iterator dtor
// CHECK-NEXT: [C] iterator dtor
// CHECK-NEXT: done
// CHECK-NEXT: [D] iterator dtor
// CHECK-NEXT: [C] iterator dtor
// CHECK-NEXT: [D] dtor
// CHECK-NEXT: [C] dtor

View File

@ -0,0 +1,207 @@
// RUN: %libomp-cxx20-compile-and-run | FileCheck %s --match-full-lines
#ifndef HEADER
#define HEADER
#include <cstdlib>
#include <cstdarg>
#include <cstdio>
#include <vector>
struct Reporter {
const char *name;
Reporter(const char *name) : name(name) { print("ctor"); }
Reporter() : name("<anon>") { print("ctor"); }
Reporter(const Reporter &that) : name(that.name) { print("copy ctor"); }
Reporter(Reporter &&that) : name(that.name) { print("move ctor"); }
~Reporter() { print("dtor"); }
const Reporter &operator=(const Reporter &that) {
print("copy assign");
this->name = that.name;
return *this;
}
const Reporter &operator=(Reporter &&that) {
print("move assign");
this->name = that.name;
return *this;
}
struct Iterator {
const Reporter *owner;
int pos;
Iterator(const Reporter *owner, int pos) : owner(owner), pos(pos) {}
Iterator(const Iterator &that) : owner(that.owner), pos(that.pos) {
owner->print("iterator copy ctor");
}
Iterator(Iterator &&that) : owner(that.owner), pos(that.pos) {
owner->print("iterator move ctor");
}
~Iterator() { owner->print("iterator dtor"); }
const Iterator &operator=(const Iterator &that) {
owner->print("iterator copy assign");
this->owner = that.owner;
this->pos = that.pos;
return *this;
}
const Iterator &operator=(Iterator &&that) {
owner->print("iterator move assign");
this->owner = that.owner;
this->pos = that.pos;
return *this;
}
bool operator==(const Iterator &that) const {
owner->print("iterator %d == %d", 2 - this->pos, 2 - that.pos);
return this->pos == that.pos;
}
Iterator &operator++() {
owner->print("iterator prefix ++");
pos -= 1;
return *this;
}
Iterator operator++(int) {
owner->print("iterator postfix ++");
auto result = *this;
pos -= 1;
return result;
}
int operator*() const {
int result = 2 - pos;
owner->print("iterator deref: %i", result);
return result;
}
size_t operator-(const Iterator &that) const {
int result = (2 - this->pos) - (2 - that.pos);
owner->print("iterator distance: %d", result);
return result;
}
Iterator operator+(int steps) const {
owner->print("iterator advance: %i += %i", 2 - this->pos, steps);
return Iterator(owner, pos - steps);
}
void print(const char *msg) const { owner->print(msg); }
};
Iterator begin() const {
print("begin()");
return Iterator(this, 2);
}
Iterator end() const {
print("end()");
return Iterator(this, -1);
}
void print(const char *msg, ...) const {
va_list args;
va_start(args, msg);
printf("[%s] ", name);
vprintf(msg, args);
printf("\n");
va_end(args);
}
};
int main() {
printf("do\n");
#pragma omp parallel for collapse(2) num_threads(1)
for (int i = 0; i < 3; ++i)
#pragma omp fuse
{
for (Reporter c{"init-stmt"}; auto &&v : Reporter("range"))
printf("i=%d v=%d\n", i, v);
for (int vv = 0; vv < 3; ++vv)
printf("i=%d vv=%d\n", i, vv);
}
printf("done\n");
return EXIT_SUCCESS;
}
#endif /* HEADER */
// CHECK: do
// CHECK-NEXT: [init-stmt] ctor
// CHECK-NEXT: [range] ctor
// CHECK-NEXT: [range] end()
// CHECK-NEXT: [range] begin()
// CHECK-NEXT: [range] begin()
// CHECK-NEXT: [range] iterator distance: 3
// CHECK-NEXT: [range] iterator advance: 0 += 0
// CHECK-NEXT: [range] iterator move assign
// CHECK-NEXT: [range] iterator deref: 0
// CHECK-NEXT: i=0 v=0
// CHECK-NEXT: [range] iterator dtor
// CHECK-NEXT: i=0 vv=0
// CHECK-NEXT: [range] iterator advance: 0 += 1
// CHECK-NEXT: [range] iterator move assign
// CHECK-NEXT: [range] iterator deref: 1
// CHECK-NEXT: i=0 v=1
// CHECK-NEXT: [range] iterator dtor
// CHECK-NEXT: i=0 vv=1
// CHECK-NEXT: [range] iterator advance: 0 += 2
// CHECK-NEXT: [range] iterator move assign
// CHECK-NEXT: [range] iterator deref: 2
// CHECK-NEXT: i=0 v=2
// CHECK-NEXT: [range] iterator dtor
// CHECK-NEXT: i=0 vv=2
// CHECK-NEXT: [range] iterator advance: 0 += 0
// CHECK-NEXT: [range] iterator move assign
// CHECK-NEXT: [range] iterator deref: 0
// CHECK-NEXT: i=1 v=0
// CHECK-NEXT: [range] iterator dtor
// CHECK-NEXT: i=1 vv=0
// CHECK-NEXT: [range] iterator advance: 0 += 1
// CHECK-NEXT: [range] iterator move assign
// CHECK-NEXT: [range] iterator deref: 1
// CHECK-NEXT: i=1 v=1
// CHECK-NEXT: [range] iterator dtor
// CHECK-NEXT: i=1 vv=1
// CHECK-NEXT: [range] iterator advance: 0 += 2
// CHECK-NEXT: [range] iterator move assign
// CHECK-NEXT: [range] iterator deref: 2
// CHECK-NEXT: i=1 v=2
// CHECK-NEXT: [range] iterator dtor
// CHECK-NEXT: i=1 vv=2
// CHECK-NEXT: [range] iterator advance: 0 += 0
// CHECK-NEXT: [range] iterator move assign
// CHECK-NEXT: [range] iterator deref: 0
// CHECK-NEXT: i=2 v=0
// CHECK-NEXT: [range] iterator dtor
// CHECK-NEXT: i=2 vv=0
// CHECK-NEXT: [range] iterator advance: 0 += 1
// CHECK-NEXT: [range] iterator move assign
// CHECK-NEXT: [range] iterator deref: 1
// CHECK-NEXT: i=2 v=1
// CHECK-NEXT: [range] iterator dtor
// CHECK-NEXT: i=2 vv=1
// CHECK-NEXT: [range] iterator advance: 0 += 2
// CHECK-NEXT: [range] iterator move assign
// CHECK-NEXT: [range] iterator deref: 2
// CHECK-NEXT: i=2 v=2
// CHECK-NEXT: [range] iterator dtor
// CHECK-NEXT: i=2 vv=2
// CHECK-NEXT: [range] iterator dtor
// CHECK-NEXT: [range] iterator dtor
// CHECK-NEXT: [range] iterator dtor
// CHECK-NEXT: [range] dtor
// CHECK-NEXT: [init-stmt] dtor
// CHECK-NEXT: done

View File

@ -0,0 +1,45 @@
// RUN: %libomp-cxx-compile-and-run | FileCheck %s --match-full-lines
#ifndef HEADER
#define HEADER
#include <cstdlib>
#include <cstdio>
int main() {
printf("do\n");
#pragma omp parallel for collapse(2) num_threads(1)
for (int i = 0; i < 3; ++i)
#pragma omp fuse
{
for (int j = 0; j < 3; ++j)
printf("i=%d j=%d\n", i, j);
for (int k = 0; k < 3; ++k)
printf("i=%d k=%d\n", i, k);
}
printf("done\n");
return EXIT_SUCCESS;
}
#endif /* HEADER */
// CHECK: do
// CHECK-NEXT: i=0 j=0
// CHECK-NEXT: i=0 k=0
// CHECK-NEXT: i=0 j=1
// CHECK-NEXT: i=0 k=1
// CHECK-NEXT: i=0 j=2
// CHECK-NEXT: i=0 k=2
// CHECK-NEXT: i=1 j=0
// CHECK-NEXT: i=1 k=0
// CHECK-NEXT: i=1 j=1
// CHECK-NEXT: i=1 k=1
// CHECK-NEXT: i=1 j=2
// CHECK-NEXT: i=1 k=2
// CHECK-NEXT: i=2 j=0
// CHECK-NEXT: i=2 k=0
// CHECK-NEXT: i=2 j=1
// CHECK-NEXT: i=2 k=1
// CHECK-NEXT: i=2 j=2
// CHECK-NEXT: i=2 k=2
// CHECK-NEXT: done