[clang] [llvm] [HLSL][SPIRV] Use 0 to represent unbounded resources when targeting SPIRV (PR #186022)

via cfe-commits cfe-commits at lists.llvm.org
Wed Mar 11 19:20:18 PDT 2026


=?utf-8?q?João?= Saffran <joaosaffranllvm at gmail.com>,
=?utf-8?q?João?= Saffran <joaosaffranllvm at gmail.com>
Message-ID:
In-Reply-To: <llvm.org/llvm/llvm-project/pull/186022 at github.com>


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-backend-spir-v

Author: None (joaosaffran)

<details>
<summary>Changes</summary>

SPIRV backend uses 0 to represent unbounded arrays. This patch makes unbounded resources account for this, as well as makes sure the backend uses OpTypeRuntimeArray to represent such cases.

---
Full diff: https://github.com/llvm/llvm-project/pull/186022.diff


4 Files Affected:

- (modified) clang/lib/CodeGen/CGHLSLRuntime.cpp (+6-4) 
- (modified) clang/test/CodeGenHLSL/resources/res-array-global-unbounded.hlsl (+11-6) 
- (modified) llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp (+4) 
- (added) llvm/test/CodeGen/SPIRV/hlsl-resources/unbounded-arr.ll (+34) 


``````````diff
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index b695d016c0524..04daa5e611412 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -117,11 +117,13 @@ static const ValueDecl *getArrayDecl(const ArraySubscriptExpr *ASE) {
 }
 
 // Get the total size of the array, or -1 if the array is unbounded.
-static int getTotalArraySize(ASTContext &AST, const clang::Type *Ty) {
+static int getTotalArraySize(ASTContext &AST, llvm::Triple::ArchType Arch,
+                             const clang::Type *Ty) {
   Ty = Ty->getUnqualifiedDesugaredType();
   assert(Ty->isArrayType() && "expected array type");
   if (Ty->isIncompleteArrayType())
-    return -1;
+    // Spirv uses 0 to represent unbounded arrays.
+    return Arch == llvm::Triple::ArchType::dxil ? -1 : 0;
   return AST.getConstantArrayElementCount(cast<ConstantArrayType>(Ty));
 }
 
@@ -1280,7 +1282,7 @@ std::optional<LValue> CGHLSLRuntime::emitResourceArraySubscriptExpr(
 
   // Calculate total array size (= range size).
   llvm::Value *Range = llvm::ConstantInt::getSigned(
-      CGM.IntTy, getTotalArraySize(AST, ResArrayTy));
+      CGM.IntTy, getTotalArraySize(AST, getArch(), ResArrayTy));
 
   // If the result of the subscript operation is a single resource, call the
   // constructor.
@@ -1345,7 +1347,7 @@ bool CGHLSLRuntime::emitResourceArrayCopy(LValue &LHS, Expr *RHSExpr,
       AggValueSlot::DoesNotOverlap);
 
   // Create Value for index and total array size (= range size).
-  int Size = getTotalArraySize(AST, ResArrayTy);
+  int Size = getTotalArraySize(AST, getArch(), ResArrayTy);
   llvm::Value *Zero = llvm::ConstantInt::get(CGM.IntTy, 0);
   llvm::Value *Range = llvm::ConstantInt::get(CGM.IntTy, Size);
 
diff --git a/clang/test/CodeGenHLSL/resources/res-array-global-unbounded.hlsl b/clang/test/CodeGenHLSL/resources/res-array-global-unbounded.hlsl
index 6756a26bfc124..7d5b9626db138 100644
--- a/clang/test/CodeGenHLSL/resources/res-array-global-unbounded.hlsl
+++ b/clang/test/CodeGenHLSL/resources/res-array-global-unbounded.hlsl
@@ -30,14 +30,15 @@ void main(uint GI : SV_GroupIndex) {
   // and explicit binding (u10, space1) 
   // CHECK: @hlsl::RWBuffer<float>::__createFromBinding(unsigned int, unsigned int, int, unsigned int, char const*)
   // CHECK-SAME: (ptr {{.*}} sret(%"class.hlsl::RWBuffer.0") align {{(4|8)}} %[[Tmp0]],
-  // CHECK-SAME: i32 noundef 10, i32 noundef 1, i32 noundef -1, i32 noundef 100, ptr noundef @A.str)
+  // DXIL-SAME: i32 noundef 10, i32 noundef 1, i32 noundef -1, i32 noundef 100, ptr noundef @A.str)
+  // SPV-SAME: i32 noundef 10, i32 noundef 1, i32 noundef 0, i32 noundef 100, ptr noundef @A.str)
   // CHECK-NEXT: %[[BufPtr:.*]] = call {{.*}} ptr{{.*}} @hlsl::RWBuffer<float>::operator[](unsigned int)(ptr {{.*}} %[[Tmp0]], i32 noundef 0)
   // CHECK-NEXT: %[[Value1:.*]] = load float, ptr{{.*}} %[[BufPtr]], align 4
   // CHECK-NEXT: store float %[[Value1]], ptr %a, align 4
   float a = A[100][0];
 
   // Make sure B[2][3] is translated to a local RWBuffer<int>[4] array where each array element
-  // is initialized by a constructor call with range -1 and index 52-55 and implicit binding 
+  // is initialized by a constructor call with range 0 and index 52-55 and implicit binding 
   // (space 0, order_id 0) 
   // The first index is calculated from the array dimensions (unbounded x 5 x 4) and indices (2, 3)
   // as 2 * 5 * 4 + 3 * 4 = 52 and the following indices are sequential.
@@ -45,22 +46,26 @@ void main(uint GI : SV_GroupIndex) {
   // CHECK-NEXT: %[[Ptr_Tmp2_0:.*]] = getelementptr [4 x %"class.hlsl::RWBuffer"], ptr %[[Tmp2]], i32 0, i32 0
   // CHECK-NEXT: call void @hlsl::RWBuffer<int>::__createFromImplicitBinding(unsigned int, unsigned int, int, unsigned int, char const*)
   // CHECK-SAME: (ptr {{.*}} sret(%"class.hlsl::RWBuffer") align {{(4|8)}} %[[Ptr_Tmp2_0]], 
-  // CHECK-SAME: i32 noundef 0, i32 noundef 0, i32 noundef -1, i32 noundef 52, ptr noundef @[[BufB]])
+  // DXIL-SAME: i32 noundef 0, i32 noundef 0, i32 noundef -1, i32 noundef 52, ptr noundef @[[BufB]])
+  // SPV-SAME: i32 noundef 0, i32 noundef 0, i32 noundef 0, i32 noundef 52, ptr noundef @[[BufB]])
   
   // CHECK-NEXT: %[[Ptr_Tmp2_1:.*]] = getelementptr [4 x %"class.hlsl::RWBuffer"], ptr %[[Tmp2]], i32 0, i32 1
   // CHECK-NEXT: call void @hlsl::RWBuffer<int>::__createFromImplicitBinding(unsigned int, unsigned int, int, unsigned int, char const*)
   // CHECK-SAME: (ptr {{.*}} sret(%"class.hlsl::RWBuffer") align {{(4|8)}} %[[Ptr_Tmp2_1]], 
-  // CHECK-SAME: i32 noundef 0, i32 noundef 0, i32 noundef -1, i32 noundef 53, ptr noundef @[[BufB]])
+  // DXIL-SAME: i32 noundef 0, i32 noundef 0, i32 noundef -1, i32 noundef 53, ptr noundef @[[BufB]])
+  // SPV-SAME: i32 noundef 0, i32 noundef 0, i32 noundef 0, i32 noundef 53, ptr noundef @[[BufB]])
   
   // CHECK-NEXT: %[[Ptr_Tmp2_2:.*]] = getelementptr [4 x %"class.hlsl::RWBuffer"], ptr %[[Tmp2]], i32 0, i32 2
   // CHECK-NEXT: call void @hlsl::RWBuffer<int>::__createFromImplicitBinding(unsigned int, unsigned int, int, unsigned int, char const*)
   // CHECK-SAME: (ptr {{.*}} sret(%"class.hlsl::RWBuffer") align {{(4|8)}} %[[Ptr_Tmp2_2]], 
-  // CHECK-SAME: i32 noundef 0, i32 noundef 0, i32 noundef -1, i32 noundef 54, ptr noundef @[[BufB]])
+  // DXIL-SAME: i32 noundef 0, i32 noundef 0, i32 noundef -1, i32 noundef 54, ptr noundef @[[BufB]])
+  // SPV-SAME: i32 noundef 0, i32 noundef 0, i32 noundef 0, i32 noundef 54, ptr noundef @[[BufB]])
 
   // CHECK-NEXT: %[[Ptr_Tmp2_3:.*]] = getelementptr [4 x %"class.hlsl::RWBuffer"], ptr %[[Tmp2]], i32 0, i32 3
   // CHECK-NEXT: call void @hlsl::RWBuffer<int>::__createFromImplicitBinding(unsigned int, unsigned int, int, unsigned int, char const*)
   // CHECK-SAME: (ptr {{.*}} sret(%"class.hlsl::RWBuffer") align {{(4|8)}} %[[Ptr_Tmp2_3]], 
-  // CHECK-SAME: i32 noundef 0, i32 noundef 0, i32 noundef -1, i32 noundef 55, ptr noundef @[[BufB]])
+  // DXIL-SAME: i32 noundef 0, i32 noundef 0, i32 noundef -1, i32 noundef 55, ptr noundef @[[BufB]])
+  // SPV-SAME: i32 noundef 0, i32 noundef 0, i32 noundef 0, i32 noundef 55, ptr noundef @[[BufB]])
 
   // DXIL-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %[[Tmp1]], ptr align 4 %[[Tmp2]], i32 16, i1 false)
   // SPV-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %[[Tmp1]], ptr align 8 %[[Tmp2]], i64 32, i1 false)
diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index 3e50e4a0e8c80..5e966df6372db 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -6012,6 +6012,10 @@ bool SPIRVInstructionSelector::loadHandleBeforePosition(
     SC = GR.getPointerStorageClass(ResType);
   }
 
+  if (ResType->getOpcode() == SPIRV::OpTypeImage && ArraySize == 0)
+    MIRBuilder.buildInstr(SPIRV::OpCapability)
+        .addImm(SPIRV::Capability::RuntimeDescriptorArrayEXT);
+
   Register VarReg =
       buildPointerToResource(SPIRVTypeInst(VarType), SC, Set, Binding,
                              ArraySize, IndexReg, Name, MIRBuilder);
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-resources/unbounded-arr.ll b/llvm/test/CodeGen/SPIRV/hlsl-resources/unbounded-arr.ll
new file mode 100644
index 0000000000000..8be0da57a797b
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/unbounded-arr.ll
@@ -0,0 +1,34 @@
+; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv1.6-vulkan1.3-library %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv1.6-vulkan1.3-library %s -o - -filetype=obj | spirv-val %}
+
+; CHECK-DAG: OpCapability RuntimeDescriptorArrayEXT
+; CHECK-DAG: %[[int32:[0-9]+]] = OpTypeInt 32 0
+; CHECK-DAG: %[[rwbuffer:[0-9]+]] = OpTypeImage %[[int32]] Buffer 2 0 0 2 R32i
+; CHECK-DAG: OpTypeRuntimeArray %[[rwbuffer]]
+
+; This IR was emmited from the following HLSL code:
+; [[vk::binding(0)]]
+; RWBuffer<int> Buf[] : register(u0);
+; 
+; [numthreads(4,2,1)]
+; void main(uint GI : SV_GroupIndex) {
+;     Buf[0][0] = 0;
+; }
+
+
+
+ at Buf.str = private unnamed_addr constant [4 x i8] c"Buf\00", align 1
+
+; Function Attrs: convergent noinline norecurse
+define void @main() #0 {
+entry:
+  %2 = call target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 24) @llvm.spv.resource.handlefrombinding.tspirv.SignedImage_i32_5_2_0_0_2_24t(i32 0, i32 0, i32 0, i32 0, ptr @Buf.str)
+  %3 = call noundef align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.SignedImage_i32_5_2_0_0_2_24t(target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 24)  %2, i32 0)
+  store i32 0, ptr addrspace(11) %3, align 4
+  ret void
+}
+; Function Attrs: nocallback nofree nosync nounwind willreturn memory(none)
+declare target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 24) @llvm.spv.resource.handlefrombinding.tspirv.SignedImage_i32_5_2_0_0_2_24t(i32, i32, i32, i32, ptr) #3
+
+attributes #0 = { convergent noinline norecurse "hlsl.numthreads"="4,2,1" "hlsl.shader"="compute" "no-nans-fp-math"="true" "no-signed-zeros-fp-math"="true" "no-trapping-math"="true" "stack-protector-buffer-size"="8" }
+attributes #3 = { nocallback nofree nosync nounwind willreturn memory(none) }

``````````

</details>


https://github.com/llvm/llvm-project/pull/186022


More information about the cfe-commits mailing list