[flang-commits] [flang] [mlir] [MLIR][OpenMP] Support for dispatch construct with clauses nocontext & novariants (PR #215877)

via flang-commits flang-commits at lists.llvm.org
Fri Sep 4 04:13:22 PDT 2026


https://github.com/SunilKuravinakop updated https://github.com/llvm/llvm-project/pull/215877

>From 2aa37d8ee21f0c261af78bba596f162514188fc0 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Wed, 12 Aug 2026 14:28:12 -0500
Subject: [PATCH 1/5] [MLIR][OpenMP] Adding omp.dispatch operation with
 nocontext/novariants clauses.

This patch adds the omp.dispatch operation to the OpenMP dialect, along
with the nocontext and novariants clauses, the verifier, and LLVM IR
translation. It is limited to the MLIR layer; Flang lowering follows in
later patches.

Part 1 of 3 of the !$omp dispatch support.
---
 .../mlir/Dialect/OpenMP/OpenMPClauses.td      |  54 ++++++++
 mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td |  22 ++++
 mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp  |  10 ++
 .../OpenMP/OpenMPToLLVMIRTranslation.cpp      |  21 ++++
 mlir/test/Dialect/OpenMP/dispatch.mlir        | 117 ++++++++++++++++++
 mlir/test/Target/LLVMIR/openmp-dispatch.mlir  |  72 +++++++++++
 6 files changed, 296 insertions(+)
 create mode 100644 mlir/test/Dialect/OpenMP/dispatch.mlir
 create mode 100644 mlir/test/Target/LLVMIR/openmp-dispatch.mlir

diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td
index cd1223dc1702c..7c735544858f6 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td
@@ -502,6 +502,60 @@ class OpenMP_FinalClauseSkip<
 
 def OpenMP_FinalClause : OpenMP_FinalClauseSkip<>;
 
+//===----------------------------------------------------------------------===//
+// V5.2: [7.6.2] `nocontext` clause
+//===----------------------------------------------------------------------===//
+
+class OpenMP_NocontextClauseSkip<
+    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
+    Optional<I1>:$nocontext
+  );
+
+  let optAssemblyFormat = [{
+    `nocontext` `(` $nocontext `)`
+  }];
+
+  let description = [{
+    When parameter to `nocontext` evaluates to `true`, the dispatch construct is not added
+    to the OpenMP context, so a function variant that would be selected only
+    because of the enclosing dispatch construct is not selected.
+  }];
+}
+
+def OpenMP_NocontextClause : OpenMP_NocontextClauseSkip<>;
+
+//===----------------------------------------------------------------------===//
+// V5.2: [7.6.1] `novariants` clause
+//===----------------------------------------------------------------------===//
+
+class OpenMP_NovariantsClauseSkip<
+    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
+    Optional<I1>:$novariants
+  );
+
+  let optAssemblyFormat = [{
+    `novariants` `(` $novariants `)`
+  }];
+
+  let description = [{
+    When a `novariants` clause is present and its expression evaluates to
+    `true`, no function variant is selected for the target call of the dispatch
+    region, even if one would be selected normally. The use of a variable in a
+    `novariants` clause expression causes an implicit reference to the variable
+    in all enclosing constructs.
+  }];
+}
+
+def OpenMP_NovariantsClause : OpenMP_NovariantsClauseSkip<>;
+
 //===----------------------------------------------------------------------===//
 // V5.2: [12.6.1] `grainsize` clause
 //===----------------------------------------------------------------------===//
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index 0a5736f46b088..b9c92a7ebcc57 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -2325,6 +2325,28 @@ def MaskedOp : OpenMP_Op<"masked", traits = [
   ];
 }
 
+//===----------------------------------------------------------------------===//
+// [Spec 5.1] 12.3 dispatch Construct
+//===----------------------------------------------------------------------===//
+def DispatchOp : OpenMP_Op<"dispatch", traits = [
+    AttrSizedOperandSegments, NoRegionArguments
+  ], clauses = [
+    // TODO: Complete clause list (device, depend, is_device_ptr).
+    OpenMP_NocontextClause, OpenMP_NovariantsClause, OpenMP_NowaitClause
+  ], singleRegion = true> {
+  let summary = "dispatch construct";
+  let description = [{
+    The dispatch construct enables the invocation of a variant of a
+    base procedure. The structured block of a dispatch construct is
+    a single expression statement that contains a function call or
+    a subroutine call.
+  }] # clausesDescription;
+
+  let builders = [
+    OpBuilder<(ins CArg<"const DispatchOperands &">:$clauses)>
+  ];
+}
+
 //===----------------------------------------------------------------------===//
 // [Spec 5.2] 6.5 allocate Directive
 //===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
index 67bc2bdf35619..a97ee275662dc 100644
--- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
+++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
@@ -5097,6 +5097,16 @@ void MaskedOp::build(OpBuilder &builder, OperationState &state,
   MaskedOp::build(builder, state, clauses.filteredThreadId);
 }
 
+//===----------------------------------------------------------------------===//
+// Spec 5.1: Dispatch construct (12.3)
+//===----------------------------------------------------------------------===//
+
+void DispatchOp::build(OpBuilder &builder, OperationState &state,
+                       const DispatchOperands &clauses) {
+  DispatchOp::build(builder, state, clauses.nocontext, clauses.novariants,
+                    clauses.nowait);
+}
+
 //===----------------------------------------------------------------------===//
 // Spec 5.2: Scan construct (5.6)
 //===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 85963c402bc87..886f70596ec53 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -813,6 +813,24 @@ static llvm::omp::ProcBindKind getProcBindKind(omp::ClauseProcBindKind kind) {
   llvm_unreachable("Unknown ClauseProcBindKind kind");
 }
 
+/// Convert 'dispatch' operation into LLVM IR.
+static LogicalResult
+convertOmpDispatch(Operation &opInst, llvm::IRBuilderBase &builder,
+                   LLVM::ModuleTranslation &moduleTranslation) {
+  auto dispatchOp = cast<omp::DispatchOp>(opInst);
+
+  if (failed(checkImplementationStatus(opInst)))
+    return failure();
+
+  auto &region = dispatchOp.getRegion();
+  auto result = convertOmpOpRegions(region, "omp.dispatch.region", builder,
+                                    moduleTranslation);
+  if (!result)
+    return handleError(result.takeError(), opInst);
+  builder.SetInsertPoint(*result);
+  return success();
+}
+
 /// Converts an OpenMP 'masked' operation into LLVM IR using OpenMPIRBuilder.
 static LogicalResult
 convertOmpMasked(Operation &opInst, llvm::IRBuilderBase &builder,
@@ -9831,6 +9849,9 @@ LogicalResult OpenMPDialectLLVMIRTranslationInterface::convertOperation(
           .Case([&](omp::ParallelOp op) {
             return convertOmpParallel(op, builder, moduleTranslation);
           })
+          .Case([&](omp::DispatchOp) {
+            return convertOmpDispatch(*op, builder, moduleTranslation);
+          })
           .Case([&](omp::MaskedOp) {
             return convertOmpMasked(*op, builder, moduleTranslation);
           })
diff --git a/mlir/test/Dialect/OpenMP/dispatch.mlir b/mlir/test/Dialect/OpenMP/dispatch.mlir
new file mode 100644
index 0000000000000..42ada49951134
--- /dev/null
+++ b/mlir/test/Dialect/OpenMP/dispatch.mlir
@@ -0,0 +1,117 @@
+// RUN: mlir-opt %s | mlir-opt | FileCheck %s
+
+// Variant selection (e.g. from Fortran `declare variant`) is resolved in the
+// frontend, so at the MLIR level the dispatch region simply wraps a call to the
+// selected variant procedure.
+
+// CHECK-LABEL: func.func @omp_dispatch
+// CHECK-SAME: (%[[X:.*]]: memref<i32>)
+func.func @omp_dispatch(%x : memref<i32>) -> () {
+  // CHECK: omp.dispatch {
+  // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
+  // CHECK-NEXT: omp.terminator
+  // CHECK-NEXT: }
+  omp.dispatch {
+    func.call @variant(%x) : (memref<i32>) -> ()
+    omp.terminator
+  }
+  return
+}
+
+// Test that the generic form of omp.dispatch roundtrips to pretty-printed form.
+// CHECK-LABEL: func.func @omp_dispatch_generic_to_pretty
+// CHECK-SAME: (%[[X:.*]]: memref<i32>)
+func.func @omp_dispatch_generic_to_pretty(%x : memref<i32>) -> () {
+  // A plain call (outside any dispatch region) is left untouched.
+  // CHECK: call @omp_dispatch(%[[X]]) : (memref<i32>) -> ()
+  func.call @omp_dispatch(%x) : (memref<i32>) -> ()
+  // CHECK: omp.dispatch {
+  // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
+  // CHECK-NEXT: omp.terminator
+  // CHECK-NEXT: }
+  "omp.dispatch" () ({
+    func.call @variant(%x) : (memref<i32>) -> ()
+    "omp.terminator" () : () -> ()
+  }) : () -> ()
+  return
+}
+
+// Test the nowait clause on omp.dispatch.
+// CHECK-LABEL: func.func @omp_dispatch_nowait
+// CHECK-SAME: (%[[X:.*]]: memref<i32>)
+func.func @omp_dispatch_nowait(%x : memref<i32>) -> () {
+  // CHECK: omp.dispatch nowait {
+  // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
+  // CHECK-NEXT: omp.terminator
+  // CHECK-NEXT: }
+  omp.dispatch nowait {
+    func.call @variant(%x) : (memref<i32>) -> ()
+    omp.terminator
+  }
+  return
+}
+
+// novariants clause round-trip; the frontend materializes the runtime
+// base/variant selection inside the region.
+// CHECK-LABEL: func.func @omp_dispatch_novariants
+// CHECK-SAME: (%[[COND:.*]]: i1, %[[X:.*]]: memref<i32>)
+func.func @omp_dispatch_novariants(%cond : i1, %x : memref<i32>) -> () {
+  // CHECK: omp.dispatch novariants(%[[COND]]) {
+  // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
+  // CHECK-NEXT: omp.terminator
+  // CHECK-NEXT: }
+  omp.dispatch novariants(%cond) {
+    func.call @variant(%x) : (memref<i32>) -> ()
+    omp.terminator
+  }
+  return
+}
+
+// novariants and nowait together.
+// CHECK-LABEL: func.func @omp_dispatch_novariants_nowait
+// CHECK-SAME: (%[[COND:.*]]: i1, %[[X:.*]]: memref<i32>)
+func.func @omp_dispatch_novariants_nowait(%cond : i1, %x : memref<i32>) -> () {
+  // CHECK: omp.dispatch novariants(%[[COND]]) nowait {
+  // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
+  // CHECK-NEXT: omp.terminator
+  // CHECK-NEXT: }
+  omp.dispatch novariants(%cond) nowait {
+    func.call @variant(%x) : (memref<i32>) -> ()
+    omp.terminator
+  }
+  return
+}
+
+// nocontext clause round-trip; the frontend materializes the runtime
+// base/variant selection inside the region.
+// CHECK-LABEL: func.func @omp_dispatch_nocontext
+// CHECK-SAME: (%[[COND:.*]]: i1, %[[X:.*]]: memref<i32>)
+func.func @omp_dispatch_nocontext(%cond : i1, %x : memref<i32>) -> () {
+  // CHECK: omp.dispatch nocontext(%[[COND]]) {
+  // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
+  // CHECK-NEXT: omp.terminator
+  // CHECK-NEXT: }
+  omp.dispatch nocontext(%cond) {
+    func.call @variant(%x) : (memref<i32>) -> ()
+    omp.terminator
+  }
+  return
+}
+
+// nocontext and novariants together.
+// CHECK-LABEL: func.func @omp_dispatch_nocontext_novariants
+// CHECK-SAME: (%[[COND:.*]]: i1, %[[X:.*]]: memref<i32>)
+func.func @omp_dispatch_nocontext_novariants(%cond : i1, %x : memref<i32>) -> () {
+  // CHECK: omp.dispatch nocontext(%[[COND]]) novariants(%[[COND]]) {
+  // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
+  // CHECK-NEXT: omp.terminator
+  // CHECK-NEXT: }
+  omp.dispatch nocontext(%cond) novariants(%cond) {
+    func.call @variant(%x) : (memref<i32>) -> ()
+    omp.terminator
+  }
+  return
+}
+
+// CHECK-LABEL: func.func private @variant(memref<i32>)
+func.func private @variant(memref<i32>) -> ()
diff --git a/mlir/test/Target/LLVMIR/openmp-dispatch.mlir b/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
new file mode 100644
index 0000000000000..8f027eceb4836
--- /dev/null
+++ b/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
@@ -0,0 +1,72 @@
+// RUN: mlir-translate -mlir-to-llvmir %s | FileCheck %s
+
+module attributes {omp.is_target_device = false, omp.is_gpu = false, omp.version = #omp.version<version = 31>} {
+  // CHECK-LABEL: define void @_QMfuncsPfoo_variant()
+  llvm.func @_QMfuncsPfoo_variant() {
+    llvm.return
+  }
+  // CHECK-LABEL: define void @_QMfuncsPfoo_dispatch()
+  llvm.func @_QMfuncsPfoo_dispatch() {
+    llvm.return
+  }
+  // CHECK-LABEL: define void @_QQmain()
+  llvm.func @_QQmain() {
+    // CHECK: call void @_QMfuncsPfoo_dispatch()
+    llvm.call @_QMfuncsPfoo_dispatch() : () -> ()
+    // CHECK: br label %omp.dispatch.region
+    // CHECK: omp.dispatch.region:
+    omp.dispatch {
+      // CHECK: call void @_QMfuncsPfoo_variant()
+      llvm.call @_QMfuncsPfoo_variant() : () -> ()
+      // CHECK: br label %omp.region.cont
+      omp.terminator
+    }
+    // CHECK: omp.region.cont:
+    llvm.return
+  }
+  // The nowait clause is accepted; it is a no-op in the current synchronous
+  // inline lowering, producing the same dispatch region.
+  // CHECK-LABEL: define void @test_dispatch_nowait()
+  llvm.func @test_dispatch_nowait() {
+    // CHECK: br label %omp.dispatch.region
+    // CHECK: omp.dispatch.region:
+    omp.dispatch nowait {
+      // CHECK: call void @_QMfuncsPfoo_variant()
+      llvm.call @_QMfuncsPfoo_variant() : () -> ()
+      // CHECK: br label %omp.region.cont
+      omp.terminator
+    }
+    // CHECK: omp.region.cont:
+    llvm.return
+  }
+  // The novariants operand is ignored at translation; the region already holds
+  // the runtime base/variant selection.
+  // CHECK-LABEL: define void @test_dispatch_novariants(i1
+  llvm.func @test_dispatch_novariants(%cond : i1) {
+    // CHECK: br label %omp.dispatch.region
+    // CHECK: omp.dispatch.region:
+    omp.dispatch novariants(%cond) {
+      // CHECK: call void @_QMfuncsPfoo_variant()
+      llvm.call @_QMfuncsPfoo_variant() : () -> ()
+      // CHECK: br label %omp.region.cont
+      omp.terminator
+    }
+    // CHECK: omp.region.cont:
+    llvm.return
+  }
+  // The nocontext operand is ignored at translation; the region already holds
+  // the runtime base/variant selection.
+  // CHECK-LABEL: define void @test_dispatch_nocontext(i1
+  llvm.func @test_dispatch_nocontext(%cond : i1) {
+    // CHECK: br label %omp.dispatch.region
+    // CHECK: omp.dispatch.region:
+    omp.dispatch nocontext(%cond) {
+      // CHECK: call void @_QMfuncsPfoo_variant()
+      llvm.call @_QMfuncsPfoo_variant() : () -> ()
+      // CHECK: br label %omp.region.cont
+      omp.terminator
+    }
+    // CHECK: omp.region.cont:
+    llvm.return
+  }
+}

>From dd41cb2693bc7ae0f9931ad4abdb9261b5ece813 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Sun, 16 Aug 2026 10:49:06 -0500
Subject: [PATCH 2/5] nowait is marked as asynchronous implementation by
 calling checkNowait().

---
 .../Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp  |  5 +++++
 mlir/test/Target/LLVMIR/openmp-dispatch.mlir      | 15 ---------------
 mlir/test/Target/LLVMIR/openmp-todo.mlir          | 11 +++++++++++
 3 files changed, 16 insertions(+), 15 deletions(-)

diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 886f70596ec53..ac7b7a82bf618 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -512,6 +512,11 @@ static LogicalResult checkImplementationStatus(Operation &op) {
         checkTaskReductionByref(op, result);
       })
       .Case([&](omp::TaskwaitOp op) { checkNowait(op, result); })
+      .Case([&](omp::DispatchOp op) {
+        // nowait clause requests asynchronous dispatch and is not yet honored,
+        // so diagnose it rather than silently dropping it.
+        checkNowait(op, result);
+      })
       .Case([&](omp::TaskloopContextOp op) {
         checkAllocate(op, result);
         checkInReduction(op, result);
diff --git a/mlir/test/Target/LLVMIR/openmp-dispatch.mlir b/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
index 8f027eceb4836..47a349f02e3b1 100644
--- a/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
@@ -24,21 +24,6 @@ module attributes {omp.is_target_device = false, omp.is_gpu = false, omp.version
     // CHECK: omp.region.cont:
     llvm.return
   }
-  // The nowait clause is accepted; it is a no-op in the current synchronous
-  // inline lowering, producing the same dispatch region.
-  // CHECK-LABEL: define void @test_dispatch_nowait()
-  llvm.func @test_dispatch_nowait() {
-    // CHECK: br label %omp.dispatch.region
-    // CHECK: omp.dispatch.region:
-    omp.dispatch nowait {
-      // CHECK: call void @_QMfuncsPfoo_variant()
-      llvm.call @_QMfuncsPfoo_variant() : () -> ()
-      // CHECK: br label %omp.region.cont
-      omp.terminator
-    }
-    // CHECK: omp.region.cont:
-    llvm.return
-  }
   // The novariants operand is ignored at translation; the region already holds
   // the runtime base/variant selection.
   // CHECK-LABEL: define void @test_dispatch_novariants(i1
diff --git a/mlir/test/Target/LLVMIR/openmp-todo.mlir b/mlir/test/Target/LLVMIR/openmp-todo.mlir
index e29659255f690..9fbaa22c14551 100644
--- a/mlir/test/Target/LLVMIR/openmp-todo.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-todo.mlir
@@ -635,6 +635,17 @@ llvm.func @taskwait_nowait() {
 
 // -----
 
+llvm.func @dispatch_nowait() {
+  // expected-error at below {{not yet implemented: Unhandled clause nowait in omp.dispatch operation}}
+  // expected-error at below {{LLVM Translation failed for operation: omp.dispatch}}
+  omp.dispatch nowait {
+    omp.terminator
+  }
+  llvm.return
+}
+
+// -----
+
 llvm.func @teams_allocate(%x : !llvm.ptr) {
   // expected-error at below {{not yet implemented: Unhandled clause allocate in omp.teams operation}}
   // expected-error at below {{LLVM Translation failed for operation: omp.teams}}

>From c69ab57e84e7b0e281d20f85ce42a942acf3e4c5 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <koops at hpe.com>
Date: Wed, 26 Aug 2026 12:13:22 -0500
Subject: [PATCH 3/5] Taking care of Abidh's review comments: 1) The producer
 of the region materializes the base/variant selection    and the LLVM IR
 translation deliberately ignores these operands. 2) Reword the misleading
 "runtime base/variant selection" test comments to state    the selection is
 done by the region producer, not at translation time.

Other minor changes.
---
 .../mlir/Dialect/OpenMP/OpenMPClauses.td      | 116 ++++++++++--------
 mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td |   7 +-
 mlir/test/Dialect/OpenMP/dispatch.mlir        |  10 +-
 mlir/test/Target/LLVMIR/openmp-dispatch.mlir  |  10 +-
 4 files changed, 79 insertions(+), 64 deletions(-)

diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td
index 7c735544858f6..86ce056134bd5 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td
@@ -502,60 +502,6 @@ class OpenMP_FinalClauseSkip<
 
 def OpenMP_FinalClause : OpenMP_FinalClauseSkip<>;
 
-//===----------------------------------------------------------------------===//
-// V5.2: [7.6.2] `nocontext` clause
-//===----------------------------------------------------------------------===//
-
-class OpenMP_NocontextClauseSkip<
-    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
-    Optional<I1>:$nocontext
-  );
-
-  let optAssemblyFormat = [{
-    `nocontext` `(` $nocontext `)`
-  }];
-
-  let description = [{
-    When parameter to `nocontext` evaluates to `true`, the dispatch construct is not added
-    to the OpenMP context, so a function variant that would be selected only
-    because of the enclosing dispatch construct is not selected.
-  }];
-}
-
-def OpenMP_NocontextClause : OpenMP_NocontextClauseSkip<>;
-
-//===----------------------------------------------------------------------===//
-// V5.2: [7.6.1] `novariants` clause
-//===----------------------------------------------------------------------===//
-
-class OpenMP_NovariantsClauseSkip<
-    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
-    Optional<I1>:$novariants
-  );
-
-  let optAssemblyFormat = [{
-    `novariants` `(` $novariants `)`
-  }];
-
-  let description = [{
-    When a `novariants` clause is present and its expression evaluates to
-    `true`, no function variant is selected for the target call of the dispatch
-    region, even if one would be selected normally. The use of a variable in a
-    `novariants` clause expression causes an implicit reference to the variable
-    in all enclosing constructs.
-  }];
-}
-
-def OpenMP_NovariantsClause : OpenMP_NovariantsClauseSkip<>;
-
 //===----------------------------------------------------------------------===//
 // V5.2: [12.6.1] `grainsize` clause
 //===----------------------------------------------------------------------===//
@@ -1049,6 +995,36 @@ class OpenMP_MergeableClauseSkip<
 
 def OpenMP_MergeableClause : OpenMP_MergeableClauseSkip<>;
 
+//===----------------------------------------------------------------------===//
+// V5.2: [7.6.2] `nocontext` clause
+//===----------------------------------------------------------------------===//
+
+class OpenMP_NocontextClauseSkip<
+    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
+    Optional<I1>:$nocontext
+  );
+
+  let optAssemblyFormat = [{
+    `nocontext` `(` $nocontext `)`
+  }];
+
+  let description = [{
+    When parameter to `nocontext` evaluates to `true`, the dispatch construct is
+    not added to the OpenMP context, so a function variant that would be selected
+    only because of the enclosing dispatch construct is not selected.
+
+    At the dialect level this operand carries no semantics of its own. The
+    producer of the region is responsible for materializing the base/variant
+    selection, and the LLVM IR translation deliberately ignores this operand.
+  }];
+}
+
+def OpenMP_NocontextClause : OpenMP_NocontextClauseSkip<>;
+
 //===----------------------------------------------------------------------===//
 // V5.2: [15.7] `nogroup` clause
 //===----------------------------------------------------------------------===//
@@ -1124,6 +1100,38 @@ class OpenMP_NotinbranchClauseSkip<
 }
 def OpenMP_NotinbranchClause : OpenMP_NotinbranchClauseSkip<>;
 
+//===----------------------------------------------------------------------===//
+// V5.2: [7.6.1] `novariants` clause
+//===----------------------------------------------------------------------===//
+
+class OpenMP_NovariantsClauseSkip<
+    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
+    Optional<I1>:$novariants
+  );
+
+  let optAssemblyFormat = [{
+    `novariants` `(` $novariants `)`
+  }];
+
+  let description = [{
+    When a `novariants` clause is present and its expression evaluates to
+    `true`, no function variant is selected for the target call of the dispatch
+    region, even if one would be selected normally. The use of a variable in a
+    `novariants` clause expression causes an implicit reference to the variable
+    in all enclosing constructs.
+
+    At the dialect level this operand carries no semantics of its own. The
+    producer of the region is responsible for materializing the base/variant
+    selection, and the LLVM IR translation deliberately ignores this operand.
+  }];
+}
+
+def OpenMP_NovariantsClause : OpenMP_NovariantsClauseSkip<>;
+
 //===----------------------------------------------------------------------===//
 // V5.2: [15.6] `nowait` clause
 //===----------------------------------------------------------------------===//
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index b9c92a7ebcc57..044a96d9ac20d 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -2326,7 +2326,7 @@ def MaskedOp : OpenMP_Op<"masked", traits = [
 }
 
 //===----------------------------------------------------------------------===//
-// [Spec 5.1] 12.3 dispatch Construct
+// [Spec 5.2] 7.6 dispatch Construct
 //===----------------------------------------------------------------------===//
 def DispatchOp : OpenMP_Op<"dispatch", traits = [
     AttrSizedOperandSegments, NoRegionArguments
@@ -2340,6 +2340,11 @@ def DispatchOp : OpenMP_Op<"dispatch", traits = [
     base procedure. The structured block of a dispatch construct is
     a single expression statement that contains a function call or
     a subroutine call.
+
+    The producer of the region is responsible for materializing the base/variant
+    selection inside the region. The `nocontext` and `novariants` operands are
+    retained on the op only so the producer can read them back during call
+    lowering; the LLVM IR translation deliberately ignores these operands.
   }] # clausesDescription;
 
   let builders = [
diff --git a/mlir/test/Dialect/OpenMP/dispatch.mlir b/mlir/test/Dialect/OpenMP/dispatch.mlir
index 42ada49951134..34427687920b9 100644
--- a/mlir/test/Dialect/OpenMP/dispatch.mlir
+++ b/mlir/test/Dialect/OpenMP/dispatch.mlir
@@ -1,8 +1,8 @@
 // RUN: mlir-opt %s | mlir-opt | FileCheck %s
 
-// Variant selection (e.g. from Fortran `declare variant`) is resolved in the
-// frontend, so at the MLIR level the dispatch region simply wraps a call to the
-// selected variant procedure.
+// Variant selection (e.g. from Fortran `declare variant`) is resolved by the
+// producer of the region, so at the MLIR level the dispatch region simply wraps
+// a call to the selected variant procedure.
 
 // CHECK-LABEL: func.func @omp_dispatch
 // CHECK-SAME: (%[[X:.*]]: memref<i32>)
@@ -51,7 +51,7 @@ func.func @omp_dispatch_nowait(%x : memref<i32>) -> () {
   return
 }
 
-// novariants clause round-trip; the frontend materializes the runtime
+// novariants clause round-trip; the producer of the region materializes the
 // base/variant selection inside the region.
 // CHECK-LABEL: func.func @omp_dispatch_novariants
 // CHECK-SAME: (%[[COND:.*]]: i1, %[[X:.*]]: memref<i32>)
@@ -82,7 +82,7 @@ func.func @omp_dispatch_novariants_nowait(%cond : i1, %x : memref<i32>) -> () {
   return
 }
 
-// nocontext clause round-trip; the frontend materializes the runtime
+// nocontext clause round-trip; the producer of the region materializes the
 // base/variant selection inside the region.
 // CHECK-LABEL: func.func @omp_dispatch_nocontext
 // CHECK-SAME: (%[[COND:.*]]: i1, %[[X:.*]]: memref<i32>)
diff --git a/mlir/test/Target/LLVMIR/openmp-dispatch.mlir b/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
index 47a349f02e3b1..5dbd0d52646c0 100644
--- a/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
@@ -15,6 +15,8 @@ module attributes {omp.is_target_device = false, omp.is_gpu = false, omp.version
     llvm.call @_QMfuncsPfoo_dispatch() : () -> ()
     // CHECK: br label %omp.dispatch.region
     // CHECK: omp.dispatch.region:
+    // The producer of the region has already materialized the base/variant
+    // selection; MLIR only translates the call inside the region.
     omp.dispatch {
       // CHECK: call void @_QMfuncsPfoo_variant()
       llvm.call @_QMfuncsPfoo_variant() : () -> ()
@@ -24,8 +26,8 @@ module attributes {omp.is_target_device = false, omp.is_gpu = false, omp.version
     // CHECK: omp.region.cont:
     llvm.return
   }
-  // The novariants operand is ignored at translation; the region already holds
-  // the runtime base/variant selection.
+  // The producer of the region materializes the base/variant selection; the
+  // LLVM IR translation deliberately ignores the novariants operand.
   // CHECK-LABEL: define void @test_dispatch_novariants(i1
   llvm.func @test_dispatch_novariants(%cond : i1) {
     // CHECK: br label %omp.dispatch.region
@@ -39,8 +41,8 @@ module attributes {omp.is_target_device = false, omp.is_gpu = false, omp.version
     // CHECK: omp.region.cont:
     llvm.return
   }
-  // The nocontext operand is ignored at translation; the region already holds
-  // the runtime base/variant selection.
+  // The producer of the region materializes the base/variant selection; the
+  // LLVM IR translation deliberately ignores the nocontext operand.
   // CHECK-LABEL: define void @test_dispatch_nocontext(i1
   llvm.func @test_dispatch_nocontext(%cond : i1) {
     // CHECK: br label %omp.dispatch.region

>From 3dc4c94f0655f207106bb89a7e1cab6a9cb80864 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <koops at hpe.com>
Date: Wed, 2 Sep 2026 12:36:02 -0500
Subject: [PATCH 4/5] Updating dispatch in flang/docs/OpenMPSupport.md

---
 flang/docs/OpenMPSupport.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/flang/docs/OpenMPSupport.md b/flang/docs/OpenMPSupport.md
index 5a5285421e724..1251180eed7a0 100644
--- a/flang/docs/OpenMPSupport.md
+++ b/flang/docs/OpenMPSupport.md
@@ -184,7 +184,7 @@ Parser/Semantics, MLIR, Lowering, or the OpenMPIRBuilder.
 | compare clause on atomic construct | <span class="part">partial</span> | SunilKuravinakop | Semantics and lowering coverage exist (`flang/test/Semantics/OpenMP/atomic-compare.f90`, `flang/test/Lower/OpenMP/atomic-compare.f90`); remaining gaps are primarily fail/capture combinations and broader type coverage. | [llvm/llvm-project#184761](https://github.com/llvm/llvm-project/pull/184761) |
 | fail clause on atomic construct | <span class="part">partial</span> | SunilKuravinakop | Semantics coverage exists (`flang/test/Semantics/OpenMP/atomic-compare.f90`), but lowering for `fail(...)` paths is still TODO (`flang/test/Lower/OpenMP/Todo/atomic-compare-fail.f90`). Complete lowering for compare+fail(+capture), then add non-TODO lowering tests. | [llvm/llvm-project#184761](https://github.com/llvm/llvm-project/pull/184761) |
 | interop construct | <span class="part">partial</span> | sshrestha-aa | Semantics coverage exists (`flang/test/Semantics/OpenMP/interop-construct.f90`), but lowering remains TODO-tracked (`flang/lib/Lower/OpenMP/OpenMP.cpp` TODO: `OpenMPInteropConstruct`; `flang/test/Lower/OpenMP/Todo/interop-construct.f90`). | [llvm/llvm-project#132343](https://github.com/llvm/llvm-project/pull/132343) |
-| dispatch construct | <span class="part">partial</span> | SunilKuravinakop | Semantics coverage exists (`flang/test/Semantics/OpenMP/dispatch.f90`), but lowering remains TODO-tracked (`flang/lib/Lower/OpenMP/OpenMP.cpp` TODO: `OpenMPDispatchConstruct`; `flang/test/Lower/OpenMP/Todo/dispatch.f90`). | [llvm/llvm-project#121982](https://github.com/llvm/llvm-project/pull/121982) |
+| dispatch construct | <span class="part">partial</span> | SunilKuravinakop | Semantics coverage exists (`flang/test/Semantics/OpenMP/dispatch.f90`), follow-on completness work still in progress:Being implemented in 3 parts, MLIR support under review | [llvm/llvm-project#121982](https://github.com/llvm/llvm-project/pull/121982), [llvm/llvm-project#215877](https://github.com/llvm/llvm-project/pull/215877) |
 | masked construct | <span class="part">partial</span> | | Covered in semantics/lowering (`flang/test/Semantics/OpenMP/masked.f90`, `flang/test/Lower/OpenMP/masked.f90`). | [llvm/llvm-project#91432](https://github.com/llvm/llvm-project/pull/91432) |
 | masked combined constructs | <span class="part">partial</span> | | Covered in lowering tests (`flang/test/Lower/OpenMP/masked_taskloop.f90`, `flang/test/Lower/OpenMP/parallel-masked-taskloop.f90`) with ongoing breadth expansion. | [llvm/llvm-project#188071](https://github.com/llvm/llvm-project/pull/188071), [llvm/llvm-project#188070](https://github.com/llvm/llvm-project/pull/188070) |
 | present map type modifier | <span class="part">partial</span> | | Semantics coverage exists (`flang/test/Semantics/OpenMP/present.f90`) and map lowering exists, with ongoing completeness checks. | [llvm/llvm-project#129586](https://github.com/llvm/llvm-project/pull/129586) |

>From 37f9c92963d5dfbf38d1cddc8ebe6a4b7fec0af4 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <koops at hpe.com>
Date: Fri, 4 Sep 2026 06:08:35 -0500
Subject: [PATCH 5/5] Handling Abidh and Saieiei feedback: Moving tests from
 dispatch.mlir to ops.mlir Spec version number and their sections. Description
 of "dispatch" in OpenMPOps.td

---
 flang/docs/OpenMPSupport.md                   |   2 +-
 mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td |  10 +-
 mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp  |   2 +-
 .../OpenMP/OpenMPToLLVMIRTranslation.cpp      |  12 +-
 mlir/test/Dialect/OpenMP/dispatch.mlir        | 117 -----------------
 mlir/test/Dialect/OpenMP/ops.mlir             | 118 ++++++++++++++++++
 mlir/test/Target/LLVMIR/openmp-dispatch.mlir  |   2 +-
 7 files changed, 137 insertions(+), 126 deletions(-)
 delete mode 100644 mlir/test/Dialect/OpenMP/dispatch.mlir

diff --git a/flang/docs/OpenMPSupport.md b/flang/docs/OpenMPSupport.md
index 1251180eed7a0..54284bf5a893b 100644
--- a/flang/docs/OpenMPSupport.md
+++ b/flang/docs/OpenMPSupport.md
@@ -184,7 +184,7 @@ Parser/Semantics, MLIR, Lowering, or the OpenMPIRBuilder.
 | compare clause on atomic construct | <span class="part">partial</span> | SunilKuravinakop | Semantics and lowering coverage exist (`flang/test/Semantics/OpenMP/atomic-compare.f90`, `flang/test/Lower/OpenMP/atomic-compare.f90`); remaining gaps are primarily fail/capture combinations and broader type coverage. | [llvm/llvm-project#184761](https://github.com/llvm/llvm-project/pull/184761) |
 | fail clause on atomic construct | <span class="part">partial</span> | SunilKuravinakop | Semantics coverage exists (`flang/test/Semantics/OpenMP/atomic-compare.f90`), but lowering for `fail(...)` paths is still TODO (`flang/test/Lower/OpenMP/Todo/atomic-compare-fail.f90`). Complete lowering for compare+fail(+capture), then add non-TODO lowering tests. | [llvm/llvm-project#184761](https://github.com/llvm/llvm-project/pull/184761) |
 | interop construct | <span class="part">partial</span> | sshrestha-aa | Semantics coverage exists (`flang/test/Semantics/OpenMP/interop-construct.f90`), but lowering remains TODO-tracked (`flang/lib/Lower/OpenMP/OpenMP.cpp` TODO: `OpenMPInteropConstruct`; `flang/test/Lower/OpenMP/Todo/interop-construct.f90`). | [llvm/llvm-project#132343](https://github.com/llvm/llvm-project/pull/132343) |
-| dispatch construct | <span class="part">partial</span> | SunilKuravinakop | Semantics coverage exists (`flang/test/Semantics/OpenMP/dispatch.f90`), follow-on completness work still in progress:Being implemented in 3 parts, MLIR support under review | [llvm/llvm-project#121982](https://github.com/llvm/llvm-project/pull/121982), [llvm/llvm-project#215877](https://github.com/llvm/llvm-project/pull/215877) |
+| dispatch construct | <span class="part">partial</span> | SunilKuravinakop | Semantics coverage exists (`flang/test/Semantics/OpenMP/dispatch.f90`), follow-on completeness work still in progress: Being implemented in 3 parts, MLIR support under review | [llvm/llvm-project#121982](https://github.com/llvm/llvm-project/pull/121982), [llvm/llvm-project#215877](https://github.com/llvm/llvm-project/pull/215877) |
 | masked construct | <span class="part">partial</span> | | Covered in semantics/lowering (`flang/test/Semantics/OpenMP/masked.f90`, `flang/test/Lower/OpenMP/masked.f90`). | [llvm/llvm-project#91432](https://github.com/llvm/llvm-project/pull/91432) |
 | masked combined constructs | <span class="part">partial</span> | | Covered in lowering tests (`flang/test/Lower/OpenMP/masked_taskloop.f90`, `flang/test/Lower/OpenMP/parallel-masked-taskloop.f90`) with ongoing breadth expansion. | [llvm/llvm-project#188071](https://github.com/llvm/llvm-project/pull/188071), [llvm/llvm-project#188070](https://github.com/llvm/llvm-project/pull/188070) |
 | present map type modifier | <span class="part">partial</span> | | Semantics coverage exists (`flang/test/Semantics/OpenMP/present.f90`) and map lowering exists, with ongoing completeness checks. | [llvm/llvm-project#129586](https://github.com/llvm/llvm-project/pull/129586) |
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index 06979500ab134..103fa377c1742 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -2436,9 +2436,13 @@ def DispatchOp : OpenMP_Op<"dispatch", traits = [
   let summary = "dispatch construct";
   let description = [{
     The dispatch construct enables the invocation of a variant of a
-    base procedure. The structured block of a dispatch construct is
-    a single expression statement that contains a function call or
-    a subroutine call.
+    base procedure. It was introduced in OpenMP 5.0; the section numbers cited
+    here follow the OpenMP 5.2 spec (7.6). At the source level (C/C++/Fortran)
+    the structured block of a dispatch construct is a single expression
+    statement containing a function or subroutine call. That is a restriction on
+    the source form, not an invariant of this op: the region may contain
+    multiple blocks and control flow (for example, a runtime base/variant
+    branch).
 
     The producer of the region is responsible for materializing the base/variant
     selection inside the region. The `nocontext` and `novariants` operands are
diff --git a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
index 087b378a3a843..f058cd0514c13 100644
--- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
+++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
@@ -5427,7 +5427,7 @@ void MaskedOp::build(OpBuilder &builder, OperationState &state,
 }
 
 //===----------------------------------------------------------------------===//
-// Spec 5.1: Dispatch construct (12.3)
+// Spec 5.2: Dispatch construct (7.6)
 //===----------------------------------------------------------------------===//
 
 void DispatchOp::build(OpBuilder &builder, OperationState &state,
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index d7d6bef84f489..5931c2bf6f50e 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -573,9 +573,15 @@ static LogicalResult checkImplementationStatus(Operation &op) {
       })
       .Case([&](omp::TaskwaitOp op) { checkNowait(op, result); })
       .Case([&](omp::DispatchOp op) {
-        // nowait clause requests asynchronous dispatch and is not yet honored,
-        // so diagnose it rather than silently dropping it.
-        checkNowait(op, result);
+        // In OpenMP 5.0/5.1 `dispatch` creates an implicit task and `nowait`
+        // controls whether that task is included; in OpenMP 5.2 `nowait` has no
+        // effect on `dispatch`. The 5.0/5.1 asynchronous-task behavior is not
+        // yet implemented, so diagnose `nowait` for those versions; for 5.2 it
+        // is a legal no-op and is accepted.
+        int64_t version = omp::getOpenMPVersionAttribute(
+            op->getParentOfType<ModuleOp>(), /*fallback=*/50);
+        if (version == 50 || version == 51)
+          checkNowait(op, result);
       })
       .Case([&](omp::TaskloopContextOp op) {
         checkAllocate(op, result);
diff --git a/mlir/test/Dialect/OpenMP/dispatch.mlir b/mlir/test/Dialect/OpenMP/dispatch.mlir
deleted file mode 100644
index 34427687920b9..0000000000000
--- a/mlir/test/Dialect/OpenMP/dispatch.mlir
+++ /dev/null
@@ -1,117 +0,0 @@
-// RUN: mlir-opt %s | mlir-opt | FileCheck %s
-
-// Variant selection (e.g. from Fortran `declare variant`) is resolved by the
-// producer of the region, so at the MLIR level the dispatch region simply wraps
-// a call to the selected variant procedure.
-
-// CHECK-LABEL: func.func @omp_dispatch
-// CHECK-SAME: (%[[X:.*]]: memref<i32>)
-func.func @omp_dispatch(%x : memref<i32>) -> () {
-  // CHECK: omp.dispatch {
-  // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
-  // CHECK-NEXT: omp.terminator
-  // CHECK-NEXT: }
-  omp.dispatch {
-    func.call @variant(%x) : (memref<i32>) -> ()
-    omp.terminator
-  }
-  return
-}
-
-// Test that the generic form of omp.dispatch roundtrips to pretty-printed form.
-// CHECK-LABEL: func.func @omp_dispatch_generic_to_pretty
-// CHECK-SAME: (%[[X:.*]]: memref<i32>)
-func.func @omp_dispatch_generic_to_pretty(%x : memref<i32>) -> () {
-  // A plain call (outside any dispatch region) is left untouched.
-  // CHECK: call @omp_dispatch(%[[X]]) : (memref<i32>) -> ()
-  func.call @omp_dispatch(%x) : (memref<i32>) -> ()
-  // CHECK: omp.dispatch {
-  // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
-  // CHECK-NEXT: omp.terminator
-  // CHECK-NEXT: }
-  "omp.dispatch" () ({
-    func.call @variant(%x) : (memref<i32>) -> ()
-    "omp.terminator" () : () -> ()
-  }) : () -> ()
-  return
-}
-
-// Test the nowait clause on omp.dispatch.
-// CHECK-LABEL: func.func @omp_dispatch_nowait
-// CHECK-SAME: (%[[X:.*]]: memref<i32>)
-func.func @omp_dispatch_nowait(%x : memref<i32>) -> () {
-  // CHECK: omp.dispatch nowait {
-  // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
-  // CHECK-NEXT: omp.terminator
-  // CHECK-NEXT: }
-  omp.dispatch nowait {
-    func.call @variant(%x) : (memref<i32>) -> ()
-    omp.terminator
-  }
-  return
-}
-
-// novariants clause round-trip; the producer of the region materializes the
-// base/variant selection inside the region.
-// CHECK-LABEL: func.func @omp_dispatch_novariants
-// CHECK-SAME: (%[[COND:.*]]: i1, %[[X:.*]]: memref<i32>)
-func.func @omp_dispatch_novariants(%cond : i1, %x : memref<i32>) -> () {
-  // CHECK: omp.dispatch novariants(%[[COND]]) {
-  // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
-  // CHECK-NEXT: omp.terminator
-  // CHECK-NEXT: }
-  omp.dispatch novariants(%cond) {
-    func.call @variant(%x) : (memref<i32>) -> ()
-    omp.terminator
-  }
-  return
-}
-
-// novariants and nowait together.
-// CHECK-LABEL: func.func @omp_dispatch_novariants_nowait
-// CHECK-SAME: (%[[COND:.*]]: i1, %[[X:.*]]: memref<i32>)
-func.func @omp_dispatch_novariants_nowait(%cond : i1, %x : memref<i32>) -> () {
-  // CHECK: omp.dispatch novariants(%[[COND]]) nowait {
-  // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
-  // CHECK-NEXT: omp.terminator
-  // CHECK-NEXT: }
-  omp.dispatch novariants(%cond) nowait {
-    func.call @variant(%x) : (memref<i32>) -> ()
-    omp.terminator
-  }
-  return
-}
-
-// nocontext clause round-trip; the producer of the region materializes the
-// base/variant selection inside the region.
-// CHECK-LABEL: func.func @omp_dispatch_nocontext
-// CHECK-SAME: (%[[COND:.*]]: i1, %[[X:.*]]: memref<i32>)
-func.func @omp_dispatch_nocontext(%cond : i1, %x : memref<i32>) -> () {
-  // CHECK: omp.dispatch nocontext(%[[COND]]) {
-  // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
-  // CHECK-NEXT: omp.terminator
-  // CHECK-NEXT: }
-  omp.dispatch nocontext(%cond) {
-    func.call @variant(%x) : (memref<i32>) -> ()
-    omp.terminator
-  }
-  return
-}
-
-// nocontext and novariants together.
-// CHECK-LABEL: func.func @omp_dispatch_nocontext_novariants
-// CHECK-SAME: (%[[COND:.*]]: i1, %[[X:.*]]: memref<i32>)
-func.func @omp_dispatch_nocontext_novariants(%cond : i1, %x : memref<i32>) -> () {
-  // CHECK: omp.dispatch nocontext(%[[COND]]) novariants(%[[COND]]) {
-  // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
-  // CHECK-NEXT: omp.terminator
-  // CHECK-NEXT: }
-  omp.dispatch nocontext(%cond) novariants(%cond) {
-    func.call @variant(%x) : (memref<i32>) -> ()
-    omp.terminator
-  }
-  return
-}
-
-// CHECK-LABEL: func.func private @variant(memref<i32>)
-func.func private @variant(memref<i32>) -> ()
diff --git a/mlir/test/Dialect/OpenMP/ops.mlir b/mlir/test/Dialect/OpenMP/ops.mlir
index a0aff2cdfda0e..81aed0a45f2ac 100644
--- a/mlir/test/Dialect/OpenMP/ops.mlir
+++ b/mlir/test/Dialect/OpenMP/ops.mlir
@@ -4474,3 +4474,121 @@ func.func @omp_interop_depend(%obj : !llvm.ptr, %dep : !llvm.ptr) -> () {
   omp.interop.destroy %obj : !llvm.ptr depend(taskdependout -> %dep : !llvm.ptr)
   return
 }
+
+// -----
+
+// Variant selection (e.g. from Fortran `declare variant`) is resolved by the
+// producer of the region, so at the MLIR level the dispatch region simply wraps
+// a call to the selected variant procedure.
+
+// CHECK-LABEL: func.func @omp_dispatch
+// CHECK-SAME: (%[[X:.*]]: memref<i32>)
+func.func @omp_dispatch(%x : memref<i32>) -> () {
+  // CHECK: omp.dispatch {
+  // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
+  // CHECK-NEXT: omp.terminator
+  // CHECK-NEXT: }
+  omp.dispatch {
+    func.call @variant(%x) : (memref<i32>) -> ()
+    omp.terminator
+  }
+  return
+}
+
+// Test that the generic form of omp.dispatch roundtrips to pretty-printed form.
+// CHECK-LABEL: func.func @omp_dispatch_generic_to_pretty
+// CHECK-SAME: (%[[X:.*]]: memref<i32>)
+func.func @omp_dispatch_generic_to_pretty(%x : memref<i32>) -> () {
+  // A plain call (outside any dispatch region) is left untouched.
+  // CHECK: call @omp_dispatch(%[[X]]) : (memref<i32>) -> ()
+  func.call @omp_dispatch(%x) : (memref<i32>) -> ()
+  // CHECK: omp.dispatch {
+  // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
+  // CHECK-NEXT: omp.terminator
+  // CHECK-NEXT: }
+  "omp.dispatch" () ({
+    func.call @variant(%x) : (memref<i32>) -> ()
+    "omp.terminator" () : () -> ()
+  }) : () -> ()
+  return
+}
+
+// Test the nowait clause on omp.dispatch.
+// CHECK-LABEL: func.func @omp_dispatch_nowait
+// CHECK-SAME: (%[[X:.*]]: memref<i32>)
+func.func @omp_dispatch_nowait(%x : memref<i32>) -> () {
+  // CHECK: omp.dispatch nowait {
+  // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
+  // CHECK-NEXT: omp.terminator
+  // CHECK-NEXT: }
+  omp.dispatch nowait {
+    func.call @variant(%x) : (memref<i32>) -> ()
+    omp.terminator
+  }
+  return
+}
+
+// novariants clause round-trip; the producer of the region materializes the
+// base/variant selection inside the region.
+// CHECK-LABEL: func.func @omp_dispatch_novariants
+// CHECK-SAME: (%[[COND:.*]]: i1, %[[X:.*]]: memref<i32>)
+func.func @omp_dispatch_novariants(%cond : i1, %x : memref<i32>) -> () {
+  // CHECK: omp.dispatch novariants(%[[COND]]) {
+  // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
+  // CHECK-NEXT: omp.terminator
+  // CHECK-NEXT: }
+  omp.dispatch novariants(%cond) {
+    func.call @variant(%x) : (memref<i32>) -> ()
+    omp.terminator
+  }
+  return
+}
+
+// novariants and nowait together.
+// CHECK-LABEL: func.func @omp_dispatch_novariants_nowait
+// CHECK-SAME: (%[[COND:.*]]: i1, %[[X:.*]]: memref<i32>)
+func.func @omp_dispatch_novariants_nowait(%cond : i1, %x : memref<i32>) -> () {
+  // CHECK: omp.dispatch novariants(%[[COND]]) nowait {
+  // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
+  // CHECK-NEXT: omp.terminator
+  // CHECK-NEXT: }
+  omp.dispatch novariants(%cond) nowait {
+    func.call @variant(%x) : (memref<i32>) -> ()
+    omp.terminator
+  }
+  return
+}
+
+// nocontext clause round-trip; the producer of the region materializes the
+// base/variant selection inside the region.
+// CHECK-LABEL: func.func @omp_dispatch_nocontext
+// CHECK-SAME: (%[[COND:.*]]: i1, %[[X:.*]]: memref<i32>)
+func.func @omp_dispatch_nocontext(%cond : i1, %x : memref<i32>) -> () {
+  // CHECK: omp.dispatch nocontext(%[[COND]]) {
+  // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
+  // CHECK-NEXT: omp.terminator
+  // CHECK-NEXT: }
+  omp.dispatch nocontext(%cond) {
+    func.call @variant(%x) : (memref<i32>) -> ()
+    omp.terminator
+  }
+  return
+}
+
+// nocontext and novariants together.
+// CHECK-LABEL: func.func @omp_dispatch_nocontext_novariants
+// CHECK-SAME: (%[[COND:.*]]: i1, %[[X:.*]]: memref<i32>)
+func.func @omp_dispatch_nocontext_novariants(%cond : i1, %x : memref<i32>) -> () {
+  // CHECK: omp.dispatch nocontext(%[[COND]]) novariants(%[[COND]]) {
+  // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
+  // CHECK-NEXT: omp.terminator
+  // CHECK-NEXT: }
+  omp.dispatch nocontext(%cond) novariants(%cond) {
+    func.call @variant(%x) : (memref<i32>) -> ()
+    omp.terminator
+  }
+  return
+}
+
+// CHECK-LABEL: func.func private @variant(memref<i32>)
+func.func private @variant(memref<i32>) -> ()
diff --git a/mlir/test/Target/LLVMIR/openmp-dispatch.mlir b/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
index 5dbd0d52646c0..ecdf2c97ac597 100644
--- a/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
@@ -1,6 +1,6 @@
 // RUN: mlir-translate -mlir-to-llvmir %s | FileCheck %s
 
-module attributes {omp.is_target_device = false, omp.is_gpu = false, omp.version = #omp.version<version = 31>} {
+module attributes {omp.is_target_device = false, omp.is_gpu = false, omp.version = #omp.version<version = 51>} {
   // CHECK-LABEL: define void @_QMfuncsPfoo_variant()
   llvm.func @_QMfuncsPfoo_variant() {
     llvm.return



More information about the flang-commits mailing list