https://github.com/RiverDave updated https://github.com/llvm/llvm-project/pull/206576
>From be1086b6453cc16402913c60f145a39abe7b3630 Mon Sep 17 00:00:00 2001 From: David Rivera <[email protected]> Date: Mon, 29 Jun 2026 15:46:11 -0400 Subject: [PATCH 1/5] [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 9ea186489ac65..00c8cfe008408 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td @@ -53,6 +53,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 6d149e6b1ccaa..158b97adfbe4e 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -6141,6 +6141,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 606b4fd9d3fa6..b36424aa31ded 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" @@ -2374,6 +2375,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 c728fb4c32ae57382a1f8903bed2a6ef1b20ff8d Mon Sep 17 00:00:00 2001 From: David Rivera <[email protected]> Date: Mon, 29 Jun 2026 16:00:27 -0400 Subject: [PATCH 2/5] 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 b36424aa31ded..5415e9285801d 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" >From 4054988194f0af15917f36936cbd6f686b8dcaf4 Mon Sep 17 00:00:00 2001 From: David Rivera <[email protected]> Date: Fri, 14 Aug 2026 08:11:59 -0400 Subject: [PATCH 3/5] [CIR] Drop `cir.offload.container` and represent offload modules through a unit market on MLIR modules. --- .../include/clang/CIR/Dialect/IR/CIRAttrs.td | 42 +++++ .../include/clang/CIR/Dialect/IR/CIRDialect.h | 13 ++ .../clang/CIR/Dialect/IR/CIRDialect.td | 3 + clang/include/clang/CIR/Dialect/IR/CIROps.td | 59 ------- clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 166 +++++++++++------- .../test/CIR/IR/invalid-offload-container.cir | 70 +++++--- clang/test/CIR/IR/offload-container.cir | 38 ++-- 7 files changed, 229 insertions(+), 162 deletions(-) diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td index 838d8975177fd..691b0c311389d 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td @@ -115,6 +115,48 @@ def CIR_SourceLanguageAttr : CIR_EnumAttr<CIR_SourceLanguage, "lang"> { }]; } +//===----------------------------------------------------------------------===// +// OffloadKindAttr +//===----------------------------------------------------------------------===// + +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 of a module in an offload container"; + let description = [{ + Marks a module as either the host module or one of the device modules of + an offload container, keeping a host CIR module and its associated device + CIR modules in one IR unit while offload merge/split pipeline passes need + visibility into both sides. + + A module carrying the `cir.offload.container` unit attribute is such a + container. Its body holds only nested modules: the first one is the host + module and must carry `cir.offload.kind = #cir.offload_kind<host>`, and + all remaining ones are device modules and must carry + `cir.offload.kind = #cir.offload_kind<device>`. There must be at least one + device module. Keeping the host module first gives later passes a simple + convention for finding the host side while iterating the remaining device + modules. + + Example: + ```mlir + module attributes {cir.offload.container} { + module @host attributes {cir.offload.kind = #cir.offload_kind<host>} {} + module @device attributes {cir.offload.kind = #cir.offload_kind<device>} {} + } + ``` + + The attribute names `cir.offload.container` and `cir.offload.kind` are + defined by the `getOffloadContainerAttrName` and `getOffloadKindAttrName` + methods in the CIRDialect class. + }]; +} + //===----------------------------------------------------------------------===// // ArgPassingKind + RecordLayoutAttr //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.h b/clang/include/clang/CIR/Dialect/IR/CIRDialect.h index 2f1ef5b6cb9e0..c61bd414036cb 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.h +++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.h @@ -88,6 +88,19 @@ RecordLayoutAttr getRecordLayout(mlir::ModuleOp mod, mlir::StringAttr name); /// Same lookup as getRecordLayout, but returns a null attribute instead of /// asserting when the record has no layout entry. RecordLayoutAttr tryGetRecordLayout(mlir::ModuleOp mod, mlir::StringAttr name); +RecordLayoutAttr getRecordLayout(mlir::ModuleOp module, mlir::StringAttr name); + +/// Returns whether the module is an offload container, i.e. whether it carries +/// the cir.offload.container unit attribute. See CIR_OffloadKindAttr for the +/// structure such a module is required to have. +bool isOffloadContainer(mlir::ModuleOp module); + +/// Returns the host module of an offload container. +mlir::ModuleOp getOffloadHostModule(mlir::ModuleOp container); + +/// Returns the device modules of an offload container, in container order. +llvm::iterator_range<mlir::Block::op_iterator<mlir::ModuleOp>> +getOffloadDeviceModules(mlir::ModuleOp container); } // namespace cir // TableGen'erated files for MLIR dialects require that a macro be defined when diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td index 00c8cfe008408..7c5c2d20bbaf4 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td @@ -26,6 +26,8 @@ def CIR_Dialect : Dialect { let useDefaultAttributePrinterParser = 1; + let hasOperationAttrVerify = 1; + // Enable constant materialization for the CIR dialect. This generates a // declaration for the cir::CIRDialect::materializeConstant function. This // hook is necessary for canonicalization to properly handle attributes @@ -53,6 +55,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 getOffloadContainerAttrName() { return "cir.offload.container"; } static llvm::StringRef getOffloadKindAttrName() { return "cir.offload.kind"; } static llvm::StringRef getOperandSegmentSizesAttrName() { return "operandSegmentSizes"; } static llvm::StringRef getNoCallerSavedRegsAttrName() { return "no_caller_saved_registers"; } diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 158b97adfbe4e..6d149e6b1ccaa 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -6141,65 +6141,6 @@ 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 5415e9285801d..45e3f011e6269 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -111,6 +111,112 @@ Operation *cir::CIRDialect::materializeConstant(mlir::OpBuilder &builder, mlir::cast<mlir::TypedAttr>(value)); } +//===----------------------------------------------------------------------===// +// Offload container helpers +//===----------------------------------------------------------------------===// + +bool cir::isOffloadContainer(mlir::ModuleOp module) { + return module->hasAttr(cir::CIRDialect::getOffloadContainerAttrName()); +} + +mlir::ModuleOp cir::getOffloadHostModule(mlir::ModuleOp container) { + assert(isOffloadContainer(container) && "expected an offload container"); + return mlir::cast<mlir::ModuleOp>(container.getBody()->front()); +} + +llvm::iterator_range<mlir::Block::op_iterator<mlir::ModuleOp>> +cir::getOffloadDeviceModules(mlir::ModuleOp container) { + assert(isOffloadContainer(container) && "expected an offload container"); + mlir::Block &body = *container.getBody(); + auto begin = body.op_begin<mlir::ModuleOp>(); + auto end = body.op_end<mlir::ModuleOp>(); + if (begin != end) + ++begin; + return {begin, end}; +} + +//===----------------------------------------------------------------------===// +// Dialect attribute verification +//===----------------------------------------------------------------------===// + +static LogicalResult verifyOffloadKind(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(); +} + +// A module marked with `cir.offload.container` holds the host module followed +// by one or more device modules, each tagged with `cir.offload.kind`. Keeping +// the host module first gives later offload passes a simple convention for +// finding the host side while iterating the remaining device modules. +static LogicalResult verifyOffloadContainer(mlir::Operation *op) { + auto container = mlir::dyn_cast<mlir::ModuleOp>(op); + if (!container) + return op->emitError() << "expects '" + << cir::CIRDialect::getOffloadContainerAttrName() + << "' attribute to be attached to '" + << mlir::ModuleOp::getOperationName() << "'"; + + mlir::Block &body = *container.getBody(); + if (body.empty()) + return container.emitOpError() + << "expects host module as the first nested op"; + + auto host = mlir::dyn_cast<mlir::ModuleOp>(body.front()); + if (!host) + return container.emitOpError() + << "expects host module as the first nested op"; + if (failed(verifyOffloadKind(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 container.emitOpError() + << "expects only nested builtin.module ops"; + if (failed(verifyOffloadKind(module, cir::OffloadKind::Device))) + return failure(); + ++numDevices; + } + + if (numDevices == 0) + return container.emitOpError() << "expects at least one device module"; + return success(); +} + +LogicalResult +cir::CIRDialect::verifyOperationAttribute(mlir::Operation *op, + mlir::NamedAttribute attr) { + if (attr.getName() == getOffloadContainerAttrName()) { + if (!mlir::isa<mlir::UnitAttr>(attr.getValue())) + return op->emitError() << "expects '" << getOffloadContainerAttrName() + << "' to be a unit attribute"; + return verifyOffloadContainer(op); + } + + // The container verifier owns the structural contract between a container + // and the modules it holds. All this can add is that the kind attribute + // never lands on something that is not a module. + if (attr.getName() == getOffloadKindAttrName() && + !mlir::isa<mlir::ModuleOp>(op)) + return op->emitError() << "expects '" << getOffloadKindAttrName() + << "' attribute to be attached to '" + << mlir::ModuleOp::getOperationName() << "'"; + + return success(); +} + //===----------------------------------------------------------------------===// // Helpers //===----------------------------------------------------------------------===// @@ -2375,66 +2481,6 @@ 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 index 219442f39674a..92d4213765489 100644 --- a/clang/test/CIR/IR/invalid-offload-container.cir +++ b/clang/test/CIR/IR/invalid-offload-container.cir @@ -1,54 +1,80 @@ // 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 attributes {cir.offload.container} { + 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 attributes {cir.offload.container} { + module @host_0 attributes {cir.offload.kind = #cir.offload_kind<host>} { + } + 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 attributes {cir.offload.container} { + module @host { // expected-error {{expects 'cir.offload.kind' offload kind attribute}} + } +} + +// ----- + +// expected-error@+1 {{expects only nested builtin.module ops}} +module attributes {cir.offload.container} { + module @host attributes {cir.offload.kind = #cir.offload_kind<host>} { + } + cir.func @f() { + cir.return + } +} + +// ----- + +// expected-error@+1 {{expects at least one device module}} +module attributes {cir.offload.container} { + module @host attributes {cir.offload.kind = #cir.offload_kind<host>} { } } // ----- 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> + // The op below keeps the CIR dialect loaded, which is what makes the + // dialect attribute verifier run on the empty container module. + cir.func @f() { + cir.return + } + // expected-error@+1 {{expects host module as the first nested op}} + module @container attributes {cir.offload.container} { } } // ----- module { - cir.offload.container { // expected-error {{expects at least one device module}} - builtin.module @host attributes {cir.offload.kind = #cir.offload_kind<host>} { - } + cir.func @f() { + cir.return {cir.offload.container} // expected-error {{expects 'cir.offload.container' attribute to be attached to 'builtin.module'}} } } // ----- module { - cir.offload.container { // expected-error {{expects host module as the first nested op}} + cir.func @f() { + cir.return {cir.offload.kind = #cir.offload_kind<host>} // expected-error {{expects 'cir.offload.kind' attribute to be attached to 'builtin.module'}} + } +} + +// ----- + +// expected-error@+1 {{expects 'cir.offload.container' to be a unit attribute}} +module attributes {cir.offload.container = 0 : i32} { + module @host attributes {cir.offload.kind = #cir.offload_kind<host>} { + } + module @device_0 attributes {cir.offload.kind = #cir.offload_kind<device>} { } } diff --git a/clang/test/CIR/IR/offload-container.cir b/clang/test/CIR/IR/offload-container.cir index b7b53aa973ce5..f1e00bc63fed7 100644 --- a/clang/test/CIR/IR/offload-container.cir +++ b/clang/test/CIR/IR/offload-container.cir @@ -1,32 +1,28 @@ // 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>} { - } +module attributes {cir.offload.container} { + module @host attributes {cir.offload.kind = #cir.offload_kind<host>} { + } + 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>} { +// CHECK: module attributes {cir.offload.container} { +// CHECK: module @host attributes {cir.offload.kind = #cir.offload_kind<host>} { +// CHECK: 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>} { - } +module attributes {cir.offload.container} { + module @host attributes {cir.offload.kind = #cir.offload_kind<host>} { + } + module @device_0 attributes {cir.offload.kind = #cir.offload_kind<device>} { + } + 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>} { +// CHECK: module attributes {cir.offload.container} { +// CHECK: module @host attributes {cir.offload.kind = #cir.offload_kind<host>} { +// CHECK: module @device_0 attributes {cir.offload.kind = #cir.offload_kind<device>} { +// CHECK: module @device_1 attributes {cir.offload.kind = #cir.offload_kind<device>} { >From 1b10091e99962aca0245127138d9133e48382a17 Mon Sep 17 00:00:00 2001 From: David Rivera <[email protected]> Date: Wed, 2 Sep 2026 01:32:56 -0400 Subject: [PATCH 4/5] address various coments --- .../include/clang/CIR/Dialect/IR/CIRAttrs.td | 32 +++++++++-------- .../include/clang/CIR/Dialect/IR/CIRDialect.h | 13 ------- clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 35 +++---------------- 3 files changed, 23 insertions(+), 57 deletions(-) diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td index 691b0c311389d..cadb8d0fa1f84 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td @@ -129,28 +129,32 @@ def CIR_OffloadKind : CIR_I32EnumAttr<"OffloadKind", "offload kind", [ def CIR_OffloadKindAttr : CIR_EnumAttr<CIR_OffloadKind, "offload_kind"> { let summary = "Offload kind of a module in an offload container"; let description = [{ - Marks a module as either the host module or one of the device modules of - an offload container, keeping a host CIR module and its associated device - CIR modules in one IR unit while offload merge/split pipeline passes need - visibility into both sides. - - A module carrying the `cir.offload.container` unit attribute is such a - container. Its body holds only nested modules: the first one is the host - module and must carry `cir.offload.kind = #cir.offload_kind<host>`, and - all remaining ones are device modules and must carry - `cir.offload.kind = #cir.offload_kind<device>`. There must be at least one - device module. Keeping the host module first gives later passes a simple - convention for finding the host side while iterating the remaining device - modules. + Marks a module nested inside an offload container - identified by the + `cir.offload.container` unit attribute - as either the host module (carrying + `cir.offload.kind = #cir.offload_kind<host>`) or one of the device modules + (carrying `cir.offload.kind = #cir.offload_kind<device>`) of the offload + container. This creates a tiered module layout, where a singular offload + container contains a single host module and one or more device modules, + granting the offload merge/split pipeline passes the cross-boundary view. Example: ```mlir module attributes {cir.offload.container} { module @host attributes {cir.offload.kind = #cir.offload_kind<host>} {} - module @device attributes {cir.offload.kind = #cir.offload_kind<device>} {} + module @device1 attributes {cir.offload.kind = #cir.offload_kind<device>} {} + module @device2 attributes {cir.offload.kind = #cir.offload_kind<device>} {} } ``` + Note: In order to simplify offload passes, the offload container module + requires that the first module inside it is the host module and the rest + of the modules are device modules. + + The nested modules are plain `builtin.module` ops rather than a dedicated + container op, so that the existing module infrastructure applies to them + unchanged; the layout above is enforced by the CIR dialect attribute + verifier, following the `gpu.container_module` precedent. + The attribute names `cir.offload.container` and `cir.offload.kind` are defined by the `getOffloadContainerAttrName` and `getOffloadKindAttrName` methods in the CIRDialect class. diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.h b/clang/include/clang/CIR/Dialect/IR/CIRDialect.h index c61bd414036cb..2f1ef5b6cb9e0 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.h +++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.h @@ -88,19 +88,6 @@ RecordLayoutAttr getRecordLayout(mlir::ModuleOp mod, mlir::StringAttr name); /// Same lookup as getRecordLayout, but returns a null attribute instead of /// asserting when the record has no layout entry. RecordLayoutAttr tryGetRecordLayout(mlir::ModuleOp mod, mlir::StringAttr name); -RecordLayoutAttr getRecordLayout(mlir::ModuleOp module, mlir::StringAttr name); - -/// Returns whether the module is an offload container, i.e. whether it carries -/// the cir.offload.container unit attribute. See CIR_OffloadKindAttr for the -/// structure such a module is required to have. -bool isOffloadContainer(mlir::ModuleOp module); - -/// Returns the host module of an offload container. -mlir::ModuleOp getOffloadHostModule(mlir::ModuleOp container); - -/// Returns the device modules of an offload container, in container order. -llvm::iterator_range<mlir::Block::op_iterator<mlir::ModuleOp>> -getOffloadDeviceModules(mlir::ModuleOp container); } // namespace cir // TableGen'erated files for MLIR dialects require that a macro be defined when diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index 45e3f011e6269..16ecedd10690b 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -111,30 +111,6 @@ Operation *cir::CIRDialect::materializeConstant(mlir::OpBuilder &builder, mlir::cast<mlir::TypedAttr>(value)); } -//===----------------------------------------------------------------------===// -// Offload container helpers -//===----------------------------------------------------------------------===// - -bool cir::isOffloadContainer(mlir::ModuleOp module) { - return module->hasAttr(cir::CIRDialect::getOffloadContainerAttrName()); -} - -mlir::ModuleOp cir::getOffloadHostModule(mlir::ModuleOp container) { - assert(isOffloadContainer(container) && "expected an offload container"); - return mlir::cast<mlir::ModuleOp>(container.getBody()->front()); -} - -llvm::iterator_range<mlir::Block::op_iterator<mlir::ModuleOp>> -cir::getOffloadDeviceModules(mlir::ModuleOp container) { - assert(isOffloadContainer(container) && "expected an offload container"); - mlir::Block &body = *container.getBody(); - auto begin = body.op_begin<mlir::ModuleOp>(); - auto end = body.op_end<mlir::ModuleOp>(); - if (begin != end) - ++begin; - return {begin, end}; -} - //===----------------------------------------------------------------------===// // Dialect attribute verification //===----------------------------------------------------------------------===// @@ -178,20 +154,19 @@ static LogicalResult verifyOffloadContainer(mlir::Operation *op) { if (failed(verifyOffloadKind(host, cir::OffloadKind::Host))) return failure(); - unsigned numDevices = 0; auto it = body.begin(); - for (++it; it != body.end(); ++it) { + ++it; + if (it == body.end()) + return container.emitOpError() << "expects at least one device module"; + + for (; it != body.end(); ++it) { auto module = mlir::dyn_cast<mlir::ModuleOp>(*it); if (!module) return container.emitOpError() << "expects only nested builtin.module ops"; if (failed(verifyOffloadKind(module, cir::OffloadKind::Device))) return failure(); - ++numDevices; } - - if (numDevices == 0) - return container.emitOpError() << "expects at least one device module"; return success(); } >From e2cf561b191db9e4fead2cff042dfeaecbd0dc5d Mon Sep 17 00:00:00 2001 From: David Rivera <[email protected]> Date: Mon, 7 Sep 2026 16:55:27 -0400 Subject: [PATCH 5/5] [CIR] Migrate CIR_OffloadKind off the legacy EnumAttrInfo hierarchy CIR_I32EnumAttr and its genSpecializedAttr flag were removed by the upstream enum migration (#220889); CIR_OffloadKind now derives from CIR_I32Enum like the other CIR enums. --- clang/include/clang/CIR/Dialect/IR/CIRAttrs.td | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td index cadb8d0fa1f84..ac6f2718d2fcd 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td @@ -119,12 +119,10 @@ def CIR_SourceLanguageAttr : CIR_EnumAttr<CIR_SourceLanguage, "lang"> { // OffloadKindAttr //===----------------------------------------------------------------------===// -def CIR_OffloadKind : CIR_I32EnumAttr<"OffloadKind", "offload kind", [ +def CIR_OffloadKind : CIR_I32Enum<"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 of a module in an offload container"; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
