[clang] [CIR] Add offload container Op (PR #206576)
David Rivera via cfe-commits
cfe-commits at lists.llvm.org
Mon Jun 29 13:00:39 PDT 2026
https://github.com/RiverDave updated https://github.com/llvm/llvm-project/pull/206576
>From 9d0f66f6fe743d75f3cae14b5e7412e646b6116a Mon Sep 17 00:00:00 2001
From: David Rivera <davidriverg at gmail.com>
Date: Mon, 29 Jun 2026 15:46:11 -0400
Subject: [PATCH 1/2] [CIR] Add offload container operation
Introduce cir.offload.container, a CIR dialect operation that groups one host CIR module with one or more device CIR modules in a single IR unit.
Nested modules are tagged with the new cir.offload.kind enum attribute using #cir.offload_kind<host> and #cir.offload_kind<device>. The verifier enforces the structural contract expected by follow-up offload merge/split pipeline patches: host module first, device modules after it, only nested builtin.module ops, and at least one device module.
This patch only adds the IR representation and verifier tests; the passes that create, consume, or split the container are left to later patches.
---
.../clang/CIR/Dialect/IR/CIRDialect.td | 1 +
clang/include/clang/CIR/Dialect/IR/CIROps.td | 59 ++++++++++++++++++
clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 61 +++++++++++++++++++
.../test/CIR/IR/invalid-offload-container.cir | 54 ++++++++++++++++
clang/test/CIR/IR/offload-container.cir | 32 ++++++++++
5 files changed, 207 insertions(+)
create mode 100644 clang/test/CIR/IR/invalid-offload-container.cir
create mode 100644 clang/test/CIR/IR/offload-container.cir
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
index c20af04f97a1a..7d189361eee48 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
@@ -50,6 +50,7 @@ def CIR_Dialect : Dialect {
static llvm::StringRef getModuleLevelAsmAttrName() { return "cir.module_asm"; }
static llvm::StringRef getGlobalCtorsAttrName() { return "cir.global_ctors"; }
static llvm::StringRef getGlobalDtorsAttrName() { return "cir.global_dtors"; }
+ static llvm::StringRef getOffloadKindAttrName() { return "cir.offload.kind"; }
static llvm::StringRef getOperandSegmentSizesAttrName() { return "operandSegmentSizes"; }
static llvm::StringRef getNoCallerSavedRegsAttrName() { return "no_caller_saved_registers"; }
static llvm::StringRef getNoCallbackAttrName() { return "nocallback"; }
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 7d1c48b994b27..0c18aac7f4eb4 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -5672,6 +5672,65 @@ def CIR_VecSplatOp : CIR_Op<"vec.splat", [
}];
}
+//===----------------------------------------------------------------------===//
+// OffloadKind
+//===----------------------------------------------------------------------===//
+
+def CIR_OffloadKind : CIR_I32EnumAttr<"OffloadKind", "offload kind", [
+ I32EnumAttrCase<"Host", 0, "host">,
+ I32EnumAttrCase<"Device", 1, "device">
+]> {
+ let genSpecializedAttr = 0;
+}
+
+def CIR_OffloadKindAttr : CIR_EnumAttr<CIR_OffloadKind, "offload_kind"> {
+ let summary = "Offload kind (host or device)";
+}
+
+//===----------------------------------------------------------------------===//
+// OffloadContainerOp
+//===----------------------------------------------------------------------===//
+
+def CIR_OffloadContainerOp : CIR_Op<"offload.container", [
+ NoRegionArguments, NoTerminator, SingleBlock, SymbolTable]> {
+ let summary = "Container for host and device CIR modules";
+ let description = [{
+ `cir.offload.container` groups one host CIR module with one or more device
+ CIR modules for offload-aware analysis and transformation.
+
+ The body holds nested `builtin.module` operations. The first nested
+ module is the host module and must carry
+ `cir.offload.kind = #cir.offload_kind<host>`. All remaining nested
+ modules are device modules and must carry
+ `cir.offload.kind = #cir.offload_kind<device>`. There must be at least
+ one device module.
+
+ Example:
+
+ ```mlir
+ cir.offload.container {
+ builtin.module @host attributes {cir.offload.kind = #cir.offload_kind<host>} {
+ }
+ builtin.module @device_0 attributes {cir.offload.kind = #cir.offload_kind<device>} {
+ }
+ }
+ ```
+ }];
+
+ let regions = (region SizedRegion<1>:$body);
+
+ let assemblyFormat = "$body attr-dict";
+
+ let hasVerifier = 1;
+ let hasLLVMLowering = false;
+
+ let extraClassDeclaration = [{
+ mlir::ModuleOp getHostModule();
+ llvm::iterator_range<mlir::Block::op_iterator<mlir::ModuleOp>>
+ getDeviceModules();
+ }];
+}
+
//===----------------------------------------------------------------------===//
// BaseClassAddrOp
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 67cc5e09f26d0..96501d5808168 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -18,6 +18,7 @@
#include "mlir/IR/Attributes.h"
#include "mlir/IR/BuiltinTypes.h"
+#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/DialectImplementation.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/IR/Value.h"
@@ -2334,6 +2335,66 @@ LogicalResult cir::VTTAddrPointOp::verify() {
return success();
}
+//===----------------------------------------------------------------------===//
+// OffloadContainerOp
+//===----------------------------------------------------------------------===//
+
+mlir::ModuleOp cir::OffloadContainerOp::getHostModule() {
+ return mlir::cast<mlir::ModuleOp>(getBody().front().front());
+}
+
+llvm::iterator_range<mlir::Block::op_iterator<mlir::ModuleOp>>
+cir::OffloadContainerOp::getDeviceModules() {
+ mlir::Block &body = getBody().front();
+ auto begin = body.op_begin<mlir::ModuleOp>();
+ auto end = body.op_end<mlir::ModuleOp>();
+ if (begin != end)
+ ++begin;
+ return {begin, end};
+}
+
+static LogicalResult checkOffloadKind(mlir::ModuleOp module,
+ cir::OffloadKind expected) {
+ auto attr = module->getAttrOfType<cir::OffloadKindAttr>(
+ cir::CIRDialect::getOffloadKindAttrName());
+ if (!attr)
+ return module.emitOpError()
+ << "expects '" << cir::CIRDialect::getOffloadKindAttrName()
+ << "' offload kind attribute";
+ if (attr.getValue() != expected)
+ return module.emitOpError()
+ << "expects '" << cir::CIRDialect::getOffloadKindAttrName()
+ << "' value '" << cir::stringifyOffloadKind(expected) << "'";
+ return success();
+}
+
+LogicalResult cir::OffloadContainerOp::verify() {
+ mlir::Block &body = getBody().front();
+ if (body.empty())
+ return emitOpError() << "expects host module as the first nested op";
+
+ auto host = mlir::dyn_cast<mlir::ModuleOp>(body.front());
+ if (!host)
+ return emitOpError() << "expects host module as the first nested op";
+ if (failed(checkOffloadKind(host, cir::OffloadKind::Host)))
+ return failure();
+
+ unsigned numDevices = 0;
+ auto it = body.begin();
+ for (++it; it != body.end(); ++it) {
+ auto module = mlir::dyn_cast<mlir::ModuleOp>(*it);
+ if (!module)
+ return emitOpError() << "expects only nested builtin.module ops";
+ if (failed(checkOffloadKind(module, cir::OffloadKind::Device)))
+ return failure();
+ ++numDevices;
+ }
+
+ if (numDevices == 0)
+ return emitOpError() << "expects at least one device module";
+ return success();
+}
+
//===----------------------------------------------------------------------===//
// FuncOp
//===----------------------------------------------------------------------===//
diff --git a/clang/test/CIR/IR/invalid-offload-container.cir b/clang/test/CIR/IR/invalid-offload-container.cir
new file mode 100644
index 0000000000000..219442f39674a
--- /dev/null
+++ b/clang/test/CIR/IR/invalid-offload-container.cir
@@ -0,0 +1,54 @@
+// RUN: cir-opt %s -verify-diagnostics -split-input-file
+
+module {
+ cir.offload.container {
+ builtin.module @device_0 attributes {cir.offload.kind = #cir.offload_kind<device>} { // expected-error {{expects 'cir.offload.kind' value 'host'}}
+ }
+ }
+}
+
+// -----
+
+module {
+ cir.offload.container {
+ builtin.module @host_0 attributes {cir.offload.kind = #cir.offload_kind<host>} {
+ }
+ builtin.module @host_1 attributes {cir.offload.kind = #cir.offload_kind<host>} { // expected-error {{expects 'cir.offload.kind' value 'device'}}
+ }
+ }
+}
+
+// -----
+
+module {
+ cir.offload.container {
+ builtin.module @host { // expected-error {{expects 'cir.offload.kind' offload kind attribute}}
+ }
+ }
+}
+
+// -----
+
+module {
+ cir.offload.container { // expected-error {{expects only nested builtin.module ops}}
+ builtin.module @host attributes {cir.offload.kind = #cir.offload_kind<host>} {
+ }
+ cir.const #cir.int<0> : !cir.int<s, 32>
+ }
+}
+
+// -----
+
+module {
+ cir.offload.container { // expected-error {{expects at least one device module}}
+ builtin.module @host attributes {cir.offload.kind = #cir.offload_kind<host>} {
+ }
+ }
+}
+
+// -----
+
+module {
+ cir.offload.container { // expected-error {{expects host module as the first nested op}}
+ }
+}
diff --git a/clang/test/CIR/IR/offload-container.cir b/clang/test/CIR/IR/offload-container.cir
new file mode 100644
index 0000000000000..b7b53aa973ce5
--- /dev/null
+++ b/clang/test/CIR/IR/offload-container.cir
@@ -0,0 +1,32 @@
+// RUN: cir-opt %s -split-input-file --verify-roundtrip | FileCheck %s
+
+module {
+ cir.offload.container {
+ builtin.module @host attributes {cir.offload.kind = #cir.offload_kind<host>} {
+ }
+ builtin.module @device_0 attributes {cir.offload.kind = #cir.offload_kind<device>} {
+ }
+ }
+}
+
+// CHECK: cir.offload.container {
+// CHECK: builtin.module @host attributes {cir.offload.kind = #cir.offload_kind<host>} {
+// CHECK: builtin.module @device_0 attributes {cir.offload.kind = #cir.offload_kind<device>} {
+
+// -----
+
+module {
+ cir.offload.container {
+ builtin.module @host attributes {cir.offload.kind = #cir.offload_kind<host>} {
+ }
+ builtin.module @device_0 attributes {cir.offload.kind = #cir.offload_kind<device>} {
+ }
+ builtin.module @device_1 attributes {cir.offload.kind = #cir.offload_kind<device>} {
+ }
+ }
+}
+
+// CHECK: cir.offload.container {
+// CHECK: builtin.module @host attributes {cir.offload.kind = #cir.offload_kind<host>} {
+// CHECK: builtin.module @device_0 attributes {cir.offload.kind = #cir.offload_kind<device>} {
+// CHECK: builtin.module @device_1 attributes {cir.offload.kind = #cir.offload_kind<device>} {
>From 999b3bb1a5f14302ee224dc073863035bec829be Mon Sep 17 00:00:00 2001
From: David Rivera <davidriverg at gmail.com>
Date: Mon, 29 Jun 2026 16:00:27 -0400
Subject: [PATCH 2/2] fix fmt
---
clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 96501d5808168..1c1924099db7d 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -17,8 +17,8 @@
#include "clang/CIR/Dialect/IR/CIRTypes.h"
#include "mlir/IR/Attributes.h"
-#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/BuiltinOps.h"
+#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/DialectImplementation.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/IR/Value.h"
More information about the cfe-commits
mailing list