[llvm] [SPIR-V] Emit variable pointer capability for resources (PR #225471)

Helena Kotas via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 22 12:49:38 PDT 2026


https://github.com/hekota updated https://github.com/llvm/llvm-project/pull/225471

>From 9d2bfb4c2527e12f79f0f84dc53a296d70772667 Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Tue, 22 Sep 2026 11:08:18 -0700
Subject: [PATCH 1/5] [SPIR-V] Emit variable pointer capability for resources

Co-authored-by: Copilot App <223556219+Copilot at users.noreply.github.com>
---
 .../Target/SPIRV/SPIRVInstructionSelector.cpp |  7 +++++
 llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp |  5 ++++
 .../variable-pointers-storage-buffer.ll       | 28 +++++++++++++++++++
 3 files changed, 40 insertions(+)
 create mode 100644 llvm/test/CodeGen/SPIRV/pointers/variable-pointers-storage-buffer.ll

diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index 0ea9e552ce2e3a..c532307ffa2860 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -7645,6 +7645,13 @@ bool SPIRVInstructionSelector::loadHandleBeforePosition(
   if (IsStructuredBuffer) {
     VarType = GR.getPointeeType(ResType);
     SC = GR.getPointerStorageClass(ResType);
+    if (SC == SPIRV::StorageClass::StorageBuffer) {
+      if (!STI.isAtLeastSPIRVVer(VersionTuple(1, 3)))
+        MIRBuilder.buildInstr(SPIRV::OpExtension)
+            .addImm(SPIRV::Extension::SPV_KHR_variable_pointers);
+      MIRBuilder.buildInstr(SPIRV::OpCapability)
+          .addImm(SPIRV::Capability::VariablePointersStorageBuffer);
+    }
   }
 
   // ArraySize 0 means an unbounded array and we need to set to required
diff --git a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
index 39918b6610fce1..b54de9ba6ca215 100644
--- a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
@@ -1052,6 +1052,11 @@ void RequirementHandler::initAvailableCapabilitiesForVulkan(
                     Capability::StorageImageMultisample,
                     Capability::ImageMSArray});
 
+  if (ST.isAtLeastSPIRVVer(VersionTuple(1, 3)) ||
+      ST.canUseExtension(Extension::SPV_KHR_variable_pointers))
+    addAvailableCaps({Capability::VariablePointersStorageBuffer,
+                      Capability::VariablePointers});
+
   // Became core in Vulkan 1.2
   if (ST.isAtLeastSPIRVVer(VersionTuple(1, 5))) {
     addAvailableCaps(
diff --git a/llvm/test/CodeGen/SPIRV/pointers/variable-pointers-storage-buffer.ll b/llvm/test/CodeGen/SPIRV/pointers/variable-pointers-storage-buffer.ll
new file mode 100644
index 00000000000000..c3e4c54e3a1bdb
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/pointers/variable-pointers-storage-buffer.ll
@@ -0,0 +1,28 @@
+; RUN: llc -mtriple=spirv1.6-unknown-vulkan1.3-compute %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -mtriple=spirv1.6-unknown-vulkan1.3-compute %s -o - -filetype=obj | spirv-val %}
+
+; A function-local aggregate containing a storage buffer pointer requires
+; VariablePointersStorageBuffer, even when the pointer is nested.
+
+%struct.A = type { target("spirv.VulkanBuffer", [0 x i32], 12, 1) }
+
+ at .str = private unnamed_addr constant [4 x i8] c"Buf\00", align 1
+
+define void @main() #0 {
+entry:
+  %handle = call target("spirv.VulkanBuffer", [0 x i32], 12, 1) @llvm.spv.resource.handlefrombinding(i32 0, i32 0, i32 1, i32 0, ptr @.str)
+  %a = alloca %struct.A, align 4
+  %value = insertvalue %struct.A poison, target("spirv.VulkanBuffer", [0 x i32], 12, 1) %handle, 0
+  store volatile %struct.A %value, ptr %a, align 4
+  ret void
+}
+
+; CHECK: OpCapability VariablePointersStorageBuffer
+; CHECK: %[[BUFFER:[0-9]+]] = OpTypePointer StorageBuffer
+; CHECK: %[[A:[0-9]+]] = OpTypeStruct %[[BUFFER]]
+; CHECK: %[[A_PTR:[0-9]+]] = OpTypePointer Function %[[A]]
+; CHECK: OpVariable %[[A_PTR]] Function
+
+declare target("spirv.VulkanBuffer", [0 x i32], 12, 1) @llvm.spv.resource.handlefrombinding(i32, i32, i32, i32, ptr)
+
+attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }

>From 6e61ff4b0de1347d93f49cee8585658c51040367 Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Tue, 22 Sep 2026 11:08:41 -0700
Subject: [PATCH 2/5] [SPIR-V] Normalize variable pointer test line endings

Co-authored-by: Copilot App <223556219+Copilot at users.noreply.github.com>
---
 .../variable-pointers-storage-buffer.ll       | 56 +++++++++----------
 1 file changed, 28 insertions(+), 28 deletions(-)

diff --git a/llvm/test/CodeGen/SPIRV/pointers/variable-pointers-storage-buffer.ll b/llvm/test/CodeGen/SPIRV/pointers/variable-pointers-storage-buffer.ll
index c3e4c54e3a1bdb..7b60a205831581 100644
--- a/llvm/test/CodeGen/SPIRV/pointers/variable-pointers-storage-buffer.ll
+++ b/llvm/test/CodeGen/SPIRV/pointers/variable-pointers-storage-buffer.ll
@@ -1,28 +1,28 @@
-; RUN: llc -mtriple=spirv1.6-unknown-vulkan1.3-compute %s -o - | FileCheck %s
-; RUN: %if spirv-tools %{ llc -mtriple=spirv1.6-unknown-vulkan1.3-compute %s -o - -filetype=obj | spirv-val %}
-
-; A function-local aggregate containing a storage buffer pointer requires
-; VariablePointersStorageBuffer, even when the pointer is nested.
-
-%struct.A = type { target("spirv.VulkanBuffer", [0 x i32], 12, 1) }
-
- at .str = private unnamed_addr constant [4 x i8] c"Buf\00", align 1
-
-define void @main() #0 {
-entry:
-  %handle = call target("spirv.VulkanBuffer", [0 x i32], 12, 1) @llvm.spv.resource.handlefrombinding(i32 0, i32 0, i32 1, i32 0, ptr @.str)
-  %a = alloca %struct.A, align 4
-  %value = insertvalue %struct.A poison, target("spirv.VulkanBuffer", [0 x i32], 12, 1) %handle, 0
-  store volatile %struct.A %value, ptr %a, align 4
-  ret void
-}
-
-; CHECK: OpCapability VariablePointersStorageBuffer
-; CHECK: %[[BUFFER:[0-9]+]] = OpTypePointer StorageBuffer
-; CHECK: %[[A:[0-9]+]] = OpTypeStruct %[[BUFFER]]
-; CHECK: %[[A_PTR:[0-9]+]] = OpTypePointer Function %[[A]]
-; CHECK: OpVariable %[[A_PTR]] Function
-
-declare target("spirv.VulkanBuffer", [0 x i32], 12, 1) @llvm.spv.resource.handlefrombinding(i32, i32, i32, i32, ptr)
-
-attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
+; RUN: llc -mtriple=spirv1.6-unknown-vulkan1.3-compute %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -mtriple=spirv1.6-unknown-vulkan1.3-compute %s -o - -filetype=obj | spirv-val %}
+
+; A function-local aggregate containing a storage buffer pointer requires
+; VariablePointersStorageBuffer, even when the pointer is nested.
+
+%struct.A = type { target("spirv.VulkanBuffer", [0 x i32], 12, 1) }
+
+ at .str = private unnamed_addr constant [4 x i8] c"Buf\00", align 1
+
+define void @main() #0 {
+entry:
+  %handle = call target("spirv.VulkanBuffer", [0 x i32], 12, 1) @llvm.spv.resource.handlefrombinding(i32 0, i32 0, i32 1, i32 0, ptr @.str)
+  %a = alloca %struct.A, align 4
+  %value = insertvalue %struct.A poison, target("spirv.VulkanBuffer", [0 x i32], 12, 1) %handle, 0
+  store volatile %struct.A %value, ptr %a, align 4
+  ret void
+}
+
+; CHECK: OpCapability VariablePointersStorageBuffer
+; CHECK: %[[BUFFER:[0-9]+]] = OpTypePointer StorageBuffer
+; CHECK: %[[A:[0-9]+]] = OpTypeStruct %[[BUFFER]]
+; CHECK: %[[A_PTR:[0-9]+]] = OpTypePointer Function %[[A]]
+; CHECK: OpVariable %[[A_PTR]] Function
+
+declare target("spirv.VulkanBuffer", [0 x i32], 12, 1) @llvm.spv.resource.handlefrombinding(i32, i32, i32, i32, ptr)
+
+attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }

>From ecbfebe9b26a48a30d80554a97eecea3d84c0981 Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Tue, 22 Sep 2026 12:02:14 -0700
Subject: [PATCH 3/5] [SPIR-V] Limit variable pointer capability emission

Only require VariablePointersStorageBuffer when a function-local variable recursively contains a StorageBuffer pointer.

Co-authored-by: Copilot App <223556219+Copilot at users.noreply.github.com>
---
 .../Target/SPIRV/SPIRVInstructionSelector.cpp | 47 ++++++++++++++++---
 .../pointers/structured-buffer-access.ll      |  1 +
 2 files changed, 41 insertions(+), 7 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index c532307ffa2860..3bc8b5ce542670 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -7100,6 +7100,34 @@ static bool isConcreteSPIRVType(SPIRVTypeInst Ty,
   return true;
 }
 
+static bool containsStorageBufferPointer(SPIRVTypeInst Ty,
+                                         const SPIRVGlobalRegistry &GR,
+                                         SmallSet<Register, 8> &Visited) {
+  Register TypeReg = Ty->getOperand(0).getReg();
+  if (!Visited.insert(TypeReg).second)
+    return false;
+
+  switch (Ty->getOpcode()) {
+  case SPIRV::OpTypePointer:
+    if (Ty->getOperand(1).getImm() == SPIRV::StorageClass::StorageBuffer)
+      return true;
+    return containsStorageBufferPointer(
+        GR.getSPIRVTypeForVReg(Ty->getOperand(2).getReg()), GR, Visited);
+  case SPIRV::OpTypeArray:
+  case SPIRV::OpTypeRuntimeArray:
+    return containsStorageBufferPointer(
+        GR.getSPIRVTypeForVReg(Ty->getOperand(1).getReg()), GR, Visited);
+  case SPIRV::OpTypeStruct:
+    for (unsigned I = 1; I < Ty->getNumOperands(); ++I)
+      if (containsStorageBufferPointer(
+              GR.getSPIRVTypeForVReg(Ty->getOperand(I).getReg()), GR, Visited))
+        return true;
+    return false;
+  default:
+    return false;
+  }
+}
+
 bool SPIRVInstructionSelector::selectAbort(MachineInstr &I) const {
   assert(I.getNumExplicitOperands() == 2);
 
@@ -7159,6 +7187,18 @@ bool SPIRVInstructionSelector::selectFrameIndex(Register ResVReg,
   unsigned Opcode =
       UseUntypedPointers ? SPIRV::OpUntypedVariableKHR : SPIRV::OpVariable;
 
+  if (!UseUntypedPointers) {
+    SmallSet<Register, 8> Visited;
+    if (containsStorageBufferPointer(ResType, GR, Visited)) {
+      MachineIRBuilder MIRBuilder(I);
+      if (!STI.isAtLeastSPIRVVer(VersionTuple(1, 3)))
+        MIRBuilder.buildInstr(SPIRV::OpExtension)
+            .addImm(SPIRV::Extension::SPV_KHR_variable_pointers);
+      MIRBuilder.buildInstr(SPIRV::OpCapability)
+          .addImm(SPIRV::Capability::VariablePointersStorageBuffer);
+    }
+  }
+
   auto MIB = BuildMI(*It->getParent(), It, It->getDebugLoc(), TII.get(Opcode))
                  .addDef(ResVReg)
                  .addUse(GR.getSPIRVTypeID(ResType))
@@ -7645,13 +7685,6 @@ bool SPIRVInstructionSelector::loadHandleBeforePosition(
   if (IsStructuredBuffer) {
     VarType = GR.getPointeeType(ResType);
     SC = GR.getPointerStorageClass(ResType);
-    if (SC == SPIRV::StorageClass::StorageBuffer) {
-      if (!STI.isAtLeastSPIRVVer(VersionTuple(1, 3)))
-        MIRBuilder.buildInstr(SPIRV::OpExtension)
-            .addImm(SPIRV::Extension::SPV_KHR_variable_pointers);
-      MIRBuilder.buildInstr(SPIRV::OpCapability)
-          .addImm(SPIRV::Capability::VariablePointersStorageBuffer);
-    }
   }
 
   // ArraySize 0 means an unbounded array and we need to set to required
diff --git a/llvm/test/CodeGen/SPIRV/pointers/structured-buffer-access.ll b/llvm/test/CodeGen/SPIRV/pointers/structured-buffer-access.ll
index 8400db5b7420d0..89971f0e708a86 100644
--- a/llvm/test/CodeGen/SPIRV/pointers/structured-buffer-access.ll
+++ b/llvm/test/CodeGen/SPIRV/pointers/structured-buffer-access.ll
@@ -27,6 +27,7 @@
 @.str.2 = private unnamed_addr constant [4 x i8] c"Out\00", align 1
 
 define void @main() local_unnamed_addr #0 {
+; CHECK-NOT: OpCapability VariablePointersStorageBuffer
 ; CHECK-LABEL: main
 ; CHECK:       %42 = OpFunction %2 None %3 ; -- Begin function main
 ; CHECK-NEXT:    %1 = OpLabel

>From 2c93fed315144d61e05d541275f459ef57039473 Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Tue, 22 Sep 2026 12:39:25 -0700
Subject: [PATCH 4/5] [SPIR-V] Avoid redundant variable pointer scans

Co-authored-by: Copilot App <223556219+Copilot at users.noreply.github.com>
---
 .../Target/SPIRV/SPIRVInstructionSelector.cpp | 36 +++++++++++++------
 1 file changed, 26 insertions(+), 10 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index 3bc8b5ce542670..fb9ff0d39bf177 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -7128,6 +7128,22 @@ static bool containsStorageBufferPointer(SPIRVTypeInst Ty,
   }
 }
 
+static bool containsStorageBufferPointer(SPIRVTypeInst Ty,
+                                         const SPIRVGlobalRegistry &GR) {
+  SmallSet<Register, 8> Visited;
+  return containsStorageBufferPointer(Ty, GR, Visited);
+}
+
+static bool hasCapability(const MachineFunction &MF,
+                          SPIRV::Capability::Capability Capability) {
+  return llvm::any_of(MF, [Capability](const MachineBasicBlock &MBB) {
+    return llvm::any_of(MBB, [Capability](const MachineInstr &MI) {
+      return MI.getOpcode() == SPIRV::OpCapability &&
+             MI.getOperand(0).getImm() == Capability;
+    });
+  });
+}
+
 bool SPIRVInstructionSelector::selectAbort(MachineInstr &I) const {
   assert(I.getNumExplicitOperands() == 2);
 
@@ -7187,16 +7203,16 @@ bool SPIRVInstructionSelector::selectFrameIndex(Register ResVReg,
   unsigned Opcode =
       UseUntypedPointers ? SPIRV::OpUntypedVariableKHR : SPIRV::OpVariable;
 
-  if (!UseUntypedPointers) {
-    SmallSet<Register, 8> Visited;
-    if (containsStorageBufferPointer(ResType, GR, Visited)) {
-      MachineIRBuilder MIRBuilder(I);
-      if (!STI.isAtLeastSPIRVVer(VersionTuple(1, 3)))
-        MIRBuilder.buildInstr(SPIRV::OpExtension)
-            .addImm(SPIRV::Extension::SPV_KHR_variable_pointers);
-      MIRBuilder.buildInstr(SPIRV::OpCapability)
-          .addImm(SPIRV::Capability::VariablePointersStorageBuffer);
-    }
+  if (!UseUntypedPointers &&
+      !hasCapability(*I.getMF(),
+                     SPIRV::Capability::VariablePointersStorageBuffer) &&
+      containsStorageBufferPointer(ResType, GR)) {
+    MachineIRBuilder MIRBuilder(I);
+    if (!STI.isAtLeastSPIRVVer(VersionTuple(1, 3)))
+      MIRBuilder.buildInstr(SPIRV::OpExtension)
+          .addImm(SPIRV::Extension::SPV_KHR_variable_pointers);
+    MIRBuilder.buildInstr(SPIRV::OpCapability)
+        .addImm(SPIRV::Capability::VariablePointersStorageBuffer);
   }
 
   auto MIB = BuildMI(*It->getParent(), It, It->getDebugLoc(), TII.get(Opcode))

>From 5d6e8d1433ebe3fefba41f770b630e13b3266fa2 Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Tue, 22 Sep 2026 12:49:20 -0700
Subject: [PATCH 5/5] [SPIR-V] Rely on capability deduplication

Co-authored-by: Copilot App <223556219+Copilot at users.noreply.github.com>
---
 .../lib/Target/SPIRV/SPIRVInstructionSelector.cpp | 15 +--------------
 1 file changed, 1 insertion(+), 14 deletions(-)

diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index fb9ff0d39bf177..562344ac52b581 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -7134,16 +7134,6 @@ static bool containsStorageBufferPointer(SPIRVTypeInst Ty,
   return containsStorageBufferPointer(Ty, GR, Visited);
 }
 
-static bool hasCapability(const MachineFunction &MF,
-                          SPIRV::Capability::Capability Capability) {
-  return llvm::any_of(MF, [Capability](const MachineBasicBlock &MBB) {
-    return llvm::any_of(MBB, [Capability](const MachineInstr &MI) {
-      return MI.getOpcode() == SPIRV::OpCapability &&
-             MI.getOperand(0).getImm() == Capability;
-    });
-  });
-}
-
 bool SPIRVInstructionSelector::selectAbort(MachineInstr &I) const {
   assert(I.getNumExplicitOperands() == 2);
 
@@ -7203,10 +7193,7 @@ bool SPIRVInstructionSelector::selectFrameIndex(Register ResVReg,
   unsigned Opcode =
       UseUntypedPointers ? SPIRV::OpUntypedVariableKHR : SPIRV::OpVariable;
 
-  if (!UseUntypedPointers &&
-      !hasCapability(*I.getMF(),
-                     SPIRV::Capability::VariablePointersStorageBuffer) &&
-      containsStorageBufferPointer(ResType, GR)) {
+  if (!UseUntypedPointers && containsStorageBufferPointer(ResType, GR)) {
     MachineIRBuilder MIRBuilder(I);
     if (!STI.isAtLeastSPIRVVer(VersionTuple(1, 3)))
       MIRBuilder.buildInstr(SPIRV::OpExtension)



More information about the llvm-commits mailing list