[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