[Mlir-commits] [llvm] [mlir] [MLIR][OpenMP] add interchange operation in the OpenMP mlir dialect (PR #186381)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Fri Mar 13 05:28:04 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir
Author: Ferran Toda (NouTimbaler)
<details>
<summary>Changes</summary>
This patch adds the `interchange` operation for the OpenMP MLIR dialect. This patch also contains the translation to LLVMIR using the IRBuilder.
This is a continuation of #<!-- -->183435
---
Patch is 28.79 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/186381.diff
9 Files Affected:
- (modified) llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h (+14)
- (modified) llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp (+84)
- (modified) mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td (+26)
- (modified) mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td (+19)
- (modified) mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp (+119-24)
- (modified) mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp (+43)
- (added) mlir/test/Dialect/OpenMP/cli-interchange.mlir (+63)
- (added) mlir/test/Dialect/OpenMP/invalid-interchange.mlir (+167)
- (added) mlir/test/Target/LLVMIR/openmp-cli-interchange01.mlir (+86)
``````````diff
diff --git a/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h b/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h
index 9885ffc8b2065..1fa23c26f3e69 100644
--- a/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h
+++ b/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h
@@ -1433,6 +1433,20 @@ class OpenMPIRBuilder {
/// \param Loop The loop to unroll. The loop will be invalidated.
LLVM_ABI void unrollLoopFull(DebugLoc DL, CanonicalLoopInfo *Loop);
+ /// Interchange the order of the nested loops.
+ ///
+ /// @param DL Debug location for instructions added by interchange.
+ /// @param Loops Loops affected by loop interchange. The
+ /// CanonicalLoopInfo objects are invalidated by this method, i.e. should not
+ /// used after interchange.
+ /// @param Permutation The new order in wich the \p Loops will be arranged.
+ ///
+ /// \returns A list of generated loops. Contains the same loops as the input
+ /// loop nest reordered.
+ LLVM_ABI std::vector<CanonicalLoopInfo *>
+ interchangeLoops(DebugLoc DL, ArrayRef<CanonicalLoopInfo *> Loops,
+ std::vector<int> Permutation);
+
/// Fully or partially unroll a loop. How the loop is unrolled is determined
/// using LLVM's LoopUnrollPass.
///
diff --git a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
index bde653d4e56ed..8791770eb34a9 100644
--- a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
+++ b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
@@ -6761,6 +6761,90 @@ void OpenMPIRBuilder::unrollLoopHeuristic(DebugLoc, CanonicalLoopInfo *Loop) {
});
}
+std::vector<CanonicalLoopInfo *>
+OpenMPIRBuilder::interchangeLoops(DebugLoc DL,
+ ArrayRef<CanonicalLoopInfo *> Loops,
+ std::vector<int> Permutation) {
+ assert(Permutation.size() == Loops.size() &&
+ "The permutation must have every input loop");
+ int NumLoops = Loops.size();
+ assert(NumLoops >= 2 && "At least two loops to interchange required");
+
+ CanonicalLoopInfo *OutermostLoop = Loops.front();
+ CanonicalLoopInfo *InnermostLoop = Loops.back();
+ Function *F = OutermostLoop->getBody()->getParent();
+
+ // Loop control blocks that may become orphaned later.
+ SmallVector<BasicBlock *> OldControlBBs;
+ for (CanonicalLoopInfo *Loop : Loops)
+ Loop->collectControlBlocks(OldControlBBs);
+
+ // Create the new loop nest structure
+ std::vector<CanonicalLoopInfo *> Result;
+
+ BasicBlock *Enter = OutermostLoop->getPreheader();
+ BasicBlock *Continue = OutermostLoop->getAfter();
+ BasicBlock *OutroInsertBefore = InnermostLoop->getExit();
+
+ for (int i = 0; i < NumLoops; i++) {
+ int loop_n = Permutation[i] - 1;
+ assert(Loops[loop_n]->isValid() &&
+ "All input loops must be valid canonical loops");
+
+ CanonicalLoopInfo *newLoop = createLoopSkeleton(
+ DL, Loops[loop_n]->getTripCount(), F, InnermostLoop->getBody(),
+ OutroInsertBefore, "interchange" + Twine(i));
+ redirectAllPredecessorsTo(Loops[i]->getPreheader(), newLoop->getPreheader(),
+ DL);
+ redirectTo(Enter, newLoop->getPreheader(), DL);
+ redirectTo(newLoop->getAfter(), Continue, DL);
+
+ // Setup the position where the next loop connects to this loop.
+ Enter = newLoop->getBody();
+ Continue = newLoop->getLatch();
+ OutroInsertBefore = newLoop->getLatch();
+
+ Result.push_back(newLoop);
+ }
+
+ // Detach the original loops
+ for (int i = 0; i < NumLoops - 1; i++) {
+
+ BasicBlock *body = Loops[i]->getBody();
+ BasicBlock *region =
+ cast<BranchInst>(body->getTerminator())->getSuccessor(0);
+ BasicBlock *cont = Loops[i]->getLatch()->getUniquePredecessor();
+
+ // Add the old "bodies" to delete
+ OldControlBBs.push_back(body);
+ OldControlBBs.push_back(region);
+ OldControlBBs.push_back(cont);
+ }
+ // Append the original loop nest body into the generated loop nest body.
+ redirectTo(Enter, InnermostLoop->getBody(), DL);
+ redirectAllPredecessorsTo(InnermostLoop->getLatch(), Continue, DL);
+
+ // Replace the original induction variable with the new induction variable
+ Builder.restoreIP(Result.back()->getBodyIP());
+ for (int i = 0; i < NumLoops; ++i) {
+ int loop_n = Permutation[i] - 1;
+ Value *OrigIndVar = Loops[loop_n]->getIndVar();
+ OrigIndVar->replaceAllUsesWith(Result[i]->getIndVar());
+ }
+
+ // Remove unused parts of the original loops.
+ removeUnusedBlocksFromParent(OldControlBBs);
+
+ for (CanonicalLoopInfo *L : Loops)
+ L->invalidate();
+
+#ifndef NDEBUG
+ for (CanonicalLoopInfo *GenL : Result)
+ GenL->assertOK();
+#endif
+ return Result;
+}
+
void OpenMPIRBuilder::createIfVersion(CanonicalLoopInfo *CanonicalLoop,
Value *IfCond, ValueToValueMapTy &VMap,
LoopAnalysis &LIA, LoopInfo &LI, Loop *L,
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td
index 23c2fbdfd7368..c1c291f1487b6 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td
@@ -1131,6 +1131,7 @@ class OpenMP_LooprangeClauseSkip<
bit description = false, bit extraClassDeclaration = false>
: OpenMP_Clause<traits, arguments, assemblyFormat, description,
extraClassDeclaration> {
+
let arguments = (ins OptionalAttr<I64Attr>:$first,
OptionalAttr<I64Attr>:$count);
@@ -1148,6 +1149,31 @@ class OpenMP_LooprangeClauseSkip<
def OpenMP_LooprangeClause : OpenMP_LooprangeClauseSkip<>;
+//===----------------------------------------------------------------------===//
+// V6.0: `permutation` clause
+//===----------------------------------------------------------------------===//
+
+class OpenMP_PermutationClauseSkip<
+ bit traits = false, bit arguments = false, bit assemblyFormat = false,
+ bit description = false, bit extraClassDeclaration = false>
+ : OpenMP_Clause<traits, arguments, assemblyFormat, description,
+ extraClassDeclaration> {
+
+ let arguments = (ins OptionalAttr<ArrayAttr>:$permutation);
+
+ let optAssemblyFormat = [{
+ `permutation` `(` $permutation `)`
+ }];
+
+ let description = [{
+ The `permutation` clause contains the new order in which the loops affected
+ by a loop interchange are arranged. The clause contains a list of positive
+ numbers known beforehand.
+ }];
+}
+
+def OpenMP_PermutationClause : OpenMP_PermutationClauseSkip<>;
+
//===----------------------------------------------------------------------===//
// V5.2: [10.1.2] `num_threads` clause
//===----------------------------------------------------------------------===//
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index 88c8ab4f6f949..64743d3e7c365 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -577,6 +577,25 @@ def FuseOp
let hasVerifier = 1;
}
+//===----------------------------------------------------------------------===//
+// OpenMP interchange operation
+//===----------------------------------------------------------------------===//
+
+def InterchangeOp
+ : OpenMPTransformBase_Op<"interchange",
+ clauses = [OpenMP_PermutationClause]> {
+ let summary = "OpenMP interchange operation";
+ let description = [{
+ Represents the OpenMP interchange directive introduced in OpenMP 6.0.
+
+ The construct takes a loop nest and rearranges the affected loops in the
+ order specified by the `permutation` clause. The `permutation clause must
+ be present and its elements known beforehand.
+ }]#clausesDescription;
+
+ let hasVerifier = 1;
+}
+
//===----------------------------------------------------------------------===//
// 2.8.3 Workshare Construct
//===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
index e0559e850faf6..43bc13d40fbd9 100644
--- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
+++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
@@ -3517,6 +3517,8 @@ void NewCliOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
else
return "fused";
})
+ .Case(
+ [&](InterchangeOp op) -> std::string { return "interchange"; })
.Case([&](TileOp op) -> std::string {
auto [generateesFirst, generateesCount] =
op.getGenerateesODSOperandIndexAndLength();
@@ -3706,6 +3708,30 @@ CanonicalLoopOp::getGenerateesODSOperandIndexAndLength() {
return getODSOperandIndexAndLength(odsIndex_cli);
}
+// Canonical loop must be perfectly nested, i.e. the body of the parent must
+// only contain the omp.canonical_loop of the nested loops, and
+// omp.terminator
+bool isPerfectlyNested(CanonicalLoopOp parentLoop, CanonicalLoopOp loop) {
+ auto &parentBody = parentLoop.getRegion();
+ if (!parentBody.hasOneBlock())
+ return false;
+ auto &parentBlock = parentBody.getBlocks().front();
+
+ auto nestedLoopIt = parentBlock.begin();
+ if (nestedLoopIt == parentBlock.end() ||
+ (&*nestedLoopIt != loop.getOperation()))
+ return false;
+
+ auto termIt = std::next(nestedLoopIt);
+ if (termIt == parentBlock.end() || !isa<TerminatorOp>(termIt))
+ return false;
+
+ if (std::next(termIt) != parentBlock.end())
+ return false;
+
+ return true;
+}
+
//===----------------------------------------------------------------------===//
// UnrollHeuristicOp
//===----------------------------------------------------------------------===//
@@ -3843,30 +3869,7 @@ static LogicalResult checkApplyeesNesting(TileOp op) {
parentIVs.insert(parentLoop.getInductionVar());
- // Canonical loop must be perfectly nested, i.e. the body of the parent must
- // only contain the omp.canonical_loop of the nested loops, and
- // omp.terminator
- bool isPerfectlyNested = [&]() {
- auto &parentBody = parentLoop.getRegion();
- if (!parentBody.hasOneBlock())
- return false;
- auto &parentBlock = parentBody.getBlocks().front();
-
- auto nestedLoopIt = parentBlock.begin();
- if (nestedLoopIt == parentBlock.end() ||
- (&*nestedLoopIt != loop.getOperation()))
- return false;
-
- auto termIt = std::next(nestedLoopIt);
- if (termIt == parentBlock.end() || !isa<TerminatorOp>(termIt))
- return false;
-
- if (std::next(termIt) != parentBlock.end())
- return false;
-
- return true;
- }();
- if (!isPerfectlyNested)
+ if (!isPerfectlyNested(parentLoop, loop))
return op.emitOpError() << "tiled loop nest must be perfectly nested";
if (parentIVs.contains(loop.getTripCount()))
@@ -3964,6 +3967,98 @@ std::pair<unsigned, unsigned> FuseOp::getGenerateesODSOperandIndexAndLength() {
return getODSOperandIndexAndLength(odsIndex_generatees);
}
+//===----------------------------------------------------------------------===//
+// InterchangeOp
+//===----------------------------------------------------------------------===//
+
+static void printLoopTransformClis(OpAsmPrinter &p, InterchangeOp op,
+ OperandRange generatees,
+ OperandRange applyees) {
+ if (!generatees.empty())
+ p << '(' << llvm::interleaved(generatees) << ')';
+
+ if (!applyees.empty())
+ p << " <- (" << llvm::interleaved(applyees) << ')';
+}
+
+LogicalResult InterchangeOp::verify() {
+ if (getApplyees().size() < 2)
+ return emitOpError() << "must apply to at least two loops";
+
+ if (!getPermutation().has_value())
+ return emitOpError() << "must have permutation attribute";
+
+ auto permutation = getPermutation().value();
+ if (permutation.size() != getApplyees().size())
+ return emitOpError() << "expecting the same number of permutation "
+ "attributes and applyees";
+
+ llvm::SmallVector<bool> found(permutation.size(), false);
+ for (auto &val : permutation) {
+ int perm = llvm::dyn_cast<IntegerAttr>(val).getInt();
+ if (perm <= 0)
+ return emitOpError()
+ << "permutation attribute must be a positive integer";
+ if ((unsigned)perm - 1 < permutation.size())
+ found[perm - 1] = true;
+ }
+ for (bool b : found) {
+ if (!b)
+ return emitOpError()
+ << "every integer from 1 must appear in the permutation attribute";
+ }
+
+ if (!getGeneratees().empty() &&
+ getApplyees().size() != getGeneratees().size())
+ return emitOpError()
+ << "expecting the same number of generatees and applyees";
+
+ DenseSet<Value> parentIVs;
+
+ Value parent = getApplyees().front();
+ for (auto &&applyee : llvm::drop_begin(getApplyees())) {
+ auto [parentCreate, parentGen, parentCons] = decodeCli(parent);
+ auto [create, gen, cons] = decodeCli(applyee);
+
+ if (!parentGen)
+ return emitOpError() << "applyee CLI has no generator";
+
+ auto parentLoop = dyn_cast_or_null<CanonicalLoopOp>(parentGen->getOwner());
+ if (!parentGen)
+ return emitOpError()
+ << "currently only supports omp.canonical_loop as applyee";
+
+ parentIVs.insert(parentLoop.getInductionVar());
+
+ if (!gen)
+ return emitOpError() << "applyee CLI has no generator";
+ auto loop = dyn_cast_or_null<CanonicalLoopOp>(gen->getOwner());
+ if (!loop)
+ return emitOpError()
+ << "currently only supports omp.canonical_loop as applyee";
+
+ if (!isPerfectlyNested(parentLoop, loop))
+ return emitOpError() << "interchanged loop nest must be perfectly nested";
+
+ if (parentIVs.contains(loop.getTripCount()))
+ return emitOpError() << "interchanged loop nest must be rectangular";
+
+ parent = applyee;
+ }
+
+ return success();
+}
+
+std::pair<unsigned, unsigned>
+InterchangeOp::getApplyeesODSOperandIndexAndLength() {
+ return getODSOperandIndexAndLength(odsIndex_applyees);
+}
+
+std::pair<unsigned, unsigned>
+InterchangeOp::getGenerateesODSOperandIndexAndLength() {
+ return getODSOperandIndexAndLength(odsIndex_generatees);
+}
+
//===----------------------------------------------------------------------===//
// Critical construct (2.17.1)
//===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 3e7a6c88aec3a..09bc40f379c22 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -3855,6 +3855,46 @@ static LogicalResult applyFuse(omp::FuseOp op, llvm::IRBuilderBase &builder,
return success();
}
+/// Apply a `#pragma omp interchange` / `!$omp interchange` transformation using
+/// the OpenMPIRBuilder.
+static LogicalResult
+applyInterchange(omp::InterchangeOp op, llvm::IRBuilderBase &builder,
+ LLVM::ModuleTranslation &moduleTranslation) {
+ llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
+ llvm::OpenMPIRBuilder::LocationDescription loc(builder);
+
+ SmallVector<llvm::CanonicalLoopInfo *> translatedLoops;
+
+ for (Value applyee : op.getApplyees()) {
+ llvm::CanonicalLoopInfo *consBuilderCLI =
+ moduleTranslation.lookupOMPLoop(applyee);
+ assert(applyee && "Canonical loop must already been translated");
+ translatedLoops.push_back(consBuilderCLI);
+ }
+
+ auto perm = op.getPermutation().value();
+ std::vector<int> permutation;
+ for (auto &val : perm) {
+ auto intVal = llvm::dyn_cast<mlir::IntegerAttr>(val);
+ assert(intVal && "permutation attributes must be integers");
+ permutation.push_back(intVal.getInt());
+ }
+
+ auto generatedLoops =
+ ompBuilder->interchangeLoops(loc.DL, translatedLoops, permutation);
+ if (!op.getGeneratees().empty()) {
+ for (auto [mlirLoop, genLoop] :
+ zip_equal(op.getGeneratees(), generatedLoops))
+ moduleTranslation.mapOmpLoop(mlirLoop, genLoop);
+ }
+
+ // CLIs can only be consumed once
+ for (Value applyee : op.getApplyees())
+ moduleTranslation.invalidateOmpLoop(applyee);
+
+ return success();
+}
+
/// Convert an Atomic Ordering attribute to llvm::AtomicOrdering.
static llvm::AtomicOrdering
convertAtomicOrdering(std::optional<omp::ClauseMemoryOrderKind> ao) {
@@ -7335,6 +7375,9 @@ LogicalResult OpenMPDialectLLVMIRTranslationInterface::convertOperation(
.Case([&](omp::FuseOp op) {
return applyFuse(op, builder, moduleTranslation);
})
+ .Case([&](omp::InterchangeOp op) {
+ return applyInterchange(op, builder, moduleTranslation);
+ })
.Case([&](omp::TargetAllocMemOp) {
return convertTargetAllocMemOp(*op, builder, moduleTranslation);
})
diff --git a/mlir/test/Dialect/OpenMP/cli-interchange.mlir b/mlir/test/Dialect/OpenMP/cli-interchange.mlir
new file mode 100644
index 0000000000000..a177c2a898ca6
--- /dev/null
+++ b/mlir/test/Dialect/OpenMP/cli-interchange.mlir
@@ -0,0 +1,63 @@
+// RUN: mlir-opt %s | FileCheck %s --enable-var-scope
+// RUN: mlir-opt %s | mlir-opt | FileCheck %s --enable-var-scope
+
+
+// Raw syntax check (MLIR output is always pretty-printed)
+// CHECK-LABEL: @omp_interchange_raw(
+// CHECK-SAME: %[[tc1:.+]]: i32, %[[tc2:.+]]: i32) {
+func.func @omp_interchange_raw(%tc1 : i32, %tc2 : i32) -> () {
+ // CHECK-NEXT: %canonloop = omp.new_cli
+ %cli_outer = "omp.new_cli" () : () -> (!omp.cli)
+ // CHECK-NEXT: %canonloop_d1 = omp.new_cli
+ %cli_inner = "omp.new_cli" () : () -> (!omp.cli)
+ // CHECK-NEXT: %interchange = omp.new_cli
+ %interchange1 = "omp.new_cli" () : () -> (!omp.cli)
+ // CHECK-NEXT: %interchange_0 = omp.new_cli
+ %interchange2 = "omp.new_cli" () : () -> (!omp.cli)
+ // CHECK-NEXT: omp.canonical_loop(%canonloop) %iv : i32 in range(%[[tc1]]) {
+ "omp.canonical_loop" (%tc1, %cli_outer) ({
+ ^bb0(%iv: i32):
+ // CHECK-NEXT: omp.canonical_loop(%canonloop_d1) %iv_d1 : i32 in range(%[[tc2]]) {
+ "omp.canonical_loop" (%tc2, %cli_inner) ({
+ ^bb0(%iv_d1: i32):
+ // CHECK: omp.terminator
+ omp.terminator
+ }) : (i32, !omp.cli) -> ()
+ // CHECK: omp.terminator
+ omp.terminator
+ }) : (i32, !omp.cli) -> ()
+ // CHECK: omp.interchange (%interchange, %interchange_0) <- (%canonloop, %canonloop_d1)
+ // CHECK-SAME: permutation([2 : i32, 1 : i32])
+ "omp.interchange" (%interchange1, %interchange2, %cli_outer, %cli_inner) <{operandSegmentSizes = array<i32: 2, 2>, permutation = [2 : i32, 1 : i32]}> : (!omp.cli, !omp.cli, !omp.cli, !omp.cli) -> ()
+ return
+}
+
+
+// Pretty syntax check
+// CHECK-LABEL: @omp_interchange_pretty(
+// CHECK-SAME: %[[tc1:.+]]: i32, %[[tc2:.+]]: i32) {
+func.func @omp_interchange_pretty(%tc1 : i32, %tc2 : i32) -> () {
+ // CHECK-NEXT: %canonloop = omp.new_cli
+ %cli_outer = omp.new_cli
+ // CHECK-NEXT: %canonloop_d1 = omp.new_cli
+ %cli_inner = omp.new_cli
+ // CHECK-NEXT: %interchange = omp.new_cli
+ %interchange1 = omp.new_cli
+ // CHECK-NEXT: %interchange_0 = omp.new_cli
+ %interchange2 = omp.new_cli
+ // CHECK-NEXT: omp.canonical_loop(%canonloop) %iv : i32 in range(%[[tc1]]) {
+ omp.canonical_loop(%cli_outer) %iv_outer : i32 in range(%tc1) {
+ // CHECK-NEXT: omp.canonical_loop(%canonloop_d1) %iv_d1 : i32 in range(%[[tc2]]) {
+ omp.canonical_loop(%cli_inner) %iv_inner : i32 in range(%tc2) {
+ // CHECK: omp.terminator
+ omp.terminator
+ }
+ // CHECK: omp.terminator
+ omp.terminator
+ }
+ // CHECK: omp.interchange (%interchange, %interchange_0) <- (%canonloop, %canonloop_d1)
+ // CHECK-SAME: permutation([2 : i32, 1 : i32])
+ omp.interchange (%interchange1, %interchange2) <- (%cli_outer, %cli_inner) permutation([2 : i32, 1 : i32])
+ return
+}
+
diff --git a/mlir/test/Dialect/OpenMP/invalid-interchange.mlir b/mlir/test/Dialect/OpenMP/invalid-interchange.mlir
new file mode 100644
index 0000000000000..563a103f7f9cb
--- /dev/null
+++ b/mlir/test/Dialect/OpenMP/invalid-interchange.mlir
@@ -0,0 +1,167 @@
+// RUN: mlir-opt -split-input-file -verify-diagnostic...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/186381
More information about the Mlir-commits
mailing list