[Mlir-commits] [mlir] [mlir][SPIR-V] Add LifetimeStart and LifetimeStop operations (PR #232191)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Wed Oct 14 23:18:04 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir
Author: Alexander Shaposhnikov (alexander-shaposhnikov)
<details>
<summary>Changes</summary>
Add spirv.LifetimeStart and spirv.LifetimeStop with assembly syntax, verification, and generated binary serialization and deserialization. Both operations require function scope and the Kernel capability.
---
Full diff: https://github.com/llvm/llvm-project/pull/232191.diff
5 Files Affected:
- (modified) mlir/include/mlir/Dialect/SPIRV/IR/SPIRVBase.td (+4-1)
- (modified) mlir/include/mlir/Dialect/SPIRV/IR/SPIRVControlFlowOps.td (+88)
- (modified) mlir/lib/Dialect/SPIRV/IR/ControlFlowOps.cpp (+20)
- (modified) mlir/test/Dialect/SPIRV/IR/control-flow-ops.mlir (+72)
- (added) mlir/test/Target/SPIRV/lifetime.mlir (+23)
``````````diff
diff --git a/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVBase.td b/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVBase.td
index 7e799fc019107..54934572860b9 100644
--- a/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVBase.td
+++ b/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVBase.td
@@ -4679,6 +4679,8 @@ def SPIRV_OC_OpKill : I32EnumAttrCase<"OpKill", 252>;
def SPIRV_OC_OpReturn : I32EnumAttrCase<"OpReturn", 253>;
def SPIRV_OC_OpReturnValue : I32EnumAttrCase<"OpReturnValue", 254>;
def SPIRV_OC_OpUnreachable : I32EnumAttrCase<"OpUnreachable", 255>;
+def SPIRV_OC_OpLifetimeStart : I32EnumAttrCase<"OpLifetimeStart", 256>;
+def SPIRV_OC_OpLifetimeStop : I32EnumAttrCase<"OpLifetimeStop", 257>;
def SPIRV_OC_OpGroupBroadcast : I32EnumAttrCase<"OpGroupBroadcast", 263>;
def SPIRV_OC_OpGroupIAdd : I32EnumAttrCase<"OpGroupIAdd", 264>;
def SPIRV_OC_OpGroupFAdd : I32EnumAttrCase<"OpGroupFAdd", 265>;
@@ -4846,7 +4848,8 @@ def SPIRV_OpcodeAttr :
SPIRV_OC_OpPhi, SPIRV_OC_OpLoopMerge, SPIRV_OC_OpSelectionMerge,
SPIRV_OC_OpLabel, SPIRV_OC_OpBranch, SPIRV_OC_OpBranchConditional,
SPIRV_OC_OpSwitch, SPIRV_OC_OpKill, SPIRV_OC_OpReturn, SPIRV_OC_OpReturnValue,
- SPIRV_OC_OpUnreachable, SPIRV_OC_OpGroupBroadcast, SPIRV_OC_OpGroupIAdd,
+ SPIRV_OC_OpUnreachable, SPIRV_OC_OpLifetimeStart, SPIRV_OC_OpLifetimeStop,
+ SPIRV_OC_OpGroupBroadcast, SPIRV_OC_OpGroupIAdd,
SPIRV_OC_OpGroupFAdd, SPIRV_OC_OpGroupFMin, SPIRV_OC_OpGroupUMin,
SPIRV_OC_OpGroupSMin, SPIRV_OC_OpGroupFMax, SPIRV_OC_OpGroupUMax,
SPIRV_OC_OpGroupSMax, SPIRV_OC_OpNoLine, SPIRV_OC_OpModuleProcessed,
diff --git a/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVControlFlowOps.td b/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVControlFlowOps.td
index bc2bad2d24dbb..b29472271c1ec 100644
--- a/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVControlFlowOps.td
+++ b/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVControlFlowOps.td
@@ -395,6 +395,94 @@ def SPIRV_KillOp : SPIRV_Op<"Kill", [Terminator]> {
// -----
+def SPIRV_LifetimeStartOp : SPIRV_Op<"LifetimeStart", [InFunctionScope]> {
+ let summary = "Declare that an object was not defined before this instruction.";
+
+ let description = [{
+ Pointer is a pointer to the object whose lifetime is starting. Its type
+ must be an OpTypePointer with Storage Class Function.
+
+ Size is an unsigned 32-bit integer. Size must be 0 if Pointer is a
+ pointer to a non-void type or the Addresses capability is not declared.
+ If Size is non-zero, it is the number of bytes of memory whose lifetime
+ is starting.
+
+ <!-- End of AutoGen section -->
+
+ Only zero sizes are currently supported because SPIR-V dialect pointers
+ cannot point to void.
+
+ #### Example:
+
+ ```mlir
+ %0 = spirv.Variable : !spirv.ptr<f32, Function>
+ spirv.LifetimeStart %0, 0 : !spirv.ptr<f32, Function>
+ ```
+ }];
+
+ let availability = [
+ MinVersion<SPIRV_V_1_0>,
+ MaxVersion<SPIRV_V_1_6>,
+ Extension<[]>,
+ Capability<[SPIRV_C_Kernel]>
+ ];
+
+ let arguments = (ins
+ SPIRV_AnyPtr:$pointer,
+ I32Attr:$size
+ );
+
+ let results = (outs);
+
+ let assemblyFormat = "$pointer `,` $size attr-dict `:` type($pointer)";
+}
+
+// -----
+
+def SPIRV_LifetimeStopOp : SPIRV_Op<"LifetimeStop", [InFunctionScope]> {
+ let summary = "Declare that an object is dead after this instruction.";
+
+ let description = [{
+ Pointer is a pointer to the object whose lifetime is ending. Its type
+ must be an OpTypePointer with Storage Class Function.
+
+ Size is an unsigned 32-bit integer. Size must be 0 if Pointer is a
+ pointer to a non-void type or the Addresses capability is not declared.
+ If Size is non-zero, it is the number of bytes of memory whose lifetime
+ is ending.
+
+ <!-- End of AutoGen section -->
+
+ Only zero sizes are currently supported because SPIR-V dialect pointers
+ cannot point to void.
+
+ #### Example:
+
+ ```mlir
+ %0 = spirv.Variable : !spirv.ptr<f32, Function>
+ spirv.LifetimeStop %0, 0 : !spirv.ptr<f32, Function>
+ ```
+ }];
+
+ let availability = [
+ MinVersion<SPIRV_V_1_0>,
+ MaxVersion<SPIRV_V_1_6>,
+ Extension<[]>,
+ Capability<[SPIRV_C_Kernel]>
+ ];
+
+ let arguments = (ins
+ SPIRV_AnyPtr:$pointer,
+ I32Attr:$size
+ );
+
+ let results = (outs);
+
+ let assemblyFormat = "$pointer `,` $size attr-dict `:` type($pointer)";
+}
+
+// -----
+
def SPIRV_LoopOp : SPIRV_Op<"mlir.loop", [InFunctionScope]> {
let summary = "Define a structured loop.";
diff --git a/mlir/lib/Dialect/SPIRV/IR/ControlFlowOps.cpp b/mlir/lib/Dialect/SPIRV/IR/ControlFlowOps.cpp
index 44cee60decdbd..685a2bcc71169 100644
--- a/mlir/lib/Dialect/SPIRV/IR/ControlFlowOps.cpp
+++ b/mlir/lib/Dialect/SPIRV/IR/ControlFlowOps.cpp
@@ -304,6 +304,26 @@ Block *SwitchOp::getSuccessorForOperands(ArrayRef<Attribute> operands) {
return nullptr;
}
+//===----------------------------------------------------------------------===//
+// spirv.LifetimeStart / spirv.LifetimeStop
+//===----------------------------------------------------------------------===//
+
+template <typename LifetimeOpTy>
+static LogicalResult verifyLifetimeOp(LifetimeOpTy op) {
+ auto ptrType = cast<spirv::PointerType>(op.getPointer().getType());
+ if (ptrType.getStorageClass() != spirv::StorageClass::Function)
+ return op.emitOpError("pointer must have the Function storage class");
+
+ if (op.getSize() != 0)
+ return op.emitOpError("size must be 0 for a pointer to a non-void type");
+
+ return success();
+}
+
+LogicalResult LifetimeStartOp::verify() { return verifyLifetimeOp(*this); }
+
+LogicalResult LifetimeStopOp::verify() { return verifyLifetimeOp(*this); }
+
//===----------------------------------------------------------------------===//
// spirv.mlir.loop
//===----------------------------------------------------------------------===//
diff --git a/mlir/test/Dialect/SPIRV/IR/control-flow-ops.mlir b/mlir/test/Dialect/SPIRV/IR/control-flow-ops.mlir
index 374d6ca134de5..4259e66615035 100644
--- a/mlir/test/Dialect/SPIRV/IR/control-flow-ops.mlir
+++ b/mlir/test/Dialect/SPIRV/IR/control-flow-ops.mlir
@@ -1080,6 +1080,78 @@ func.func @kill() {
// -----
+//===----------------------------------------------------------------------===//
+// spirv.LifetimeStart / spirv.LifetimeStop
+//===----------------------------------------------------------------------===//
+
+// CHECK-LABEL: func @lifetime
+func.func @lifetime() {
+ %0 = spirv.Variable : !spirv.ptr<f32, Function>
+ // CHECK: spirv.LifetimeStart %{{.*}}, 0 : !spirv.ptr<f32, Function>
+ spirv.LifetimeStart %0, 0 : !spirv.ptr<f32, Function>
+ // CHECK: spirv.LifetimeStop %{{.*}}, 0 : !spirv.ptr<f32, Function>
+ spirv.LifetimeStop %0, 0 : !spirv.ptr<f32, Function>
+
+ %1 = spirv.Bitcast %0 : !spirv.ptr<f32, Function> to !spirv.ptr<i8, Function>
+ // CHECK: spirv.LifetimeStart %{{.*}}, 0 : !spirv.ptr<i8, Function>
+ spirv.LifetimeStart %1, 0 : !spirv.ptr<i8, Function>
+ // CHECK: spirv.LifetimeStop %{{.*}}, 0 : !spirv.ptr<i8, Function>
+ spirv.LifetimeStop %1, 0 : !spirv.ptr<i8, Function>
+ spirv.Return
+}
+
+// -----
+
+func.func @lifetime_start_wrong_storage_class(%arg0: !spirv.ptr<f32, CrossWorkgroup>) {
+ // expected-error @+1 {{pointer must have the Function storage class}}
+ spirv.LifetimeStart %arg0, 0 : !spirv.ptr<f32, CrossWorkgroup>
+ spirv.Return
+}
+
+// -----
+
+func.func @lifetime_stop_wrong_storage_class(%arg0: !spirv.ptr<f32, CrossWorkgroup>) {
+ // expected-error @+1 {{pointer must have the Function storage class}}
+ spirv.LifetimeStop %arg0, 0 : !spirv.ptr<f32, CrossWorkgroup>
+ spirv.Return
+}
+
+// -----
+
+func.func @lifetime_start_size_on_typed_pointer() {
+ %0 = spirv.Variable : !spirv.ptr<f32, Function>
+ // expected-error @+1 {{size must be 0 for a pointer to a non-void type}}
+ spirv.LifetimeStart %0, 4 : !spirv.ptr<f32, Function>
+ spirv.Return
+}
+
+// -----
+
+func.func @lifetime_stop_size_on_typed_pointer() {
+ %0 = spirv.Variable : !spirv.ptr<f32, Function>
+ // expected-error @+1 {{size must be 0 for a pointer to a non-void type}}
+ spirv.LifetimeStop %0, 4 : !spirv.ptr<f32, Function>
+ spirv.Return
+}
+
+// -----
+
+func.func @lifetime_start_size_on_byte_pointer(%ptr: !spirv.ptr<i8, Function>) {
+ // expected-error @+1 {{size must be 0 for a pointer to a non-void type}}
+ spirv.LifetimeStart %ptr, 4 : !spirv.ptr<i8, Function>
+ spirv.Return
+}
+
+// -----
+
+func.func @lifetime_stop_size_on_byte_pointer(%ptr: !spirv.ptr<i8, Function>) {
+ // expected-error @+1 {{size must be 0 for a pointer to a non-void type}}
+ spirv.LifetimeStop %ptr, 4 : !spirv.ptr<i8, Function>
+ spirv.Return
+}
+
+// -----
+
//===----------------------------------------------------------------------===//
// spirv.Switch
//===----------------------------------------------------------------------===//
diff --git a/mlir/test/Target/SPIRV/lifetime.mlir b/mlir/test/Target/SPIRV/lifetime.mlir
new file mode 100644
index 0000000000000..cbb0c9b69d3c8
--- /dev/null
+++ b/mlir/test/Target/SPIRV/lifetime.mlir
@@ -0,0 +1,23 @@
+// RUN: mlir-translate -no-implicit-module -test-spirv-roundtrip %s | FileCheck %s
+
+// RUN: %if spirv-tools %{ rm -rf %t %}
+// RUN: %if spirv-tools %{ mkdir %t %}
+// RUN: %if spirv-tools %{ mlir-translate --no-implicit-module --serialize-spirv --split-input-file --spirv-save-validation-files-with-prefix=%t/module %s %}
+// RUN: %if spirv-tools %{ spirv-val %t %}
+
+spirv.module Physical64 OpenCL requires #spirv.vce<v1.0, [Kernel, Addresses, Int8, Linkage], []> {
+ spirv.func @lifetime() "None" {
+ %0 = spirv.Variable : !spirv.ptr<f32, Function>
+ // CHECK: spirv.LifetimeStart %{{.*}}, 0 : !spirv.ptr<f32, Function>
+ spirv.LifetimeStart %0, 0 : !spirv.ptr<f32, Function>
+ // CHECK: spirv.LifetimeStop %{{.*}}, 0 : !spirv.ptr<f32, Function>
+ spirv.LifetimeStop %0, 0 : !spirv.ptr<f32, Function>
+
+ %1 = spirv.Bitcast %0 : !spirv.ptr<f32, Function> to !spirv.ptr<i8, Function>
+ // CHECK: spirv.LifetimeStart %{{.*}}, 0 : !spirv.ptr<i8, Function>
+ spirv.LifetimeStart %1, 0 : !spirv.ptr<i8, Function>
+ // CHECK: spirv.LifetimeStop %{{.*}}, 0 : !spirv.ptr<i8, Function>
+ spirv.LifetimeStop %1, 0 : !spirv.ptr<i8, Function>
+ spirv.Return
+ }
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/232191
More information about the Mlir-commits
mailing list