[flang-commits] [flang] [mlir] [flang][OpenMP] Support for "!$omp dispatch". (PR #203320)
via flang-commits
flang-commits at lists.llvm.org
Fri Aug 7 03:22:31 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/12] 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 ®ion = 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/12] 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/12] 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 ®ion = 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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;
>From 9b010ef03d1940ca0d8bdb1c2877915678ba6456 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Fri, 7 Aug 2026 05:18:51 -0500
Subject: [PATCH 12/12] Support for nocontext clause in dispatch construct.
---
flang/include/flang/Lower/OpenMP.h | 11 ++-
flang/lib/Lower/ConvertCall.cpp | 72 +++++++++++++------
flang/lib/Lower/OpenMP/ClauseProcessor.cpp | 17 +++++
flang/lib/Lower/OpenMP/ClauseProcessor.h | 2 +
flang/lib/Lower/OpenMP/OpenMP.cpp | 5 +-
flang/lib/Lower/OpenMP/Utils.cpp | 33 +++++++--
flang/lib/Lower/OpenMP/Utils.h | 7 +-
flang/test/Fir/OpenMP/dispatch.fir | 41 +++++++++++
flang/test/Integration/OpenMP/dispatch.f90 | 22 ++++++
flang/test/Lower/OpenMP/Todo/dispatch.f90 | 5 --
flang/test/Lower/OpenMP/dispatch.f90 | 53 +++++++++++++-
flang/test/Semantics/OpenMP/dispatch.f90 | 25 +++++++
.../mlir/Dialect/OpenMP/OpenMPClauses.td | 26 +++++++
mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td | 6 +-
mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp | 3 +-
mlir/test/Dialect/OpenMP/dispatch.mlir | 31 ++++++++
mlir/test/Target/LLVMIR/openmp-dispatch.mlir | 15 ++++
17 files changed, 332 insertions(+), 42 deletions(-)
diff --git a/flang/include/flang/Lower/OpenMP.h b/flang/include/flang/Lower/OpenMP.h
index 22f0caa09db52..f98e047053812 100644
--- a/flang/include/flang/Lower/OpenMP.h
+++ b/flang/include/flang/Lower/OpenMP.h
@@ -111,14 +111,21 @@ void materializeOpenMPDeclareMappers(
namespace omp {
/// If \p base carries OpenMP DECLARE VARIANT entries, return the variant symbol
/// that best matches the enclosing OpenMP context, or nullptr if none matches.
-/// \p base is expected to have variant entries.
+/// \p base is expected to have variant entries. When \p excludeDispatchContext
+/// is set, the innermost enclosing `dispatch` construct is removed from the
+/// context before matching, implementing the `nocontext` clause.
const Fortran::semantics::Symbol *
resolveDeclareVariantCallee(const Fortran::semantics::Symbol &base,
- AbstractConverter &converter);
+ AbstractConverter &converter,
+ bool excludeDispatchContext = false);
/// Return the i1 `novariants` value of an enclosing `omp.dispatch`, or a null
/// Value if there is none.
mlir::Value getEnclosingDispatchNovariants(mlir::OpBuilder &builder);
+
+/// Return the i1 `nocontext` value of an enclosing `omp.dispatch`, or a null
+/// Value if there is none.
+mlir::Value getEnclosingDispatchNocontext(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 bc5ee9e63ce39..e96ddd5d3afc4 100644
--- a/flang/lib/Lower/ConvertCall.cpp
+++ b/flang/lib/Lower/ConvertCall.cpp
@@ -580,28 +580,60 @@ 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.
+ // OpenMP dispatch `novariants`/`nocontext`: at runtime pick the right target
+ // via an indirect call, evaluating arguments once. All candidate procedures
+ // 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.
+ mlir::Value novariantsCond =
+ Fortran::lower::omp::getEnclosingDispatchNovariants(builder);
+ mlir::Value nocontextCond =
+ Fortran::lower::omp::getEnclosingDispatchNocontext(builder);
+ const Fortran::semantics::Symbol *baseSym =
+ caller.getCallDescription().proc().GetSymbol();
+ const Fortran::semantics::Symbol *selectedSym = caller.getProcedureSymbol();
+ // A runtime choice is only needed when a variant was actually selected for
+ // the enclosing dispatch context (otherwise the base is already the call
+ // target and dropping the dispatch construct cannot introduce a variant).
+ if ((novariantsCond || nocontextCond) && baseSym && selectedSym &&
+ &baseSym->GetUltimate() != &selectedSym->GetUltimate()) {
+ const Fortran::semantics::Symbol &baseUlt = baseSym->GetUltimate();
+ const Fortran::semantics::Symbol &selectedUlt =
+ selectedSym->GetUltimate();
+
+ auto addrOfSym =
+ [&](const Fortran::semantics::Symbol &sym) -> mlir::Value {
+ return fir::AddrOfOp::create(
+ builder, loc, funcType,
+ builder.getSymbolRefAttr(converter.mangleName(sym)));
+ };
+
+ // Start from the variant selected with the dispatch construct in context.
+ mlir::Value target =
+ fir::AddrOfOp::create(builder, loc, funcType, funcSymbolAttr);
+
+ // `nocontext(true)`: re-select the variant with the dispatch construct
+ // removed from the OpenMP context. That may resolve to a different
+ // variant (e.g. one matching `device={kind(host)}`) or to the base
+ // procedure.
+ if (nocontextCond) {
+ const Fortran::semantics::Symbol *nocontextSym =
+ Fortran::lower::omp::resolveDeclareVariantCallee(
+ baseUlt, converter, /*excludeDispatchContext=*/true);
+ const Fortran::semantics::Symbol &nocontextUlt =
+ nocontextSym ? nocontextSym->GetUltimate() : baseUlt;
+ if (&nocontextUlt != &selectedUlt)
+ target = mlir::arith::SelectOp::create(
+ builder, loc, nocontextCond, addrOfSym(nocontextUlt), target);
}
+
+ // `novariants(true)` takes final precedence: always call the base.
+ if (novariantsCond)
+ target = mlir::arith::SelectOp::create(builder, loc, novariantsCond,
+ addrOfSym(baseUlt), target);
+
+ funcPointer = target;
+ funcSymbolAttr = {}; // Mark as an indirect call.
}
}
diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.cpp b/flang/lib/Lower/OpenMP/ClauseProcessor.cpp
index 5e7c5b6a87c9b..dd491f0731859 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::processNocontext(
+ lower::StatementContext &stmtCtx,
+ mlir::omp::NocontextClauseOps &result) const {
+ const parser::CharBlock *source = nullptr;
+ if (auto *clause = findUniqueClause<omp::clause::Nocontext>(&source)) {
+ fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
+ mlir::Location clauseLocation = converter.genLocation(*source);
+
+ mlir::Value nocontextVal =
+ fir::getBase(converter.genExprValue(clause->v, stmtCtx));
+ result.nocontext = firOpBuilder.createConvert(
+ clauseLocation, firOpBuilder.getI1Type(), nocontextVal);
+ return true;
+ }
+ return false;
+}
+
bool ClauseProcessor::processNovariants(
lower::StatementContext &stmtCtx,
mlir::omp::NovariantsClauseOps &result) const {
diff --git a/flang/lib/Lower/OpenMP/ClauseProcessor.h b/flang/lib/Lower/OpenMP/ClauseProcessor.h
index 456153ae2c97e..d3481db2f116f 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 processNocontext(lower::StatementContext &stmtCtx,
+ mlir::omp::NocontextClauseOps &result) const;
bool processNovariants(lower::StatementContext &stmtCtx,
mlir::omp::NovariantsClauseOps &result) const;
bool processNowait(mlir::omp::NowaitClauseOps &result) const;
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index eba25f4236264..3b44ef14cca81 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -2477,10 +2477,11 @@ static void genDispatchClauses(lower::AbstractConverter &converter,
const List<Clause> &clauses, mlir::Location loc,
mlir::omp::DispatchOperands &clauseOps) {
ClauseProcessor cp(converter, semaCtx, clauses);
+ cp.processNocontext(stmtCtx, clauseOps);
cp.processNovariants(stmtCtx, clauseOps);
cp.processNowait(clauseOps);
- cp.processTODO<clause::Depend, clause::Device, clause::IsDevicePtr,
- clause::Nocontext>(loc, llvm::omp::Directive::OMPD_dispatch);
+ cp.processTODO<clause::Depend, clause::Device, clause::IsDevicePtr>(
+ loc, llvm::omp::Directive::OMPD_dispatch);
}
static void genFlushClauses(lower::AbstractConverter &converter,
diff --git a/flang/lib/Lower/OpenMP/Utils.cpp b/flang/lib/Lower/OpenMP/Utils.cpp
index c8127d7e89ca7..46a88fbd8414d 100644
--- a/flang/lib/Lower/OpenMP/Utils.cpp
+++ b/flang/lib/Lower/OpenMP/Utils.cpp
@@ -1370,18 +1370,29 @@ semantics::omp::OmpVariantMatchContext makeVariantMatchContext(
void collectEnclosingConstructTraits(
mlir::Operation *op,
- llvm::SmallVectorImpl<llvm::omp::TraitProperty> &constructTraits) {
+ llvm::SmallVectorImpl<llvm::omp::TraitProperty> &constructTraits,
+ bool excludeNearestDispatch) {
// Collect enclosing OpenMP operations so variants chosen by an outer
// metadirective are part of this metadirective's context. For example, an
// inner metadirective inside `target` and an outer-selected `parallel` must
// be able to match construct={target, parallel}. The final reverse yields
// outermost-to-innermost order as required by OMPContext.
+ //
+ // When \p excludeNearestDispatch is set, the innermost enclosing `dispatch`
+ // construct is left out of the trait set. This implements the `nocontext`
+ // clause, which removes its own dispatch construct from the OpenMP context
+ // used for variant selection.
+ bool skippedDispatch = false;
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::DispatchOp>(op)) {
+ if (excludeNearestDispatch && !skippedDispatch)
+ skippedDispatch = true;
+ else
+ 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);
@@ -1397,7 +1408,8 @@ void collectEnclosingConstructTraits(
const semantics::Symbol *
resolveDeclareVariantCallee(const semantics::Symbol &base,
- AbstractConverter &converter) {
+ AbstractConverter &converter,
+ bool excludeDispatchContext) {
const semantics::Symbol &ultimate{base.GetUltimate()};
const auto *details{ultimate.detailsIf<semantics::SubprogramDetails>()};
@@ -1438,7 +1450,7 @@ resolveDeclareVariantCallee(const semantics::Symbol &base,
llvm::SmallVector<llvm::omp::TraitProperty, 8> constructTraits;
collectEnclosingConstructTraits(
converter.getFirOpBuilder().getInsertionBlock()->getParentOp(),
- constructTraits);
+ constructTraits, excludeDispatchContext);
semantics::omp::OmpVariantMatchContext ompCtx =
makeVariantMatchContext(converter.getModuleOp(), constructTraits);
@@ -1459,6 +1471,15 @@ mlir::Value getEnclosingDispatchNovariants(mlir::OpBuilder &builder) {
return {};
}
+mlir::Value getEnclosingDispatchNocontext(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.getNocontext();
+ return {};
+}
+
} // namespace omp
} // namespace lower
} // namespace Fortran
diff --git a/flang/lib/Lower/OpenMP/Utils.h b/flang/lib/Lower/OpenMP/Utils.h
index 94f85c43f7033..ac6ee4d368b3a 100644
--- a/flang/lib/Lower/OpenMP/Utils.h
+++ b/flang/lib/Lower/OpenMP/Utils.h
@@ -262,10 +262,13 @@ std::optional<llvm::SmallVector<mlir::Value>> getIteratorElementIndices(
/// Walk the already-emitted MLIR parent operations starting from \p op and
/// collect the implied OpenMP construct traits in outermost-to-innermost
/// order. Used by metadirective lowering and declare-variant call resolution
-/// to build the `ConstructTraits` of an `OMPContext`.
+/// to build the `ConstructTraits` of an `OMPContext`. When
+/// \p excludeNearestDispatch is set, the innermost enclosing `dispatch`
+/// construct is omitted (used to implement the `nocontext` clause).
void collectEnclosingConstructTraits(
mlir::Operation *op,
- llvm::SmallVectorImpl<llvm::omp::TraitProperty> &constructTraits);
+ llvm::SmallVectorImpl<llvm::omp::TraitProperty> &constructTraits,
+ bool excludeNearestDispatch = false);
/// Build the OpenMP variant-matching context for \p module. The device flag,
/// host triple, offload triple, and target features are read from the module;
diff --git a/flang/test/Fir/OpenMP/dispatch.fir b/flang/test/Fir/OpenMP/dispatch.fir
index 07c9abcf48b5e..b6682cda5babc 100644
--- a/flang/test/Fir/OpenMP/dispatch.fir
+++ b/flang/test/Fir/OpenMP/dispatch.fir
@@ -78,4 +78,45 @@ module attributes {dlti.dl_spec = #dlti.dl_spec<!llvm.ptr<270> = dense<32> : vec
}
return
}
+ // nocontext: runtime select of base/variant address, then indirect call.
+ // CHECK-LABEL: func.func @omp_dispatch_nocontext
+ func.func @omp_dispatch_nocontext(%cond : i1) {
+ // CHECK: omp.dispatch nocontext(%[[COND:.*]]) {
+ omp.dispatch nocontext(%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
+ }
+ // nocontext with multiple matching variants: the frontend re-resolves variant
+ // selection with the dispatch construct removed, so the runtime select picks
+ // between the two variants (device={kind(host)} vs construct={dispatch}),
+ // not the base procedure.
+ // CHECK-LABEL: func.func @omp_dispatch_nocontext_variant
+ func.func @omp_dispatch_nocontext_variant(%cond : i1) {
+ // CHECK: omp.dispatch nocontext(%[[COND:.*]]) {
+ omp.dispatch nocontext(%cond) {
+ // CHECK: %[[VARIANT:.*]] = fir.address_of(@_QMfuncsPdispatch_variant) : () -> ()
+ %variant = fir.address_of(@_QMfuncsPdispatch_variant) : () -> ()
+ // CHECK: %[[HOST:.*]] = fir.address_of(@_QMfuncsPhost_variant) : () -> ()
+ %host = fir.address_of(@_QMfuncsPhost_variant) : () -> ()
+ // CHECK: %[[TARGET:.*]] = arith.select %[[COND]], %[[HOST]], %[[VARIANT]] : () -> ()
+ %target = arith.select %cond, %host, %variant : () -> ()
+ // CHECK: fir.call %[[TARGET]]()
+ fir.call %target() fastmath<contract> : () -> ()
+ // CHECK: omp.terminator
+ omp.terminator
+ }
+ return
+ }
+ func.func private @_QMfuncsPdispatch_variant()
+ func.func private @_QMfuncsPhost_variant()
}
diff --git a/flang/test/Integration/OpenMP/dispatch.f90 b/flang/test/Integration/OpenMP/dispatch.f90
index df3a5fb482d98..9741f0147352f 100644
--- a/flang/test/Integration/OpenMP/dispatch.f90
+++ b/flang/test/Integration/OpenMP/dispatch.f90
@@ -34,6 +34,18 @@
!CHECK: br label %omp.region.cont
!CHECK: omp.region.cont:
+! nocontext: runtime select of base/variant address, then indirect call.
+!CHECK-LABEL: define void @test_nocontext_(
+!CHECK-SAME: ptr noalias %[[NARG:[0-9]+]])
+!CHECK: %[[NLOAD:.*]] = load i32, ptr %[[NARG]], align 4
+!CHECK: %[[NCOND:.*]] = icmp ne i32 %[[NLOAD]], 0
+!CHECK: br label %omp.dispatch.region
+!CHECK: omp.dispatch.region:
+!CHECK: %[[NTARGET:.*]] = select i1 %[[NCOND]], ptr @_QMfuncsPfoo_dispatch, ptr @_QMfuncsPfoo_variant
+!CHECK: call void %[[NTARGET]]()
+!CHECK: br label %omp.region.cont
+!CHECK: omp.region.cont:
+
module funcs
implicit none
@@ -70,3 +82,13 @@ subroutine test_novariants(cond)
call foo_dispatch()
end subroutine
+
+subroutine test_nocontext(cond)
+ use funcs
+ implicit none
+ logical :: cond
+
+ !$omp dispatch nocontext(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 8e93317e07286..ccf32f2dad193 100644
--- a/flang/test/Lower/OpenMP/Todo/dispatch.f90
+++ b/flang/test/Lower/OpenMP/Todo/dispatch.f90
@@ -1,12 +1,10 @@
! 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
! 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
subroutine sub()
#ifdef IS_DEVICE_PTR
@@ -22,9 +20,6 @@ subroutine sub()
#endif
#ifdef IS_DEVICE_PTR
!$omp dispatch is_device_ptr(x)
-#endif
-#ifdef NOCONTEXT
-!$omp dispatch nocontext(.false.)
#endif
call foo()
contains
diff --git a/flang/test/Lower/OpenMP/dispatch.f90 b/flang/test/Lower/OpenMP/dispatch.f90
index e51a77f2f73f2..c73efb86a1c6d 100644
--- a/flang/test/Lower/OpenMP/dispatch.f90
+++ b/flang/test/Lower/OpenMP/dispatch.f90
@@ -2,7 +2,9 @@
! 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`.
+! is replaced by a call to its variant `foo_variant`. `base_routine` additionally
+! carries a `device={kind(host)}` variant to exercise re-resolution under
+! `nocontext` when more than one variant matches.
module funcs
implicit none
@@ -20,6 +22,25 @@ subroutine foo_dispatch()
print *, "in foo_dispatch"
end subroutine
+ !HLFIR-LABEL: func @_QMfuncsPdispatch_variant
+ subroutine dispatch_variant()
+ print *, "in dispatch_variant"
+ end subroutine
+
+ !HLFIR-LABEL: func @_QMfuncsPhost_variant
+ subroutine host_variant()
+ print *, "in host_variant"
+ end subroutine
+
+ ! `base_routine` has two variants: `dispatch_variant` matches
+ ! `construct={dispatch}` and `host_variant` matches `device={kind(host)}`.
+ !HLFIR-LABEL: func @_QMfuncsPbase_routine
+ subroutine base_routine()
+ !$omp declare variant(base_routine:dispatch_variant) match(construct={dispatch})
+ !$omp declare variant(base_routine:host_variant) match(device={kind(host)})
+ print *, "in base_routine"
+ end subroutine
+
end module funcs
!HLFIR-LABEL: func @_QQmain
@@ -59,4 +80,34 @@ program dispatch_test
call foo_dispatch()
!HLFIR: omp.terminator
!HLFIR: }
+
+ ! nocontext: the dispatch construct is dropped from the OpenMP context when
+ ! the condition is true, so the same base/variant runtime select is emitted.
+ !HLFIR: %[[NCOND:.*]] = fir.load %{{.*}} : !fir.ref<!fir.logical<4>>
+ !HLFIR: %[[NCOND_I1:.*]] = fir.convert %[[NCOND]] : (!fir.logical<4>) -> i1
+ !HLFIR: omp.dispatch nocontext(%[[NCOND_I1]]) {
+ !$omp dispatch nocontext(cond)
+ !HLFIR: %[[NVARIANT:.*]] = fir.address_of(@_QMfuncsPfoo_variant) : () -> ()
+ !HLFIR: %[[NBASE:.*]] = fir.address_of(@_QMfuncsPfoo_dispatch) : () -> ()
+ !HLFIR: %[[NTARGET:.*]] = arith.select %[[NCOND_I1]], %[[NBASE]], %[[NVARIANT]] : () -> ()
+ !HLFIR: fir.call %[[NTARGET]]() {{.*}}: () -> ()
+ call foo_dispatch()
+ !HLFIR: omp.terminator
+ !HLFIR: }
+
+ ! nocontext with two matching variants: with the dispatch construct removed
+ ! from the context, `construct={dispatch}` no longer matches and selection
+ ! re-resolves to the `device={kind(host)}` variant, so the runtime select is
+ ! between the two variants (not the base procedure).
+ !HLFIR: %[[MCOND:.*]] = fir.load %{{.*}} : !fir.ref<!fir.logical<4>>
+ !HLFIR: %[[MCOND_I1:.*]] = fir.convert %[[MCOND]] : (!fir.logical<4>) -> i1
+ !HLFIR: omp.dispatch nocontext(%[[MCOND_I1]]) {
+ !$omp dispatch nocontext(cond)
+ !HLFIR: %[[MVARIANT:.*]] = fir.address_of(@_QMfuncsPdispatch_variant) : () -> ()
+ !HLFIR: %[[MHOST:.*]] = fir.address_of(@_QMfuncsPhost_variant) : () -> ()
+ !HLFIR: %[[MTARGET:.*]] = arith.select %[[MCOND_I1]], %[[MHOST]], %[[MVARIANT]] : () -> ()
+ !HLFIR: fir.call %[[MTARGET]]() {{.*}}: () -> ()
+ call base_routine()
+ !HLFIR: omp.terminator
+ !HLFIR: }
end program
diff --git a/flang/test/Semantics/OpenMP/dispatch.f90 b/flang/test/Semantics/OpenMP/dispatch.f90
index 3b351183f9dd4..ad617a7afe9c4 100644
--- a/flang/test/Semantics/OpenMP/dispatch.f90
+++ b/flang/test/Semantics/OpenMP/dispatch.f90
@@ -43,3 +43,28 @@ subroutine sb6
!$omp dispatch novariants(i)
call foo()
end subroutine
+
+subroutine sb7
+ logical :: c
+ integer :: r
+ ! The nocontext clause is accepted; the body validation still applies.
+ !$omp dispatch nocontext(c)
+!ERROR: The body of the DISPATCH construct should be a function or a subroutine call
+ print *, r
+end subroutine
+
+subroutine sb8
+ logical :: a, b
+ ! nocontext has the `unique` property (OpenMP 5.2, 7.6.2).
+!ERROR: At most one NOCONTEXT clause can appear on DISPATCH directive
+ !$omp dispatch nocontext(a) nocontext(b)
+ call foo()
+end subroutine
+
+subroutine sb9
+ integer :: i
+ ! nocontext requires an expression of logical type (OpenMP 5.2, 7.6.2).
+!ERROR: Must have LOGICAL type, but is INTEGER(4)
+ !$omp dispatch nocontext(i)
+ call foo()
+end subroutine
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td
index bdc786da861a7..7c735544858f6 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td
@@ -502,6 +502,32 @@ class OpenMP_FinalClauseSkip<
def OpenMP_FinalClause : OpenMP_FinalClauseSkip<>;
+//===----------------------------------------------------------------------===//
+// V5.2: [7.6.2] `nocontext` clause
+//===----------------------------------------------------------------------===//
+
+class OpenMP_NocontextClauseSkip<
+ bit traits = false, bit arguments = false, bit assemblyFormat = false,
+ bit description = false, bit extraClassDeclaration = false
+ > : OpenMP_Clause<traits, arguments, assemblyFormat, description,
+ extraClassDeclaration> {
+ let arguments = (ins
+ Optional<I1>:$nocontext
+ );
+
+ let optAssemblyFormat = [{
+ `nocontext` `(` $nocontext `)`
+ }];
+
+ let description = [{
+ When parameter to `nocontext` evaluates to `true`, the dispatch construct is not added
+ to the OpenMP context, so a function variant that would be selected only
+ because of the enclosing dispatch construct is not selected.
+ }];
+}
+
+def OpenMP_NocontextClause : OpenMP_NocontextClauseSkip<>;
+
//===----------------------------------------------------------------------===//
// V5.2: [7.6.1] `novariants` clause
//===----------------------------------------------------------------------===//
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index d9231c73a873e..b9c92a7ebcc57 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -2329,10 +2329,10 @@ def MaskedOp : OpenMP_Op<"masked", traits = [
// [Spec 5.1] 12.3 dispatch Construct
//===----------------------------------------------------------------------===//
def DispatchOp : OpenMP_Op<"dispatch", traits = [
- NoRegionArguments
+ AttrSizedOperandSegments, NoRegionArguments
], clauses = [
- // TODO: Complete clause list (device, depend, is_device_ptr, nocontext).
- OpenMP_NovariantsClause, OpenMP_NowaitClause
+ // TODO: Complete clause list (device, depend, is_device_ptr).
+ OpenMP_NocontextClause, 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 a92689baa1b6e..a97ee275662dc 100644
--- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
+++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
@@ -5103,7 +5103,8 @@ void MaskedOp::build(OpBuilder &builder, OperationState &state,
void DispatchOp::build(OpBuilder &builder, OperationState &state,
const DispatchOperands &clauses) {
- DispatchOp::build(builder, state, clauses.novariants, clauses.nowait);
+ DispatchOp::build(builder, state, clauses.nocontext, clauses.novariants,
+ clauses.nowait);
}
//===----------------------------------------------------------------------===//
diff --git a/mlir/test/Dialect/OpenMP/dispatch.mlir b/mlir/test/Dialect/OpenMP/dispatch.mlir
index d42d9bef1d416..42ada49951134 100644
--- a/mlir/test/Dialect/OpenMP/dispatch.mlir
+++ b/mlir/test/Dialect/OpenMP/dispatch.mlir
@@ -82,5 +82,36 @@ func.func @omp_dispatch_novariants_nowait(%cond : i1, %x : memref<i32>) -> () {
return
}
+// nocontext clause round-trip; the frontend materializes the runtime
+// base/variant selection inside the region.
+// CHECK-LABEL: func.func @omp_dispatch_nocontext
+// CHECK-SAME: (%[[COND:.*]]: i1, %[[X:.*]]: memref<i32>)
+func.func @omp_dispatch_nocontext(%cond : i1, %x : memref<i32>) -> () {
+ // CHECK: omp.dispatch nocontext(%[[COND]]) {
+ // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
+ // CHECK-NEXT: omp.terminator
+ // CHECK-NEXT: }
+ omp.dispatch nocontext(%cond) {
+ func.call @variant(%x) : (memref<i32>) -> ()
+ omp.terminator
+ }
+ return
+}
+
+// nocontext and novariants together.
+// CHECK-LABEL: func.func @omp_dispatch_nocontext_novariants
+// CHECK-SAME: (%[[COND:.*]]: i1, %[[X:.*]]: memref<i32>)
+func.func @omp_dispatch_nocontext_novariants(%cond : i1, %x : memref<i32>) -> () {
+ // CHECK: omp.dispatch nocontext(%[[COND]]) novariants(%[[COND]]) {
+ // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
+ // CHECK-NEXT: omp.terminator
+ // CHECK-NEXT: }
+ omp.dispatch nocontext(%cond) novariants(%cond) {
+ func.call @variant(%x) : (memref<i32>) -> ()
+ omp.terminator
+ }
+ return
+}
+
// CHECK-LABEL: func.func private @variant(memref<i32>)
func.func private @variant(memref<i32>) -> ()
diff --git a/mlir/test/Target/LLVMIR/openmp-dispatch.mlir b/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
index a075a8c5a5458..8f027eceb4836 100644
--- a/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
@@ -54,4 +54,19 @@ module attributes {omp.is_target_device = false, omp.is_gpu = false, omp.version
// CHECK: omp.region.cont:
llvm.return
}
+ // The nocontext operand is ignored at translation; the region already holds
+ // the runtime base/variant selection.
+ // CHECK-LABEL: define void @test_dispatch_nocontext(i1
+ llvm.func @test_dispatch_nocontext(%cond : i1) {
+ // CHECK: br label %omp.dispatch.region
+ // CHECK: omp.dispatch.region:
+ omp.dispatch nocontext(%cond) {
+ // CHECK: call void @_QMfuncsPfoo_variant()
+ llvm.call @_QMfuncsPfoo_variant() : () -> ()
+ // CHECK: br label %omp.region.cont
+ omp.terminator
+ }
+ // CHECK: omp.region.cont:
+ llvm.return
+ }
}
More information about the flang-commits
mailing list