[flang-commits] [flang] [llvm] [Flang][OpenMP] Add semantic support for OpenMP Loop Interchange and permutation clause in Flang (PR #183435)

Ferran Toda via flang-commits flang-commits at lists.llvm.org
Mon Mar 16 07:22:38 PDT 2026


https://github.com/NouTimbaler updated https://github.com/llvm/llvm-project/pull/183435

>From d86b48e490f3c23eabc0199118e5ae9dbbb127f8 Mon Sep 17 00:00:00 2001
From: Ferran Toda <ferran.todacasaban at bsc.es>
Date: Thu, 12 Mar 2026 02:14:58 +0000
Subject: [PATCH 1/2] keep only parse and semantics

---
 .../flang/Semantics/openmp-directive-sets.h   |   2 +
 flang/lib/Lower/OpenMP/OpenMP.cpp             |  14 +++
 flang/lib/Parser/openmp-parsers.cpp           |   3 +-
 flang/lib/Semantics/check-omp-loop.cpp        |  36 ++++++
 flang/lib/Semantics/check-omp-structure.cpp   |   1 -
 flang/lib/Semantics/resolve-directives.cpp    |  60 +++++++---
 flang/test/Parser/OpenMP/do-interchange.f90   |  34 ++++++
 flang/test/Parser/OpenMP/interchange-fail.f90 |  31 +++++
 .../Parser/OpenMP/interchange-permutation.f90 |  35 ++++++
 flang/test/Parser/OpenMP/interchange.f90      |  30 +++++
 .../OpenMP/interchange-permutation.f90        | 108 ++++++++++++++++++
 flang/test/Semantics/OpenMP/interchange01.f90 |  43 +++++++
 llvm/include/llvm/Frontend/OpenMP/OMP.td      |   2 +-
 13 files changed, 382 insertions(+), 17 deletions(-)
 create mode 100644 flang/test/Parser/OpenMP/do-interchange.f90
 create mode 100644 flang/test/Parser/OpenMP/interchange-fail.f90
 create mode 100644 flang/test/Parser/OpenMP/interchange-permutation.f90
 create mode 100644 flang/test/Parser/OpenMP/interchange.f90
 create mode 100644 flang/test/Semantics/OpenMP/interchange-permutation.f90
 create mode 100644 flang/test/Semantics/OpenMP/interchange01.f90

diff --git a/flang/include/flang/Semantics/openmp-directive-sets.h b/flang/include/flang/Semantics/openmp-directive-sets.h
index 609a7be700c28..5e9979d032028 100644
--- a/flang/include/flang/Semantics/openmp-directive-sets.h
+++ b/flang/include/flang/Semantics/openmp-directive-sets.h
@@ -278,12 +278,14 @@ static const OmpDirectiveSet loopConstructSet{
     Directive::OMPD_fuse,
     Directive::OMPD_tile,
     Directive::OMPD_unroll,
+    Directive::OMPD_interchange,
 };
 
 static const OmpDirectiveSet loopTransformationSet{
     Directive::OMPD_tile,
     Directive::OMPD_unroll,
     Directive::OMPD_fuse,
+    Directive::OMPD_interchange,
 };
 
 static const OmpDirectiveSet nonPartialVarSet{
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index e2328b39c180f..ae5f6f50bda09 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -2245,6 +2245,16 @@ static void genCanonicalLoopNest(
   firOpBuilder.setInsertionPointAfter(loops.front());
 }
 
+static void genInterchangeOp(Fortran::lower::AbstractConverter &converter,
+                             Fortran::lower::SymMap &symTable,
+                             lower::StatementContext &stmtCtx,
+                             Fortran::semantics::SemanticsContext &semaCtx,
+                             Fortran::lower::pft::Evaluation &eval,
+                             mlir::Location loc, const ConstructQueue &queue,
+                             ConstructQueue::const_iterator item) {
+  TODO(converter.getCurrentLocation(), "OpenMP Interchange");
+}
+
 static void genTileOp(Fortran::lower::AbstractConverter &converter,
                       Fortran::lower::SymMap &symTable,
                       lower::StatementContext &stmtCtx,
@@ -3740,6 +3750,10 @@ static void genOMPDispatch(lower::AbstractConverter &converter,
     newOp = genTeamsOp(converter, symTable, stmtCtx, semaCtx, eval, loc, queue,
                        item);
     break;
+  case llvm::omp::Directive::OMPD_interchange:
+    genInterchangeOp(converter, symTable, stmtCtx, semaCtx, eval, loc, queue,
+                     item);
+    break;
   case llvm::omp::Directive::OMPD_tile:
     genTileOp(converter, symTable, stmtCtx, semaCtx, eval, loc, queue, item);
     break;
diff --git a/flang/lib/Parser/openmp-parsers.cpp b/flang/lib/Parser/openmp-parsers.cpp
index 7838173d791a1..72b95397059bc 100644
--- a/flang/lib/Parser/openmp-parsers.cpp
+++ b/flang/lib/Parser/openmp-parsers.cpp
@@ -1608,7 +1608,7 @@ TYPE_PARSER( //
     "SIZES" >> construct<OmpClause>(construct<OmpClause::Sizes>(
                    parenthesized(nonemptyList(scalarIntExpr)))) ||
     "PERMUTATION" >> construct<OmpClause>(construct<OmpClause::Permutation>(
-                         parenthesized(nonemptyList(scalarIntExpr)))) ||
+                         parenthesized(nonemptyList(scalarIntConstantExpr)))) ||
     "THREADS"_id >> construct<OmpClause>(construct<OmpClause::Threads>()) ||
     "THREADSET" >> construct<OmpClause>(construct<OmpClause::Threadset>(
                        parenthesized(Parser<OmpThreadsetClause>{}))) ||
@@ -2487,6 +2487,7 @@ static constexpr DirectiveSet GetLoopDirectives() {
       unsigned(Directive::OMPD_fuse),
       unsigned(Directive::OMPD_tile),
       unsigned(Directive::OMPD_unroll),
+      unsigned(Directive::OMPD_interchange),
   };
   return loopDirectives;
 }
diff --git a/flang/lib/Semantics/check-omp-loop.cpp b/flang/lib/Semantics/check-omp-loop.cpp
index 818768d9af61e..5a07d459736a6 100644
--- a/flang/lib/Semantics/check-omp-loop.cpp
+++ b/flang/lib/Semantics/check-omp-loop.cpp
@@ -805,6 +805,42 @@ void OmpStructureChecker::Enter(const parser::OmpClause::Sizes &c) {
         /*paramName=*/"parameter", /*allowZero=*/false);
 }
 
+void OmpStructureChecker::Enter(const parser::OmpClause::Permutation &c) {
+  llvm::omp::Clause clause = llvm::omp::Clause::OMPC_permutation;
+  CheckAllowedClause(clause);
+  if (c.v.size() < 2)
+    context_.Say(GetContext().clauseSource,
+        "The %s clause must have a length of at least two"_err_en_US,
+        parser::ToUpperCaseLetters(getClauseName(clause).str()));
+
+  llvm::SmallVector<bool> found(c.v.size(), false);
+  bool cont = true;
+  for (const auto &val : c.v) {
+    if (const auto v{GetIntValue(val)}) {
+      if (*v <= 0) {
+        cont = false;
+        context_.Say(GetContext().clauseSource,
+            "The parameter of the %s clause must be "
+            "a constant positive integer expression"_err_en_US,
+            parser::ToUpperCaseLetters(getClauseName(clause).str()));
+      } else if ((unsigned)*v - 1 < c.v.size()) {
+        found[*v - 1] = true;
+      }
+    } else
+      cont = false;
+  }
+
+  if (!cont)
+    return;
+  for (auto i : found) {
+    if (!i) {
+      context_.Say(GetContext().clauseSource,
+          "Every integer from 1 must appear in the %s clause"_err_en_US,
+          parser::ToUpperCaseLetters(getClauseName(clause).str()));
+    }
+  }
+}
+
 void OmpStructureChecker::Enter(const parser::OmpClause::Looprange &x) {
   CheckAllowedClause(llvm::omp::Clause::OMPC_looprange);
   auto &[first, count]{x.v.t};
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index 431c41f443f7a..179581469e5c9 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -5785,7 +5785,6 @@ CHECK_SIMPLE_CLAUSE(OmpxAttribute, OMPC_ompx_attribute)
 CHECK_SIMPLE_CLAUSE(Order, OMPC_order)
 CHECK_SIMPLE_CLAUSE(Otherwise, OMPC_otherwise)
 CHECK_SIMPLE_CLAUSE(Partial, OMPC_partial)
-CHECK_SIMPLE_CLAUSE(Permutation, OMPC_permutation)
 CHECK_SIMPLE_CLAUSE(ProcBind, OMPC_proc_bind)
 CHECK_SIMPLE_CLAUSE(Read, OMPC_read)
 CHECK_SIMPLE_CLAUSE(Relaxed, OMPC_relaxed)
diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp
index c8ffa22d6bb5f..50c47bb682092 100644
--- a/flang/lib/Semantics/resolve-directives.cpp
+++ b/flang/lib/Semantics/resolve-directives.cpp
@@ -2130,6 +2130,10 @@ static bool isSizesClause(const parser::OmpClause *clause) {
   return std::holds_alternative<parser::OmpClause::Sizes>(clause->u);
 }
 
+static bool isCollapseClause(const parser::OmpClause *clause) {
+  return std::holds_alternative<parser::OmpClause::Collapse>(clause->u);
+}
+
 std::int64_t OmpAttributeVisitor::SetAssociatedMaxClause(
     llvm::SmallVector<std::int64_t> &levels,
     llvm::SmallVector<const parser::OmpClause *> &clauses) {
@@ -2138,13 +2142,14 @@ std::int64_t OmpAttributeVisitor::SetAssociatedMaxClause(
   // does not exeed the number of tiled loops.
   std::int64_t tileLevel = 0;
   for (auto [level, clause] : llvm::zip_equal(levels, clauses))
-    if (isSizesClause(clause))
+    if (clause && isSizesClause(clause))
       tileLevel = level;
 
   std::int64_t maxLevel = 1;
   const parser::OmpClause *maxClause = nullptr;
   for (auto [level, clause] : llvm::zip_equal(levels, clauses)) {
-    if (tileLevel > 0 && tileLevel < level) {
+    if (clause && isCollapseClause(clause) && tileLevel > 0 &&
+        tileLevel < level) {
       context_.Say(clause->source,
           "The value of the parameter in the COLLAPSE clause must"
           " not be larger than the number of the number of tiled loops"
@@ -2180,6 +2185,17 @@ void OmpAttributeVisitor::CollectNumAffectedLoopsFromLoopConstruct(
 
   CollectNumAffectedLoopsFromClauses(clauseList, levels, clauses);
   CollectNumAffectedLoopsFromInnerLoopContruct(x, levels, clauses);
+
+  bool has_permutation =
+      llvm::any_of(clauseList.v, [](const parser::OmpClause &c) {
+        return c.Id() == llvm::omp::Clause::OMPC_permutation;
+      });
+  if (x.BeginDir().DirName().v == llvm::omp::Directive::OMPD_interchange &&
+      !has_permutation) {
+    // OMPD_interchange with no permutation clause needs a level 2 nest
+    levels.push_back(2);
+    clauses.push_back(nullptr);
+  }
 }
 
 void OmpAttributeVisitor::CollectNumAffectedLoopsFromInnerLoopContruct(
@@ -2222,6 +2238,11 @@ void OmpAttributeVisitor::CollectNumAffectedLoopsFromClauses(
       levels.push_back(tclause->v.size());
       clauses.push_back(&clause);
     }
+    if (const auto iclause{
+            std::get_if<parser::OmpClause::Permutation>(&clause.u)}) {
+      levels.push_back(iclause->v.size());
+      clauses.push_back(&clause);
+    }
   }
 }
 
@@ -2400,18 +2421,29 @@ void OmpAttributeVisitor::PrivatizeAssociatedLoopIndexAndCheckLoopLevel(
 
 void OmpAttributeVisitor::CheckAssocLoopLevel(
     std::int64_t level, const parser::OmpClause *clause) {
-  if (clause && level != 0) {
-    switch (clause->Id()) {
-    case llvm::omp::OMPC_sizes:
-      context_.Say(clause->source,
-          "The SIZES clause has more entries than there are nested canonical loops."_err_en_US);
-      break;
-    default:
-      context_.Say(clause->source,
-          "The value of the parameter in the COLLAPSE or ORDERED clause must"
-          " not be larger than the number of nested loops"
-          " following the construct."_err_en_US);
-      break;
+  if (level != 0) {
+    if (clause) {
+      switch (clause->Id()) {
+      case llvm::omp::OMPC_sizes:
+        context_.Say(clause->source,
+            "The SIZES clause has more entries than there are nested canonical loops."_err_en_US);
+        break;
+      case llvm::omp::OMPC_permutation:
+        context_.Say(clause->source,
+            "The PERMUTATION clause has more entries than there are nested canonical loops."_err_en_US);
+        break;
+      default:
+        context_.Say(clause->source,
+            "The value of the parameter in the COLLAPSE or ORDERED clause must"
+            " not be larger than the number of nested loops"
+            " following the construct."_err_en_US);
+        break;
+      }
+    } else if (GetContext().directive ==
+        llvm::omp::Directive::OMPD_interchange) {
+      // OMPD_interchange with no permutation clause needs a level 2 nest
+      context_.Say(GetContext().directiveSource,
+          "The INTERCHANGE construct must be followed by a canonical loop nest of at least 2 levels"_err_en_US);
     }
   }
 }
diff --git a/flang/test/Parser/OpenMP/do-interchange.f90 b/flang/test/Parser/OpenMP/do-interchange.f90
new file mode 100644
index 0000000000000..e5fbc288cef39
--- /dev/null
+++ b/flang/test/Parser/OpenMP/do-interchange.f90
@@ -0,0 +1,34 @@
+! RUN: %flang_fc1 -fdebug-unparse -fopenmp -fopenmp-version=60 %s | FileCheck --ignore-case %s
+! RUN: %flang_fc1 -fdebug-dump-parse-tree -fopenmp -fopenmp-version=60 %s | FileCheck --check-prefix="PARSE-TREE" %s
+
+subroutine openmp_do_interchange(x)
+
+  integer :: x, y
+
+!CHECK: !$omp do
+!CHECK: !$omp interchange permutation
+!$omp do
+!$omp interchange permutation(2,1)
+!CHECK: do
+  do x = 1, 100
+  !CHECK: do
+    do y = 1, 100
+      call F1()
+  !CHECK: end do
+    end do
+!CHECK: end do
+  end do
+!CHECK: !$omp end interchange
+!$omp end interchange
+!$omp end do
+
+!PARSE-TREE:| | ExecutionPartConstruct -> ExecutableConstruct -> OpenMPConstruct -> OpenMPLoopConstruct
+!PARSE-TREE:| | | OmpBeginLoopDirective
+!PARSE-TREE:| | | Block
+!PARSE-TREE:| | | | ExecutionPartConstruct -> ExecutableConstruct -> OpenMPConstruct -> OpenMPLoopConstruct
+!PARSE-TREE:| | | | | OmpBeginLoopDirective
+!PARSE-TREE:| | | | | | OmpDirectiveName -> llvm::omp::Directive = interchange
+!PARSE-TREE:| | | | | Block
+!PARSE-TREE:| | | | | | ExecutionPartConstruct -> ExecutableConstruct -> DoConstruct
+
+END subroutine openmp_do_interchange
diff --git a/flang/test/Parser/OpenMP/interchange-fail.f90 b/flang/test/Parser/OpenMP/interchange-fail.f90
new file mode 100644
index 0000000000000..d83ef1746f30f
--- /dev/null
+++ b/flang/test/Parser/OpenMP/interchange-fail.f90
@@ -0,0 +1,31 @@
+! RUN: split-file %s %t
+! RUN: not %flang_fc1 -fsyntax-only -fopenmp -fopenmp-version=60 %t/stray_end1.f90 2>&1 | FileCheck %t/stray_end1.f90
+! RUN: not %flang_fc1 -fsyntax-only -fopenmp -fopenmp-version=60 %t/stray_end2.f90 2>&1 | FileCheck %t/stray_end2.f90
+! RUN: not %flang_fc1 -fsyntax-only -fopenmp -fopenmp-version=60 %t/stray_begin.f90 2>&1 | FileCheck %t/stray_begin.f90
+
+
+!--- stray_end1.f90
+! Parser error
+
+subroutine stray_end1
+  !CHECK: error: Misplaced OpenMP end-directive
+  !$omp end interchange
+end subroutine
+
+
+!--- stray_end2.f90
+
+subroutine stray_end2
+  print *
+  !CHECK: error: Misplaced OpenMP end-directive
+  !$omp end interchange
+end subroutine
+
+
+!--- stray_begin.f90
+
+subroutine stray_begin
+  !CHECK: error: This construct should contain a DO-loop or a loop-nest-generating OpenMP construct
+  !$omp interchange permutation(2,1)
+end subroutine
+
diff --git a/flang/test/Parser/OpenMP/interchange-permutation.f90 b/flang/test/Parser/OpenMP/interchange-permutation.f90
new file mode 100644
index 0000000000000..53392e050a662
--- /dev/null
+++ b/flang/test/Parser/OpenMP/interchange-permutation.f90
@@ -0,0 +1,35 @@
+! RUN: %flang_fc1 -fdebug-unparse -fopenmp -fopenmp-version=60 %s | FileCheck --ignore-case %s
+! RUN: %flang_fc1 -fdebug-dump-parse-tree -fopenmp -fopenmp-version=60 %s | FileCheck --check-prefix="PARSE-TREE" %s
+
+subroutine openmp_interchange(x)
+
+  integer :: x, y
+
+!CHECK: !$omp interchange permutation(2_4,1_4)
+!$omp interchange permutation(2,1)
+!CHECK: do
+  do x = 1, 100
+  !CHECK: do
+    do y = 1, 100
+      call F1()
+  !CHECK: end do
+    end do
+!CHECK: end do
+  end do
+!CHECK: !$omp end interchange
+!$omp end interchange
+
+!PARSE-TREE: OpenMPConstruct -> OpenMPLoopConstruct
+!PARSE-TREE: OmpBeginLoopDirective
+!PARSE-TREE:  OmpDirectiveName -> llvm::omp::Directive = interchange
+!PARSE-TREE:   OmpClauseList -> OmpClause -> Permutation -> Scalar -> Integer -> Constant -> Expr = '2_4'
+!PARSE-TREE:     LiteralConstant -> IntLiteralConstant = '2'
+!PARSE-TREE:   Scalar -> Integer -> Constant -> Expr = '1_4'
+!PARSE-TREE:     LiteralConstant -> IntLiteralConstant = '1'
+!PARSE-TREE:     Flags = {}
+!PARSE-TREE:   DoConstruct
+!PARSE-TREE:   EndDoStmt
+!PARSE-TREE: OmpEndLoopDirective
+!PARSE-TREE: OmpDirectiveName -> llvm::omp::Directive = interchange
+
+END subroutine openmp_interchange
diff --git a/flang/test/Parser/OpenMP/interchange.f90 b/flang/test/Parser/OpenMP/interchange.f90
new file mode 100644
index 0000000000000..8aba562724428
--- /dev/null
+++ b/flang/test/Parser/OpenMP/interchange.f90
@@ -0,0 +1,30 @@
+! RUN: %flang_fc1 -fdebug-unparse -fopenmp -fopenmp-version=60 %s | FileCheck --ignore-case %s
+! RUN: %flang_fc1 -fdebug-dump-parse-tree -fopenmp -fopenmp-version=60 %s | FileCheck --check-prefix="PARSE-TREE" %s
+
+subroutine openmp_interchange(x)
+
+  integer :: x, y
+
+!CHECK: !$omp interchange
+!$omp interchange
+!CHECK: do
+  do x = 1, 100
+  !CHECK: do
+    do y = 1, 100
+      call F1()
+  !CHECK: end do
+    end do
+!CHECK: end do
+  end do
+!CHECK: !$omp end interchange
+!$omp end interchange
+
+!PARSE-TREE: OpenMPConstruct -> OpenMPLoopConstruct
+!PARSE-TREE: OmpBeginLoopDirective
+!PARSE-TREE:  OmpDirectiveName -> llvm::omp::Directive = interchange
+!PARSE-TREE:   DoConstruct
+!PARSE-TREE:   EndDoStmt
+!PARSE-TREE: OmpEndLoopDirective
+!PARSE-TREE: OmpDirectiveName -> llvm::omp::Directive = interchange
+
+END subroutine openmp_interchange
diff --git a/flang/test/Semantics/OpenMP/interchange-permutation.f90 b/flang/test/Semantics/OpenMP/interchange-permutation.f90
new file mode 100644
index 0000000000000..f0309b159157d
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/interchange-permutation.f90
@@ -0,0 +1,108 @@
+! Testing the Semantics of interchange
+!RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=60
+
+
+subroutine double_permutation
+  implicit none
+  integer i, j
+
+  !ERROR: At most one PERMUTATION clause can appear on the INTERCHANGE directive
+  !$omp interchange permutation(2,1) permutation(2,1)
+  do i = 1, 5
+  do j = 1, 5
+    print *, i
+  end do
+  end do
+end subroutine
+
+subroutine zero_parameter
+  implicit none
+  integer i, j
+
+  !ERROR: The parameter of the PERMUTATION clause must be a constant positive integer expression
+  !$omp interchange permutation(0,1)
+  do i = 1, 5
+  do j = 1, 5
+    print *, i
+  end do
+  end do
+end subroutine
+
+
+subroutine negative_parameter
+  implicit none
+  integer i, j
+
+  !ERROR: The parameter of the PERMUTATION clause must be a constant positive integer expression
+  !$omp interchange permutation(2,-1)
+  do i = 1, 5
+  do j = 1, 5
+    print *, i
+  end do
+  end do
+end subroutine
+
+
+subroutine constant_parameter
+  implicit none
+  integer i, j, a
+
+  !ERROR: Must be a constant value
+  !$omp interchange permutation(2,a)
+  do i = 1, 5
+  do j = 1, 5
+    print *, i
+  end do
+  end do
+end subroutine
+
+subroutine insufficient_loops
+  implicit none
+  integer i
+
+  !ERROR: The PERMUTATION clause has more entries than there are nested canonical loops.
+  !$omp interchange permutation(2, 1)
+  do i = 1, 5
+    print *, i
+  end do
+end subroutine
+
+subroutine minimum_parameters
+  implicit none
+  integer i, j
+
+  !ERROR: The PERMUTATION clause must have a length of at least two
+  !$omp interchange permutation(1)
+  do i = 1, 5
+    do j = 1, 5
+      print *, i
+    end do
+  end do
+end subroutine
+
+subroutine parameter_number
+  implicit none
+  integer i, j
+
+  !ERROR: Every integer from 1 must appear in the PERMUTATION clause
+  !$omp interchange permutation(1,1)
+  do i = 1, 5
+    do j = 1, 5
+      print *, i
+    end do
+  end do
+end subroutine
+
+subroutine parameter_number2
+  implicit none
+  integer i, j
+
+  !ERROR: Every integer from 1 must appear in the PERMUTATION clause
+  !$omp interchange permutation(1,3)
+  do i = 1, 5
+    do j = 1, 5
+      print *, i
+    end do
+  end do
+end subroutine
+
diff --git a/flang/test/Semantics/OpenMP/interchange01.f90 b/flang/test/Semantics/OpenMP/interchange01.f90
new file mode 100644
index 0000000000000..1bcdcea17a1da
--- /dev/null
+++ b/flang/test/Semantics/OpenMP/interchange01.f90
@@ -0,0 +1,43 @@
+! Testing the Semantics of interchange
+!RUN: %python %S/../test_errors.py %s %flang -fopenmp -fopenmp-version=51
+
+
+subroutine on_unroll
+  implicit none
+  integer i, j
+
+  !ERROR: OpenMP loop construct cannot apply to a fully unrolled loop
+  !$omp interchange
+  !$omp unroll
+  do i = 1, 5
+    do j = 1, 5
+      print *, i
+    end do
+  end do
+end subroutine
+
+subroutine loop_assoc
+  implicit none
+  integer :: i, j
+
+  !$omp interchange
+  !ERROR: The associated loop of a loop-associated directive cannot be a DO WHILE.
+  do while (i <= 10)
+    do j = 1, 5
+      i = i + 1
+      print *, i
+    end do
+  end do
+end subroutine
+
+subroutine insufficient_loops
+  implicit none
+  integer i
+
+  !ERROR: The INTERCHANGE construct must be followed by a canonical loop nest of at least 2 levels
+  !$omp interchange 
+  do i = 1, 5
+    print *, i
+  end do
+end subroutine
+
diff --git a/llvm/include/llvm/Frontend/OpenMP/OMP.td b/llvm/include/llvm/Frontend/OpenMP/OMP.td
index cb688959f7519..773804b262a24 100644
--- a/llvm/include/llvm/Frontend/OpenMP/OMP.td
+++ b/llvm/include/llvm/Frontend/OpenMP/OMP.td
@@ -453,7 +453,7 @@ def OMPC_Partial: Clause<[Spelling<"partial">]> {
 }
 def OMPC_Permutation: Clause<[Spelling<"permutation">]> {
   let clangClass = "OMPPermutationClause";
-  let flangClass = "ScalarIntExpr";
+  let flangClass = "ScalarIntConstantExpr";
   let isValueList = true;
 }
 def OMPC_Priority : Clause<[Spelling<"priority">]> {

>From 8e576b28257c940eb9dc8c362c867185df49124b Mon Sep 17 00:00:00 2001
From: Ferran Toda <ferran.todacasaban at bsc.es>
Date: Mon, 16 Mar 2026 14:22:18 +0000
Subject: [PATCH 2/2] add Todo test

---
 flang/test/Lower/OpenMP/Todo/interchange.f90 | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
 create mode 100644 flang/test/Lower/OpenMP/Todo/interchange.f90

diff --git a/flang/test/Lower/OpenMP/Todo/interchange.f90 b/flang/test/Lower/OpenMP/Todo/interchange.f90
new file mode 100644
index 0000000000000..123b8fc657ed9
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/interchange.f90
@@ -0,0 +1,15 @@
+! Tests reduction processor behavior when a reduction symbol is not supported.
+
+! RUN: %not_todo_cmd %flang_fc1 -emit-hlfir -fopenmp -o - %s 2>&1 | FileCheck %s
+
+subroutine foo
+  implicit none
+  integer :: j, i
+
+  !CHECK: not yet implemented: OpenMP Interchange
+  !$omp interchange
+  do i=1,10
+    do j=1,10
+    end do
+  end do
+end subroutine



More information about the flang-commits mailing list