[Mlir-commits] [mlir] [MLIR][OpenMP] Support for dispatch construct with clauses nocontext & novariants (PR #215877)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Sun Aug 16 08:54:25 PDT 2026
https://github.com/SunilKuravinakop updated https://github.com/llvm/llvm-project/pull/215877
>From 2aa37d8ee21f0c261af78bba596f162514188fc0 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Wed, 12 Aug 2026 14:28:12 -0500
Subject: [PATCH 1/2] [MLIR][OpenMP] Adding omp.dispatch operation with
nocontext/novariants clauses.
This patch adds the omp.dispatch operation to the OpenMP dialect, along
with the nocontext and novariants clauses, the verifier, and LLVM IR
translation. It is limited to the MLIR layer; Flang lowering follows in
later patches.
Part 1 of 3 of the !$omp dispatch support.
---
.../mlir/Dialect/OpenMP/OpenMPClauses.td | 54 ++++++++
mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td | 22 ++++
mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp | 10 ++
.../OpenMP/OpenMPToLLVMIRTranslation.cpp | 21 ++++
mlir/test/Dialect/OpenMP/dispatch.mlir | 117 ++++++++++++++++++
mlir/test/Target/LLVMIR/openmp-dispatch.mlir | 72 +++++++++++
6 files changed, 296 insertions(+)
create mode 100644 mlir/test/Dialect/OpenMP/dispatch.mlir
create mode 100644 mlir/test/Target/LLVMIR/openmp-dispatch.mlir
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td
index cd1223dc1702c..7c735544858f6 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPClauses.td
@@ -502,6 +502,60 @@ class OpenMP_FinalClauseSkip<
def OpenMP_FinalClause : OpenMP_FinalClauseSkip<>;
+//===----------------------------------------------------------------------===//
+// V5.2: [7.6.2] `nocontext` clause
+//===----------------------------------------------------------------------===//
+
+class OpenMP_NocontextClauseSkip<
+ bit traits = false, bit arguments = false, bit assemblyFormat = false,
+ bit description = false, bit extraClassDeclaration = false
+ > : OpenMP_Clause<traits, arguments, assemblyFormat, description,
+ extraClassDeclaration> {
+ let arguments = (ins
+ Optional<I1>:$nocontext
+ );
+
+ let optAssemblyFormat = [{
+ `nocontext` `(` $nocontext `)`
+ }];
+
+ let description = [{
+ When parameter to `nocontext` evaluates to `true`, the dispatch construct is not added
+ to the OpenMP context, so a function variant that would be selected only
+ because of the enclosing dispatch construct is not selected.
+ }];
+}
+
+def OpenMP_NocontextClause : OpenMP_NocontextClauseSkip<>;
+
+//===----------------------------------------------------------------------===//
+// V5.2: [7.6.1] `novariants` clause
+//===----------------------------------------------------------------------===//
+
+class OpenMP_NovariantsClauseSkip<
+ bit traits = false, bit arguments = false, bit assemblyFormat = false,
+ bit description = false, bit extraClassDeclaration = false
+ > : OpenMP_Clause<traits, arguments, assemblyFormat, description,
+ extraClassDeclaration> {
+ let arguments = (ins
+ Optional<I1>:$novariants
+ );
+
+ let optAssemblyFormat = [{
+ `novariants` `(` $novariants `)`
+ }];
+
+ let description = [{
+ When a `novariants` clause is present and its expression evaluates to
+ `true`, no function variant is selected for the target call of the dispatch
+ region, even if one would be selected normally. The use of a variable in a
+ `novariants` clause expression causes an implicit reference to the variable
+ in all enclosing constructs.
+ }];
+}
+
+def OpenMP_NovariantsClause : OpenMP_NovariantsClauseSkip<>;
+
//===----------------------------------------------------------------------===//
// V5.2: [12.6.1] `grainsize` clause
//===----------------------------------------------------------------------===//
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index 0a5736f46b088..b9c92a7ebcc57 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -2325,6 +2325,28 @@ def MaskedOp : OpenMP_Op<"masked", traits = [
];
}
+//===----------------------------------------------------------------------===//
+// [Spec 5.1] 12.3 dispatch Construct
+//===----------------------------------------------------------------------===//
+def DispatchOp : OpenMP_Op<"dispatch", traits = [
+ AttrSizedOperandSegments, NoRegionArguments
+ ], clauses = [
+ // TODO: Complete clause list (device, depend, is_device_ptr).
+ OpenMP_NocontextClause, OpenMP_NovariantsClause, OpenMP_NowaitClause
+ ], singleRegion = true> {
+ let summary = "dispatch construct";
+ let description = [{
+ The dispatch construct enables the invocation of a variant of a
+ base procedure. The structured block of a dispatch construct is
+ a single expression statement that contains a function call or
+ a subroutine call.
+ }] # clausesDescription;
+
+ let builders = [
+ OpBuilder<(ins CArg<"const DispatchOperands &">:$clauses)>
+ ];
+}
+
//===----------------------------------------------------------------------===//
// [Spec 5.2] 6.5 allocate Directive
//===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
index 67bc2bdf35619..a97ee275662dc 100644
--- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
+++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
@@ -5097,6 +5097,16 @@ void MaskedOp::build(OpBuilder &builder, OperationState &state,
MaskedOp::build(builder, state, clauses.filteredThreadId);
}
+//===----------------------------------------------------------------------===//
+// Spec 5.1: Dispatch construct (12.3)
+//===----------------------------------------------------------------------===//
+
+void DispatchOp::build(OpBuilder &builder, OperationState &state,
+ const DispatchOperands &clauses) {
+ DispatchOp::build(builder, state, clauses.nocontext, clauses.novariants,
+ clauses.nowait);
+}
+
//===----------------------------------------------------------------------===//
// Spec 5.2: Scan construct (5.6)
//===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 85963c402bc87..886f70596ec53 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -813,6 +813,24 @@ static llvm::omp::ProcBindKind getProcBindKind(omp::ClauseProcBindKind kind) {
llvm_unreachable("Unknown ClauseProcBindKind kind");
}
+/// Convert 'dispatch' operation into LLVM IR.
+static LogicalResult
+convertOmpDispatch(Operation &opInst, llvm::IRBuilderBase &builder,
+ LLVM::ModuleTranslation &moduleTranslation) {
+ auto dispatchOp = cast<omp::DispatchOp>(opInst);
+
+ if (failed(checkImplementationStatus(opInst)))
+ return failure();
+
+ auto ®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,
@@ -9831,6 +9849,9 @@ LogicalResult OpenMPDialectLLVMIRTranslationInterface::convertOperation(
.Case([&](omp::ParallelOp op) {
return convertOmpParallel(op, builder, moduleTranslation);
})
+ .Case([&](omp::DispatchOp) {
+ return convertOmpDispatch(*op, builder, moduleTranslation);
+ })
.Case([&](omp::MaskedOp) {
return convertOmpMasked(*op, builder, moduleTranslation);
})
diff --git a/mlir/test/Dialect/OpenMP/dispatch.mlir b/mlir/test/Dialect/OpenMP/dispatch.mlir
new file mode 100644
index 0000000000000..42ada49951134
--- /dev/null
+++ b/mlir/test/Dialect/OpenMP/dispatch.mlir
@@ -0,0 +1,117 @@
+// RUN: mlir-opt %s | mlir-opt | FileCheck %s
+
+// Variant selection (e.g. from Fortran `declare variant`) is resolved in the
+// frontend, so at the MLIR level the dispatch region simply wraps a call to the
+// selected variant procedure.
+
+// CHECK-LABEL: func.func @omp_dispatch
+// CHECK-SAME: (%[[X:.*]]: memref<i32>)
+func.func @omp_dispatch(%x : memref<i32>) -> () {
+ // CHECK: omp.dispatch {
+ // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
+ // CHECK-NEXT: omp.terminator
+ // CHECK-NEXT: }
+ omp.dispatch {
+ func.call @variant(%x) : (memref<i32>) -> ()
+ omp.terminator
+ }
+ return
+}
+
+// Test that the generic form of omp.dispatch roundtrips to pretty-printed form.
+// CHECK-LABEL: func.func @omp_dispatch_generic_to_pretty
+// CHECK-SAME: (%[[X:.*]]: memref<i32>)
+func.func @omp_dispatch_generic_to_pretty(%x : memref<i32>) -> () {
+ // A plain call (outside any dispatch region) is left untouched.
+ // CHECK: call @omp_dispatch(%[[X]]) : (memref<i32>) -> ()
+ func.call @omp_dispatch(%x) : (memref<i32>) -> ()
+ // CHECK: omp.dispatch {
+ // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
+ // CHECK-NEXT: omp.terminator
+ // CHECK-NEXT: }
+ "omp.dispatch" () ({
+ func.call @variant(%x) : (memref<i32>) -> ()
+ "omp.terminator" () : () -> ()
+ }) : () -> ()
+ return
+}
+
+// Test the nowait clause on omp.dispatch.
+// CHECK-LABEL: func.func @omp_dispatch_nowait
+// CHECK-SAME: (%[[X:.*]]: memref<i32>)
+func.func @omp_dispatch_nowait(%x : memref<i32>) -> () {
+ // CHECK: omp.dispatch nowait {
+ // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
+ // CHECK-NEXT: omp.terminator
+ // CHECK-NEXT: }
+ omp.dispatch nowait {
+ func.call @variant(%x) : (memref<i32>) -> ()
+ omp.terminator
+ }
+ return
+}
+
+// novariants clause round-trip; the frontend materializes the runtime
+// base/variant selection inside the region.
+// CHECK-LABEL: func.func @omp_dispatch_novariants
+// CHECK-SAME: (%[[COND:.*]]: i1, %[[X:.*]]: memref<i32>)
+func.func @omp_dispatch_novariants(%cond : i1, %x : memref<i32>) -> () {
+ // CHECK: omp.dispatch novariants(%[[COND]]) {
+ // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
+ // CHECK-NEXT: omp.terminator
+ // CHECK-NEXT: }
+ omp.dispatch novariants(%cond) {
+ func.call @variant(%x) : (memref<i32>) -> ()
+ omp.terminator
+ }
+ return
+}
+
+// novariants and nowait together.
+// CHECK-LABEL: func.func @omp_dispatch_novariants_nowait
+// CHECK-SAME: (%[[COND:.*]]: i1, %[[X:.*]]: memref<i32>)
+func.func @omp_dispatch_novariants_nowait(%cond : i1, %x : memref<i32>) -> () {
+ // CHECK: omp.dispatch novariants(%[[COND]]) nowait {
+ // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
+ // CHECK-NEXT: omp.terminator
+ // CHECK-NEXT: }
+ omp.dispatch novariants(%cond) nowait {
+ func.call @variant(%x) : (memref<i32>) -> ()
+ omp.terminator
+ }
+ return
+}
+
+// nocontext clause round-trip; the frontend materializes the runtime
+// base/variant selection inside the region.
+// CHECK-LABEL: func.func @omp_dispatch_nocontext
+// CHECK-SAME: (%[[COND:.*]]: i1, %[[X:.*]]: memref<i32>)
+func.func @omp_dispatch_nocontext(%cond : i1, %x : memref<i32>) -> () {
+ // CHECK: omp.dispatch nocontext(%[[COND]]) {
+ // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
+ // CHECK-NEXT: omp.terminator
+ // CHECK-NEXT: }
+ omp.dispatch nocontext(%cond) {
+ func.call @variant(%x) : (memref<i32>) -> ()
+ omp.terminator
+ }
+ return
+}
+
+// nocontext and novariants together.
+// CHECK-LABEL: func.func @omp_dispatch_nocontext_novariants
+// CHECK-SAME: (%[[COND:.*]]: i1, %[[X:.*]]: memref<i32>)
+func.func @omp_dispatch_nocontext_novariants(%cond : i1, %x : memref<i32>) -> () {
+ // CHECK: omp.dispatch nocontext(%[[COND]]) novariants(%[[COND]]) {
+ // CHECK-NEXT: func.call @variant(%[[X]]) : (memref<i32>) -> ()
+ // CHECK-NEXT: omp.terminator
+ // CHECK-NEXT: }
+ omp.dispatch nocontext(%cond) novariants(%cond) {
+ func.call @variant(%x) : (memref<i32>) -> ()
+ omp.terminator
+ }
+ return
+}
+
+// CHECK-LABEL: func.func private @variant(memref<i32>)
+func.func private @variant(memref<i32>) -> ()
diff --git a/mlir/test/Target/LLVMIR/openmp-dispatch.mlir b/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
new file mode 100644
index 0000000000000..8f027eceb4836
--- /dev/null
+++ b/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
@@ -0,0 +1,72 @@
+// RUN: mlir-translate -mlir-to-llvmir %s | FileCheck %s
+
+module attributes {omp.is_target_device = false, omp.is_gpu = false, omp.version = #omp.version<version = 31>} {
+ // CHECK-LABEL: define void @_QMfuncsPfoo_variant()
+ llvm.func @_QMfuncsPfoo_variant() {
+ llvm.return
+ }
+ // CHECK-LABEL: define void @_QMfuncsPfoo_dispatch()
+ llvm.func @_QMfuncsPfoo_dispatch() {
+ llvm.return
+ }
+ // CHECK-LABEL: define void @_QQmain()
+ llvm.func @_QQmain() {
+ // CHECK: call void @_QMfuncsPfoo_dispatch()
+ llvm.call @_QMfuncsPfoo_dispatch() : () -> ()
+ // CHECK: br label %omp.dispatch.region
+ // CHECK: omp.dispatch.region:
+ omp.dispatch {
+ // CHECK: call void @_QMfuncsPfoo_variant()
+ llvm.call @_QMfuncsPfoo_variant() : () -> ()
+ // CHECK: br label %omp.region.cont
+ omp.terminator
+ }
+ // CHECK: omp.region.cont:
+ llvm.return
+ }
+ // The nowait clause is accepted; it is a no-op in the current synchronous
+ // inline lowering, producing the same dispatch region.
+ // CHECK-LABEL: define void @test_dispatch_nowait()
+ llvm.func @test_dispatch_nowait() {
+ // CHECK: br label %omp.dispatch.region
+ // CHECK: omp.dispatch.region:
+ omp.dispatch nowait {
+ // CHECK: call void @_QMfuncsPfoo_variant()
+ llvm.call @_QMfuncsPfoo_variant() : () -> ()
+ // CHECK: br label %omp.region.cont
+ omp.terminator
+ }
+ // CHECK: omp.region.cont:
+ llvm.return
+ }
+ // The novariants operand is ignored at translation; the region already holds
+ // the runtime base/variant selection.
+ // CHECK-LABEL: define void @test_dispatch_novariants(i1
+ llvm.func @test_dispatch_novariants(%cond : i1) {
+ // CHECK: br label %omp.dispatch.region
+ // CHECK: omp.dispatch.region:
+ omp.dispatch novariants(%cond) {
+ // CHECK: call void @_QMfuncsPfoo_variant()
+ llvm.call @_QMfuncsPfoo_variant() : () -> ()
+ // CHECK: br label %omp.region.cont
+ omp.terminator
+ }
+ // CHECK: omp.region.cont:
+ llvm.return
+ }
+ // The nocontext operand is ignored at translation; the region already holds
+ // the runtime base/variant selection.
+ // CHECK-LABEL: define void @test_dispatch_nocontext(i1
+ llvm.func @test_dispatch_nocontext(%cond : i1) {
+ // CHECK: br label %omp.dispatch.region
+ // CHECK: omp.dispatch.region:
+ omp.dispatch nocontext(%cond) {
+ // CHECK: call void @_QMfuncsPfoo_variant()
+ llvm.call @_QMfuncsPfoo_variant() : () -> ()
+ // CHECK: br label %omp.region.cont
+ omp.terminator
+ }
+ // CHECK: omp.region.cont:
+ llvm.return
+ }
+}
>From dd41cb2693bc7ae0f9931ad4abdb9261b5ece813 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Sun, 16 Aug 2026 10:49:06 -0500
Subject: [PATCH 2/2] nowait is marked as asynchronous implementation by
calling checkNowait().
---
.../Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp | 5 +++++
mlir/test/Target/LLVMIR/openmp-dispatch.mlir | 15 ---------------
mlir/test/Target/LLVMIR/openmp-todo.mlir | 11 +++++++++++
3 files changed, 16 insertions(+), 15 deletions(-)
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 886f70596ec53..ac7b7a82bf618 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -512,6 +512,11 @@ static LogicalResult checkImplementationStatus(Operation &op) {
checkTaskReductionByref(op, result);
})
.Case([&](omp::TaskwaitOp op) { checkNowait(op, result); })
+ .Case([&](omp::DispatchOp op) {
+ // nowait clause requests asynchronous dispatch and is not yet honored,
+ // so diagnose it rather than silently dropping it.
+ checkNowait(op, result);
+ })
.Case([&](omp::TaskloopContextOp op) {
checkAllocate(op, result);
checkInReduction(op, result);
diff --git a/mlir/test/Target/LLVMIR/openmp-dispatch.mlir b/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
index 8f027eceb4836..47a349f02e3b1 100644
--- a/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-dispatch.mlir
@@ -24,21 +24,6 @@ module attributes {omp.is_target_device = false, omp.is_gpu = false, omp.version
// CHECK: omp.region.cont:
llvm.return
}
- // The nowait clause is accepted; it is a no-op in the current synchronous
- // inline lowering, producing the same dispatch region.
- // CHECK-LABEL: define void @test_dispatch_nowait()
- llvm.func @test_dispatch_nowait() {
- // CHECK: br label %omp.dispatch.region
- // CHECK: omp.dispatch.region:
- omp.dispatch nowait {
- // CHECK: call void @_QMfuncsPfoo_variant()
- llvm.call @_QMfuncsPfoo_variant() : () -> ()
- // CHECK: br label %omp.region.cont
- omp.terminator
- }
- // CHECK: omp.region.cont:
- llvm.return
- }
// The novariants operand is ignored at translation; the region already holds
// the runtime base/variant selection.
// CHECK-LABEL: define void @test_dispatch_novariants(i1
diff --git a/mlir/test/Target/LLVMIR/openmp-todo.mlir b/mlir/test/Target/LLVMIR/openmp-todo.mlir
index e29659255f690..9fbaa22c14551 100644
--- a/mlir/test/Target/LLVMIR/openmp-todo.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-todo.mlir
@@ -635,6 +635,17 @@ llvm.func @taskwait_nowait() {
// -----
+llvm.func @dispatch_nowait() {
+ // expected-error at below {{not yet implemented: Unhandled clause nowait in omp.dispatch operation}}
+ // expected-error at below {{LLVM Translation failed for operation: omp.dispatch}}
+ omp.dispatch nowait {
+ omp.terminator
+ }
+ llvm.return
+}
+
+// -----
+
llvm.func @teams_allocate(%x : !llvm.ptr) {
// expected-error at below {{not yet implemented: Unhandled clause allocate in omp.teams operation}}
// expected-error at below {{LLVM Translation failed for operation: omp.teams}}
More information about the Mlir-commits
mailing list