mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-17 00:36:34 +00:00
[Clang][OpenMP] Add reverse directive (#92916)
Add the reverse directive which will be introduced in the upcoming OpenMP 6.0 specification. A preview has been published in [Technical Report 12](https://www.openmp.org/wp-content/uploads/openmp-TR12.pdf). --------- Co-authored-by: Alexey Bataev <a.bataev@outlook.com>
This commit is contained in:
parent
26cb88e321
commit
80865c01e1
@ -2146,6 +2146,10 @@ enum CXCursorKind {
|
||||
*/
|
||||
CXCursor_OMPScopeDirective = 306,
|
||||
|
||||
/** OpenMP reverse directive.
|
||||
*/
|
||||
CXCursor_OMPReverseDirective = 307,
|
||||
|
||||
/** OpenACC Compute Construct.
|
||||
*/
|
||||
CXCursor_OpenACCComputeConstruct = 320,
|
||||
|
@ -3032,6 +3032,9 @@ DEF_TRAVERSE_STMT(OMPTileDirective,
|
||||
DEF_TRAVERSE_STMT(OMPUnrollDirective,
|
||||
{ TRY_TO(TraverseOMPExecutableDirective(S)); })
|
||||
|
||||
DEF_TRAVERSE_STMT(OMPReverseDirective,
|
||||
{ TRY_TO(TraverseOMPExecutableDirective(S)); })
|
||||
|
||||
DEF_TRAVERSE_STMT(OMPForDirective,
|
||||
{ TRY_TO(TraverseOMPExecutableDirective(S)); })
|
||||
|
||||
|
@ -1007,8 +1007,9 @@ public:
|
||||
Stmt *getPreInits() const;
|
||||
|
||||
static bool classof(const Stmt *T) {
|
||||
return T->getStmtClass() == OMPTileDirectiveClass ||
|
||||
T->getStmtClass() == OMPUnrollDirectiveClass;
|
||||
Stmt::StmtClass C = T->getStmtClass();
|
||||
return C == OMPTileDirectiveClass || C == OMPUnrollDirectiveClass ||
|
||||
C == OMPReverseDirectiveClass;
|
||||
}
|
||||
};
|
||||
|
||||
@ -5711,6 +5712,70 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/// Represents the '#pragma omp reverse' loop transformation directive.
|
||||
///
|
||||
/// \code
|
||||
/// #pragma omp reverse
|
||||
/// for (int i = 0; i < n; ++i)
|
||||
/// ...
|
||||
/// \endcode
|
||||
class OMPReverseDirective final : public OMPLoopTransformationDirective {
|
||||
friend class ASTStmtReader;
|
||||
friend class OMPExecutableDirective;
|
||||
|
||||
/// Offsets of child members.
|
||||
enum {
|
||||
PreInitsOffset = 0,
|
||||
TransformedStmtOffset,
|
||||
};
|
||||
|
||||
explicit OMPReverseDirective(SourceLocation StartLoc, SourceLocation EndLoc)
|
||||
: OMPLoopTransformationDirective(OMPReverseDirectiveClass,
|
||||
llvm::omp::OMPD_reverse, StartLoc,
|
||||
EndLoc, 1) {}
|
||||
|
||||
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 reverse'.
|
||||
///
|
||||
/// \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 AssociatedStmt The outermost associated loop.
|
||||
/// \param TransformedStmt The loop nest after tiling, or nullptr in
|
||||
/// dependent contexts.
|
||||
/// \param PreInits Helper preinits statements for the loop nest.
|
||||
static OMPReverseDirective *
|
||||
Create(const ASTContext &C, SourceLocation StartLoc, SourceLocation EndLoc,
|
||||
Stmt *AssociatedStmt, Stmt *TransformedStmt, Stmt *PreInits);
|
||||
|
||||
/// Build an empty '#pragma omp reverse' AST node for deserialization.
|
||||
///
|
||||
/// \param C Context of the AST.
|
||||
/// \param NumClauses Number of clauses to allocate.
|
||||
static OMPReverseDirective *CreateEmpty(const ASTContext &C);
|
||||
|
||||
/// Gets/sets the associated loops after the transformation, i.e. after
|
||||
/// de-sugaring.
|
||||
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() == OMPReverseDirectiveClass;
|
||||
}
|
||||
};
|
||||
|
||||
/// This represents '#pragma omp scan' directive.
|
||||
///
|
||||
/// \code
|
||||
|
@ -230,6 +230,7 @@ def OMPSimdDirective : StmtNode<OMPLoopDirective>;
|
||||
def OMPLoopTransformationDirective : StmtNode<OMPLoopBasedDirective, 1>;
|
||||
def OMPTileDirective : StmtNode<OMPLoopTransformationDirective>;
|
||||
def OMPUnrollDirective : StmtNode<OMPLoopTransformationDirective>;
|
||||
def OMPReverseDirective : StmtNode<OMPLoopTransformationDirective>;
|
||||
def OMPForDirective : StmtNode<OMPLoopDirective>;
|
||||
def OMPForSimdDirective : StmtNode<OMPLoopDirective>;
|
||||
def OMPSectionsDirective : StmtNode<OMPExecutableDirective>;
|
||||
|
@ -423,6 +423,9 @@ public:
|
||||
StmtResult ActOnOpenMPUnrollDirective(ArrayRef<OMPClause *> Clauses,
|
||||
Stmt *AStmt, SourceLocation StartLoc,
|
||||
SourceLocation EndLoc);
|
||||
/// Called on well-formed '#pragma omp reverse'.
|
||||
StmtResult ActOnOpenMPReverseDirective(Stmt *AStmt, SourceLocation StartLoc,
|
||||
SourceLocation EndLoc);
|
||||
/// Called on well-formed '\#pragma omp for' after parsing
|
||||
/// of the associated statement.
|
||||
StmtResult
|
||||
|
@ -1895,6 +1895,7 @@ enum StmtCode {
|
||||
STMT_OMP_SIMD_DIRECTIVE,
|
||||
STMT_OMP_TILE_DIRECTIVE,
|
||||
STMT_OMP_UNROLL_DIRECTIVE,
|
||||
STMT_OMP_REVERSE_DIRECTIVE,
|
||||
STMT_OMP_FOR_DIRECTIVE,
|
||||
STMT_OMP_FOR_SIMD_DIRECTIVE,
|
||||
STMT_OMP_SECTIONS_DIRECTIVE,
|
||||
|
@ -449,6 +449,24 @@ OMPUnrollDirective *OMPUnrollDirective::CreateEmpty(const ASTContext &C,
|
||||
SourceLocation(), SourceLocation());
|
||||
}
|
||||
|
||||
OMPReverseDirective *
|
||||
OMPReverseDirective::Create(const ASTContext &C, SourceLocation StartLoc,
|
||||
SourceLocation EndLoc, Stmt *AssociatedStmt,
|
||||
Stmt *TransformedStmt, Stmt *PreInits) {
|
||||
OMPReverseDirective *Dir = createDirective<OMPReverseDirective>(
|
||||
C, std::nullopt, AssociatedStmt, TransformedStmtOffset + 1, StartLoc,
|
||||
EndLoc);
|
||||
Dir->setTransformedStmt(TransformedStmt);
|
||||
Dir->setPreInits(PreInits);
|
||||
return Dir;
|
||||
}
|
||||
|
||||
OMPReverseDirective *OMPReverseDirective::CreateEmpty(const ASTContext &C) {
|
||||
return createEmptyDirective<OMPReverseDirective>(
|
||||
C, /*NumClauses=*/0, /*HasAssociatedStmt=*/true,
|
||||
TransformedStmtOffset + 1, SourceLocation(), SourceLocation());
|
||||
}
|
||||
|
||||
OMPForSimdDirective *
|
||||
OMPForSimdDirective::Create(const ASTContext &C, SourceLocation StartLoc,
|
||||
SourceLocation EndLoc, unsigned CollapsedNum,
|
||||
|
@ -763,6 +763,11 @@ void StmtPrinter::VisitOMPUnrollDirective(OMPUnrollDirective *Node) {
|
||||
PrintOMPExecutableDirective(Node);
|
||||
}
|
||||
|
||||
void StmtPrinter::VisitOMPReverseDirective(OMPReverseDirective *Node) {
|
||||
Indent() << "#pragma omp reverse";
|
||||
PrintOMPExecutableDirective(Node);
|
||||
}
|
||||
|
||||
void StmtPrinter::VisitOMPForDirective(OMPForDirective *Node) {
|
||||
Indent() << "#pragma omp for";
|
||||
PrintOMPExecutableDirective(Node);
|
||||
|
@ -985,6 +985,10 @@ void StmtProfiler::VisitOMPUnrollDirective(const OMPUnrollDirective *S) {
|
||||
VisitOMPLoopTransformationDirective(S);
|
||||
}
|
||||
|
||||
void StmtProfiler::VisitOMPReverseDirective(const OMPReverseDirective *S) {
|
||||
VisitOMPLoopTransformationDirective(S);
|
||||
}
|
||||
|
||||
void StmtProfiler::VisitOMPForDirective(const OMPForDirective *S) {
|
||||
VisitOMPLoopDirective(S);
|
||||
}
|
||||
|
@ -684,7 +684,7 @@ bool clang::isOpenMPLoopBoundSharingDirective(OpenMPDirectiveKind Kind) {
|
||||
}
|
||||
|
||||
bool clang::isOpenMPLoopTransformationDirective(OpenMPDirectiveKind DKind) {
|
||||
return DKind == OMPD_tile || DKind == OMPD_unroll;
|
||||
return DKind == OMPD_tile || DKind == OMPD_unroll || DKind == OMPD_reverse;
|
||||
}
|
||||
|
||||
bool clang::isOpenMPCombinedParallelADirective(OpenMPDirectiveKind DKind) {
|
||||
|
@ -222,6 +222,9 @@ void CodeGenFunction::EmitStmt(const Stmt *S, ArrayRef<const Attr *> Attrs) {
|
||||
case Stmt::OMPUnrollDirectiveClass:
|
||||
EmitOMPUnrollDirective(cast<OMPUnrollDirective>(*S));
|
||||
break;
|
||||
case Stmt::OMPReverseDirectiveClass:
|
||||
EmitOMPReverseDirective(cast<OMPReverseDirective>(*S));
|
||||
break;
|
||||
case Stmt::OMPForDirectiveClass:
|
||||
EmitOMPForDirective(cast<OMPForDirective>(*S));
|
||||
break;
|
||||
|
@ -187,6 +187,8 @@ class OMPLoopScope : public CodeGenFunction::RunCleanupsScope {
|
||||
PreInits = Tile->getPreInits();
|
||||
} else if (const auto *Unroll = dyn_cast<OMPUnrollDirective>(&S)) {
|
||||
PreInits = Unroll->getPreInits();
|
||||
} else if (const auto *Reverse = dyn_cast<OMPReverseDirective>(&S)) {
|
||||
PreInits = Reverse->getPreInits();
|
||||
} else {
|
||||
llvm_unreachable("Unknown loop-based directive kind.");
|
||||
}
|
||||
@ -2762,6 +2764,12 @@ void CodeGenFunction::EmitOMPTileDirective(const OMPTileDirective &S) {
|
||||
EmitStmt(S.getTransformedStmt());
|
||||
}
|
||||
|
||||
void CodeGenFunction::EmitOMPReverseDirective(const OMPReverseDirective &S) {
|
||||
// Emit the de-sugared statement.
|
||||
OMPTransformDirectiveScopeRAII ReverseScope(*this, &S);
|
||||
EmitStmt(S.getTransformedStmt());
|
||||
}
|
||||
|
||||
void CodeGenFunction::EmitOMPUnrollDirective(const OMPUnrollDirective &S) {
|
||||
bool UseOMPIRBuilder = CGM.getLangOpts().OpenMPIRBuilder;
|
||||
|
||||
|
@ -3817,6 +3817,7 @@ public:
|
||||
void EmitOMPSimdDirective(const OMPSimdDirective &S);
|
||||
void EmitOMPTileDirective(const OMPTileDirective &S);
|
||||
void EmitOMPUnrollDirective(const OMPUnrollDirective &S);
|
||||
void EmitOMPReverseDirective(const OMPReverseDirective &S);
|
||||
void EmitOMPForDirective(const OMPForDirective &S);
|
||||
void EmitOMPForSimdDirective(const OMPForSimdDirective &S);
|
||||
void EmitOMPSectionsDirective(const OMPSectionsDirective &S);
|
||||
|
@ -2885,6 +2885,7 @@ StmtResult Parser::ParseOpenMPDeclarativeOrExecutableDirective(
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OMPD_reverse:
|
||||
case OMPD_declare_target: {
|
||||
SourceLocation DTLoc = ConsumeAnyToken();
|
||||
bool HasClauses = Tok.isNot(tok::annot_pragma_openmp_end);
|
||||
|
@ -1466,6 +1466,7 @@ CanThrowResult Sema::canThrow(const Stmt *S) {
|
||||
case Stmt::OMPSimdDirectiveClass:
|
||||
case Stmt::OMPTileDirectiveClass:
|
||||
case Stmt::OMPUnrollDirectiveClass:
|
||||
case Stmt::OMPReverseDirectiveClass:
|
||||
case Stmt::OMPSingleDirectiveClass:
|
||||
case Stmt::OMPTargetDataDirectiveClass:
|
||||
case Stmt::OMPTargetDirectiveClass:
|
||||
|
@ -4405,6 +4405,7 @@ void SemaOpenMP::ActOnOpenMPRegionStart(OpenMPDirectiveKind DKind,
|
||||
case OMPD_section:
|
||||
case OMPD_tile:
|
||||
case OMPD_unroll:
|
||||
case OMPD_reverse:
|
||||
break;
|
||||
default:
|
||||
processCapturedRegions(SemaRef, DKind, CurScope,
|
||||
@ -6284,6 +6285,11 @@ StmtResult SemaOpenMP::ActOnOpenMPExecutableDirective(
|
||||
Res = ActOnOpenMPUnrollDirective(ClausesWithImplicit, AStmt, StartLoc,
|
||||
EndLoc);
|
||||
break;
|
||||
case OMPD_reverse:
|
||||
assert(ClausesWithImplicit.empty() &&
|
||||
"reverse directive does not support any clauses");
|
||||
Res = ActOnOpenMPReverseDirective(AStmt, StartLoc, EndLoc);
|
||||
break;
|
||||
case OMPD_for:
|
||||
Res = ActOnOpenMPForDirective(ClausesWithImplicit, AStmt, StartLoc, EndLoc,
|
||||
VarsWithInheritedDSA);
|
||||
@ -14040,6 +14046,8 @@ bool SemaOpenMP::checkTransformableLoopNest(
|
||||
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
|
||||
llvm_unreachable("Unhandled loop transformation");
|
||||
|
||||
@ -14658,6 +14666,193 @@ StmtResult SemaOpenMP::ActOnOpenMPUnrollDirective(ArrayRef<OMPClause *> Clauses,
|
||||
buildPreInits(Context, PreInits));
|
||||
}
|
||||
|
||||
StmtResult SemaOpenMP::ActOnOpenMPReverseDirective(Stmt *AStmt,
|
||||
SourceLocation StartLoc,
|
||||
SourceLocation EndLoc) {
|
||||
ASTContext &Context = getASTContext();
|
||||
Scope *CurScope = SemaRef.getCurScope();
|
||||
|
||||
// Empty statement should only be possible if there already was an error.
|
||||
if (!AStmt)
|
||||
return StmtError();
|
||||
|
||||
constexpr unsigned NumLoops = 1;
|
||||
Stmt *Body = nullptr;
|
||||
SmallVector<OMPLoopBasedDirective::HelperExprs, NumLoops> LoopHelpers(
|
||||
NumLoops);
|
||||
SmallVector<SmallVector<Stmt *, 0>, NumLoops + 1> OriginalInits;
|
||||
if (!checkTransformableLoopNest(OMPD_reverse, AStmt, NumLoops, LoopHelpers,
|
||||
Body, OriginalInits))
|
||||
return StmtError();
|
||||
|
||||
// Delay applying the transformation to when template is completely
|
||||
// instantiated.
|
||||
if (SemaRef.CurContext->isDependentContext())
|
||||
return OMPReverseDirective::Create(Context, StartLoc, EndLoc, AStmt,
|
||||
nullptr, nullptr);
|
||||
|
||||
assert(LoopHelpers.size() == NumLoops &&
|
||||
"Expecting a single-dimensional loop iteration space");
|
||||
assert(OriginalInits.size() == NumLoops &&
|
||||
"Expecting a single-dimensional loop iteration space");
|
||||
OMPLoopBasedDirective::HelperExprs &LoopHelper = LoopHelpers.front();
|
||||
|
||||
// Find the loop statement.
|
||||
Stmt *LoopStmt = nullptr;
|
||||
collectLoopStmts(AStmt, {LoopStmt});
|
||||
|
||||
// Determine the PreInit declarations.
|
||||
SmallVector<Stmt *> PreInits;
|
||||
addLoopPreInits(Context, LoopHelper, LoopStmt, OriginalInits[0], PreInits);
|
||||
|
||||
auto *IterationVarRef = cast<DeclRefExpr>(LoopHelper.IterationVarRef);
|
||||
QualType IVTy = IterationVarRef->getType();
|
||||
uint64_t IVWidth = Context.getTypeSize(IVTy);
|
||||
auto *OrigVar = cast<DeclRefExpr>(LoopHelper.Counters.front());
|
||||
|
||||
// Iteration variable SourceLocations.
|
||||
SourceLocation OrigVarLoc = OrigVar->getExprLoc();
|
||||
SourceLocation OrigVarLocBegin = OrigVar->getBeginLoc();
|
||||
SourceLocation OrigVarLocEnd = OrigVar->getEndLoc();
|
||||
|
||||
// Locations pointing to the transformation.
|
||||
SourceLocation TransformLoc = StartLoc;
|
||||
SourceLocation TransformLocBegin = StartLoc;
|
||||
SourceLocation TransformLocEnd = EndLoc;
|
||||
|
||||
// Internal variable names.
|
||||
std::string OrigVarName = OrigVar->getNameInfo().getAsString();
|
||||
SmallString<64> ForwardIVName(".forward.iv.");
|
||||
ForwardIVName += OrigVarName;
|
||||
SmallString<64> ReversedIVName(".reversed.iv.");
|
||||
ReversedIVName += OrigVarName;
|
||||
|
||||
// LoopHelper.Updates will read the logical iteration number from
|
||||
// LoopHelper.IterationVarRef, compute the value of the user loop counter of
|
||||
// that logical iteration from it, then assign it to the user loop counter
|
||||
// variable. We cannot directly use LoopHelper.IterationVarRef as the
|
||||
// induction variable of the generated loop because it may cause an underflow:
|
||||
// \code{.c}
|
||||
// for (unsigned i = 0; i < n; ++i)
|
||||
// body(i);
|
||||
// \endcode
|
||||
//
|
||||
// Naive reversal:
|
||||
// \code{.c}
|
||||
// for (unsigned i = n-1; i >= 0; --i)
|
||||
// body(i);
|
||||
// \endcode
|
||||
//
|
||||
// Instead, we introduce a new iteration variable representing the logical
|
||||
// iteration counter of the original loop, convert it to the logical iteration
|
||||
// number of the reversed loop, then let LoopHelper.Updates compute the user's
|
||||
// loop iteration variable from it.
|
||||
// \code{.cpp}
|
||||
// for (auto .forward.iv = 0; .forward.iv < n; ++.forward.iv) {
|
||||
// auto .reversed.iv = n - .forward.iv - 1;
|
||||
// i = (.reversed.iv + 0) * 1; // LoopHelper.Updates
|
||||
// body(i); // Body
|
||||
// }
|
||||
// \endcode
|
||||
|
||||
// Subexpressions with more than one use. One of the constraints of an AST is
|
||||
// that every node object must appear at most once, hence we define a lambda
|
||||
// that creates a new AST node at every use.
|
||||
CaptureVars CopyTransformer(SemaRef);
|
||||
auto MakeNumIterations = [&CopyTransformer, &LoopHelper]() -> Expr * {
|
||||
return AssertSuccess(
|
||||
CopyTransformer.TransformExpr(LoopHelper.NumIterations));
|
||||
};
|
||||
|
||||
// Create the iteration variable for the forward loop (from 0 to n-1).
|
||||
VarDecl *ForwardIVDecl =
|
||||
buildVarDecl(SemaRef, {}, IVTy, ForwardIVName, nullptr, OrigVar);
|
||||
auto MakeForwardRef = [&SemaRef = this->SemaRef, ForwardIVDecl, IVTy,
|
||||
OrigVarLoc]() {
|
||||
return buildDeclRefExpr(SemaRef, ForwardIVDecl, IVTy, OrigVarLoc);
|
||||
};
|
||||
|
||||
// Iteration variable for the reversed induction variable (from n-1 downto 0):
|
||||
// Reuse the iteration variable created by checkOpenMPLoop.
|
||||
auto *ReversedIVDecl = cast<VarDecl>(IterationVarRef->getDecl());
|
||||
ReversedIVDecl->setDeclName(
|
||||
&SemaRef.PP.getIdentifierTable().get(ReversedIVName));
|
||||
|
||||
// For init-statement:
|
||||
// \code{.cpp}
|
||||
// auto .forward.iv = 0;
|
||||
// \endcode
|
||||
auto *Zero = IntegerLiteral::Create(Context, llvm::APInt::getZero(IVWidth),
|
||||
ForwardIVDecl->getType(), OrigVarLoc);
|
||||
SemaRef.AddInitializerToDecl(ForwardIVDecl, Zero, /*DirectInit=*/false);
|
||||
StmtResult Init = new (Context)
|
||||
DeclStmt(DeclGroupRef(ForwardIVDecl), OrigVarLocBegin, OrigVarLocEnd);
|
||||
if (!Init.isUsable())
|
||||
return StmtError();
|
||||
|
||||
// Forward iv cond-expression:
|
||||
// \code{.cpp}
|
||||
// .forward.iv < MakeNumIterations()
|
||||
// \endcode
|
||||
ExprResult Cond =
|
||||
SemaRef.BuildBinOp(CurScope, LoopHelper.Cond->getExprLoc(), BO_LT,
|
||||
MakeForwardRef(), MakeNumIterations());
|
||||
if (!Cond.isUsable())
|
||||
return StmtError();
|
||||
|
||||
// Forward incr-statement:
|
||||
// \code{.c}
|
||||
// ++.forward.iv
|
||||
// \endcode
|
||||
ExprResult Incr = SemaRef.BuildUnaryOp(CurScope, LoopHelper.Inc->getExprLoc(),
|
||||
UO_PreInc, MakeForwardRef());
|
||||
if (!Incr.isUsable())
|
||||
return StmtError();
|
||||
|
||||
// Reverse the forward-iv:
|
||||
// \code{.cpp}
|
||||
// auto .reversed.iv = MakeNumIterations() - 1 - .forward.iv
|
||||
// \endcode
|
||||
auto *One = IntegerLiteral::Create(Context, llvm::APInt(IVWidth, 1), IVTy,
|
||||
TransformLoc);
|
||||
ExprResult Minus = SemaRef.BuildBinOp(CurScope, TransformLoc, BO_Sub,
|
||||
MakeNumIterations(), One);
|
||||
if (!Minus.isUsable())
|
||||
return StmtError();
|
||||
Minus = SemaRef.BuildBinOp(CurScope, TransformLoc, BO_Sub, Minus.get(),
|
||||
MakeForwardRef());
|
||||
if (!Minus.isUsable())
|
||||
return StmtError();
|
||||
StmtResult InitReversed = new (Context) DeclStmt(
|
||||
DeclGroupRef(ReversedIVDecl), TransformLocBegin, TransformLocEnd);
|
||||
if (!InitReversed.isUsable())
|
||||
return StmtError();
|
||||
SemaRef.AddInitializerToDecl(ReversedIVDecl, Minus.get(),
|
||||
/*DirectInit=*/false);
|
||||
|
||||
// The new loop body.
|
||||
SmallVector<Stmt *, 4> BodyStmts;
|
||||
BodyStmts.reserve(LoopHelper.Updates.size() + 2 +
|
||||
(isa<CXXForRangeStmt>(LoopStmt) ? 1 : 0));
|
||||
BodyStmts.push_back(InitReversed.get());
|
||||
llvm::append_range(BodyStmts, LoopHelper.Updates);
|
||||
if (auto *CXXRangeFor = dyn_cast<CXXForRangeStmt>(LoopStmt))
|
||||
BodyStmts.push_back(CXXRangeFor->getLoopVarStmt());
|
||||
BodyStmts.push_back(Body);
|
||||
auto *ReversedBody =
|
||||
CompoundStmt::Create(Context, BodyStmts, FPOptionsOverride(),
|
||||
Body->getBeginLoc(), Body->getEndLoc());
|
||||
|
||||
// Finally create the reversed For-statement.
|
||||
auto *ReversedFor = new (Context)
|
||||
ForStmt(Context, Init.get(), Cond.get(), nullptr, Incr.get(),
|
||||
ReversedBody, LoopHelper.Init->getBeginLoc(),
|
||||
LoopHelper.Init->getBeginLoc(), LoopHelper.Inc->getEndLoc());
|
||||
return OMPReverseDirective::Create(Context, StartLoc, EndLoc, AStmt,
|
||||
ReversedFor,
|
||||
buildPreInits(Context, PreInits));
|
||||
}
|
||||
|
||||
OMPClause *SemaOpenMP::ActOnOpenMPSingleExprClause(OpenMPClauseKind Kind,
|
||||
Expr *Expr,
|
||||
SourceLocation StartLoc,
|
||||
|
@ -9239,6 +9239,17 @@ TreeTransform<Derived>::TransformOMPUnrollDirective(OMPUnrollDirective *D) {
|
||||
return Res;
|
||||
}
|
||||
|
||||
template <typename Derived>
|
||||
StmtResult
|
||||
TreeTransform<Derived>::TransformOMPReverseDirective(OMPReverseDirective *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) {
|
||||
|
@ -2445,6 +2445,10 @@ void ASTStmtReader::VisitOMPUnrollDirective(OMPUnrollDirective *D) {
|
||||
VisitOMPLoopTransformationDirective(D);
|
||||
}
|
||||
|
||||
void ASTStmtReader::VisitOMPReverseDirective(OMPReverseDirective *D) {
|
||||
VisitOMPLoopTransformationDirective(D);
|
||||
}
|
||||
|
||||
void ASTStmtReader::VisitOMPForDirective(OMPForDirective *D) {
|
||||
VisitOMPLoopDirective(D);
|
||||
D->setHasCancel(Record.readBool());
|
||||
@ -3464,6 +3468,15 @@ Stmt *ASTReader::ReadStmtFromStream(ModuleFile &F) {
|
||||
break;
|
||||
}
|
||||
|
||||
case STMT_OMP_REVERSE_DIRECTIVE: {
|
||||
assert(Record[ASTStmtReader::NumStmtFields] == 1 &&
|
||||
"Reverse directive accepts only a single loop");
|
||||
assert(Record[ASTStmtReader::NumStmtFields + 1] == 0 &&
|
||||
"Reverse directive has no clauses");
|
||||
S = OMPReverseDirective::CreateEmpty(Context);
|
||||
break;
|
||||
}
|
||||
|
||||
case STMT_OMP_FOR_DIRECTIVE: {
|
||||
unsigned CollapsedNum = Record[ASTStmtReader::NumStmtFields];
|
||||
unsigned NumClauses = Record[ASTStmtReader::NumStmtFields + 1];
|
||||
|
@ -2437,6 +2437,11 @@ void ASTStmtWriter::VisitOMPUnrollDirective(OMPUnrollDirective *D) {
|
||||
Code = serialization::STMT_OMP_UNROLL_DIRECTIVE;
|
||||
}
|
||||
|
||||
void ASTStmtWriter::VisitOMPReverseDirective(OMPReverseDirective *D) {
|
||||
VisitOMPLoopTransformationDirective(D);
|
||||
Code = serialization::STMT_OMP_REVERSE_DIRECTIVE;
|
||||
}
|
||||
|
||||
void ASTStmtWriter::VisitOMPForDirective(OMPForDirective *D) {
|
||||
VisitOMPLoopDirective(D);
|
||||
Record.writeBool(D->hasCancel());
|
||||
|
159
clang/test/OpenMP/reverse_ast_print.cpp
Normal file
159
clang/test/OpenMP/reverse_ast_print.cpp
Normal file
@ -0,0 +1,159 @@
|
||||
// Check no warnings/errors
|
||||
// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 -fsyntax-only -verify %s
|
||||
// expected-no-diagnostics
|
||||
|
||||
// Check AST and unparsing
|
||||
// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 -ast-dump %s | FileCheck %s --check-prefix=DUMP
|
||||
// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 -ast-print %s | FileCheck %s --check-prefix=PRINT
|
||||
|
||||
// Check same results after serialization round-trip
|
||||
// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 -emit-pch -o %t %s
|
||||
// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 -include-pch %t -ast-dump-all %s | FileCheck %s --check-prefix=DUMP
|
||||
// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 -include-pch %t -ast-print %s | FileCheck %s --check-prefix=PRINT
|
||||
|
||||
#ifndef HEADER
|
||||
#define HEADER
|
||||
|
||||
// placeholder for loop body code.
|
||||
extern "C" void body(...);
|
||||
|
||||
// PRINT-LABEL: void foo1(
|
||||
// DUMP-LABEL: FunctionDecl {{.*}} foo1
|
||||
void foo1() {
|
||||
// PRINT: #pragma omp reverse
|
||||
// DUMP: OMPReverseDirective
|
||||
#pragma omp reverse
|
||||
// PRINT: for (int i = 7; i < 17; i += 3)
|
||||
// DUMP-NEXT: ForStmt
|
||||
for (int i = 7; i < 17; i += 3)
|
||||
// PRINT: body(i);
|
||||
// DUMP: CallExpr
|
||||
body(i);
|
||||
}
|
||||
|
||||
|
||||
// PRINT-LABEL: void foo2(
|
||||
// DUMP-LABEL: FunctionDecl {{.*}} foo2
|
||||
void foo2(int start, int end, int step) {
|
||||
// PRINT: #pragma omp reverse
|
||||
// DUMP: OMPReverseDirective
|
||||
#pragma omp reverse
|
||||
// PRINT: for (int i = start; i < end; i += step)
|
||||
// DUMP-NEXT: ForStmt
|
||||
for (int i = start; i < end; i += step)
|
||||
// PRINT: body(i);
|
||||
// DUMP: CallExpr
|
||||
body(i);
|
||||
}
|
||||
|
||||
|
||||
// PRINT-LABEL: void foo3(
|
||||
// DUMP-LABEL: FunctionDecl {{.*}} foo3
|
||||
void foo3() {
|
||||
// PRINT: #pragma omp for
|
||||
// DUMP: OMPForDirective
|
||||
// DUMP-NEXT: CapturedStmt
|
||||
// DUMP-NEXT: CapturedDecl
|
||||
#pragma omp for
|
||||
// PRINT: #pragma omp reverse
|
||||
// DUMP-NEXT: OMPReverseDirective
|
||||
#pragma omp reverse
|
||||
for (int i = 7; i < 17; i += 3)
|
||||
// PRINT: body(i);
|
||||
// DUMP: CallExpr
|
||||
body(i);
|
||||
}
|
||||
|
||||
|
||||
// PRINT-LABEL: void foo4(
|
||||
// DUMP-LABEL: FunctionDecl {{.*}} foo4
|
||||
void foo4() {
|
||||
// PRINT: #pragma omp for collapse(2)
|
||||
// DUMP: OMPForDirective
|
||||
// DUMP-NEXT: OMPCollapseClause
|
||||
// DUMP-NEXT: ConstantExpr
|
||||
// DUMP-NEXT: value: Int 2
|
||||
// DUMP-NEXT: IntegerLiteral {{.*}} 2
|
||||
// DUMP-NEXT: CapturedStmt
|
||||
// DUMP-NEXT: CapturedDecl
|
||||
#pragma omp for collapse(2)
|
||||
// PRINT: #pragma omp reverse
|
||||
// DUMP: OMPReverseDirective
|
||||
#pragma omp reverse
|
||||
// PRINT: for (int i = 7; i < 17; i += 1)
|
||||
// DUMP-NEXT: ForStmt
|
||||
for (int i = 7; i < 17; i += 1)
|
||||
// PRINT: for (int j = 7; j < 17; j += 1)
|
||||
// DUMP: ForStmt
|
||||
for (int j = 7; j < 17; j += 1)
|
||||
// PRINT: body(i, j);
|
||||
// DUMP: CallExpr
|
||||
body(i, j);
|
||||
}
|
||||
|
||||
|
||||
// PRINT-LABEL: void foo5(
|
||||
// DUMP-LABEL: FunctionDecl {{.*}} foo5
|
||||
void foo5(int start, int end, int step) {
|
||||
// PRINT: #pragma omp for collapse(2)
|
||||
// DUMP: OMPForDirective
|
||||
// DUMP-NEXT: OMPCollapseClause
|
||||
// DUMP-NEXT: ConstantExpr
|
||||
// DUMP-NEXT: value: Int 2
|
||||
// DUMP-NEXT: IntegerLiteral {{.*}} 2
|
||||
// DUMP-NEXT: CapturedStmt
|
||||
// DUMP-NEXT: CapturedDecl
|
||||
#pragma omp for collapse(2)
|
||||
// PRINT: for (int i = 7; i < 17; i += 1)
|
||||
// DUMP-NEXT: ForStmt
|
||||
for (int i = 7; i < 17; i += 1)
|
||||
// PRINT: #pragma omp reverse
|
||||
// DUMP: OMPReverseDirective
|
||||
#pragma omp reverse
|
||||
// PRINT: for (int j = 7; j < 17; j += 1)
|
||||
// DUMP-NEXT: ForStmt
|
||||
for (int j = 7; j < 17; j += 1)
|
||||
// PRINT: body(i, j);
|
||||
// DUMP: CallExpr
|
||||
body(i, j);
|
||||
}
|
||||
|
||||
|
||||
// PRINT-LABEL: void foo6(
|
||||
// DUMP-LABEL: FunctionTemplateDecl {{.*}} foo6
|
||||
template<typename T, T Step>
|
||||
void foo6(T start, T end) {
|
||||
// PRINT: #pragma omp reverse
|
||||
// DUMP: OMPReverseDirective
|
||||
#pragma omp reverse
|
||||
// PRINT-NEXT: for (T i = start; i < end; i += Step)
|
||||
// DUMP-NEXT: ForStmt
|
||||
for (T i = start; i < end; i += Step)
|
||||
// PRINT-NEXT: body(i);
|
||||
// DUMP: CallExpr
|
||||
body(i);
|
||||
}
|
||||
|
||||
// Also test instantiating the template.
|
||||
void tfoo6() {
|
||||
foo6<int,3>(0, 42);
|
||||
}
|
||||
|
||||
|
||||
// PRINT-LABEL: void foo7(
|
||||
// DUMP-LABEL: FunctionDecl {{.*}} foo7
|
||||
void foo7() {
|
||||
double arr[128];
|
||||
// PRINT: #pragma omp reverse
|
||||
// DUMP: OMPReverseDirective
|
||||
#pragma omp reverse
|
||||
// PRINT-NEXT: for (auto &&v : arr)
|
||||
// DUMP-NEXT: CXXForRangeStmt
|
||||
for (auto &&v : arr)
|
||||
// PRINT-NEXT: body(v);
|
||||
// DUMP: CallExpr
|
||||
body(v);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
1554
clang/test/OpenMP/reverse_codegen.cpp
Normal file
1554
clang/test/OpenMP/reverse_codegen.cpp
Normal file
File diff suppressed because it is too large
Load Diff
40
clang/test/OpenMP/reverse_messages.cpp
Normal file
40
clang/test/OpenMP/reverse_messages.cpp
Normal file
@ -0,0 +1,40 @@
|
||||
// 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 reverse' must be a for loop}}
|
||||
#pragma omp reverse
|
||||
;
|
||||
|
||||
// expected-error@+2 {{statement after '#pragma omp reverse' must be a for loop}}
|
||||
#pragma omp reverse
|
||||
int b = 0;
|
||||
|
||||
// expected-error@+2 {{statement after '#pragma omp reverse' must be a for loop}}
|
||||
#pragma omp reverse
|
||||
#pragma omp for
|
||||
for (int i = 0; i < 7; ++i)
|
||||
;
|
||||
|
||||
{
|
||||
// expected-error@+2 {{expected statement}}
|
||||
#pragma omp reverse
|
||||
}
|
||||
|
||||
// expected-error@+2 {{condition of OpenMP for loop must be a relational comparison ('<', '<=', '>', '>=', or '!=') of loop variable 'i'}}
|
||||
#pragma omp reverse
|
||||
for (int i = 0; i/3<7; ++i)
|
||||
;
|
||||
|
||||
// expected-error@+1 {{unexpected OpenMP clause 'sizes' in directive '#pragma omp reverse'}}
|
||||
#pragma omp reverse sizes(5)
|
||||
for (int i = 0; i < 7; ++i)
|
||||
;
|
||||
|
||||
// expected-warning@+1 {{extra tokens at the end of '#pragma omp reverse' are ignored}}
|
||||
#pragma omp reverse foo
|
||||
for (int i = 0; i < 7; ++i)
|
||||
;
|
||||
|
||||
}
|
||||
|
@ -2182,6 +2182,7 @@ public:
|
||||
VisitOMPLoopTransformationDirective(const OMPLoopTransformationDirective *D);
|
||||
void VisitOMPTileDirective(const OMPTileDirective *D);
|
||||
void VisitOMPUnrollDirective(const OMPUnrollDirective *D);
|
||||
void VisitOMPReverseDirective(const OMPReverseDirective *D);
|
||||
void VisitOMPForDirective(const OMPForDirective *D);
|
||||
void VisitOMPForSimdDirective(const OMPForSimdDirective *D);
|
||||
void VisitOMPSectionsDirective(const OMPSectionsDirective *D);
|
||||
@ -3228,6 +3229,10 @@ void EnqueueVisitor::VisitOMPUnrollDirective(const OMPUnrollDirective *D) {
|
||||
VisitOMPLoopTransformationDirective(D);
|
||||
}
|
||||
|
||||
void EnqueueVisitor::VisitOMPReverseDirective(const OMPReverseDirective *D) {
|
||||
VisitOMPLoopTransformationDirective(D);
|
||||
}
|
||||
|
||||
void EnqueueVisitor::VisitOMPForDirective(const OMPForDirective *D) {
|
||||
VisitOMPLoopDirective(D);
|
||||
}
|
||||
@ -6097,6 +6102,8 @@ CXString clang_getCursorKindSpelling(enum CXCursorKind Kind) {
|
||||
return cxstring::createRef("OMPTileDirective");
|
||||
case CXCursor_OMPUnrollDirective:
|
||||
return cxstring::createRef("OMPUnrollDirective");
|
||||
case CXCursor_OMPReverseDirective:
|
||||
return cxstring::createRef("OMPReverseDirective");
|
||||
case CXCursor_OMPForDirective:
|
||||
return cxstring::createRef("OMPForDirective");
|
||||
case CXCursor_OMPForSimdDirective:
|
||||
|
@ -673,6 +673,9 @@ CXCursor cxcursor::MakeCXCursor(const Stmt *S, const Decl *Parent,
|
||||
case Stmt::OMPUnrollDirectiveClass:
|
||||
K = CXCursor_OMPUnrollDirective;
|
||||
break;
|
||||
case Stmt::OMPReverseDirectiveClass:
|
||||
K = CXCursor_OMPReverseDirective;
|
||||
break;
|
||||
case Stmt::OMPForDirectiveClass:
|
||||
K = CXCursor_OMPForDirective;
|
||||
break;
|
||||
|
@ -837,6 +837,10 @@ def OMP_Requires : Directive<"requires"> {
|
||||
let association = AS_None;
|
||||
let category = CA_Informational;
|
||||
}
|
||||
def OMP_Reverse : Directive<"reverse"> {
|
||||
let association = AS_Loop;
|
||||
let category = CA_Executable;
|
||||
}
|
||||
def OMP_Scan : Directive<"scan"> {
|
||||
let allowedClauses = [
|
||||
VersionedClause<OMPC_Exclusive, 50>,
|
||||
|
162
openmp/runtime/test/transform/reverse/foreach.cpp
Normal file
162
openmp/runtime/test/transform/reverse/foreach.cpp
Normal file
@ -0,0 +1,162 @@
|
||||
// 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 reverse
|
||||
for (Reporter c{"init-stmt"}; auto &&v : Reporter("range"))
|
||||
printf("v=%d\n", v);
|
||||
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 += 2
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 2
|
||||
// CHECK-NEXT: v=2
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 1
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 1
|
||||
// CHECK-NEXT: v=1
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 0
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 0
|
||||
// CHECK-NEXT: v=0
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// 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
|
25
openmp/runtime/test/transform/reverse/intfor.c
Normal file
25
openmp/runtime/test/transform/reverse/intfor.c
Normal file
@ -0,0 +1,25 @@
|
||||
// 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 reverse
|
||||
for (int i = 7; i < 19; i += 3)
|
||||
printf("i=%d\n", i);
|
||||
printf("done\n");
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
#endif /* HEADER */
|
||||
|
||||
// CHECK: do
|
||||
// CHECK-NEXT: i=16
|
||||
// CHECK-NEXT: i=13
|
||||
// CHECK-NEXT: i=10
|
||||
// CHECK-NEXT: i=7
|
||||
// CHECK-NEXT: done
|
164
openmp/runtime/test/transform/reverse/iterfor.cpp
Normal file
164
openmp/runtime/test/transform/reverse/iterfor.cpp
Normal file
@ -0,0 +1,164 @@
|
||||
// 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 range("range");
|
||||
#pragma omp reverse
|
||||
for (auto it = range.begin(); it != range.end(); ++it)
|
||||
printf("v=%d\n", *it);
|
||||
printf("done\n");
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
#endif /* HEADER */
|
||||
|
||||
// CHECK: do
|
||||
// CHECK-NEXT: [range] ctor
|
||||
// CHECK-NEXT: [range] begin()
|
||||
// CHECK-NEXT: [range] begin()
|
||||
// CHECK-NEXT: [range] end()
|
||||
// CHECK-NEXT: [range] iterator distance: 3
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 2
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 2
|
||||
// CHECK-NEXT: v=2
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 1
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 1
|
||||
// CHECK-NEXT: v=1
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 0
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 0
|
||||
// CHECK-NEXT: v=0
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: done
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] dtor
|
@ -0,0 +1,285 @@
|
||||
// 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(3) num_threads(1)
|
||||
for (int i = 0; i < 3; ++i)
|
||||
#pragma omp reverse
|
||||
for (Reporter c{"init-stmt"}; auto &&v : Reporter("range"))
|
||||
for (int k = 0; k < 3; ++k)
|
||||
printf("i=%d j=%d k=%d\n", i, v, k);
|
||||
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 += 2
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 2
|
||||
// CHECK-NEXT: i=0 j=2 k=0
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 2
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 2
|
||||
// CHECK-NEXT: i=0 j=2 k=1
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 2
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 2
|
||||
// CHECK-NEXT: i=0 j=2 k=2
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 1
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 1
|
||||
// CHECK-NEXT: i=0 j=1 k=0
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 1
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 1
|
||||
// CHECK-NEXT: i=0 j=1 k=1
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 1
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 1
|
||||
// CHECK-NEXT: i=0 j=1 k=2
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 0
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 0
|
||||
// CHECK-NEXT: i=0 j=0 k=0
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 0
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 0
|
||||
// CHECK-NEXT: i=0 j=0 k=1
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 0
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 0
|
||||
// CHECK-NEXT: i=0 j=0 k=2
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 2
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 2
|
||||
// CHECK-NEXT: i=1 j=2 k=0
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 2
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 2
|
||||
// CHECK-NEXT: i=1 j=2 k=1
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 2
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 2
|
||||
// CHECK-NEXT: i=1 j=2 k=2
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 1
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 1
|
||||
// CHECK-NEXT: i=1 j=1 k=0
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 1
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 1
|
||||
// CHECK-NEXT: i=1 j=1 k=1
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 1
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 1
|
||||
// CHECK-NEXT: i=1 j=1 k=2
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 0
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 0
|
||||
// CHECK-NEXT: i=1 j=0 k=0
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 0
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 0
|
||||
// CHECK-NEXT: i=1 j=0 k=1
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 0
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 0
|
||||
// CHECK-NEXT: i=1 j=0 k=2
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 2
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 2
|
||||
// CHECK-NEXT: i=2 j=2 k=0
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 2
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 2
|
||||
// CHECK-NEXT: i=2 j=2 k=1
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 2
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 2
|
||||
// CHECK-NEXT: i=2 j=2 k=2
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 1
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 1
|
||||
// CHECK-NEXT: i=2 j=1 k=0
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 1
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 1
|
||||
// CHECK-NEXT: i=2 j=1 k=1
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 1
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 1
|
||||
// CHECK-NEXT: i=2 j=1 k=2
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 0
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 0
|
||||
// CHECK-NEXT: i=2 j=0 k=0
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 0
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 0
|
||||
// CHECK-NEXT: i=2 j=0 k=1
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// CHECK-NEXT: [range] iterator advance: 0 += 0
|
||||
// CHECK-NEXT: [range] iterator move assign
|
||||
// CHECK-NEXT: [range] iterator deref: 0
|
||||
// CHECK-NEXT: i=2 j=0 k=2
|
||||
// CHECK-NEXT: [range] iterator dtor
|
||||
// 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
|
@ -0,0 +1,51 @@
|
||||
// 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(3) num_threads(1)
|
||||
for (int i = 0; i < 3; ++i)
|
||||
#pragma omp reverse
|
||||
for (int j = 0; j < 3; ++j)
|
||||
for (int k = 0; k < 3; ++k)
|
||||
printf("i=%d j=%d k=%d\n", i, j, k);
|
||||
printf("done\n");
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
#endif /* HEADER */
|
||||
|
||||
// CHECK: do
|
||||
// CHECK-NEXT: i=0 j=2 k=0
|
||||
// CHECK-NEXT: i=0 j=2 k=1
|
||||
// CHECK-NEXT: i=0 j=2 k=2
|
||||
// CHECK-NEXT: i=0 j=1 k=0
|
||||
// CHECK-NEXT: i=0 j=1 k=1
|
||||
// CHECK-NEXT: i=0 j=1 k=2
|
||||
// CHECK-NEXT: i=0 j=0 k=0
|
||||
// CHECK-NEXT: i=0 j=0 k=1
|
||||
// CHECK-NEXT: i=0 j=0 k=2
|
||||
// CHECK-NEXT: i=1 j=2 k=0
|
||||
// CHECK-NEXT: i=1 j=2 k=1
|
||||
// CHECK-NEXT: i=1 j=2 k=2
|
||||
// CHECK-NEXT: i=1 j=1 k=0
|
||||
// CHECK-NEXT: i=1 j=1 k=1
|
||||
// CHECK-NEXT: i=1 j=1 k=2
|
||||
// CHECK-NEXT: i=1 j=0 k=0
|
||||
// CHECK-NEXT: i=1 j=0 k=1
|
||||
// CHECK-NEXT: i=1 j=0 k=2
|
||||
// CHECK-NEXT: i=2 j=2 k=0
|
||||
// CHECK-NEXT: i=2 j=2 k=1
|
||||
// CHECK-NEXT: i=2 j=2 k=2
|
||||
// CHECK-NEXT: i=2 j=1 k=0
|
||||
// CHECK-NEXT: i=2 j=1 k=1
|
||||
// CHECK-NEXT: i=2 j=1 k=2
|
||||
// CHECK-NEXT: i=2 j=0 k=0
|
||||
// CHECK-NEXT: i=2 j=0 k=1
|
||||
// CHECK-NEXT: i=2 j=0 k=2
|
||||
// CHECK-NEXT: done
|
Loading…
x
Reference in New Issue
Block a user