[flang-commits] [flang] [mlir] [flang][OpenMP] Support for "!$omp dispatch". (PR #203320)

via flang-commits flang-commits at lists.llvm.org
Wed Jul 29 04:07:12 PDT 2026


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

>From d007fc287982ed638ef3900307ca700f0d529f87 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Thu, 11 Jun 2026 10:56:08 -0500
Subject: [PATCH 01/11] Support for "!$omp dispatch".

---
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 33 ++++++-
 flang/test/Integration/OpenMP/dispatch.f90    | 76 ++++++++++++++++
 flang/test/Lower/OpenMP/Todo/dispatch.f90     | 12 ---
 flang/test/Lower/OpenMP/dispatch.f90          | 89 +++++++++++++++++++
 mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td | 15 ++++
 .../OpenMP/OpenMPToLLVMIRTranslation.cpp      | 17 ++++
 mlir/test/Dialect/OpenMP/dispatch.mlir        | 77 ++++++++++++++++
 mlir/test/Target/LLVMIR/openmp-dispatch.mlir  | 81 +++++++++++++++++
 8 files changed, 385 insertions(+), 15 deletions(-)
 create mode 100644 flang/test/Integration/OpenMP/dispatch.f90
 delete mode 100644 flang/test/Lower/OpenMP/Todo/dispatch.f90
 create mode 100644 flang/test/Lower/OpenMP/dispatch.f90
 create mode 100644 mlir/test/Dialect/OpenMP/dispatch.mlir
 create mode 100644 mlir/test/Target/LLVMIR/openmp-dispatch.mlir

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index be876c563433a..758d8eb8148e5 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -2592,6 +2592,18 @@ genMasterOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
       queue, item);
 }
 
+static mlir::omp::DispatchOp
+genDispatchOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
+              semantics::SemanticsContext &semaCtx,
+              lower::pft::Evaluation &eval, mlir::Location loc,
+              const ConstructQueue &queue,
+              ConstructQueue::const_iterator item) {
+  return genOpWithBody<mlir::omp::DispatchOp>(
+      OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval,
+                        llvm::omp::Directive::OMPD_dispatch),
+      queue, item);
+}
+
 static mlir::omp::OrderedOp
 genOrderedOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
              semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval,
@@ -3940,6 +3952,9 @@ static void genOMPDispatch(lower::AbstractConverter &converter,
   case llvm::omp::Directive::OMPD_barrier:
     newOp = genBarrierOp(converter, symTable, semaCtx, eval, loc, queue, item);
     break;
+  case llvm::omp::Directive::OMPD_dispatch:
+    newOp = genDispatchOp(converter, symTable, semaCtx, eval, loc, queue, item);
+    break;
   case llvm::omp::Directive::OMPD_distribute:
     newOp = genStandaloneDistribute(converter, symTable, stmtCtx, semaCtx, eval,
                                     loc, queue, item);
@@ -5312,9 +5327,21 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
 static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
                    semantics::SemanticsContext &semaCtx,
                    lower::pft::Evaluation &eval,
-                   const parser::OpenMPDispatchConstruct &) {
-  if (!semaCtx.langOptions().OpenMPSimd)
-    TODO(converter.getCurrentLocation(), "OpenMPDispatchConstruct");
+                   const parser::OpenMPDispatchConstruct &dispatchConstruct) {
+  const parser::OmpDirectiveSpecification &beginSpec =
+      dispatchConstruct.BeginDir();
+  List<Clause> clauses = makeClauses(beginSpec.Clauses(), semaCtx);
+  if (auto &endSpec = dispatchConstruct.EndDir())
+    clauses.append(makeClauses(endSpec->Clauses(), semaCtx));
+
+  llvm::omp::Directive directive = beginSpec.DirId();
+  mlir::Location currentLocation = converter.genLocation(beginSpec.source);
+
+  ConstructQueue queue{
+      buildConstructQueue(converter.getFirOpBuilder().getModule(), semaCtx,
+                          eval, beginSpec.source, directive, clauses)};
+  genOMPDispatch(converter, symTable, semaCtx, eval, currentLocation, queue,
+                 queue.begin());
 }
 
 static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
diff --git a/flang/test/Integration/OpenMP/dispatch.f90 b/flang/test/Integration/OpenMP/dispatch.f90
new file mode 100644
index 0000000000000..c4d16690c8e29
--- /dev/null
+++ b/flang/test/Integration/OpenMP/dispatch.f90
@@ -0,0 +1,76 @@
+!===----------------------------------------------------------------------===!
+! This directory can be used to add Integration tests involving multiple
+! stages of the compiler (for eg. from Fortran to LLVM IR). It should not
+! contain executable tests. We should only add tests here sparingly and only
+! if there is no other way to test. Repeat this message in each test that is
+! added to this directory and sub-directories.
+!===----------------------------------------------------------------------===!
+
+!RUN: %flang_fc1 -emit-llvm -fopenmp %s -o - | FileCheck %s
+
+!CHECK-LABEL: define void @_QMfuncsPfoo_variant1()
+!CHECK: call ptr @_FortranAioBeginExternalListOutput
+
+!CHECK-LABEL: define void @_QMfuncsPfoo_variant2()
+!CHECK: call ptr @_FortranAioBeginExternalListOutput
+
+!CHECK-LABEL: define void @_QMfuncsPfoo_dispatch()
+!CHECK: %[[COND:.*]] = load i32, ptr @_QMfuncsEfoo_sub
+!CHECK: %[[CMP:.*]] = icmp ne i32 %[[COND]], 0
+!CHECK: br i1 %[[CMP]], label %[[IF_TRUE:.*]], label %[[IF_FALSE:.*]]
+!CHECK: [[IF_TRUE]]:
+!CHECK: call void @_QMfuncsPfoo_variant2()
+!CHECK: [[IF_FALSE]]:
+!CHECK: call void @_QMfuncsPfoo_variant1()
+
+!CHECK-LABEL: define void @_QQmain()
+!CHECK: store i32 0, ptr @_QMfuncsEfoo_sub
+!CHECK: br label %omp.dispatch.region
+!CHECK: omp.dispatch.region:
+!CHECK: call void @_QMfuncsPfoo_dispatch()
+!CHECK: br label %omp.region.cont
+!CHECK: omp.region.cont:
+!CHECK: store i32 1, ptr @_QMfuncsEfoo_sub
+!CHECK: br label %omp.dispatch.region2
+!CHECK: omp.dispatch.region2:
+!CHECK: call void @_QMfuncsPfoo_dispatch()
+!CHECK: br label %omp.region.cont1
+!CHECK: omp.region.cont1:
+
+module funcs
+  implicit none
+  logical :: foo_sub
+
+contains
+
+  subroutine foo_variant1()
+    print *, "in foo_variant1"
+  end subroutine
+
+  subroutine foo_variant2()
+    print *, "in foo_variant2"
+  end subroutine
+
+  subroutine foo_dispatch()
+    if (foo_sub) then
+      call foo_variant2()
+    else
+      call foo_variant1()
+    end if
+  end subroutine
+
+end module funcs
+
+program dispatch_test
+  use funcs
+  implicit none
+
+  foo_sub = .false.
+  !$omp dispatch
+  call foo_dispatch()
+
+  foo_sub = .true.
+  !$omp dispatch
+  call foo_dispatch()
+
+end program
diff --git a/flang/test/Lower/OpenMP/Todo/dispatch.f90 b/flang/test/Lower/OpenMP/Todo/dispatch.f90
deleted file mode 100644
index 380dfa14eaae1..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/dispatch.f90
+++ /dev/null
@@ -1,12 +0,0 @@
-! RUN: %not_todo_cmd %flang_fc1 -emit-fir -fopenmp -fopenmp-version=51 -o - %s 2>&1 | FileCheck %s
-
-! CHECK: not yet implemented: OpenMPDispatchConstruct
-program p
-  integer r
-  r = 1
-!$omp dispatch nowait
-  call foo()
-contains
-  subroutine foo
-  end subroutine
-end program p
diff --git a/flang/test/Lower/OpenMP/dispatch.f90 b/flang/test/Lower/OpenMP/dispatch.f90
new file mode 100644
index 0000000000000..df86503817163
--- /dev/null
+++ b/flang/test/Lower/OpenMP/dispatch.f90
@@ -0,0 +1,89 @@
+!RUN: %flang_fc1 -emit-hlfir -fopenmp %s -o - | FileCheck %s --check-prefix=HLFIR
+!RUN: %flang_fc1 -emit-mlir -fopenmp %s -o - | FileCheck %s --check-prefix=FIR
+
+!HLFIR-LABEL: func @_QMfuncsPfoo_dispatch
+!HLFIR: %[[XD_H:.*]]:2 = hlfir.declare %{{.*}} {{{.*}}uniq_name = {{.*}}foo_dispatch{{.*}}x{{.*}}
+!HLFIR: %[[LOAD_H:.*]] = fir.load %[[XD_H]]#0 : !fir.ref<i32>
+!HLFIR: %[[C1_H:.*]] = arith.constant 1 : i32
+!HLFIR: %[[CMP_H:.*]] = arith.cmpi eq, %[[LOAD_H]], %[[C1_H]] : i32
+!HLFIR: fir.if %[[CMP_H]] {
+!HLFIR:   fir.call @_QMfuncsPvariant1() {{.*}}: () -> ()
+!HLFIR: } else {
+!HLFIR:   fir.call @_QMfuncsPvariant2() {{.*}}: () -> ()
+!HLFIR: }
+
+!FIR-LABEL: func @_QMfuncsPfoo_dispatch
+!FIR: %[[XD_F:.*]] = fir.declare %{{.*}} {{{.*}}uniq_name = {{.*}}foo_dispatch{{.*}}x{{.*}}
+!FIR: %[[LOAD_F:.*]] = fir.load %[[XD_F]] : !fir.ref<i32>
+!FIR: %[[CMP_F:.*]] = arith.cmpi eq, %[[LOAD_F]], %{{.*}} : i32
+!FIR: fir.if %[[CMP_F]] {
+!FIR:   fir.call @_QMfuncsPvariant1() {{.*}}: () -> ()
+!FIR: } else {
+!FIR:   fir.call @_QMfuncsPvariant2() {{.*}}: () -> ()
+!FIR: }
+
+module funcs
+  implicit none
+
+contains
+
+  subroutine variant1()
+    print *, "in variant1"
+  end subroutine
+
+  subroutine variant2()
+    print *, "in variant2"
+  end subroutine
+
+  subroutine foo_dispatch(x)
+    integer, intent(in) :: x
+    if (x == 1) then
+      call variant1()
+    else
+      call variant2()
+    end if
+  end subroutine
+
+end module funcs
+
+!HLFIR-LABEL: func @_QQmain
+!FIR-LABEL: func @_QQmain
+!FIR-DAG: %[[C1:.*]] = arith.constant 1 : i32
+!FIR-DAG: %[[C2:.*]] = arith.constant 2 : i32
+!FIR: %[[X:.*]] = fir.declare %{{.*}} {uniq_name = {{.*}}x{{.*}}
+program dispatch_test
+  use funcs
+  implicit none
+  integer :: x
+
+  !HLFIR: %[[X:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = {{.*}}x{{.*}}
+  !HLFIR: %[[C1:.*]] = arith.constant 1 : i32
+  !HLFIR: hlfir.assign %[[C1]] to %[[X]]#0 : i32, !fir.ref<i32>
+  !FIR: fir.store %[[C1]] to %[[X]] : !fir.ref<i32>
+  x = 1
+  !HLFIR: omp.dispatch {
+  !FIR: omp.dispatch {
+  !$omp dispatch
+  !HLFIR:   fir.call @_QMfuncsPfoo_dispatch(%[[X]]#0) {{.*}}: (!fir.ref<i32>) -> ()
+  !FIR:   fir.call @_QMfuncsPfoo_dispatch(%[[X]]) {{.*}}: (!fir.ref<i32>) -> ()
+    call foo_dispatch(x)
+  !HLFIR:   omp.terminator
+  !FIR:   omp.terminator
+  !HLFIR: }
+  !FIR: }
+
+  !HLFIR: %[[C2:.*]] = arith.constant 2 : i32
+  !HLFIR: hlfir.assign %[[C2]] to %[[X]]#0 : i32, !fir.ref<i32>
+  !FIR: fir.store %[[C2]] to %[[X]] : !fir.ref<i32>
+  x = 2
+  !HLFIR: omp.dispatch {
+  !FIR: omp.dispatch {
+  !$omp dispatch
+  !HLFIR:   fir.call @_QMfuncsPfoo_dispatch(%[[X]]#0) {{.*}}: (!fir.ref<i32>) -> ()
+  !FIR:   fir.call @_QMfuncsPfoo_dispatch(%[[X]]) {{.*}}: (!fir.ref<i32>) -> ()
+    call foo_dispatch(x)
+  !HLFIR:   omp.terminator
+  !FIR:   omp.terminator
+  !HLFIR: }
+  !FIR: }
+end program
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index 51e7080db5b29..6170e40a835c6 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -2286,6 +2286,21 @@ def MaskedOp : OpenMP_Op<"masked", clauses = [
   ];
 }
 
+//===----------------------------------------------------------------------===//
+// [Spec 5.1] 12.3 dispatch Construct
+//===----------------------------------------------------------------------===//
+def DispatchOp : OpenMP_Op<"dispatch", 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.
+  }];
+
+  let assemblyFormat = "$region attr-dict";
+}
+
 //===----------------------------------------------------------------------===//
 // [Spec 5.2] 6.5 allocate Directive
 //===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index d35e8612e158b..12818a5e56ac6 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -761,6 +761,20 @@ 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);
+  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,
@@ -8815,6 +8829,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..c935f56836185
--- /dev/null
+++ b/mlir/test/Dialect/OpenMP/dispatch.mlir
@@ -0,0 +1,77 @@
+// RUN: mlir-opt %s | mlir-opt | FileCheck %s
+
+// CHECK-LABEL: func @foo_dispatch
+// CHECK-SAME: (%[[X:.*]]: memref<i32>)
+func.func @foo_dispatch(%x : memref<i32>) -> () {
+  // CHECK: %[[V:.*]] = memref.load %[[X]][] : memref<i32>
+  // CHECK: %[[C1:.*]] = arith.constant 1 : i32
+  // CHECK: %[[CMP:.*]] = arith.cmpi eq, %[[V]], %[[C1]] : i32
+  // CHECK: cf.cond_br %[[CMP]], ^[[BB1:.*]], ^[[BB2:.*]]
+  %v = memref.load %x[] : memref<i32>
+  %c1 = arith.constant 1 : i32
+  %cmp = arith.cmpi eq, %v, %c1 : i32
+  cf.cond_br %cmp, ^bb1, ^bb2
+// CHECK: ^[[BB1]]:
+// CHECK: call @variant1() : () -> ()
+^bb1:
+  func.call @variant1() : () -> ()
+  cf.br ^bb3
+// CHECK: ^[[BB2]]:
+// CHECK: call @variant2() : () -> ()
+^bb2:
+  func.call @variant2() : () -> ()
+  cf.br ^bb3
+^bb3:
+  return
+}
+
+// Test that the generic form of omp.dispatch roundtrips to pretty-printed form.
+// CHECK-LABEL: func @omp_dispatch_generic_to_pretty
+// CHECK-SAME: (%[[X:.*]]: memref<i32>)
+func.func @omp_dispatch_generic_to_pretty(%x : memref<i32>) -> () {
+  // CHECK: omp.dispatch {
+  // CHECK-NEXT: func.call @foo_dispatch(%[[X]]) : (memref<i32>) -> ()
+  // CHECK-NEXT: omp.terminator
+  // CHECK-NEXT: }
+  "omp.dispatch" () ({
+    func.call @foo_dispatch(%x) : (memref<i32>) -> ()
+    "omp.terminator" () : () -> ()
+  }) : () -> ()
+  return
+}
+
+// Test multiple dispatch regions with stores selecting different variants.
+// CHECK-LABEL: func @omp_dispatch_multiple
+// CHECK-SAME: (%[[X:.*]]: memref<i32>)
+func.func @omp_dispatch_multiple(%x : memref<i32>) -> () {
+  // CHECK: %[[C1:.*]] = arith.constant 1 : i32
+  // CHECK: memref.store %[[C1]], %[[X]][] : memref<i32>
+  %c1 = arith.constant 1 : i32
+  memref.store %c1, %x[] : memref<i32>
+  // CHECK: omp.dispatch {
+  // CHECK-NEXT: func.call @foo_dispatch(%[[X]]) : (memref<i32>) -> ()
+  // CHECK-NEXT: omp.terminator
+  // CHECK-NEXT: }
+  "omp.dispatch" () ({
+    "func.call" (%x) {callee = @foo_dispatch} : (memref<i32>) -> ()
+    "omp.terminator" () : () -> ()
+  }) : () -> ()
+  // CHECK: %[[C2:.*]] = arith.constant 2 : i32
+  // CHECK: memref.store %[[C2]], %[[X]][] : memref<i32>
+  %c2 = arith.constant 2 : i32
+  memref.store %c2, %x[] : memref<i32>
+  // CHECK: omp.dispatch {
+  // CHECK-NEXT: func.call @foo_dispatch(%[[X]]) : (memref<i32>) -> ()
+  // CHECK-NEXT: omp.terminator
+  // CHECK-NEXT: }
+  "omp.dispatch" () ({
+    "func.call" (%x) {callee = @foo_dispatch} : (memref<i32>) -> ()
+    "omp.terminator" () : () -> ()
+  }) : () -> ()
+  return
+}
+
+// CHECK-LABEL: func private @variant1()
+// CHECK-LABEL: func private @variant2()
+func.func private @variant1() -> ()
+func.func private @variant2() -> ()
diff --git a/mlir/test/Target/LLVMIR/openmp-dispatch.mlir b/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
new file mode 100644
index 0000000000000..8a3a7a6b5d8e6
--- /dev/null
+++ b/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
@@ -0,0 +1,81 @@
+// RUN: mlir-translate -mlir-to-llvmir %s | FileCheck %s
+
+llvm.mlir.global external @x() {addr_space = 0 : i32} : i32 {
+  %0 = llvm.mlir.constant(0 : i32) : i32
+  llvm.return %0 : i32
+}
+
+llvm.func @variant1() -> ()
+llvm.func @variant2() -> ()
+
+// CHECK-LABEL: define void @foo_dispatch()
+llvm.func @foo_dispatch() {
+  // CHECK: %[[ADDR:.*]] = load i32, ptr @x
+  // CHECK: %[[CMP:.*]] = icmp eq i32 %[[ADDR]], 1
+  // CHECK: br i1 %[[CMP]], label %[[BB1:.*]], label %[[BB2:.*]]
+  %0 = llvm.mlir.addressof @x : !llvm.ptr
+  %1 = llvm.load %0 : !llvm.ptr -> i32
+  %c1 = llvm.mlir.constant(1 : i32) : i32
+  %cmp = llvm.icmp "eq" %1, %c1 : i32
+  llvm.cond_br %cmp, ^bb1, ^bb2
+// CHECK: [[BB1]]:
+// CHECK: call void @variant1()
+^bb1:
+  llvm.call @variant1() : () -> ()
+  llvm.br ^bb3
+// CHECK: [[BB2]]:
+// CHECK: call void @variant2()
+^bb2:
+  llvm.call @variant2() : () -> ()
+  llvm.br ^bb3
+^bb3:
+  llvm.return
+}
+
+// CHECK-LABEL: define void @test_omp_dispatch()
+llvm.func @test_omp_dispatch() {
+  // CHECK: store i32 1, ptr @x
+  %0 = llvm.mlir.addressof @x : !llvm.ptr
+  %c1 = llvm.mlir.constant(1 : i32) : i32
+  llvm.store %c1, %0 : i32, !llvm.ptr
+  // CHECK: br label %omp.dispatch.region
+  omp.dispatch {
+    // CHECK: omp.dispatch.region:
+    // CHECK-NEXT: call void @foo_dispatch()
+    llvm.call @foo_dispatch() : () -> ()
+    // CHECK-NEXT: br label %omp.region.cont
+    omp.terminator
+  }
+  // CHECK: omp.region.cont:
+  llvm.return
+}
+
+// CHECK-LABEL: define void @test_omp_dispatch_multiple()
+llvm.func @test_omp_dispatch_multiple() {
+  // CHECK: store i32 1, ptr @x
+  %0 = llvm.mlir.addressof @x : !llvm.ptr
+  %c1 = llvm.mlir.constant(1 : i32) : i32
+  llvm.store %c1, %0 : i32, !llvm.ptr
+  // CHECK: br label %omp.dispatch.region
+  omp.dispatch {
+    // CHECK: omp.dispatch.region:
+    // CHECK-NEXT: call void @foo_dispatch()
+    llvm.call @foo_dispatch() : () -> ()
+    // CHECK-NEXT: br label %omp.region.cont
+    omp.terminator
+  }
+  // CHECK: omp.region.cont:
+  // CHECK: store i32 2, ptr @x
+  %c2 = llvm.mlir.constant(2 : i32) : i32
+  llvm.store %c2, %0 : i32, !llvm.ptr
+  // CHECK: br label %omp.dispatch.region2
+  omp.dispatch {
+    // CHECK: omp.dispatch.region2:
+    // CHECK-NEXT: call void @foo_dispatch()
+    llvm.call @foo_dispatch() : () -> ()
+    // CHECK-NEXT: br label %omp.region.cont1
+    omp.terminator
+  }
+  // CHECK: omp.region.cont1:
+  llvm.return
+}

>From b50c11a322615d4e189303f9b4b73e1c6246c7ae Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Thu, 11 Jun 2026 12:19:36 -0500
Subject: [PATCH 02/11] Checking for no caluses support with "omp dispatch".

---
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 3 +++
 mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td | 7 ++++---
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 758d8eb8148e5..71bc392d0e8d9 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -5337,6 +5337,9 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
   llvm::omp::Directive directive = beginSpec.DirId();
   mlir::Location currentLocation = converter.genLocation(beginSpec.source);
 
+  if (!clauses.empty())
+    TODO(currentLocation, "OpenMP Dispatch clauses");
+
   ConstructQueue queue{
       buildConstructQueue(converter.getFirOpBuilder().getModule(), semaCtx,
                           eval, beginSpec.source, directive, clauses)};
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index 6170e40a835c6..47c7587c5d08b 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -2293,9 +2293,10 @@ def DispatchOp : OpenMP_Op<"dispatch", 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.
+    base procedure (without any construct's clauses: device, is_device_ptr,
+    nowait, nocontext, novariants). The structured block of a dispatch
+    construct is a single expression statement that contains a function
+    call or a subroutine call.
   }];
 
   let assemblyFormat = "$region attr-dict";

>From 3ca936ebb5aff08e318fcd4abede5951e77fcb07 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Thu, 18 Jun 2026 11:50:04 -0500
Subject: [PATCH 03/11] Handling feedbacks suggested by Sergio Afonso.

---
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 37 ++++++++++++-------
 flang/test/Lower/OpenMP/dispatch.f90          | 26 +------------
 mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td | 11 +++---
 .../OpenMP/OpenMPToLLVMIRTranslation.cpp      |  4 ++
 mlir/test/Dialect/OpenMP/dispatch.mlir        |  1 +
 mlir/test/Target/LLVMIR/openmp-dispatch.mlir  |  1 +
 6 files changed, 36 insertions(+), 44 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 33e67ed2c3ab7..fe7d17807727a 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -1661,6 +1661,16 @@ static void genDistributeClauses(lower::AbstractConverter &converter,
   cp.processOrder(clauseOps);
 }
 
+static void genDispatchClauses(lower::AbstractConverter &converter,
+                               semantics::SemanticsContext &semaCtx,
+                               const List<Clause> &clauses,
+                               mlir::Location loc) {
+  ClauseProcessor cp(converter, semaCtx, clauses);
+  cp.processTODO<clause::Depend, clause::Device, clause::IsDevicePtr,
+                 clause::Nocontext, clause::Novariants, clause::Nowait>(
+      loc, llvm::omp::Directive::OMPD_dispatch);
+}
+
 static void genFlushClauses(lower::AbstractConverter &converter,
                             semantics::SemanticsContext &semaCtx,
                             const ObjectList &objects,
@@ -2154,6 +2164,18 @@ genCriticalOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
       queue, item, nameAttr);
 }
 
+static mlir::omp::DispatchOp
+genDispatchOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
+              semantics::SemanticsContext &semaCtx,
+              lower::pft::Evaluation &eval, mlir::Location loc,
+              const ConstructQueue &queue,
+              ConstructQueue::const_iterator item) {
+  return genOpWithBody<mlir::omp::DispatchOp>(
+      OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval,
+                        llvm::omp::Directive::OMPD_dispatch),
+      queue, item);
+}
+
 static mlir::omp::FlushOp
 genFlushOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
            semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval,
@@ -2592,18 +2614,6 @@ genMasterOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
       queue, item);
 }
 
-static mlir::omp::DispatchOp
-genDispatchOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
-              semantics::SemanticsContext &semaCtx,
-              lower::pft::Evaluation &eval, mlir::Location loc,
-              const ConstructQueue &queue,
-              ConstructQueue::const_iterator item) {
-  return genOpWithBody<mlir::omp::DispatchOp>(
-      OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval,
-                        llvm::omp::Directive::OMPD_dispatch),
-      queue, item);
-}
-
 static mlir::omp::OrderedOp
 genOrderedOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
              semantics::SemanticsContext &semaCtx, lower::pft::Evaluation &eval,
@@ -5294,8 +5304,7 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
   llvm::omp::Directive directive = beginSpec.DirId();
   mlir::Location currentLocation = converter.genLocation(beginSpec.source);
 
-  if (!clauses.empty())
-    TODO(currentLocation, "OpenMP Dispatch clauses");
+  genDispatchClauses(converter, semaCtx, clauses, currentLocation);
 
   ConstructQueue queue{
       buildConstructQueue(converter.getFirOpBuilder().getModule(), semaCtx,
diff --git a/flang/test/Lower/OpenMP/dispatch.f90 b/flang/test/Lower/OpenMP/dispatch.f90
index df86503817163..ecc7b1b31454f 100644
--- a/flang/test/Lower/OpenMP/dispatch.f90
+++ b/flang/test/Lower/OpenMP/dispatch.f90
@@ -1,5 +1,4 @@
 !RUN: %flang_fc1 -emit-hlfir -fopenmp %s -o - | FileCheck %s --check-prefix=HLFIR
-!RUN: %flang_fc1 -emit-mlir -fopenmp %s -o - | FileCheck %s --check-prefix=FIR
 
 !HLFIR-LABEL: func @_QMfuncsPfoo_dispatch
 !HLFIR: %[[XD_H:.*]]:2 = hlfir.declare %{{.*}} {{{.*}}uniq_name = {{.*}}foo_dispatch{{.*}}x{{.*}}
@@ -12,16 +11,6 @@
 !HLFIR:   fir.call @_QMfuncsPvariant2() {{.*}}: () -> ()
 !HLFIR: }
 
-!FIR-LABEL: func @_QMfuncsPfoo_dispatch
-!FIR: %[[XD_F:.*]] = fir.declare %{{.*}} {{{.*}}uniq_name = {{.*}}foo_dispatch{{.*}}x{{.*}}
-!FIR: %[[LOAD_F:.*]] = fir.load %[[XD_F]] : !fir.ref<i32>
-!FIR: %[[CMP_F:.*]] = arith.cmpi eq, %[[LOAD_F]], %{{.*}} : i32
-!FIR: fir.if %[[CMP_F]] {
-!FIR:   fir.call @_QMfuncsPvariant1() {{.*}}: () -> ()
-!FIR: } else {
-!FIR:   fir.call @_QMfuncsPvariant2() {{.*}}: () -> ()
-!FIR: }
-
 module funcs
   implicit none
 
@@ -35,6 +24,7 @@ subroutine variant2()
     print *, "in variant2"
   end subroutine
 
+  !TODO: replace with declare_variant when the support is merged.
   subroutine foo_dispatch(x)
     integer, intent(in) :: x
     if (x == 1) then
@@ -47,10 +37,6 @@ subroutine foo_dispatch(x)
 end module funcs
 
 !HLFIR-LABEL: func @_QQmain
-!FIR-LABEL: func @_QQmain
-!FIR-DAG: %[[C1:.*]] = arith.constant 1 : i32
-!FIR-DAG: %[[C2:.*]] = arith.constant 2 : i32
-!FIR: %[[X:.*]] = fir.declare %{{.*}} {uniq_name = {{.*}}x{{.*}}
 program dispatch_test
   use funcs
   implicit none
@@ -59,31 +45,21 @@ program dispatch_test
   !HLFIR: %[[X:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = {{.*}}x{{.*}}
   !HLFIR: %[[C1:.*]] = arith.constant 1 : i32
   !HLFIR: hlfir.assign %[[C1]] to %[[X]]#0 : i32, !fir.ref<i32>
-  !FIR: fir.store %[[C1]] to %[[X]] : !fir.ref<i32>
   x = 1
   !HLFIR: omp.dispatch {
-  !FIR: omp.dispatch {
   !$omp dispatch
   !HLFIR:   fir.call @_QMfuncsPfoo_dispatch(%[[X]]#0) {{.*}}: (!fir.ref<i32>) -> ()
-  !FIR:   fir.call @_QMfuncsPfoo_dispatch(%[[X]]) {{.*}}: (!fir.ref<i32>) -> ()
     call foo_dispatch(x)
   !HLFIR:   omp.terminator
-  !FIR:   omp.terminator
   !HLFIR: }
-  !FIR: }
 
   !HLFIR: %[[C2:.*]] = arith.constant 2 : i32
   !HLFIR: hlfir.assign %[[C2]] to %[[X]]#0 : i32, !fir.ref<i32>
-  !FIR: fir.store %[[C2]] to %[[X]] : !fir.ref<i32>
   x = 2
   !HLFIR: omp.dispatch {
-  !FIR: omp.dispatch {
   !$omp dispatch
   !HLFIR:   fir.call @_QMfuncsPfoo_dispatch(%[[X]]#0) {{.*}}: (!fir.ref<i32>) -> ()
-  !FIR:   fir.call @_QMfuncsPfoo_dispatch(%[[X]]) {{.*}}: (!fir.ref<i32>) -> ()
     call foo_dispatch(x)
   !HLFIR:   omp.terminator
-  !FIR:   omp.terminator
   !HLFIR: }
-  !FIR: }
 end program
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index 47c7587c5d08b..490bee24799c2 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -2289,14 +2289,15 @@ def MaskedOp : OpenMP_Op<"masked", clauses = [
 //===----------------------------------------------------------------------===//
 // [Spec 5.1] 12.3 dispatch Construct
 //===----------------------------------------------------------------------===//
-def DispatchOp : OpenMP_Op<"dispatch", singleRegion = true> {
+def DispatchOp : OpenMP_Op<"dispatch", singleRegion = true, clauses = [
+     // TODO: Complete clause lists (device, is_device_ptr,nowait, nocontext, novariants).
+   ]> {
   let summary = "dispatch construct";
   let description = [{
     The dispatch construct enables the invocation of a variant of a
-    base procedure (without any construct's clauses: device, is_device_ptr,
-    nowait, nocontext, novariants). The structured block of a dispatch
-    construct is a single expression statement that contains a function
-    call or a subroutine call.
+    base procedure. The structured block of a dispatch construct is
+    a single expression statement that contains a function call or
+    a subroutine call.
   }];
 
   let assemblyFormat = "$region attr-dict";
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 12818a5e56ac6..04c36b87d73ad 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -766,6 +766,10 @@ 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);
diff --git a/mlir/test/Dialect/OpenMP/dispatch.mlir b/mlir/test/Dialect/OpenMP/dispatch.mlir
index c935f56836185..b8d4b93a6c8e3 100644
--- a/mlir/test/Dialect/OpenMP/dispatch.mlir
+++ b/mlir/test/Dialect/OpenMP/dispatch.mlir
@@ -41,6 +41,7 @@ func.func @omp_dispatch_generic_to_pretty(%x : memref<i32>) -> () {
 }
 
 // Test multiple dispatch regions with stores selecting different variants.
+// TODO: Use declare_variant when the support is merged.
 // CHECK-LABEL: func @omp_dispatch_multiple
 // CHECK-SAME: (%[[X:.*]]: memref<i32>)
 func.func @omp_dispatch_multiple(%x : memref<i32>) -> () {
diff --git a/mlir/test/Target/LLVMIR/openmp-dispatch.mlir b/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
index 8a3a7a6b5d8e6..d947a871ca30c 100644
--- a/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
@@ -8,6 +8,7 @@ llvm.mlir.global external @x() {addr_space = 0 : i32} : i32 {
 llvm.func @variant1() -> ()
 llvm.func @variant2() -> ()
 
+// TODO: Add support for declare_variant, when support to it is merged.
 // CHECK-LABEL: define void @foo_dispatch()
 llvm.func @foo_dispatch() {
   // CHECK: %[[ADDR:.*]] = load i32, ptr @x

>From 6eacbc3cd3d8e278d7b51ea898fe27e009de2228 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Fri, 3 Jul 2026 01:09:09 -0500
Subject: [PATCH 04/11] 1) Handling -fc1 -fopenmp-simd 2) Todo testcase for
 clauses not supported in dispatch

---
 flang/lib/Semantics/rewrite-parse-tree.cpp |  5 +++
 flang/test/Lower/OpenMP/Todo/dispatch.f90  | 43 ++++++++++++++++++++++
 flang/test/Semantics/OpenMP/simd-only.f90  | 12 ++++++
 3 files changed, 60 insertions(+)
 create mode 100644 flang/test/Lower/OpenMP/Todo/dispatch.f90

diff --git a/flang/lib/Semantics/rewrite-parse-tree.cpp b/flang/lib/Semantics/rewrite-parse-tree.cpp
index 4e1c9bae9c153..76a60c75488e3 100644
--- a/flang/lib/Semantics/rewrite-parse-tree.cpp
+++ b/flang/lib/Semantics/rewrite-parse-tree.cpp
@@ -191,6 +191,11 @@ void RewriteMutator::OpenMPSimdOnly(
                        &omp->value().u)}) {
           it = replaceInlineBlock(std::get<parser::Block>(ompBlock->t), it);
           continue;
+        } else if (auto *ompDispatch{
+                       std::get_if<parser::OpenMPDispatchConstruct>(
+                           &omp->value().u)}) {
+          it = replaceInlineBlock(std::get<parser::Block>(ompDispatch->t), it);
+          continue;
         } else if (auto *ompLoop{std::get_if<parser::OpenMPLoopConstruct>(
                        &omp->value().u)}) {
           if (LoopConstructIsSIMD(ompLoop)) {
diff --git a/flang/test/Lower/OpenMP/Todo/dispatch.f90 b/flang/test/Lower/OpenMP/Todo/dispatch.f90
new file mode 100644
index 0000000000000..f100cb40ba3d7
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/dispatch.f90
@@ -0,0 +1,43 @@
+! RUN: %not_todo_cmd %flang_fc1 -emit-fir -fopenmp -fopenmp-version=52 -cpp -DDEPEND -o - %s 2>&1 | FileCheck %s --check-prefix=DEPEND
+! RUN: %not_todo_cmd %flang_fc1 -emit-fir -fopenmp -fopenmp-version=52 -cpp -DDEVICE -o - %s 2>&1 | FileCheck %s --check-prefix=DEVICE
+! RUN: %not_todo_cmd %flang_fc1 -emit-fir -fopenmp -fopenmp-version=52 -cpp -DIS_DEVICE_PTR -o - %s 2>&1 | FileCheck %s --check-prefix=IS_DEVICE_PTR
+! RUN: %not_todo_cmd %flang_fc1 -emit-fir -fopenmp -fopenmp-version=52 -cpp -DNOCONTEXT -o - %s 2>&1 | FileCheck %s --check-prefix=NOCONTEXT
+! RUN: %not_todo_cmd %flang_fc1 -emit-fir -fopenmp -fopenmp-version=52 -cpp -DNOVARIANTS -o - %s 2>&1 | FileCheck %s --check-prefix=NOVARIANTS
+! RUN: %not_todo_cmd %flang_fc1 -emit-fir -fopenmp -fopenmp-version=52 -cpp -DNOWAIT -o - %s 2>&1 | FileCheck %s --check-prefix=NOWAIT
+
+! DEPEND: not yet implemented: Unhandled clause DEPEND in DISPATCH construct
+! DEVICE: not yet implemented: Unhandled clause DEVICE in DISPATCH construct
+! IS_DEVICE_PTR: not yet implemented: Unhandled clause IS_DEVICE_PTR in DISPATCH construct
+! NOCONTEXT: not yet implemented: Unhandled clause NOCONTEXT in DISPATCH construct
+! NOVARIANTS: not yet implemented: Unhandled clause NOVARIANTS in DISPATCH construct
+! NOWAIT: not yet implemented: Unhandled clause NOWAIT in DISPATCH construct
+
+subroutine sub()
+#ifdef IS_DEVICE_PTR
+  use iso_c_binding
+  type(c_ptr) :: x
+#endif
+  integer :: r
+#ifdef DEPEND
+!$omp dispatch depend(inout: r)
+#endif
+#ifdef DEVICE
+!$omp dispatch device(0)
+#endif
+#ifdef IS_DEVICE_PTR
+!$omp dispatch is_device_ptr(x)
+#endif
+#ifdef NOCONTEXT
+!$omp dispatch nocontext(.false.)
+#endif
+#ifdef NOVARIANTS
+!$omp dispatch novariants(.true.)
+#endif
+#ifdef NOWAIT
+!$omp dispatch nowait
+#endif
+  call foo()
+contains
+  subroutine foo
+  end subroutine
+end subroutine sub
diff --git a/flang/test/Semantics/OpenMP/simd-only.f90 b/flang/test/Semantics/OpenMP/simd-only.f90
index 01370af0a00a7..860befaf43247 100644
--- a/flang/test/Semantics/OpenMP/simd-only.f90
+++ b/flang/test/Semantics/OpenMP/simd-only.f90
@@ -414,3 +414,15 @@ module test_declare_mapper
   ! CHECK-NOT: DeclarationConstruct -> SpecificationConstruct -> OpenMPDeclarativeConstruct -> OmpDeclareMapperDirective
   !$omp declare mapper(myvec_t :: v) map(v, v%data(1:v%len))
 end module
+
+! CHECK-LABEL: Name = 'test_dispatch'
+subroutine test_dispatch()
+  ! CHECK-NOT: ExecutionPartConstruct -> ExecutableConstruct -> OpenMPConstruct -> OpenMPDispatchConstruct
+  ! CHECK-NOT: OmpDirectiveName -> llvm::omp::Directive = dispatch
+  ! CHECK: ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> CallStmt
+  !$omp dispatch
+  call foo()
+contains
+  subroutine foo()
+  end subroutine
+end subroutine

>From 8f25901612c660e29e53ab9c7e162a21a04ad966 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Fri, 3 Jul 2026 07:14:38 -0500
Subject: [PATCH 05/11] Support for "dispatch nowait" except for MLIR to
 LLVMIR. Support in LLVM IR depends on "declare variant".

---
 flang/lib/Lower/OpenMP/OpenMP.cpp                 | 14 ++++++++------
 flang/test/Lower/OpenMP/Todo/dispatch.f90         |  5 -----
 flang/test/Lower/OpenMP/dispatch.f90              |  7 +++++++
 mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td     | 12 ++++++++----
 mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp      |  9 +++++++++
 .../Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp  |  1 +
 mlir/test/Dialect/OpenMP/dispatch.mlir            | 15 +++++++++++++++
 mlir/test/Target/LLVMIR/openmp-todo.mlir          | 11 +++++++++++
 8 files changed, 59 insertions(+), 15 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 4c2a238605f8d..959f7352e2c76 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -1774,11 +1774,12 @@ static void genDistributeClauses(lower::AbstractConverter &converter,
 
 static void genDispatchClauses(lower::AbstractConverter &converter,
                                semantics::SemanticsContext &semaCtx,
-                               const List<Clause> &clauses,
-                               mlir::Location loc) {
+                               const List<Clause> &clauses, mlir::Location loc,
+                               mlir::omp::DispatchOperands &clauseOps) {
   ClauseProcessor cp(converter, semaCtx, clauses);
+  cp.processNowait(clauseOps);
   cp.processTODO<clause::Depend, clause::Device, clause::IsDevicePtr,
-                 clause::Nocontext, clause::Novariants, clause::Nowait>(
+                 clause::Nocontext, clause::Novariants>(
       loc, llvm::omp::Directive::OMPD_dispatch);
 }
 
@@ -2276,10 +2277,13 @@ genDispatchOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
               lower::pft::Evaluation &eval, mlir::Location loc,
               const ConstructQueue &queue,
               ConstructQueue::const_iterator item) {
+  mlir::omp::DispatchOperands clauseOps;
+  genDispatchClauses(converter, semaCtx, item->clauses, loc, clauseOps);
+
   return genOpWithBody<mlir::omp::DispatchOp>(
       OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval,
                         llvm::omp::Directive::OMPD_dispatch),
-      queue, item);
+      queue, item, clauseOps);
 }
 
 static mlir::omp::FlushOp
@@ -5426,8 +5430,6 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
   llvm::omp::Directive directive = beginSpec.DirId();
   mlir::Location currentLocation = converter.genLocation(beginSpec.source);
 
-  genDispatchClauses(converter, semaCtx, clauses, currentLocation);
-
   ConstructQueue queue{
       buildConstructQueue(converter.getFirOpBuilder().getModule(), semaCtx,
                           eval, beginSpec.source, directive, clauses)};
diff --git a/flang/test/Lower/OpenMP/Todo/dispatch.f90 b/flang/test/Lower/OpenMP/Todo/dispatch.f90
index f100cb40ba3d7..579d47d457def 100644
--- a/flang/test/Lower/OpenMP/Todo/dispatch.f90
+++ b/flang/test/Lower/OpenMP/Todo/dispatch.f90
@@ -3,14 +3,12 @@
 ! RUN: %not_todo_cmd %flang_fc1 -emit-fir -fopenmp -fopenmp-version=52 -cpp -DIS_DEVICE_PTR -o - %s 2>&1 | FileCheck %s --check-prefix=IS_DEVICE_PTR
 ! RUN: %not_todo_cmd %flang_fc1 -emit-fir -fopenmp -fopenmp-version=52 -cpp -DNOCONTEXT -o - %s 2>&1 | FileCheck %s --check-prefix=NOCONTEXT
 ! RUN: %not_todo_cmd %flang_fc1 -emit-fir -fopenmp -fopenmp-version=52 -cpp -DNOVARIANTS -o - %s 2>&1 | FileCheck %s --check-prefix=NOVARIANTS
-! RUN: %not_todo_cmd %flang_fc1 -emit-fir -fopenmp -fopenmp-version=52 -cpp -DNOWAIT -o - %s 2>&1 | FileCheck %s --check-prefix=NOWAIT
 
 ! DEPEND: not yet implemented: Unhandled clause DEPEND in DISPATCH construct
 ! DEVICE: not yet implemented: Unhandled clause DEVICE in DISPATCH construct
 ! IS_DEVICE_PTR: not yet implemented: Unhandled clause IS_DEVICE_PTR in DISPATCH construct
 ! NOCONTEXT: not yet implemented: Unhandled clause NOCONTEXT in DISPATCH construct
 ! NOVARIANTS: not yet implemented: Unhandled clause NOVARIANTS in DISPATCH construct
-! NOWAIT: not yet implemented: Unhandled clause NOWAIT in DISPATCH construct
 
 subroutine sub()
 #ifdef IS_DEVICE_PTR
@@ -32,9 +30,6 @@ subroutine sub()
 #endif
 #ifdef NOVARIANTS
 !$omp dispatch novariants(.true.)
-#endif
-#ifdef NOWAIT
-!$omp dispatch nowait
 #endif
   call foo()
 contains
diff --git a/flang/test/Lower/OpenMP/dispatch.f90 b/flang/test/Lower/OpenMP/dispatch.f90
index ecc7b1b31454f..1b184a18a91af 100644
--- a/flang/test/Lower/OpenMP/dispatch.f90
+++ b/flang/test/Lower/OpenMP/dispatch.f90
@@ -62,4 +62,11 @@ program dispatch_test
     call foo_dispatch(x)
   !HLFIR:   omp.terminator
   !HLFIR: }
+
+  !HLFIR: omp.dispatch nowait {
+  !$omp dispatch nowait
+  !HLFIR:   fir.call @_QMfuncsPfoo_dispatch(%[[X]]#0) {{.*}}: (!fir.ref<i32>) -> ()
+    call foo_dispatch(x)
+  !HLFIR:   omp.terminator
+  !HLFIR: }
 end program
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index f048a9da71063..bb478c52381c5 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -2289,17 +2289,21 @@ def MaskedOp : OpenMP_Op<"masked", clauses = [
 // [Spec 5.1] 12.3 dispatch Construct
 //===----------------------------------------------------------------------===//
 def DispatchOp : OpenMP_Op<"dispatch", singleRegion = true, clauses = [
-     // TODO: Complete clause lists (device, is_device_ptr,nowait, nocontext, novariants).
-   ]> {
+    // TODO: Complete clause list (device, depend, is_device_ptr, nocontext,
+    // novariants).
+    OpenMP_NowaitClause
+  ]> {
   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 assemblyFormat = "$region attr-dict";
+  let builders = [
+    OpBuilder<(ins CArg<"const DispatchOperands &">:$clauses)>
+  ];
 }
 
 //===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
index c2956a1cf7b79..6c1d5c29ec95d 100644
--- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
+++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
@@ -4933,6 +4933,15 @@ 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.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 e84daaa135748..75c5e707b9933 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -480,6 +480,7 @@ static LogicalResult checkImplementationStatus(Operation &op) {
         checkDepend(op, result);
         checkNowait(op, result);
       })
+      .Case([&](omp::DispatchOp op) { checkNowait(op, result); })
       .Case([&](omp::TaskloopContextOp op) {
         checkAllocate(op, result);
         checkInReductionByref(op, result);
diff --git a/mlir/test/Dialect/OpenMP/dispatch.mlir b/mlir/test/Dialect/OpenMP/dispatch.mlir
index b8d4b93a6c8e3..c966784b1e22e 100644
--- a/mlir/test/Dialect/OpenMP/dispatch.mlir
+++ b/mlir/test/Dialect/OpenMP/dispatch.mlir
@@ -72,6 +72,21 @@ func.func @omp_dispatch_multiple(%x : memref<i32>) -> () {
   return
 }
 
+// Test the nowait clause on omp.dispatch.
+// CHECK-LABEL: 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 @foo_dispatch(%[[X]]) : (memref<i32>) -> ()
+  // CHECK-NEXT: omp.terminator
+  // CHECK-NEXT: }
+  omp.dispatch nowait {
+    func.call @foo_dispatch(%x) : (memref<i32>) -> ()
+    omp.terminator
+  }
+  return
+}
+
 // CHECK-LABEL: func private @variant1()
 // CHECK-LABEL: func private @variant2()
 func.func private @variant1() -> ()
diff --git a/mlir/test/Target/LLVMIR/openmp-todo.mlir b/mlir/test/Target/LLVMIR/openmp-todo.mlir
index 377a5bb799be4..1bf9b563f8d6c 100644
--- a/mlir/test/Target/LLVMIR/openmp-todo.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-todo.mlir
@@ -52,6 +52,17 @@ llvm.func @distribute_order(%lb : i32, %ub : i32, %step : i32) {
 
 // -----
 
+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 @parallel_allocate(%x : !llvm.ptr) {
   // expected-error at below {{not yet implemented: Unhandled clause allocate in omp.parallel operation}}
   // expected-error at below {{LLVM Translation failed for operation: omp.parallel}}

>From 7739eecf4c336629aedfa11911a4bea1cc81a6e8 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Fri, 24 Jul 2026 02:20:10 -0500
Subject: [PATCH 06/11] Integrating variants using "declare variant" in the
 tests. Adding dispatch trait to "declare variant" in Utils.cpp Handling
 comments by Chi Chun Chen.

---
 flang/lib/Lower/OpenMP/Utils.cpp             |   3 +
 flang/test/Integration/OpenMP/dispatch.f90   |  42 +-----
 flang/test/Lower/OpenMP/dispatch.f90         |  59 +++-----
 mlir/test/Dialect/OpenMP/dispatch.mlir       | 150 ++++++++-----------
 mlir/test/Target/LLVMIR/openmp-dispatch.mlir |  97 +++---------
 5 files changed, 109 insertions(+), 242 deletions(-)

diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index 8f57f00d59c58..5cb6a0713462f 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -1379,6 +1379,9 @@ void collectEnclosingConstructTraits(
   for (; op; op = op->getParentOp()) {
     if (mlir::isa<mlir::omp::WsloopOp>(op))
       constructTraits.push_back(llvm::omp::TraitProperty::construct_for_for);
+    if (mlir::isa<mlir::omp::DispatchOp>(op))
+      constructTraits.push_back(
+          llvm::omp::TraitProperty::construct_dispatch_dispatch);
     if (mlir::isa<mlir::omp::ParallelOp>(op))
       constructTraits.push_back(
           llvm::omp::TraitProperty::construct_parallel_parallel);
diff --git a/flang/test/Integration/OpenMP/dispatch.f90 b/flang/test/Integration/OpenMP/dispatch.f90
index c4d16690c8e29..1ecccab5e90d4 100644
--- a/flang/test/Integration/OpenMP/dispatch.f90
+++ b/flang/test/Integration/OpenMP/dispatch.f90
@@ -8,55 +8,32 @@
 
 !RUN: %flang_fc1 -emit-llvm -fopenmp %s -o - | FileCheck %s
 
-!CHECK-LABEL: define void @_QMfuncsPfoo_variant1()
-!CHECK: call ptr @_FortranAioBeginExternalListOutput
-
-!CHECK-LABEL: define void @_QMfuncsPfoo_variant2()
+!CHECK-LABEL: define void @_QMfuncsPfoo_variant()
 !CHECK: call ptr @_FortranAioBeginExternalListOutput
 
 !CHECK-LABEL: define void @_QMfuncsPfoo_dispatch()
-!CHECK: %[[COND:.*]] = load i32, ptr @_QMfuncsEfoo_sub
-!CHECK: %[[CMP:.*]] = icmp ne i32 %[[COND]], 0
-!CHECK: br i1 %[[CMP]], label %[[IF_TRUE:.*]], label %[[IF_FALSE:.*]]
-!CHECK: [[IF_TRUE]]:
-!CHECK: call void @_QMfuncsPfoo_variant2()
-!CHECK: [[IF_FALSE]]:
-!CHECK: call void @_QMfuncsPfoo_variant1()
+!CHECK: call ptr @_FortranAioBeginExternalListOutput
 
 !CHECK-LABEL: define void @_QQmain()
-!CHECK: store i32 0, ptr @_QMfuncsEfoo_sub
+!CHECK: call void @_QMfuncsPfoo_dispatch()
 !CHECK: br label %omp.dispatch.region
 !CHECK: omp.dispatch.region:
-!CHECK: call void @_QMfuncsPfoo_dispatch()
+!CHECK: call void @_QMfuncsPfoo_variant()
 !CHECK: br label %omp.region.cont
 !CHECK: omp.region.cont:
-!CHECK: store i32 1, ptr @_QMfuncsEfoo_sub
-!CHECK: br label %omp.dispatch.region2
-!CHECK: omp.dispatch.region2:
-!CHECK: call void @_QMfuncsPfoo_dispatch()
-!CHECK: br label %omp.region.cont1
-!CHECK: omp.region.cont1:
 
 module funcs
   implicit none
-  logical :: foo_sub
 
 contains
 
-  subroutine foo_variant1()
-    print *, "in foo_variant1"
-  end subroutine
-
-  subroutine foo_variant2()
-    print *, "in foo_variant2"
+  subroutine foo_variant()
+    print *, "in foo_variant"
   end subroutine
 
   subroutine foo_dispatch()
-    if (foo_sub) then
-      call foo_variant2()
-    else
-      call foo_variant1()
-    end if
+    !$omp declare variant(foo_dispatch:foo_variant) match(construct={dispatch})
+    print *, "in foo_dispatch"
   end subroutine
 
 end module funcs
@@ -65,11 +42,8 @@ program dispatch_test
   use funcs
   implicit none
 
-  foo_sub = .false.
-  !$omp dispatch
   call foo_dispatch()
 
-  foo_sub = .true.
   !$omp dispatch
   call foo_dispatch()
 
diff --git a/flang/test/Lower/OpenMP/dispatch.f90 b/flang/test/Lower/OpenMP/dispatch.f90
index 1b184a18a91af..4eb9439b7b35b 100644
--- a/flang/test/Lower/OpenMP/dispatch.f90
+++ b/flang/test/Lower/OpenMP/dispatch.f90
@@ -1,37 +1,23 @@
 !RUN: %flang_fc1 -emit-hlfir -fopenmp %s -o - | FileCheck %s --check-prefix=HLFIR
 
-!HLFIR-LABEL: func @_QMfuncsPfoo_dispatch
-!HLFIR: %[[XD_H:.*]]:2 = hlfir.declare %{{.*}} {{{.*}}uniq_name = {{.*}}foo_dispatch{{.*}}x{{.*}}
-!HLFIR: %[[LOAD_H:.*]] = fir.load %[[XD_H]]#0 : !fir.ref<i32>
-!HLFIR: %[[C1_H:.*]] = arith.constant 1 : i32
-!HLFIR: %[[CMP_H:.*]] = arith.cmpi eq, %[[LOAD_H]], %[[C1_H]] : i32
-!HLFIR: fir.if %[[CMP_H]] {
-!HLFIR:   fir.call @_QMfuncsPvariant1() {{.*}}: () -> ()
-!HLFIR: } else {
-!HLFIR:   fir.call @_QMfuncsPvariant2() {{.*}}: () -> ()
-!HLFIR: }
+! Variant selection is provided by DECLARE VARIANT with a `construct={dispatch}`
+! match: inside a dispatch region the call to the base procedure `foo_dispatch`
+! is replaced by a call to its variant `foo_variant`.
 
 module funcs
   implicit none
 
 contains
 
-  subroutine variant1()
-    print *, "in variant1"
+  !HLFIR-LABEL: func @_QMfuncsPfoo_variant
+  subroutine foo_variant()
+    print *, "in foo_variant"
   end subroutine
 
-  subroutine variant2()
-    print *, "in variant2"
-  end subroutine
-
-  !TODO: replace with declare_variant when the support is merged.
-  subroutine foo_dispatch(x)
-    integer, intent(in) :: x
-    if (x == 1) then
-      call variant1()
-    else
-      call variant2()
-    end if
+  !HLFIR-LABEL: func @_QMfuncsPfoo_dispatch
+  subroutine foo_dispatch()
+    !$omp declare variant(foo_dispatch:foo_variant) match(construct={dispatch})
+    print *, "in foo_dispatch"
   end subroutine
 
 end module funcs
@@ -40,33 +26,22 @@ end module funcs
 program dispatch_test
   use funcs
   implicit none
-  integer :: x
 
-  !HLFIR: %[[X:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = {{.*}}x{{.*}}
-  !HLFIR: %[[C1:.*]] = arith.constant 1 : i32
-  !HLFIR: hlfir.assign %[[C1]] to %[[X]]#0 : i32, !fir.ref<i32>
-  x = 1
-  !HLFIR: omp.dispatch {
-  !$omp dispatch
-  !HLFIR:   fir.call @_QMfuncsPfoo_dispatch(%[[X]]#0) {{.*}}: (!fir.ref<i32>) -> ()
-    call foo_dispatch(x)
-  !HLFIR:   omp.terminator
-  !HLFIR: }
+  ! A call outside any dispatch region targets the base procedure.
+  !HLFIR: fir.call @_QMfuncsPfoo_dispatch() {{.*}}: () -> ()
+  call foo_dispatch()
 
-  !HLFIR: %[[C2:.*]] = arith.constant 2 : i32
-  !HLFIR: hlfir.assign %[[C2]] to %[[X]]#0 : i32, !fir.ref<i32>
-  x = 2
   !HLFIR: omp.dispatch {
   !$omp dispatch
-  !HLFIR:   fir.call @_QMfuncsPfoo_dispatch(%[[X]]#0) {{.*}}: (!fir.ref<i32>) -> ()
-    call foo_dispatch(x)
+  !HLFIR:   fir.call @_QMfuncsPfoo_variant() {{.*}}: () -> ()
+    call foo_dispatch()
   !HLFIR:   omp.terminator
   !HLFIR: }
 
   !HLFIR: omp.dispatch nowait {
   !$omp dispatch nowait
-  !HLFIR:   fir.call @_QMfuncsPfoo_dispatch(%[[X]]#0) {{.*}}: (!fir.ref<i32>) -> ()
-    call foo_dispatch(x)
+  !HLFIR:   fir.call @_QMfuncsPfoo_variant() {{.*}}: () -> ()
+    call foo_dispatch()
   !HLFIR:   omp.terminator
   !HLFIR: }
 end program
diff --git a/mlir/test/Dialect/OpenMP/dispatch.mlir b/mlir/test/Dialect/OpenMP/dispatch.mlir
index c966784b1e22e..304753ed4bcc0 100644
--- a/mlir/test/Dialect/OpenMP/dispatch.mlir
+++ b/mlir/test/Dialect/OpenMP/dispatch.mlir
@@ -1,93 +1,63 @@
-// RUN: mlir-opt %s | mlir-opt | FileCheck %s
+// RUN: fir-opt %s | FileCheck %s
 
-// CHECK-LABEL: func @foo_dispatch
-// CHECK-SAME: (%[[X:.*]]: memref<i32>)
-func.func @foo_dispatch(%x : memref<i32>) -> () {
-  // CHECK: %[[V:.*]] = memref.load %[[X]][] : memref<i32>
-  // CHECK: %[[C1:.*]] = arith.constant 1 : i32
-  // CHECK: %[[CMP:.*]] = arith.cmpi eq, %[[V]], %[[C1]] : i32
-  // CHECK: cf.cond_br %[[CMP]], ^[[BB1:.*]], ^[[BB2:.*]]
-  %v = memref.load %x[] : memref<i32>
-  %c1 = arith.constant 1 : i32
-  %cmp = arith.cmpi eq, %v, %c1 : i32
-  cf.cond_br %cmp, ^bb1, ^bb2
-// CHECK: ^[[BB1]]:
-// CHECK: call @variant1() : () -> ()
-^bb1:
-  func.call @variant1() : () -> ()
-  cf.br ^bb3
-// CHECK: ^[[BB2]]:
-// CHECK: call @variant2() : () -> ()
-^bb2:
-  func.call @variant2() : () -> ()
-  cf.br ^bb3
-^bb3:
-  return
-}
-
-// Test that the generic form of omp.dispatch roundtrips to pretty-printed form.
-// CHECK-LABEL: func @omp_dispatch_generic_to_pretty
-// CHECK-SAME: (%[[X:.*]]: memref<i32>)
-func.func @omp_dispatch_generic_to_pretty(%x : memref<i32>) -> () {
-  // CHECK: omp.dispatch {
-  // CHECK-NEXT: func.call @foo_dispatch(%[[X]]) : (memref<i32>) -> ()
-  // CHECK-NEXT: omp.terminator
-  // CHECK-NEXT: }
-  "omp.dispatch" () ({
-    func.call @foo_dispatch(%x) : (memref<i32>) -> ()
-    "omp.terminator" () : () -> ()
-  }) : () -> ()
-  return
-}
-
-// Test multiple dispatch regions with stores selecting different variants.
-// TODO: Use declare_variant when the support is merged.
-// CHECK-LABEL: func @omp_dispatch_multiple
-// CHECK-SAME: (%[[X:.*]]: memref<i32>)
-func.func @omp_dispatch_multiple(%x : memref<i32>) -> () {
-  // CHECK: %[[C1:.*]] = arith.constant 1 : i32
-  // CHECK: memref.store %[[C1]], %[[X]][] : memref<i32>
-  %c1 = arith.constant 1 : i32
-  memref.store %c1, %x[] : memref<i32>
-  // CHECK: omp.dispatch {
-  // CHECK-NEXT: func.call @foo_dispatch(%[[X]]) : (memref<i32>) -> ()
-  // CHECK-NEXT: omp.terminator
-  // CHECK-NEXT: }
-  "omp.dispatch" () ({
-    "func.call" (%x) {callee = @foo_dispatch} : (memref<i32>) -> ()
-    "omp.terminator" () : () -> ()
-  }) : () -> ()
-  // CHECK: %[[C2:.*]] = arith.constant 2 : i32
-  // CHECK: memref.store %[[C2]], %[[X]][] : memref<i32>
-  %c2 = arith.constant 2 : i32
-  memref.store %c2, %x[] : memref<i32>
-  // CHECK: omp.dispatch {
-  // CHECK-NEXT: func.call @foo_dispatch(%[[X]]) : (memref<i32>) -> ()
-  // CHECK-NEXT: omp.terminator
-  // CHECK-NEXT: }
-  "omp.dispatch" () ({
-    "func.call" (%x) {callee = @foo_dispatch} : (memref<i32>) -> ()
-    "omp.terminator" () : () -> ()
-  }) : () -> ()
-  return
-}
-
-// Test the nowait clause on omp.dispatch.
-// CHECK-LABEL: 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 @foo_dispatch(%[[X]]) : (memref<i32>) -> ()
-  // CHECK-NEXT: omp.terminator
-  // CHECK-NEXT: }
-  omp.dispatch nowait {
-    func.call @foo_dispatch(%x) : (memref<i32>) -> ()
-    omp.terminator
+module attributes {dlti.dl_spec = #dlti.dl_spec<!llvm.ptr<270> = dense<32> : vector<4xi64>, !llvm.ptr<271> = dense<32> : vector<4xi64>, !llvm.ptr<272> = dense<64> : vector<4xi64>, i64 = dense<64> : vector<2xi64>, i128 = dense<128> : vector<2xi64>, f80 = dense<128> : vector<2xi64>, !llvm.ptr = dense<64> : vector<4xi64>, i1 = dense<8> : vector<2xi64>, i8 = dense<8> : vector<2xi64>, i16 = dense<16> : vector<2xi64>, i32 = dense<32> : vector<2xi64>, f16 = dense<16> : vector<2xi64>, f64 = dense<64> : vector<2xi64>, f128 = dense<128> : vector<2xi64>, "dlti.endianness" = "little", "dlti.mangling_mode" = "e", "dlti.legal_int_widths" = array<i32: 8, 16, 32, 64>, "dlti.stack_alignment" = 128 : i64>, fir.defaultkind = "a1c4d8i4l4r4", fir.kindmap = "", fir.relocation_model = 1 : i32, llvm.data_layout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", llvm.ident = "flang version 24.0.0 (https://github.com/SunilKuravinakop/llvm-project.git 665c356fed252c9f65f99e7609997822e0e0a908)", llvm.target_triple = "x86_64-unknown-linux-gnu", omp.flags = #omp.flags<openmp_device_version = 31>, omp.is_gpu = false, omp.is_target_device = false, omp.target_triples = [], omp.version = #omp.version<version = 31>} {
+  // CHECK-LABEL: func.func @_QMfuncsPfoo_variant
+  func.func @_QMfuncsPfoo_variant() {
+    %0 = fir.dummy_scope : !fir.dscope
+    %c6_i32 = arith.constant 6 : i32
+    %1 = fir.address_of(@_QQclXa0e1d7990b4b8f5533e92e3c2cc80a5d) : !fir.ref<!fir.char<1,89>>
+    %2 = fir.convert %1 : (!fir.ref<!fir.char<1,89>>) -> !fir.ref<i8>
+    %c35_i32 = arith.constant 35 : i32
+    %3 = fir.call @_FortranAioBeginExternalListOutput(%c6_i32, %2, %c35_i32) fastmath<contract> : (i32, !fir.ref<i8>, i32) -> !fir.ref<i8>
+    %4 = fir.address_of(@_QQclX696E20666F6F5F76617269616E74) : !fir.ref<!fir.char<1,14>>
+    %c14 = arith.constant 14 : index
+    %5:2 = hlfir.declare %4 typeparams %c14 {fortran_attrs = #fir.var_attrs<parameter>, uniq_name = "_QQclX696E20666F6F5F76617269616E74"} : (!fir.ref<!fir.char<1,14>>, index) -> (!fir.ref<!fir.char<1,14>>, !fir.ref<!fir.char<1,14>>)
+    %6 = fir.convert %5#0 : (!fir.ref<!fir.char<1,14>>) -> !fir.ref<i8>
+    %7 = fir.convert %c14 : (index) -> i64
+    %8 = fir.call @_FortranAioOutputAscii(%3, %6, %7) fastmath<contract> : (!fir.ref<i8>, !fir.ref<i8>, i64) -> i1
+    %9 = fir.call @_FortranAioEndIoStatement(%3) fastmath<contract> : (!fir.ref<i8>) -> i32
+    return
+  }
+  // CHECK-LABEL: func.func @_QMfuncsPfoo_dispatch
+  func.func @_QMfuncsPfoo_dispatch() {
+    %0 = fir.dummy_scope : !fir.dscope
+    %c6_i32 = arith.constant 6 : i32
+    %1 = fir.address_of(@_QQclXa0e1d7990b4b8f5533e92e3c2cc80a5d) : !fir.ref<!fir.char<1,89>>
+    %2 = fir.convert %1 : (!fir.ref<!fir.char<1,89>>) -> !fir.ref<i8>
+    %c40_i32 = arith.constant 40 : i32
+    %3 = fir.call @_FortranAioBeginExternalListOutput(%c6_i32, %2, %c40_i32) fastmath<contract> : (i32, !fir.ref<i8>, i32) -> !fir.ref<i8>
+    %4 = fir.address_of(@_QQclX696E20666F6F5F6469737061746368) : !fir.ref<!fir.char<1,15>>
+    %c15 = arith.constant 15 : index
+    %5:2 = hlfir.declare %4 typeparams %c15 {fortran_attrs = #fir.var_attrs<parameter>, uniq_name = "_QQclX696E20666F6F5F6469737061746368"} : (!fir.ref<!fir.char<1,15>>, index) -> (!fir.ref<!fir.char<1,15>>, !fir.ref<!fir.char<1,15>>)
+    %6 = fir.convert %5#0 : (!fir.ref<!fir.char<1,15>>) -> !fir.ref<i8>
+    %7 = fir.convert %c15 : (index) -> i64
+    %8 = fir.call @_FortranAioOutputAscii(%3, %6, %7) fastmath<contract> : (!fir.ref<i8>, !fir.ref<i8>, i64) -> i1
+    %9 = fir.call @_FortranAioEndIoStatement(%3) fastmath<contract> : (!fir.ref<i8>) -> i32
+    return
+  }
+  // CHECK-LABEL: func.func @_QQmain
+  func.func @_QQmain() attributes {fir.bindc_name = "DISPATCH_TEST"} {
+    %0 = fir.dummy_scope : !fir.dscope
+    // CHECK: fir.call @_QMfuncsPfoo_dispatch()
+    fir.call @_QMfuncsPfoo_dispatch() fastmath<contract> : () -> ()
+    // CHECK: omp.dispatch {
+    omp.dispatch {
+      // CHECK: fir.call @_QMfuncsPfoo_variant()
+      fir.call @_QMfuncsPfoo_variant() fastmath<contract> : () -> ()
+      // CHECK: omp.terminator
+      omp.terminator
+    }
+    return
+  }
+  // CHECK-LABEL: func.func @omp_dispatch_nowait
+  func.func @omp_dispatch_nowait() {
+    // CHECK: omp.dispatch nowait {
+    omp.dispatch nowait {
+      // CHECK: fir.call @_QMfuncsPfoo_variant()
+      fir.call @_QMfuncsPfoo_variant() fastmath<contract> : () -> ()
+      // CHECK: omp.terminator
+      omp.terminator
+    }
+    return
   }
-  return
 }
-
-// CHECK-LABEL: func private @variant1()
-// CHECK-LABEL: func private @variant2()
-func.func private @variant1() -> ()
-func.func private @variant2() -> ()
diff --git a/mlir/test/Target/LLVMIR/openmp-dispatch.mlir b/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
index d947a871ca30c..8a3f07f0ebbb9 100644
--- a/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
@@ -1,82 +1,27 @@
 // RUN: mlir-translate -mlir-to-llvmir %s | FileCheck %s
 
-llvm.mlir.global external @x() {addr_space = 0 : i32} : i32 {
-  %0 = llvm.mlir.constant(0 : i32) : i32
-  llvm.return %0 : i32
-}
-
-llvm.func @variant1() -> ()
-llvm.func @variant2() -> ()
-
-// TODO: Add support for declare_variant, when support to it is merged.
-// CHECK-LABEL: define void @foo_dispatch()
-llvm.func @foo_dispatch() {
-  // CHECK: %[[ADDR:.*]] = load i32, ptr @x
-  // CHECK: %[[CMP:.*]] = icmp eq i32 %[[ADDR]], 1
-  // CHECK: br i1 %[[CMP]], label %[[BB1:.*]], label %[[BB2:.*]]
-  %0 = llvm.mlir.addressof @x : !llvm.ptr
-  %1 = llvm.load %0 : !llvm.ptr -> i32
-  %c1 = llvm.mlir.constant(1 : i32) : i32
-  %cmp = llvm.icmp "eq" %1, %c1 : i32
-  llvm.cond_br %cmp, ^bb1, ^bb2
-// CHECK: [[BB1]]:
-// CHECK: call void @variant1()
-^bb1:
-  llvm.call @variant1() : () -> ()
-  llvm.br ^bb3
-// CHECK: [[BB2]]:
-// CHECK: call void @variant2()
-^bb2:
-  llvm.call @variant2() : () -> ()
-  llvm.br ^bb3
-^bb3:
-  llvm.return
-}
-
-// CHECK-LABEL: define void @test_omp_dispatch()
-llvm.func @test_omp_dispatch() {
-  // CHECK: store i32 1, ptr @x
-  %0 = llvm.mlir.addressof @x : !llvm.ptr
-  %c1 = llvm.mlir.constant(1 : i32) : i32
-  llvm.store %c1, %0 : i32, !llvm.ptr
-  // CHECK: br label %omp.dispatch.region
-  omp.dispatch {
-    // CHECK: omp.dispatch.region:
-    // CHECK-NEXT: call void @foo_dispatch()
-    llvm.call @foo_dispatch() : () -> ()
-    // CHECK-NEXT: br label %omp.region.cont
-    omp.terminator
+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: omp.region.cont:
-  llvm.return
-}
-
-// CHECK-LABEL: define void @test_omp_dispatch_multiple()
-llvm.func @test_omp_dispatch_multiple() {
-  // CHECK: store i32 1, ptr @x
-  %0 = llvm.mlir.addressof @x : !llvm.ptr
-  %c1 = llvm.mlir.constant(1 : i32) : i32
-  llvm.store %c1, %0 : i32, !llvm.ptr
-  // CHECK: br label %omp.dispatch.region
-  omp.dispatch {
-    // CHECK: omp.dispatch.region:
-    // CHECK-NEXT: call void @foo_dispatch()
-    llvm.call @foo_dispatch() : () -> ()
-    // CHECK-NEXT: br label %omp.region.cont
-    omp.terminator
+  // CHECK-LABEL: define void @_QMfuncsPfoo_dispatch()
+  llvm.func @_QMfuncsPfoo_dispatch() {
+    llvm.return
   }
-  // CHECK: omp.region.cont:
-  // CHECK: store i32 2, ptr @x
-  %c2 = llvm.mlir.constant(2 : i32) : i32
-  llvm.store %c2, %0 : i32, !llvm.ptr
-  // CHECK: br label %omp.dispatch.region2
-  omp.dispatch {
-    // CHECK: omp.dispatch.region2:
-    // CHECK-NEXT: call void @foo_dispatch()
-    llvm.call @foo_dispatch() : () -> ()
-    // CHECK-NEXT: br label %omp.region.cont1
-    omp.terminator
+  // 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
   }
-  // CHECK: omp.region.cont1:
-  llvm.return
 }

>From d515daa0dbde17522438d7cd8b095e553c2306db Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Fri, 24 Jul 2026 02:28:33 -0500
Subject: [PATCH 07/11] Handling Chi Chun Chen comments on "NoRegionArguments"

---
 mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index b8fc6174f9776..93169976e2c8b 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -2328,11 +2328,13 @@ def MaskedOp : OpenMP_Op<"masked", traits = [
 //===----------------------------------------------------------------------===//
 // [Spec 5.1] 12.3 dispatch Construct
 //===----------------------------------------------------------------------===//
-def DispatchOp : OpenMP_Op<"dispatch", singleRegion = true, clauses = [
+def DispatchOp : OpenMP_Op<"dispatch", traits = [
+    NoRegionArguments
+  ], clauses = [
     // TODO: Complete clause list (device, depend, is_device_ptr, nocontext,
     // novariants).
     OpenMP_NowaitClause
-  ]> {
+  ], singleRegion = true> {
   let summary = "dispatch construct";
   let description = [{
     The dispatch construct enables the invocation of a variant of a

>From 882d0fe84493d889a0c868869c5424498ccfa27a Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Fri, 24 Jul 2026 05:42:18 -0500
Subject: [PATCH 08/11] Support for dispatch nowait.

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

diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 59a2192e5d855..886f70596ec53 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -512,7 +512,6 @@ static LogicalResult checkImplementationStatus(Operation &op) {
         checkTaskReductionByref(op, result);
       })
       .Case([&](omp::TaskwaitOp op) { checkNowait(op, result); })
-      .Case([&](omp::DispatchOp op) { 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 8a3f07f0ebbb9..931511b836e8b 100644
--- a/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
@@ -24,4 +24,19 @@ 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
+  }
 }
diff --git a/mlir/test/Target/LLVMIR/openmp-todo.mlir b/mlir/test/Target/LLVMIR/openmp-todo.mlir
index 3e78e28be8c67..e29659255f690 100644
--- a/mlir/test/Target/LLVMIR/openmp-todo.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-todo.mlir
@@ -52,17 +52,6 @@ llvm.func @distribute_order(%lb : i32, %ub : i32, %step : i32) {
 
 // -----
 
-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 @parallel_allocate(%x : !llvm.ptr) {
   // expected-error at below {{not yet implemented: Unhandled clause allocate in omp.parallel operation}}
   // expected-error at below {{LLVM Translation failed for operation: omp.parallel}}

>From bbdbf5d8a3ac1bf4badce794800fa28019b26acb Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Fri, 24 Jul 2026 13:12:58 -0500
Subject: [PATCH 09/11] Moving fir-opt test to
 flang/test/Fir/OpenMP/dispatch.fir. New test dispatch.mlir.

---
 flang/test/Fir/OpenMP/dispatch.fir     |  63 ++++++++++++++
 mlir/test/Dialect/OpenMP/dispatch.mlir | 110 ++++++++++++-------------
 2 files changed, 114 insertions(+), 59 deletions(-)
 create mode 100644 flang/test/Fir/OpenMP/dispatch.fir

diff --git a/flang/test/Fir/OpenMP/dispatch.fir b/flang/test/Fir/OpenMP/dispatch.fir
new file mode 100644
index 0000000000000..304753ed4bcc0
--- /dev/null
+++ b/flang/test/Fir/OpenMP/dispatch.fir
@@ -0,0 +1,63 @@
+// RUN: fir-opt %s | FileCheck %s
+
+module attributes {dlti.dl_spec = #dlti.dl_spec<!llvm.ptr<270> = dense<32> : vector<4xi64>, !llvm.ptr<271> = dense<32> : vector<4xi64>, !llvm.ptr<272> = dense<64> : vector<4xi64>, i64 = dense<64> : vector<2xi64>, i128 = dense<128> : vector<2xi64>, f80 = dense<128> : vector<2xi64>, !llvm.ptr = dense<64> : vector<4xi64>, i1 = dense<8> : vector<2xi64>, i8 = dense<8> : vector<2xi64>, i16 = dense<16> : vector<2xi64>, i32 = dense<32> : vector<2xi64>, f16 = dense<16> : vector<2xi64>, f64 = dense<64> : vector<2xi64>, f128 = dense<128> : vector<2xi64>, "dlti.endianness" = "little", "dlti.mangling_mode" = "e", "dlti.legal_int_widths" = array<i32: 8, 16, 32, 64>, "dlti.stack_alignment" = 128 : i64>, fir.defaultkind = "a1c4d8i4l4r4", fir.kindmap = "", fir.relocation_model = 1 : i32, llvm.data_layout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", llvm.ident = "flang version 24.0.0 (https://github.com/SunilKuravinakop/llvm-project.git 665c356fed252c9f65f99e7609997822e0e0a908)", llvm.target_triple = "x86_64-unknown-linux-gnu", omp.flags = #omp.flags<openmp_device_version = 31>, omp.is_gpu = false, omp.is_target_device = false, omp.target_triples = [], omp.version = #omp.version<version = 31>} {
+  // CHECK-LABEL: func.func @_QMfuncsPfoo_variant
+  func.func @_QMfuncsPfoo_variant() {
+    %0 = fir.dummy_scope : !fir.dscope
+    %c6_i32 = arith.constant 6 : i32
+    %1 = fir.address_of(@_QQclXa0e1d7990b4b8f5533e92e3c2cc80a5d) : !fir.ref<!fir.char<1,89>>
+    %2 = fir.convert %1 : (!fir.ref<!fir.char<1,89>>) -> !fir.ref<i8>
+    %c35_i32 = arith.constant 35 : i32
+    %3 = fir.call @_FortranAioBeginExternalListOutput(%c6_i32, %2, %c35_i32) fastmath<contract> : (i32, !fir.ref<i8>, i32) -> !fir.ref<i8>
+    %4 = fir.address_of(@_QQclX696E20666F6F5F76617269616E74) : !fir.ref<!fir.char<1,14>>
+    %c14 = arith.constant 14 : index
+    %5:2 = hlfir.declare %4 typeparams %c14 {fortran_attrs = #fir.var_attrs<parameter>, uniq_name = "_QQclX696E20666F6F5F76617269616E74"} : (!fir.ref<!fir.char<1,14>>, index) -> (!fir.ref<!fir.char<1,14>>, !fir.ref<!fir.char<1,14>>)
+    %6 = fir.convert %5#0 : (!fir.ref<!fir.char<1,14>>) -> !fir.ref<i8>
+    %7 = fir.convert %c14 : (index) -> i64
+    %8 = fir.call @_FortranAioOutputAscii(%3, %6, %7) fastmath<contract> : (!fir.ref<i8>, !fir.ref<i8>, i64) -> i1
+    %9 = fir.call @_FortranAioEndIoStatement(%3) fastmath<contract> : (!fir.ref<i8>) -> i32
+    return
+  }
+  // CHECK-LABEL: func.func @_QMfuncsPfoo_dispatch
+  func.func @_QMfuncsPfoo_dispatch() {
+    %0 = fir.dummy_scope : !fir.dscope
+    %c6_i32 = arith.constant 6 : i32
+    %1 = fir.address_of(@_QQclXa0e1d7990b4b8f5533e92e3c2cc80a5d) : !fir.ref<!fir.char<1,89>>
+    %2 = fir.convert %1 : (!fir.ref<!fir.char<1,89>>) -> !fir.ref<i8>
+    %c40_i32 = arith.constant 40 : i32
+    %3 = fir.call @_FortranAioBeginExternalListOutput(%c6_i32, %2, %c40_i32) fastmath<contract> : (i32, !fir.ref<i8>, i32) -> !fir.ref<i8>
+    %4 = fir.address_of(@_QQclX696E20666F6F5F6469737061746368) : !fir.ref<!fir.char<1,15>>
+    %c15 = arith.constant 15 : index
+    %5:2 = hlfir.declare %4 typeparams %c15 {fortran_attrs = #fir.var_attrs<parameter>, uniq_name = "_QQclX696E20666F6F5F6469737061746368"} : (!fir.ref<!fir.char<1,15>>, index) -> (!fir.ref<!fir.char<1,15>>, !fir.ref<!fir.char<1,15>>)
+    %6 = fir.convert %5#0 : (!fir.ref<!fir.char<1,15>>) -> !fir.ref<i8>
+    %7 = fir.convert %c15 : (index) -> i64
+    %8 = fir.call @_FortranAioOutputAscii(%3, %6, %7) fastmath<contract> : (!fir.ref<i8>, !fir.ref<i8>, i64) -> i1
+    %9 = fir.call @_FortranAioEndIoStatement(%3) fastmath<contract> : (!fir.ref<i8>) -> i32
+    return
+  }
+  // CHECK-LABEL: func.func @_QQmain
+  func.func @_QQmain() attributes {fir.bindc_name = "DISPATCH_TEST"} {
+    %0 = fir.dummy_scope : !fir.dscope
+    // CHECK: fir.call @_QMfuncsPfoo_dispatch()
+    fir.call @_QMfuncsPfoo_dispatch() fastmath<contract> : () -> ()
+    // CHECK: omp.dispatch {
+    omp.dispatch {
+      // CHECK: fir.call @_QMfuncsPfoo_variant()
+      fir.call @_QMfuncsPfoo_variant() fastmath<contract> : () -> ()
+      // CHECK: omp.terminator
+      omp.terminator
+    }
+    return
+  }
+  // CHECK-LABEL: func.func @omp_dispatch_nowait
+  func.func @omp_dispatch_nowait() {
+    // CHECK: omp.dispatch nowait {
+    omp.dispatch nowait {
+      // CHECK: fir.call @_QMfuncsPfoo_variant()
+      fir.call @_QMfuncsPfoo_variant() fastmath<contract> : () -> ()
+      // CHECK: omp.terminator
+      omp.terminator
+    }
+    return
+  }
+}
diff --git a/mlir/test/Dialect/OpenMP/dispatch.mlir b/mlir/test/Dialect/OpenMP/dispatch.mlir
index 304753ed4bcc0..40445a3991098 100644
--- a/mlir/test/Dialect/OpenMP/dispatch.mlir
+++ b/mlir/test/Dialect/OpenMP/dispatch.mlir
@@ -1,63 +1,55 @@
-// RUN: fir-opt %s | FileCheck %s
+// RUN: mlir-opt %s | mlir-opt | FileCheck %s
 
-module attributes {dlti.dl_spec = #dlti.dl_spec<!llvm.ptr<270> = dense<32> : vector<4xi64>, !llvm.ptr<271> = dense<32> : vector<4xi64>, !llvm.ptr<272> = dense<64> : vector<4xi64>, i64 = dense<64> : vector<2xi64>, i128 = dense<128> : vector<2xi64>, f80 = dense<128> : vector<2xi64>, !llvm.ptr = dense<64> : vector<4xi64>, i1 = dense<8> : vector<2xi64>, i8 = dense<8> : vector<2xi64>, i16 = dense<16> : vector<2xi64>, i32 = dense<32> : vector<2xi64>, f16 = dense<16> : vector<2xi64>, f64 = dense<64> : vector<2xi64>, f128 = dense<128> : vector<2xi64>, "dlti.endianness" = "little", "dlti.mangling_mode" = "e", "dlti.legal_int_widths" = array<i32: 8, 16, 32, 64>, "dlti.stack_alignment" = 128 : i64>, fir.defaultkind = "a1c4d8i4l4r4", fir.kindmap = "", fir.relocation_model = 1 : i32, llvm.data_layout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", llvm.ident = "flang version 24.0.0 (https://github.com/SunilKuravinakop/llvm-project.git 665c356fed252c9f65f99e7609997822e0e0a908)", llvm.target_triple = "x86_64-unknown-linux-gnu", omp.flags = #omp.flags<openmp_device_version = 31>, omp.is_gpu = false, omp.is_target_device = false, omp.target_triples = [], omp.version = #omp.version<version = 31>} {
-  // CHECK-LABEL: func.func @_QMfuncsPfoo_variant
-  func.func @_QMfuncsPfoo_variant() {
-    %0 = fir.dummy_scope : !fir.dscope
-    %c6_i32 = arith.constant 6 : i32
-    %1 = fir.address_of(@_QQclXa0e1d7990b4b8f5533e92e3c2cc80a5d) : !fir.ref<!fir.char<1,89>>
-    %2 = fir.convert %1 : (!fir.ref<!fir.char<1,89>>) -> !fir.ref<i8>
-    %c35_i32 = arith.constant 35 : i32
-    %3 = fir.call @_FortranAioBeginExternalListOutput(%c6_i32, %2, %c35_i32) fastmath<contract> : (i32, !fir.ref<i8>, i32) -> !fir.ref<i8>
-    %4 = fir.address_of(@_QQclX696E20666F6F5F76617269616E74) : !fir.ref<!fir.char<1,14>>
-    %c14 = arith.constant 14 : index
-    %5:2 = hlfir.declare %4 typeparams %c14 {fortran_attrs = #fir.var_attrs<parameter>, uniq_name = "_QQclX696E20666F6F5F76617269616E74"} : (!fir.ref<!fir.char<1,14>>, index) -> (!fir.ref<!fir.char<1,14>>, !fir.ref<!fir.char<1,14>>)
-    %6 = fir.convert %5#0 : (!fir.ref<!fir.char<1,14>>) -> !fir.ref<i8>
-    %7 = fir.convert %c14 : (index) -> i64
-    %8 = fir.call @_FortranAioOutputAscii(%3, %6, %7) fastmath<contract> : (!fir.ref<i8>, !fir.ref<i8>, i64) -> i1
-    %9 = fir.call @_FortranAioEndIoStatement(%3) fastmath<contract> : (!fir.ref<i8>) -> i32
-    return
-  }
-  // CHECK-LABEL: func.func @_QMfuncsPfoo_dispatch
-  func.func @_QMfuncsPfoo_dispatch() {
-    %0 = fir.dummy_scope : !fir.dscope
-    %c6_i32 = arith.constant 6 : i32
-    %1 = fir.address_of(@_QQclXa0e1d7990b4b8f5533e92e3c2cc80a5d) : !fir.ref<!fir.char<1,89>>
-    %2 = fir.convert %1 : (!fir.ref<!fir.char<1,89>>) -> !fir.ref<i8>
-    %c40_i32 = arith.constant 40 : i32
-    %3 = fir.call @_FortranAioBeginExternalListOutput(%c6_i32, %2, %c40_i32) fastmath<contract> : (i32, !fir.ref<i8>, i32) -> !fir.ref<i8>
-    %4 = fir.address_of(@_QQclX696E20666F6F5F6469737061746368) : !fir.ref<!fir.char<1,15>>
-    %c15 = arith.constant 15 : index
-    %5:2 = hlfir.declare %4 typeparams %c15 {fortran_attrs = #fir.var_attrs<parameter>, uniq_name = "_QQclX696E20666F6F5F6469737061746368"} : (!fir.ref<!fir.char<1,15>>, index) -> (!fir.ref<!fir.char<1,15>>, !fir.ref<!fir.char<1,15>>)
-    %6 = fir.convert %5#0 : (!fir.ref<!fir.char<1,15>>) -> !fir.ref<i8>
-    %7 = fir.convert %c15 : (index) -> i64
-    %8 = fir.call @_FortranAioOutputAscii(%3, %6, %7) fastmath<contract> : (!fir.ref<i8>, !fir.ref<i8>, i64) -> i1
-    %9 = fir.call @_FortranAioEndIoStatement(%3) fastmath<contract> : (!fir.ref<i8>) -> i32
-    return
-  }
-  // CHECK-LABEL: func.func @_QQmain
-  func.func @_QQmain() attributes {fir.bindc_name = "DISPATCH_TEST"} {
-    %0 = fir.dummy_scope : !fir.dscope
-    // CHECK: fir.call @_QMfuncsPfoo_dispatch()
-    fir.call @_QMfuncsPfoo_dispatch() fastmath<contract> : () -> ()
-    // CHECK: omp.dispatch {
-    omp.dispatch {
-      // CHECK: fir.call @_QMfuncsPfoo_variant()
-      fir.call @_QMfuncsPfoo_variant() fastmath<contract> : () -> ()
-      // CHECK: omp.terminator
-      omp.terminator
-    }
-    return
+// 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
   }
-  // CHECK-LABEL: func.func @omp_dispatch_nowait
-  func.func @omp_dispatch_nowait() {
-    // CHECK: omp.dispatch nowait {
-    omp.dispatch nowait {
-      // CHECK: fir.call @_QMfuncsPfoo_variant()
-      fir.call @_QMfuncsPfoo_variant() fastmath<contract> : () -> ()
-      // CHECK: omp.terminator
-      omp.terminator
-    }
-    return
+  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
 }
+
+// CHECK-LABEL: func.func private @variant(memref<i32>)
+func.func private @variant(memref<i32>) -> ()

>From 4f696fc46ca0e97f1ddce489c6e6dd3591907cd4 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Wed, 29 Jul 2026 00:40:24 -0500
Subject: [PATCH 10/11] Support for novariants clause.

---
 flang/include/flang/Lower/OpenMP.h            |  4 +++
 flang/lib/Lower/ConvertCall.cpp               | 27 ++++++++++++++++
 flang/lib/Lower/OpenMP/ClauseProcessor.cpp    | 17 ++++++++++
 flang/lib/Lower/OpenMP/ClauseProcessor.h      |  2 ++
 flang/lib/Lower/OpenMP/OpenMP.cpp             | 22 +++++++------
 flang/lib/Lower/OpenMP/Utils.cpp              |  9 ++++++
 flang/test/Fir/OpenMP/dispatch.fir            | 18 +++++++++++
 flang/test/Integration/OpenMP/dispatch.f90    | 24 +++++++++++++-
 flang/test/Lower/OpenMP/Todo/dispatch.f90     |  5 ---
 flang/test/Lower/OpenMP/dispatch.f90          | 15 +++++++++
 flang/test/Semantics/OpenMP/dispatch.f90      | 25 +++++++++++++++
 .../mlir/Dialect/OpenMP/OpenMPClauses.td      | 28 +++++++++++++++++
 mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td |  5 ++-
 mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp  |  2 +-
 mlir/test/Dialect/OpenMP/dispatch.mlir        | 31 +++++++++++++++++++
 mlir/test/Target/LLVMIR/openmp-dispatch.mlir  | 15 +++++++++
 16 files changed, 229 insertions(+), 20 deletions(-)

diff --git a/flang/include/flang/Lower/OpenMP.h b/flang/include/flang/Lower/OpenMP.h
index a3f35498b9180..5ec16bd7a5db7 100644
--- a/flang/include/flang/Lower/OpenMP.h
+++ b/flang/include/flang/Lower/OpenMP.h
@@ -113,6 +113,10 @@ namespace omp {
 const Fortran::semantics::Symbol *
 resolveDeclareVariantCallee(const Fortran::semantics::Symbol &base,
                             AbstractConverter &converter);
+
+/// Return the i1 `novariants` value of an enclosing `omp.dispatch`, or a null
+/// Value if there is none.
+mlir::Value getEnclosingDispatchNovariants(mlir::OpBuilder &builder);
 } // namespace omp
 
 // Materialize (idempotently) the omp.declare_reduction op for one already-
diff --git a/flang/lib/Lower/ConvertCall.cpp b/flang/lib/Lower/ConvertCall.cpp
index 19678e429249b..bc5ee9e63ce39 100644
--- a/flang/lib/Lower/ConvertCall.cpp
+++ b/flang/lib/Lower/ConvertCall.cpp
@@ -18,6 +18,7 @@
 #include "flang/Lower/ConvertVariable.h"
 #include "flang/Lower/CustomIntrinsicCall.h"
 #include "flang/Lower/HlfirIntrinsics.h"
+#include "flang/Lower/OpenMP.h"
 #include "flang/Lower/PFTBuilder.h"
 #include "flang/Lower/StatementContext.h"
 #include "flang/Lower/SymbolMap.h"
@@ -35,6 +36,7 @@
 #include "flang/Optimizer/Dialect/CUF/CUFOps.h"
 #include "flang/Optimizer/Dialect/FIROpsSupport.h"
 #include "flang/Optimizer/HLFIR/HLFIROps.h"
+#include "mlir/Dialect/Arith/IR/Arith.h"
 #include "mlir/IR/IRMapping.h"
 #include "llvm/ADT/TypeSwitch.h"
 #include "llvm/Support/CommandLine.h"
@@ -578,6 +580,31 @@ Fortran::lower::genCallOpAndResult(
     funcType = *modifiedFuncType;
   }
 
+  // OpenMP dispatch `novariants`: at runtime pick base (cond true) or variant
+  // (false) via an indirect call, evaluating arguments once. Both targets share
+  // one signature; revisit if declare-variant `adjust_args`/`append_args` land.
+  if (funcSymbolAttr) {
+    if (mlir::Value novariantsCond =
+            Fortran::lower::omp::getEnclosingDispatchNovariants(builder)) {
+      const Fortran::semantics::Symbol *baseSym =
+          caller.getCallDescription().proc().GetSymbol();
+      const Fortran::semantics::Symbol *selectedSym =
+          caller.getProcedureSymbol();
+      if (baseSym && selectedSym &&
+          &baseSym->GetUltimate() != &selectedSym->GetUltimate()) {
+        mlir::Value variantAddr =
+            fir::AddrOfOp::create(builder, loc, funcType, funcSymbolAttr);
+        mlir::Value baseAddr =
+            fir::AddrOfOp::create(builder, loc, funcType,
+                                  builder.getSymbolRefAttr(converter.mangleName(
+                                      baseSym->GetUltimate())));
+        funcPointer = mlir::arith::SelectOp::create(
+            builder, loc, novariantsCond, baseAddr, variantAddr);
+        funcSymbolAttr = {}; // Mark as an indirect call.
+      }
+    }
+  }
+
   llvm::SmallVector<mlir::Value> operands;
   // First operand of indirect call is the function pointer. Cast it to
   // required function type for the call to handle procedures that have a
diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index 5a554def351ea..5e7c5b6a87c9b 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
@@ -711,6 +711,23 @@ bool ClauseProcessor::processNogroup(
   return markClauseOccurrence<omp::clause::Nogroup>(result.nogroup);
 }
 
+bool ClauseProcessor::processNovariants(
+    lower::StatementContext &stmtCtx,
+    mlir::omp::NovariantsClauseOps &result) const {
+  const parser::CharBlock *source = nullptr;
+  if (auto *clause = findUniqueClause<omp::clause::Novariants>(&source)) {
+    fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
+    mlir::Location clauseLocation = converter.genLocation(*source);
+
+    mlir::Value novariantsVal =
+        fir::getBase(converter.genExprValue(clause->v, stmtCtx));
+    result.novariants = firOpBuilder.createConvert(
+        clauseLocation, firOpBuilder.getI1Type(), novariantsVal);
+    return true;
+  }
+  return false;
+}
+
 bool ClauseProcessor::processNowait(mlir::omp::NowaitClauseOps &result) const {
   return markClauseOccurrence<omp::clause::Nowait>(result.nowait);
 }
diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.h b/flang/lib/Lower/OpenMP/ClauseProcessor.h
index cb42b6524e2e7..456153ae2c97e 100644
--- a/flang/lib/Lower/OpenMP/ClauseProcessor.h
+++ b/flang/lib/Lower/OpenMP/ClauseProcessor.h
@@ -102,6 +102,8 @@ class ClauseProcessor {
   bool processMergeable(mlir::omp::MergeableClauseOps &result) const;
   bool processNogroup(mlir::omp::NogroupClauseOps &result) const;
   bool processNotinbranch(mlir::omp::NotinbranchClauseOps &result) const;
+  bool processNovariants(lower::StatementContext &stmtCtx,
+                         mlir::omp::NovariantsClauseOps &result) const;
   bool processNowait(mlir::omp::NowaitClauseOps &result) const;
   bool processNumTasks(lower::StatementContext &stmtCtx,
                        mlir::omp::NumTasksClauseOps &result) const;
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 9fd8e35bce0c8..eba25f4236264 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -2473,13 +2473,14 @@ static void genDistributeClauses(lower::AbstractConverter &converter,
 
 static void genDispatchClauses(lower::AbstractConverter &converter,
                                semantics::SemanticsContext &semaCtx,
+                               lower::StatementContext &stmtCtx,
                                const List<Clause> &clauses, mlir::Location loc,
                                mlir::omp::DispatchOperands &clauseOps) {
   ClauseProcessor cp(converter, semaCtx, clauses);
+  cp.processNovariants(stmtCtx, clauseOps);
   cp.processNowait(clauseOps);
   cp.processTODO<clause::Depend, clause::Device, clause::IsDevicePtr,
-                 clause::Nocontext, clause::Novariants>(
-      loc, llvm::omp::Directive::OMPD_dispatch);
+                 clause::Nocontext>(loc, llvm::omp::Directive::OMPD_dispatch);
 }
 
 static void genFlushClauses(lower::AbstractConverter &converter,
@@ -2999,14 +3000,14 @@ genCriticalOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
       queue, item, nameAttr);
 }
 
-static mlir::omp::DispatchOp
-genDispatchOp(lower::AbstractConverter &converter, lower::SymMap &symTable,
-              semantics::SemanticsContext &semaCtx,
-              lower::pft::Evaluation &eval, mlir::Location loc,
-              const ConstructQueue &queue,
-              ConstructQueue::const_iterator item) {
+static mlir::omp::DispatchOp genDispatchOp(
+    lower::AbstractConverter &converter, lower::SymMap &symTable,
+    lower::StatementContext &stmtCtx, semantics::SemanticsContext &semaCtx,
+    lower::pft::Evaluation &eval, mlir::Location loc,
+    const ConstructQueue &queue, ConstructQueue::const_iterator item) {
   mlir::omp::DispatchOperands clauseOps;
-  genDispatchClauses(converter, semaCtx, item->clauses, loc, clauseOps);
+  genDispatchClauses(converter, semaCtx, stmtCtx, item->clauses, loc,
+                     clauseOps);
 
   return genOpWithBody<mlir::omp::DispatchOp>(
       OpWithBodyGenInfo(converter, symTable, semaCtx, loc, eval,
@@ -4915,7 +4916,8 @@ static void genOMPDispatch(lower::AbstractConverter &converter,
     newOp = genBarrierOp(converter, symTable, semaCtx, eval, loc, queue, item);
     break;
   case llvm::omp::Directive::OMPD_dispatch:
-    newOp = genDispatchOp(converter, symTable, semaCtx, eval, loc, queue, item);
+    newOp = genDispatchOp(converter, symTable, stmtCtx, semaCtx, eval, loc,
+                          queue, item);
     break;
   case llvm::omp::Directive::OMPD_distribute:
     newOp = genStandaloneDistribute(converter, symTable, stmtCtx, semaCtx, eval,
diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index 5cb6a0713462f..c8127d7e89ca7 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -1450,6 +1450,15 @@ resolveDeclareVariantCallee(const semantics::Symbol &base,
   return variants[bestIdx];
 }
 
+mlir::Value getEnclosingDispatchNovariants(mlir::OpBuilder &builder) {
+  mlir::Block *block = builder.getInsertionBlock();
+  for (mlir::Operation *op = block ? block->getParentOp() : nullptr; op;
+       op = op->getParentOp())
+    if (auto dispatch = mlir::dyn_cast<mlir::omp::DispatchOp>(op))
+      return dispatch.getNovariants();
+  return {};
+}
+
 } // namespace omp
 } // namespace lower
 } // namespace Fortran
diff --git a/flang/test/Fir/OpenMP/dispatch.fir b/flang/test/Fir/OpenMP/dispatch.fir
index 304753ed4bcc0..07c9abcf48b5e 100644
--- a/flang/test/Fir/OpenMP/dispatch.fir
+++ b/flang/test/Fir/OpenMP/dispatch.fir
@@ -60,4 +60,22 @@ module attributes {dlti.dl_spec = #dlti.dl_spec<!llvm.ptr<270> = dense<32> : vec
     }
     return
   }
+  // novariants: runtime select of base/variant address, then indirect call.
+  // CHECK-LABEL: func.func @omp_dispatch_novariants
+  func.func @omp_dispatch_novariants(%cond : i1) {
+    // CHECK: omp.dispatch novariants(%[[COND:.*]]) {
+    omp.dispatch novariants(%cond) {
+      // CHECK: %[[VARIANT:.*]] = fir.address_of(@_QMfuncsPfoo_variant) : () -> ()
+      %variant = fir.address_of(@_QMfuncsPfoo_variant) : () -> ()
+      // CHECK: %[[BASE:.*]] = fir.address_of(@_QMfuncsPfoo_dispatch) : () -> ()
+      %base = fir.address_of(@_QMfuncsPfoo_dispatch) : () -> ()
+      // CHECK: %[[TARGET:.*]] = arith.select %[[COND]], %[[BASE]], %[[VARIANT]] : () -> ()
+      %target = arith.select %cond, %base, %variant : () -> ()
+      // CHECK: fir.call %[[TARGET]]()
+      fir.call %target() fastmath<contract> : () -> ()
+      // CHECK: omp.terminator
+      omp.terminator
+    }
+    return
+  }
 }
diff --git a/flang/test/Integration/OpenMP/dispatch.f90 b/flang/test/Integration/OpenMP/dispatch.f90
index 1ecccab5e90d4..df3a5fb482d98 100644
--- a/flang/test/Integration/OpenMP/dispatch.f90
+++ b/flang/test/Integration/OpenMP/dispatch.f90
@@ -6,7 +6,7 @@
 ! added to this directory and sub-directories.
 !===----------------------------------------------------------------------===!
 
-!RUN: %flang_fc1 -emit-llvm -fopenmp %s -o - | FileCheck %s
+!RUN: %flang_fc1 -emit-llvm -fopenmp -fopenmp-version=52 %s -o - | FileCheck %s
 
 !CHECK-LABEL: define void @_QMfuncsPfoo_variant()
 !CHECK: call ptr @_FortranAioBeginExternalListOutput
@@ -22,6 +22,18 @@
 !CHECK: br label %omp.region.cont
 !CHECK: omp.region.cont:
 
+! novariants: runtime select of base/variant address, then indirect call.
+!CHECK-LABEL: define void @test_novariants_(
+!CHECK-SAME: ptr noalias %[[ARG:[0-9]+]])
+!CHECK: %[[LOAD:.*]] = load i32, ptr %[[ARG]], align 4
+!CHECK: %[[COND:.*]] = icmp ne i32 %[[LOAD]], 0
+!CHECK: br label %omp.dispatch.region
+!CHECK: omp.dispatch.region:
+!CHECK: %[[TARGET:.*]] = select i1 %[[COND]], ptr @_QMfuncsPfoo_dispatch, ptr @_QMfuncsPfoo_variant
+!CHECK: call void %[[TARGET]]()
+!CHECK: br label %omp.region.cont
+!CHECK: omp.region.cont:
+
 module funcs
   implicit none
 
@@ -48,3 +60,13 @@ program dispatch_test
   call foo_dispatch()
 
 end program
+
+subroutine test_novariants(cond)
+  use funcs
+  implicit none
+  logical :: cond
+
+  !$omp dispatch novariants(cond)
+  call foo_dispatch()
+
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/dispatch.f90 b/flang/test/Lower/OpenMP/Todo/dispatch.f90
index 579d47d457def..8e93317e07286 100644
--- a/flang/test/Lower/OpenMP/Todo/dispatch.f90
+++ b/flang/test/Lower/OpenMP/Todo/dispatch.f90
@@ -2,13 +2,11 @@
 ! RUN: %not_todo_cmd %flang_fc1 -emit-fir -fopenmp -fopenmp-version=52 -cpp -DDEVICE -o - %s 2>&1 | FileCheck %s --check-prefix=DEVICE
 ! RUN: %not_todo_cmd %flang_fc1 -emit-fir -fopenmp -fopenmp-version=52 -cpp -DIS_DEVICE_PTR -o - %s 2>&1 | FileCheck %s --check-prefix=IS_DEVICE_PTR
 ! RUN: %not_todo_cmd %flang_fc1 -emit-fir -fopenmp -fopenmp-version=52 -cpp -DNOCONTEXT -o - %s 2>&1 | FileCheck %s --check-prefix=NOCONTEXT
-! RUN: %not_todo_cmd %flang_fc1 -emit-fir -fopenmp -fopenmp-version=52 -cpp -DNOVARIANTS -o - %s 2>&1 | FileCheck %s --check-prefix=NOVARIANTS
 
 ! DEPEND: not yet implemented: Unhandled clause DEPEND in DISPATCH construct
 ! DEVICE: not yet implemented: Unhandled clause DEVICE in DISPATCH construct
 ! IS_DEVICE_PTR: not yet implemented: Unhandled clause IS_DEVICE_PTR in DISPATCH construct
 ! NOCONTEXT: not yet implemented: Unhandled clause NOCONTEXT in DISPATCH construct
-! NOVARIANTS: not yet implemented: Unhandled clause NOVARIANTS in DISPATCH construct
 
 subroutine sub()
 #ifdef IS_DEVICE_PTR
@@ -27,9 +25,6 @@ subroutine sub()
 #endif
 #ifdef NOCONTEXT
 !$omp dispatch nocontext(.false.)
-#endif
-#ifdef NOVARIANTS
-!$omp dispatch novariants(.true.)
 #endif
   call foo()
 contains
diff --git a/flang/test/Lower/OpenMP/dispatch.f90 b/flang/test/Lower/OpenMP/dispatch.f90
index 4eb9439b7b35b..e51a77f2f73f2 100644
--- a/flang/test/Lower/OpenMP/dispatch.f90
+++ b/flang/test/Lower/OpenMP/dispatch.f90
@@ -26,6 +26,7 @@ end module funcs
 program dispatch_test
   use funcs
   implicit none
+  logical :: cond
 
   ! A call outside any dispatch region targets the base procedure.
   !HLFIR: fir.call @_QMfuncsPfoo_dispatch() {{.*}}: () -> ()
@@ -44,4 +45,18 @@ program dispatch_test
     call foo_dispatch()
   !HLFIR:   omp.terminator
   !HLFIR: }
+
+  ! novariants: runtime select of base/variant address, then indirect call, so
+  ! the arguments are evaluated once.
+  !HLFIR:   %[[COND:.*]] = fir.load %{{.*}} : !fir.ref<!fir.logical<4>>
+  !HLFIR:   %[[COND_I1:.*]] = fir.convert %[[COND]] : (!fir.logical<4>) -> i1
+  !HLFIR: omp.dispatch novariants(%[[COND_I1]]) {
+  !$omp dispatch novariants(cond)
+  !HLFIR:   %[[VARIANT:.*]] = fir.address_of(@_QMfuncsPfoo_variant) : () -> ()
+  !HLFIR:   %[[BASE:.*]] = fir.address_of(@_QMfuncsPfoo_dispatch) : () -> ()
+  !HLFIR:   %[[TARGET:.*]] = arith.select %[[COND_I1]], %[[BASE]], %[[VARIANT]] : () -> ()
+  !HLFIR:   fir.call %[[TARGET]]() {{.*}}: () -> ()
+    call foo_dispatch()
+  !HLFIR:   omp.terminator
+  !HLFIR: }
 end program
diff --git a/flang/test/Semantics/OpenMP/dispatch.f90 b/flang/test/Semantics/OpenMP/dispatch.f90
index af0d6856ab948..3b351183f9dd4 100644
--- a/flang/test/Semantics/OpenMP/dispatch.f90
+++ b/flang/test/Semantics/OpenMP/dispatch.f90
@@ -18,3 +18,28 @@ subroutine sb3
 !ERROR: The DISPATCH construct should contain a single function or subroutine call
   !$omp dispatch
 end subroutine
+
+subroutine sb4
+  logical :: c
+  integer :: r
+  ! The novariants clause is accepted; the body validation still applies.
+  !$omp dispatch novariants(c)
+!ERROR: The body of the DISPATCH construct should be a function or a subroutine call
+  print *, r
+end subroutine
+
+subroutine sb5
+  logical :: a, b
+  ! novariants has the `unique` property (OpenMP 5.2, 7.6.1).
+!ERROR: At most one NOVARIANTS clause can appear on DISPATCH directive
+  !$omp dispatch novariants(a) novariants(b)
+  call foo()
+end subroutine
+
+subroutine sb6
+  integer :: i
+  ! novariants requires an expression of logical type (OpenMP 5.2, 7.6.1).
+!ERROR: Must have LOGICAL type, but is INTEGER(4)
+  !$omp dispatch novariants(i)
+  call foo()
+end subroutine
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td
index cd1223dc1702c..bdc786da861a7 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td
@@ -502,6 +502,34 @@ class OpenMP_FinalClauseSkip<
 
 def OpenMP_FinalClause : OpenMP_FinalClauseSkip<>;
 
+//===----------------------------------------------------------------------===//
+// 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 93169976e2c8b..d9231c73a873e 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -2331,9 +2331,8 @@ def MaskedOp : OpenMP_Op<"masked", traits = [
 def DispatchOp : OpenMP_Op<"dispatch", traits = [
     NoRegionArguments
   ], clauses = [
-    // TODO: Complete clause list (device, depend, is_device_ptr, nocontext,
-    // novariants).
-    OpenMP_NowaitClause
+    // TODO: Complete clause list (device, depend, is_device_ptr, nocontext).
+    OpenMP_NovariantsClause, OpenMP_NowaitClause
   ], singleRegion = true> {
   let summary = "dispatch construct";
   let description = [{
diff --git a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
index 1c06a87c3dcf1..a92689baa1b6e 100644
--- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
+++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
@@ -5103,7 +5103,7 @@ void MaskedOp::build(OpBuilder &builder, OperationState &state,
 
 void DispatchOp::build(OpBuilder &builder, OperationState &state,
                        const DispatchOperands &clauses) {
-  DispatchOp::build(builder, state, clauses.nowait);
+  DispatchOp::build(builder, state, clauses.novariants, clauses.nowait);
 }
 
 //===----------------------------------------------------------------------===//
diff --git a/mlir/test/Dialect/OpenMP/dispatch.mlir b/mlir/test/Dialect/OpenMP/dispatch.mlir
index 40445a3991098..d42d9bef1d416 100644
--- a/mlir/test/Dialect/OpenMP/dispatch.mlir
+++ b/mlir/test/Dialect/OpenMP/dispatch.mlir
@@ -51,5 +51,36 @@ func.func @omp_dispatch_nowait(%x : memref<i32>) -> () {
   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
+}
+
 // 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 931511b836e8b..a075a8c5a5458 100644
--- a/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
@@ -39,4 +39,19 @@ 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.
+  // 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
+  }
 }

>From 51f9a199fc086c1c4ad4e07f56f30cb2a823d5e7 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Wed, 29 Jul 2026 06:02:46 -0500
Subject: [PATCH 11/11] Including class Value and class OpBuilder in the
 predefined classes at the beginning to avoid compilation problems.

---
 flang/include/flang/Lower/OpenMP.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/flang/include/flang/Lower/OpenMP.h b/flang/include/flang/Lower/OpenMP.h
index 5ec16bd7a5db7..22f0caa09db52 100644
--- a/flang/include/flang/Lower/OpenMP.h
+++ b/flang/include/flang/Lower/OpenMP.h
@@ -22,6 +22,8 @@ namespace mlir {
 class Operation;
 class Location;
 class Type;
+class Value;
+class OpBuilder;
 namespace omp {
 enum class DeclareTargetDeviceType : uint32_t;
 enum class DeclareTargetCaptureClause : uint32_t;



More information about the flang-commits mailing list