Author: Zahira Ammarguellat Date: 2026-10-07T12:02:17Z New Revision: 05559260278ec9d911ab9396f3258a313d229cbd
URL: https://github.com/llvm/llvm-project/commit/05559260278ec9d911ab9396f3258a313d229cbd DIFF: https://github.com/llvm/llvm-project/commit/05559260278ec9d911ab9396f3258a313d229cbd.diff LOG: [OpenMP] Tile/stripe followed by reverse (#222401) `Clang` rejects `#pragma omp tile` or `#pragma omp stripe` immediately followed by `#pragma omp reverse` on the same loop nest, emitting the error: `error: statement after '#pragma omp tile' must be a for loop` See https://godbolt.org/z/d1fsfaMe9 The correct behavior is to accept it. Bare`reverse`, bare `tile`/`stripe`, and `tile` followed by `stripe` all work. Only `tile`/`stripe` and `reverse` fails. This PR fixes the issue. Added: clang/test/OpenMP/tile_stripe_reverse_ast_print.cpp openmp/runtime/test/transform/tile/tile-stripe-reverse.c Modified: clang/include/clang/AST/StmtOpenMP.h clang/lib/AST/StmtOpenMP.cpp clang/lib/Sema/SemaOpenMP.cpp clang/test/OpenMP/stripe_messages.cpp clang/test/OpenMP/tile_messages.cpp Removed: ################################################################################ diff --git a/clang/include/clang/AST/StmtOpenMP.h b/clang/include/clang/AST/StmtOpenMP.h index 773e1153ccdfc..a070d97a6db1f 100644 --- a/clang/include/clang/AST/StmtOpenMP.h +++ b/clang/include/clang/AST/StmtOpenMP.h @@ -905,6 +905,10 @@ class OMPLoopBasedDirective : public OMPExecutableDirective { return ignoreIntraTileHint(const_cast<Stmt *>(S)); } + /// Returns the statements of \p Body that precede its last statement (the + /// nested loop), such as the user counter update `omp reverse` emits. + static ArrayRef<Stmt *> getStmtsBeforeNestedLoop(Stmt *Body); + /// Calls the specified callback function for all the loops in \p CurStmt, /// from the outermost to the innermost. /// @@ -913,6 +917,13 @@ class OMPLoopBasedDirective : public OMPExecutableDirective { /// nullptr if the loop has no such hint. Callers that need the hint (see /// checkOpenMPIterationSpace) can peel \p HintWrapper themselves; everyone /// else can ignore it. + /// + /// \p RelaxNestForPeeledTransformation allows a loop generated by a peeled + /// loop-transformation directive to hold statements before the next loop, + /// such as the user counter update `omp reverse` emits, so e.g. `omp tile` + /// accepts a nested `omp reverse` on a non-innermost loop. Only set it if the + /// caller re-emits those statements in the loops it generates; otherwise they + /// are dropped. static bool doForAllLoops(Stmt *CurStmt, bool TryImperfectlyNestedLoops, unsigned NumLoops, @@ -920,25 +931,29 @@ class OMPLoopBasedDirective : public OMPExecutableDirective { Stmt * /*HintWrapper*/)> Callback, llvm::function_ref<void(OMPLoopTransformationDirective *)> - OnTransformationCallback); + OnTransformationCallback, + bool RelaxNestForPeeledTransformation = false); static bool doForAllLoops(Stmt *CurStmt, bool TryImperfectlyNestedLoops, unsigned NumLoops, llvm::function_ref<bool(unsigned, Stmt *)> Callback, llvm::function_ref<void(OMPLoopTransformationDirective *)> - OnTransformationCallback) { + OnTransformationCallback, + bool RelaxNestForPeeledTransformation = false) { auto &&NewCallback = [Callback](unsigned Cnt, Stmt *Loop, Stmt *) { return Callback(Cnt, Loop); }; return doForAllLoops(CurStmt, TryImperfectlyNestedLoops, NumLoops, - NewCallback, OnTransformationCallback); + NewCallback, OnTransformationCallback, + RelaxNestForPeeledTransformation); } static bool doForAllLoops(const Stmt *CurStmt, bool TryImperfectlyNestedLoops, unsigned NumLoops, llvm::function_ref<bool(unsigned, const Stmt *)> Callback, llvm::function_ref<void(const OMPLoopTransformationDirective *)> - OnTransformationCallback) { + OnTransformationCallback, + bool RelaxNestForPeeledTransformation = false) { auto &&NewCallback = [Callback](unsigned Cnt, Stmt *CurStmt) { return Callback(Cnt, CurStmt); }; @@ -947,28 +962,31 @@ class OMPLoopBasedDirective : public OMPExecutableDirective { OnTransformationCallback(A); }; return doForAllLoops(const_cast<Stmt *>(CurStmt), TryImperfectlyNestedLoops, - NumLoops, NewCallback, NewTransformCb); + NumLoops, NewCallback, NewTransformCb, + RelaxNestForPeeledTransformation); } /// 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) { + static bool doForAllLoops(Stmt *CurStmt, bool TryImperfectlyNestedLoops, + unsigned NumLoops, + llvm::function_ref<bool(unsigned, Stmt *)> Callback, + bool RelaxNestForPeeledTransformation = false) { auto &&TransformCb = [](OMPLoopTransformationDirective *) {}; return doForAllLoops(CurStmt, TryImperfectlyNestedLoops, NumLoops, Callback, - TransformCb); + TransformCb, RelaxNestForPeeledTransformation); } static bool doForAllLoops(const Stmt *CurStmt, bool TryImperfectlyNestedLoops, unsigned NumLoops, - llvm::function_ref<bool(unsigned, const Stmt *)> Callback) { + llvm::function_ref<bool(unsigned, const Stmt *)> Callback, + bool RelaxNestForPeeledTransformation = false) { auto &&NewCallback = [Callback](unsigned Cnt, const Stmt *CurStmt) { return Callback(Cnt, CurStmt); }; return doForAllLoops(const_cast<Stmt *>(CurStmt), TryImperfectlyNestedLoops, - NumLoops, NewCallback); + NumLoops, NewCallback, + RelaxNestForPeeledTransformation); } /// Calls the specified callback function for all the loop bodies in \p diff --git a/clang/lib/AST/StmtOpenMP.cpp b/clang/lib/AST/StmtOpenMP.cpp index 555e3454816ce..5ba11b2d8c42f 100644 --- a/clang/lib/AST/StmtOpenMP.cpp +++ b/clang/lib/AST/StmtOpenMP.cpp @@ -34,6 +34,13 @@ Stmt *OMPLoopBasedDirective::ignoreIntraTileHint(Stmt *S) { return S; } +ArrayRef<Stmt *> OMPLoopBasedDirective::getStmtsBeforeNestedLoop(Stmt *Body) { + auto *CS = dyn_cast_or_null<CompoundStmt>(Body); + if (!CS || CS->size() < 2) + return {}; + return ArrayRef(CS->body_begin(), CS->size() - 1); +} + /// Like `Stmt::IgnoreContainers`, but stops at (and preserves) an intra-tile /// hint wrapper so that the enclosing loop walker can deliver the hint to /// loop-associated analysis (e.g. `collapse`). Non-hint attributed statements @@ -133,10 +140,12 @@ OMPLoopBasedDirective::tryToFindNextInnerLoop(Stmt *CurStmt, for (Stmt *S : CS->body()) { if (!S) continue; - // Peek past an OMPCanonicalLoop wrapper and/or an intra-tile hint to - // check whether this child is loop-like; keep the original (wrapped) - // node in CurStmt so the hint still reaches the loop-analysis + // Peek past a single-child container so a nested loop or + // loop-transformation directive (e.g. `#pragma omp reverse` inside + // the body of an outer loop) is recognized here. Preserve any + // intra-tile hint wrapper so it still reaches the loop-analysis // callback. + S = ignoreContainersKeepingIntraTileHint(S); Stmt *Inner = S; if (auto *CanonLoop = dyn_cast<OMPCanonicalLoop>(Inner)) Inner = CanonLoop->getLoopStmt(); @@ -152,7 +161,7 @@ OMPLoopBasedDirective::tryToFindNextInnerLoop(Stmt *CurStmt, CurStmt = S; continue; } - S = S->IgnoreContainers(); + S = OMPLoopBasedDirective::ignoreIntraTileHint(S); if (auto *InnerCS = dyn_cast_or_null<CompoundStmt>(S)) NextStatements.push_back(InnerCS); } @@ -174,9 +183,15 @@ bool OMPLoopBasedDirective::doForAllLoops( Stmt *CurStmt, bool TryImperfectlyNestedLoops, unsigned NumLoops, llvm::function_ref<bool(unsigned, Stmt *, Stmt *)> Callback, llvm::function_ref<void(OMPLoopTransformationDirective *)> - OnTransformationCallback) { + OnTransformationCallback, + bool RelaxNestForPeeledTransformation) { CurStmt = ignoreContainersKeepingIntraTileHint(CurStmt); for (unsigned Cnt = 0; Cnt < NumLoops; ++Cnt) { + // A loop generated by a peeled transformation may hold statements before + // the next loop (e.g. `reverse` recomputes the user counter there). They + // are not intervening user code, so callers that re-emit them may look + // through them; the rest of the nest is still checked as usual. + bool PeeledTransformation = false; while (true) { auto *Dir = dyn_cast<OMPLoopTransformationDirective>(CurStmt); if (!Dir) @@ -202,6 +217,7 @@ bool OMPLoopBasedDirective::doForAllLoops( } CurStmt = TransformedStmt; + PeeledTransformation = true; } if (auto *CanonLoop = dyn_cast<OMPCanonicalLoop>(CurStmt)) CurStmt = CanonLoop->getLoopStmt(); @@ -223,6 +239,9 @@ bool OMPLoopBasedDirective::doForAllLoops( "Expected canonical for or range-based for loops."); CurStmt = cast<CXXForRangeStmt>(LoopStmt)->getBody(); } + if (RelaxNestForPeeledTransformation && PeeledTransformation && + !getStmtsBeforeNestedLoop(CurStmt).empty()) + CurStmt = cast<CompoundStmt>(CurStmt)->body_back(); CurStmt = OMPLoopBasedDirective::tryToFindNextInnerLoop( CurStmt, TryImperfectlyNestedLoops); } diff --git a/clang/lib/Sema/SemaOpenMP.cpp b/clang/lib/Sema/SemaOpenMP.cpp index efc570a8933f1..d0cf844acf6db 100644 --- a/clang/lib/Sema/SemaOpenMP.cpp +++ b/clang/lib/Sema/SemaOpenMP.cpp @@ -15376,6 +15376,11 @@ bool SemaOpenMP::checkTransformableLoopNest( SmallVectorImpl<OMPLoopBasedDirective::HelperExprs> &LoopHelpers, Stmt *&Body, SmallVectorImpl<SmallVector<Stmt *>> &OriginalInits) { OriginalInits.emplace_back(); + // Only tile and stripe re-emit the statements before the nested loop in a + // nested transformation's generated loop (see appendStmtsBeforeNestedLoop); + // other constructs would silently drop them. + bool RelaxNestForPeeledTransformation = + Kind == OMPD_tile || Kind == OMPD_stripe; bool Result = OMPLoopBasedDirective::doForAllLoops( AStmt->IgnoreContainers(), /*TryImperfectlyNestedLoops=*/false, NumLoops, [this, &LoopHelpers, &Body, &OriginalInits, @@ -15411,7 +15416,8 @@ bool SemaOpenMP::checkTransformableLoopNest( }, [&OriginalInits](OMPLoopTransformationDirective *Transform) { updatePreInits(Transform, OriginalInits.back()); - }); + }, + RelaxNestForPeeledTransformation); assert(OriginalInits.back().empty() && "No preinit after innermost loop"); OriginalInits.pop_back(); return Result; @@ -15713,7 +15719,8 @@ static void addLoopPreInits(ASTContext &Context, /// Collect the loop statements (ForStmt or CXXRangeForStmt) of the affected /// loop of a construct. -static void collectLoopStmts(Stmt *AStmt, MutableArrayRef<Stmt *> LoopStmts) { +static void collectLoopStmts(Stmt *AStmt, MutableArrayRef<Stmt *> LoopStmts, + bool RelaxNestForPeeledTransformation = false) { size_t NumLoops = LoopStmts.size(); OMPLoopBasedDirective::doForAllLoops( AStmt, /*TryImperfectlyNestedLoops=*/false, NumLoops, @@ -15721,11 +15728,25 @@ static void collectLoopStmts(Stmt *AStmt, MutableArrayRef<Stmt *> LoopStmts) { assert(!LoopStmts[Cnt] && "Loop statement must not yet be assigned"); LoopStmts[Cnt] = CurStmt; return false; - }); + }, + RelaxNestForPeeledTransformation); assert(!is_contained(LoopStmts, nullptr) && "Expecting a loop statement for each affected loop"); } +/// A non-innermost affected loop generated by a nested loop transformation +/// (e.g. `omp reverse`) computes the user counter in statements ahead of the +/// next loop. A construct that rebuilds the nest only keeps the innermost +/// body, so it must re-emit these statements in the matching loop. +static void appendStmtsBeforeNestedLoop(Stmt *LoopStmt, + SmallVectorImpl<Stmt *> &BodyParts) { + Stmt *Body = isa<ForStmt>(LoopStmt) + ? cast<ForStmt>(LoopStmt)->getBody() + : cast<CXXForRangeStmt>(LoopStmt)->getBody(); + llvm::append_range(BodyParts, + OMPLoopBasedDirective::getStmtsBeforeNestedLoop(Body)); +} + /// Build and return a DeclRefExpr for the floor induction variable using the /// SemaRef and the provided parameters. static Expr *makeFloorIVRef(Sema &SemaRef, ArrayRef<VarDecl *> FloorIndVars, @@ -15774,7 +15795,7 @@ StmtResult SemaOpenMP::ActOnOpenMPTileDirective(ArrayRef<OMPClause *> Clauses, // Collect all affected loop statements. SmallVector<Stmt *> LoopStmts(NumLoops, nullptr); - collectLoopStmts(AStmt, LoopStmts); + collectLoopStmts(AStmt, LoopStmts, /*RelaxNestForPeeledTransformation=*/true); SmallVector<Stmt *, 4> PreInits; CaptureVars CopyTransformer(SemaRef); @@ -16023,6 +16044,8 @@ StmtResult SemaOpenMP::ActOnOpenMPTileDirective(ArrayRef<OMPClause *> Clauses, BodyParts.append(LoopHelper.Updates.begin(), LoopHelper.Updates.end()); if (auto *SourceCXXFor = dyn_cast<CXXForRangeStmt>(LoopStmt)) BodyParts.push_back(SourceCXXFor->getLoopVarStmt()); + if (static_cast<unsigned>(I) + 1 < NumLoops) + appendStmtsBeforeNestedLoop(LoopStmt, BodyParts); BodyParts.push_back(Inner); Inner = CompoundStmt::Create(Context, BodyParts, FPOptionsOverride(), Inner->getBeginLoc(), Inner->getEndLoc()); @@ -16131,7 +16154,7 @@ StmtResult SemaOpenMP::ActOnOpenMPStripeDirective(ArrayRef<OMPClause *> Clauses, // Collect all affected loop statements. SmallVector<Stmt *> LoopStmts(NumLoops, nullptr); - collectLoopStmts(AStmt, LoopStmts); + collectLoopStmts(AStmt, LoopStmts, /*RelaxNestForPeeledTransformation=*/true); SmallVector<Stmt *, 4> PreInits; CaptureVars CopyTransformer(SemaRef); @@ -16293,6 +16316,8 @@ StmtResult SemaOpenMP::ActOnOpenMPStripeDirective(ArrayRef<OMPClause *> Clauses, BodyParts.append(LoopHelper.Updates.begin(), LoopHelper.Updates.end()); if (auto *SourceCXXFor = dyn_cast<CXXForRangeStmt>(LoopStmt)) BodyParts.push_back(SourceCXXFor->getLoopVarStmt()); + if (static_cast<unsigned>(I) + 1 < NumLoops) + appendStmtsBeforeNestedLoop(LoopStmt, BodyParts); BodyParts.push_back(Inner); Inner = CompoundStmt::Create(Context, BodyParts, FPOptionsOverride(), Inner->getBeginLoc(), Inner->getEndLoc()); diff --git a/clang/test/OpenMP/stripe_messages.cpp b/clang/test/OpenMP/stripe_messages.cpp index 3f171f40d71d9..2e4675c35cc6c 100644 --- a/clang/test/OpenMP/stripe_messages.cpp +++ b/clang/test/OpenMP/stripe_messages.cpp @@ -169,3 +169,16 @@ void f() { ; } } // namespace GH139433 + +// The loop generated by 'reverse' may be a non-innermost affected loop, but +// user code between it and the next affected loop is still intervening code. +void stripe_of_reverse_intervening() { + // expected-error@+3 {{statement after '#pragma omp stripe' must be a for loop}} +#pragma omp stripe sizes(2, 2) +#pragma omp reverse + for (int i = 0; i < 7; ++i) { + int k = 3; + for (int j = 0; j < 7; ++j) + ; + } +} diff --git a/clang/test/OpenMP/tile_messages.cpp b/clang/test/OpenMP/tile_messages.cpp index 021262ac9a54a..72ceef71566d6 100644 --- a/clang/test/OpenMP/tile_messages.cpp +++ b/clang/test/OpenMP/tile_messages.cpp @@ -210,3 +210,16 @@ void unroll_of_tile() { ; } } // namespace consumed_by_transformation + +// The loop generated by 'reverse' may be a non-innermost affected loop, but +// user code between it and the next affected loop is still intervening code. +void tile_of_reverse_intervening() { + // expected-error@+3 {{statement after '#pragma omp tile' must be a for loop}} +#pragma omp tile sizes(2, 2) +#pragma omp reverse + for (int i = 0; i < 7; ++i) { + int k = 3; + for (int j = 0; j < 7; ++j) + ; + } +} diff --git a/clang/test/OpenMP/tile_stripe_reverse_ast_print.cpp b/clang/test/OpenMP/tile_stripe_reverse_ast_print.cpp new file mode 100644 index 0000000000000..5eee992f50483 --- /dev/null +++ b/clang/test/OpenMP/tile_stripe_reverse_ast_print.cpp @@ -0,0 +1,79 @@ +// Check no warnings/errors +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 \ +// RUN: -fsyntax-only -verify %s +// expected-no-diagnostics + +// Check AST and unparsing +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 \ +// RUN: -ast-print %s | FileCheck %s --check-prefix=PRINT + +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 \ +// RUN: -emit-pch -o %t %s +// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -fopenmp -fopenmp-version=60 \ +// RUN: -include-pch %t -ast-print %s | FileCheck %s --check-prefix=PRINT + +#ifndef HEADER +#define HEADER + +extern "C" void body(...); + +// PRINT-LABEL: void tile_then_reverse( +void tile_then_reverse() { + // PRINT: #pragma omp tile sizes(2, 2) + // PRINT: #pragma omp reverse +#pragma omp tile sizes(2, 2) +#pragma omp reverse + for (int i = 0; i < 20; ++i) + for (int j = 0; j < 20; ++j) + body(i, j); +} + +// PRINT-LABEL: void stripe_then_reverse( +void stripe_then_reverse() { + // PRINT: #pragma omp stripe sizes(2, 2) + // PRINT: #pragma omp reverse +#pragma omp stripe sizes(2, 2) +#pragma omp reverse + for (int i = 0; i < 20; ++i) + for (int j = 0; j < 20; ++j) + body(i, j); +} + +// PRINT-LABEL: void tile_over_inner_reverse( +void tile_over_inner_reverse() { + // PRINT: #pragma omp tile sizes(2, 2) +#pragma omp tile sizes(2, 2) +#pragma omp reverse + for (int j = 0; j < 20; ++j) { + // PRINT: #pragma omp reverse +#pragma omp reverse + for (int i = 0; i < 20; ++i) + body(j, i); + } +} + +// PRINT-LABEL: void stripe_over_inner_reverse( +void stripe_over_inner_reverse() { + // PRINT: #pragma omp stripe sizes(2, 2) +#pragma omp stripe sizes(2, 2) +#pragma omp reverse + for (int j = 0; j < 20; ++j) { + // PRINT: #pragma omp reverse +#pragma omp reverse + for (int i = 0; i < 20; ++i) + body(j, i); + } +} + +// PRINT-LABEL: void for_collapse_then_reverse( +void for_collapse_then_reverse() { + // PRINT: #pragma omp for collapse(2) + // PRINT: #pragma omp reverse +#pragma omp for collapse(2) +#pragma omp reverse + for (int j = 0; j < 20; j += 2) + for (int i = 0; i < 20; i += 3) + body(j, i); +} + +#endif diff --git a/openmp/runtime/test/transform/tile/tile-stripe-reverse.c b/openmp/runtime/test/transform/tile/tile-stripe-reverse.c new file mode 100644 index 0000000000000..7f599737b1e8a --- /dev/null +++ b/openmp/runtime/test/transform/tile/tile-stripe-reverse.c @@ -0,0 +1,94 @@ +// RUN: %libomp-compile -fopenmp-version=60 && %libomp-run \ +// RUN: | FileCheck %s --match-full-lines + +// tile/stripe over a reverse that is not the innermost affected loop must +// keep the reversal (regression test for #222401). + +#ifndef HEADER +#define HEADER + +#include <stdio.h> +#include <stdlib.h> + +int main() { + printf("tile\n"); +#pragma omp tile sizes(2, 2) +#pragma omp reverse + for (int i = 0; i < 4; ++i) + for (int j = 0; j < 4; ++j) + printf("i=%d j=%d\n", i, j); + + printf("stripe\n"); +#pragma omp stripe sizes(2, 2) +#pragma omp reverse + for (int i = 0; i < 4; ++i) + for (int j = 0; j < 4; ++j) + printf("i=%d j=%d\n", i, j); + + printf("nested\n"); +#pragma omp tile sizes(2, 2) +#pragma omp reverse + for (int j = 0; j < 4; ++j) { +#pragma omp reverse + for (int i = 0; i < 4; ++i) + printf("j=%d i=%d\n", j, i); + } + + printf("done\n"); + return EXIT_SUCCESS; +} + +#endif /* HEADER */ + +// CHECK: tile +// CHECK-NEXT: i=3 j=0 +// CHECK-NEXT: i=3 j=1 +// CHECK-NEXT: i=2 j=0 +// CHECK-NEXT: i=2 j=1 +// CHECK-NEXT: i=3 j=2 +// CHECK-NEXT: i=3 j=3 +// CHECK-NEXT: i=2 j=2 +// CHECK-NEXT: i=2 j=3 +// CHECK-NEXT: i=1 j=0 +// CHECK-NEXT: i=1 j=1 +// CHECK-NEXT: i=0 j=0 +// CHECK-NEXT: i=0 j=1 +// CHECK-NEXT: i=1 j=2 +// CHECK-NEXT: i=1 j=3 +// CHECK-NEXT: i=0 j=2 +// CHECK-NEXT: i=0 j=3 +// CHECK-NEXT: stripe +// CHECK-NEXT: i=3 j=0 +// CHECK-NEXT: i=3 j=1 +// CHECK-NEXT: i=2 j=0 +// CHECK-NEXT: i=2 j=1 +// CHECK-NEXT: i=3 j=2 +// CHECK-NEXT: i=3 j=3 +// CHECK-NEXT: i=2 j=2 +// CHECK-NEXT: i=2 j=3 +// CHECK-NEXT: i=1 j=0 +// CHECK-NEXT: i=1 j=1 +// CHECK-NEXT: i=0 j=0 +// CHECK-NEXT: i=0 j=1 +// CHECK-NEXT: i=1 j=2 +// CHECK-NEXT: i=1 j=3 +// CHECK-NEXT: i=0 j=2 +// CHECK-NEXT: i=0 j=3 +// CHECK-NEXT: nested +// CHECK-NEXT: j=3 i=3 +// CHECK-NEXT: j=3 i=2 +// CHECK-NEXT: j=2 i=3 +// CHECK-NEXT: j=2 i=2 +// CHECK-NEXT: j=3 i=1 +// CHECK-NEXT: j=3 i=0 +// CHECK-NEXT: j=2 i=1 +// CHECK-NEXT: j=2 i=0 +// CHECK-NEXT: j=1 i=3 +// CHECK-NEXT: j=1 i=2 +// CHECK-NEXT: j=0 i=3 +// CHECK-NEXT: j=0 i=2 +// CHECK-NEXT: j=1 i=1 +// CHECK-NEXT: j=1 i=0 +// CHECK-NEXT: j=0 i=1 +// CHECK-NEXT: j=0 i=0 +// CHECK-NEXT: done _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
