[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