[llvm] [mlir] [MLIR][OpenMP] add interchange operation in the OpenMP mlir dialect (PR #186381)

Ferran Toda via llvm-commits llvm-commits at lists.llvm.org
Fri Mar 13 05:27:32 PDT 2026


https://github.com/NouTimbaler created https://github.com/llvm/llvm-project/pull/186381

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 

>From 3c63e926a22e40448499b2fa0af1ebcc66034a46 Mon Sep 17 00:00:00 2001
From: Ferran Toda <ferran.todacasaban at bsc.es>
Date: Fri, 13 Mar 2026 12:08:07 +0000
Subject: [PATCH] mlir, irbuilder omp interchange

---
 .../llvm/Frontend/OpenMP/OMPIRBuilder.h       |  14 ++
 llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp     |  84 +++++++++
 .../mlir/Dialect/OpenMP/OpenMPClauses.td      |  26 +++
 mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td |  19 ++
 mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp  | 143 ++++++++++++---
 .../OpenMP/OpenMPToLLVMIRTranslation.cpp      |  43 +++++
 mlir/test/Dialect/OpenMP/cli-interchange.mlir |  63 +++++++
 .../Dialect/OpenMP/invalid-interchange.mlir   | 167 ++++++++++++++++++
 .../LLVMIR/openmp-cli-interchange01.mlir      |  86 +++++++++
 9 files changed, 621 insertions(+), 24 deletions(-)
 create mode 100644 mlir/test/Dialect/OpenMP/cli-interchange.mlir
 create mode 100644 mlir/test/Dialect/OpenMP/invalid-interchange.mlir
 create mode 100644 mlir/test/Target/LLVMIR/openmp-cli-interchange01.mlir

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-diagnostics %s
+
+
+func.func @missing_permutation(%tc1 : i32, %tc2 : i32) {
+  %canonloop1 = omp.new_cli
+  %canonloop2 = omp.new_cli
+  omp.canonical_loop(%canonloop1) %iv1 : i32 in range(%tc1) {
+    omp.canonical_loop(%canonloop2) %iv2 : i32 in range(%tc2) {
+      omp.terminator
+    }
+    omp.terminator
+  }
+
+  // expected-error at +1 {{'omp.interchange' op must have permutation attribute}}
+  omp.interchange <-(%canonloop1, %canonloop2)
+
+  llvm.return
+}
+
+// -----
+
+func.func @no_loops(%tc1 : i32, %tc2 : i32) {
+  // expected-error at +1 {{'omp.interchange' op must apply to at least two loops}}
+  omp.interchange <-() permutation([2, 1])
+
+  return
+}
+
+// -----
+
+func.func @missing_loops(%tc1 : i32, %tc2 : i32) {
+  %canonloop1 = omp.new_cli
+  omp.canonical_loop(%canonloop1) %iv1 : i32 in range(%tc1) {
+    omp.terminator
+  }
+
+  // expected-error at +1 {{'omp.interchange' op must apply to at least two loops}}
+  omp.interchange <-(%canonloop1) permutation([2, 1])
+
+  llvm.return
+}
+
+// -----
+
+func.func @wrong_permutation(%tc1 : i32, %tc2 : i32) {
+  %canonloop1 = omp.new_cli
+  %canonloop2 = omp.new_cli
+  omp.canonical_loop(%canonloop1) %iv1 : i32 in range(%tc1) {
+    omp.canonical_loop(%canonloop2) %iv2 : i32 in range(%tc2) {
+      omp.terminator
+    }
+    omp.terminator
+  }
+
+  // expected-error at +1 {{'omp.interchange' op expecting the same number of permutation attributes and applyees}}
+  omp.interchange <-(%canonloop1, %canonloop2) permutation([1 : i32])
+
+  llvm.return
+}
+
+// -----
+
+func.func @insufficient_generatees(%tc1 : i32, %tc2 : i32) {
+  %canonloop1 = omp.new_cli
+  %canonloop2 = omp.new_cli
+  %canonloop3 = omp.new_cli
+  omp.canonical_loop(%canonloop1) %iv1 : i32 in range(%tc1) {
+    omp.canonical_loop(%canonloop2) %iv2 : i32 in range(%tc2) {
+      omp.terminator
+    }
+    omp.terminator
+  }
+
+  // expected-error at +1 {{'omp.interchange' op expecting the same number of generatees and applyees}}
+  omp.interchange (%canonloop3) <- (%canonloop1, %canonloop2) permutation([1 : i32, 2 : i32])
+
+  return
+}
+
+// -----
+
+func.func @zero_attribute(%tc1 : i32, %tc2 : i32) {
+  %canonloop1 = omp.new_cli
+  %canonloop2 = omp.new_cli
+  %canonloop3 = omp.new_cli
+  %canonloop4 = omp.new_cli
+  omp.canonical_loop(%canonloop1) %iv1 : i32 in range(%tc1) {
+    omp.canonical_loop(%canonloop2) %iv2 : i32 in range(%tc2) {
+      omp.terminator
+    }
+    omp.terminator
+  }
+
+  // expected-error at +1 {{'omp.interchange' op permutation attribute must be a positive integer}}
+  omp.interchange (%canonloop3, %canonloop4) <- (%canonloop1, %canonloop2) permutation([0 : i32, 2 : i32])
+
+  return
+}
+
+// -----
+
+func.func @zero_attribute(%tc1 : i32, %tc2 : i32) {
+  %canonloop1 = omp.new_cli
+  %canonloop2 = omp.new_cli
+  %canonloop3 = omp.new_cli
+  %canonloop4 = omp.new_cli
+  omp.canonical_loop(%canonloop1) %iv1 : i32 in range(%tc1) {
+    omp.canonical_loop(%canonloop2) %iv2 : i32 in range(%tc2) {
+      omp.terminator
+    }
+    omp.terminator
+  }
+
+  // expected-error at +1 {{'omp.interchange' op every integer from 1 must appear in the permutation attribute}}
+  omp.interchange (%canonloop3, %canonloop4) <- (%canonloop1, %canonloop2) permutation([1 : i32, 3 : i32])
+
+  return
+}
+
+// -----
+
+func.func @missing_generator(%tc1 : i32, %tc2 : i32) {
+  // expected-error at +1 {{'omp.new_cli' op CLI has no generator}}
+  %canonloop1 = omp.new_cli
+
+  // expected-note at +1 {{see consumer here: "omp.interchange"(%0) <{operandSegmentSizes = array<i32: 0, 1>, permutation = [1 : i32, 2 : i32]}> : (!omp.cli) -> ()}}
+  omp.interchange <-(%canonloop1) permutation([1 : i32, 2 : i32])
+
+  return
+}
+
+// -----
+
+func.func @not_perfectly_nested(%tc1 : i32, %tc2 : i32) {
+  %canonloop1 = omp.new_cli
+  %canonloop2 = omp.new_cli
+  omp.canonical_loop(%canonloop1) %iv1 : i32 in range(%tc1) {
+    %v = arith.constant 42 : i32
+    omp.canonical_loop(%canonloop2) %iv2 : i32 in range(%tc2) {
+      omp.terminator
+    }
+    omp.terminator
+  }
+
+  // expected-error at +1 {{'omp.interchange' op interchanged loop nest must be perfectly nested}}
+  omp.interchange <-(%canonloop1, %canonloop2) permutation([1 : i32, 2 : i32])
+
+  llvm.return
+}
+
+// -----
+
+func.func @non_nectangular(%tc1 : i32, %tc2 : i32) {
+  %canonloop1 = omp.new_cli
+  %canonloop2 = omp.new_cli
+  omp.canonical_loop(%canonloop1) %iv1 : i32 in range(%tc1) {
+    omp.canonical_loop(%canonloop2) %iv2 : i32 in range(%iv1) {
+      omp.terminator
+    }
+    omp.terminator
+  }
+
+  // expected-error at +1 {{'omp.interchange' op interchanged loop nest must be rectangular}}
+  omp.interchange <-(%canonloop1, %canonloop2) permutation([1 : i32, 2 : i32])
+
+  llvm.return
+}
diff --git a/mlir/test/Target/LLVMIR/openmp-cli-interchange01.mlir b/mlir/test/Target/LLVMIR/openmp-cli-interchange01.mlir
new file mode 100644
index 0000000000000..42c4a2d2af222
--- /dev/null
+++ b/mlir/test/Target/LLVMIR/openmp-cli-interchange01.mlir
@@ -0,0 +1,86 @@
+// RUN: mlir-translate -mlir-to-llvmir %s | FileCheck %s --enable-var-scope
+
+
+llvm.func @interchange_loop(%baseptr: !llvm.ptr, %tc1: i32, %tc2: i32) -> () {
+  %cli_outer = omp.new_cli
+  %cli_inner = omp.new_cli
+  omp.canonical_loop(%cli_outer) %iv1 : i32 in range(%tc1) {
+    omp.canonical_loop(%cli_inner) %iv2 : i32 in range(%tc2) {
+      %ptr = llvm.getelementptr inbounds %baseptr[%iv1] : (!llvm.ptr, i32) -> !llvm.ptr, f32
+      %val = llvm.mlir.constant(42.0 : f32) : f32
+      llvm.store %val, %ptr : f32, !llvm.ptr
+      omp.terminator
+    }
+    omp.terminator
+  }
+  omp.interchange <- (%cli_outer, %cli_inner) {permutation = [2 : i32, 1 : i32]}
+  llvm.return
+}
+
+
+// CHECK-LABEL: define void @interchange_loop(
+// CHECK-SAME:    ptr %[[VAL_0:.*]], i32 %[[VAL_1:.*]], i32 %[[VAL_2:.*]]) {
+// CHECK-NEXT:    br label  %[[OMP_INTERCHANGE0_PREHEADER:.*]]
+// CHECK-EMPTY:
+// CHECK-NEXT:  [[OMP_INTERCHANGE0_PREHEADER]]:
+// CHECK-NEXT:    br label %[[OMP_INTERCHANGE0_HEADER:.*]]
+// CHECK-EMPTY:
+// CHECK-NEXT:  [[OMP_INTERCHANGE0_HEADER]]:
+// CHECK-NEXT:    %[[OMP_INTERCHANGE0_IV:.*]] = phi i32 [ 0, %[[OMP_INTERCHANGE0_PREHEADER]] ], [ %[[OMP_INTERCHANGE0_NEXT:.*]], %[[OMP_INTERCHANGE0_INC:.*]] ]
+// CHECK-NEXT:    br label %[[OMP_INTERCHANGE0_COND:.*]]
+// CHECK-EMPTY:
+// CHECK-NEXT:  [[OMP_INTERCHANGE0_COND]]:
+// CHECK-NEXT:    %[[OMP_INTERCHANGE0_CMP:.*]] = icmp ult i32 %[[OMP_INTERCHANGE0_IV]], %[[VAL_2:.*]]
+// CHECK-NEXT:    br i1 %[[OMP_INTERCHANGE0_CMP]], label %[[OMP_INTERCHANGE0_BODY:.*]], label %[[OMP_INTERCHANGE0_EXIT:.*]]
+// CHECK-EMPTY:
+// CHECK-NEXT:  [[OMP_INTERCHANGE0_BODY]]:
+// CHECK-NEXT:    br label %[[OMP_INTERCHANGE1_PREHEADER:.*]]
+// CHECK-EMPTY:
+// CHECK-NEXT:  [[OMP_INTERCHANGE1_PREHEADER]]:
+// CHECK-NEXT:    br label %[[OMP_INTERCHANGE1_HEADER:.*]]
+// CHECK-EMPTY:
+// CHECK-NEXT:  [[OMP_INTERCHANGE1_HEADER]]:
+// CHECK-NEXT:    %[[OMP_INTERCHANGE1_IV:.*]] = phi i32 [ 0, %[[OMP_INTERCHANGE1_PREHEADER]] ], [ %[[OMP_INTERCHANGE1_NEXT:.*]], %[[OMP_INTERCHANGE1_INC:.*]] ]
+// CHECK-NEXT:    br label %[[OMP_INTERCHANGE1_COND:.*]]
+// CHECK-EMPTY:
+// CHECK-NEXT:  [[OMP_INTERCHANGE1_COND]]:
+// CHECK-NEXT:    %[[OMP_INTERCHANGE1_CMP:.*]] = icmp ult i32 %[[OMP_INTERCHANGE1_IV]], %[[VAL_1:.*]]
+// CHECK-NEXT:    br i1 %[[OMP_INTERCHANGE1_CMP]], label %[[OMP_INTERCHANGE1_BODY:.*]], label %[[OMP_INTERCHANGE1_EXIT:.*]]
+// CHECK-EMPTY:
+// CHECK-NEXT:  [[OMP_INTERCHANGE1_BODY]]:
+// CHECK-NEXT:    br label %[[OMP_OMP_LOOP_BODY4:.*]]
+// CHECK-EMPTY:
+// CHECK-NEXT:  [[OMP_OMP_LOOP_BODY4]]:
+// CHECK-NEXT:    br label %[[OMP_LOOP_REGION12:.*]]
+// CHECK-EMPTY:
+// CHECK-NEXT:  [[OMP_LOOP_REGION12]]:
+// CHECK-NEXT:    %[[VAL_3:.*]] = getelementptr inbounds float, ptr %[[VAL_0:.*]], i32 %[[OMP_INTERCHANGE1_IV]]
+// CHECK-NEXT:    store float 4.200000e+01, ptr %[[VAL_3]], align 4
+// CHECK-NEXT:    br label %[[OMP_REGION_CONT11:.*]]
+// CHECK-EMPTY:
+// CHECK-NEXT:  [[OMP_REGION_CONT11]]:
+// CHECK-NEXT:    br label %[[OMP_INTERCHANGE1_INC]]
+// CHECK-EMPTY:
+// CHECK-NEXT:  [[OMP_INTERCHANGE1_INC]]:
+// CHECK-NEXT:    %[[OMP_INTERCHANGE1_NEXT]] = add nuw i32 %[[OMP_INTERCHANGE1_IV]], 1
+// CHECK-NEXT:    br label %[[OMP_INTERCHANGE1_HEADER]]
+// CHECK-EMPTY:
+// CHECK-NEXT:  [[OMP_INTERCHANGE1_EXIT]]:
+// CHECK-NEXT:    br label %[[OMP_INTERCHANGE1_AFTER:.*]]
+// CHECK-EMPTY:
+// CHECK-NEXT:  [[OMP_INTERCHANGE1_AFTER]]:
+// CHECK-NEXT:    br label %[[OMP_INTERCHANGE0_INC]]
+// CHECK-EMPTY:
+// CHECK-NEXT:  [[OMP_INTERCHANGE0_INC]]:
+// CHECK-NEXT:    %[[OMP_INTERCHANGE0_NEXT]] = add nuw i32 %[[OMP_INTERCHANGE0_IV]], 1
+// CHECK-NEXT:    br label %[[OMP_INTERCHANGE0_HEADER]]
+// CHECK-EMPTY:
+// CHECK-NEXT:  [[OMP_INTERCHANGE0_EXIT]]:
+// CHECK-NEXT:    br label %[[OMP_INTERCHANGE0_AFTER:.*]]
+// CHECK-EMPTY:
+// CHECK-NEXT:  [[OMP_INTERCHANGE0_AFTER]]:
+// CHECK-NEXT:    br label %[[OMP_OMP_LOOP_AFTER:.*]]
+// CHECK-EMPTY:
+// CHECK-NEXT:  [[OMP_OMP_LOOP_AFTER]]:
+// CHECK-NEXT:    ret void
+



More information about the llvm-commits mailing list