Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
10a06e3
[HLSL] Add `isHLSLResourceRecordArray` method to `clang::Type`
hekota Aug 7, 2025
4e153a4
[HLSL] Add implicit binding attribute to resource arrays without bind…
hekota Aug 7, 2025
ac99f5a
one more instance to replace
hekota Aug 7, 2025
b5ca61c
Merge branch 'users/hekota/pr152450-add-is-record-array-to-type' into…
hekota Aug 7, 2025
e12a7a9
update test
hekota Aug 7, 2025
8690223
[HLSL] Global resource arrays element access
hekota Aug 7, 2025
f08e213
code review feedback, add test for dynamic indexing, typedef and iden…
hekota Aug 11, 2025
c8fd0b4
Merge branch 'main' of https://github.com/llvm/llvm-project into reso…
hekota Aug 11, 2025
227f6ae
fix build break after merge
hekota Aug 11, 2025
8bc614a
Fix bug - initialize resource arrays to poison, add test
hekota Aug 12, 2025
f0d05cd
Update global_array.hlsl SPIR-V convergence test to use custom struct
hekota Aug 12, 2025
3a4b7f2
Tests for local resource arrays
hekota Aug 12, 2025
ac62921
code review feedback - use existing methods on Type and ASTContext, a…
hekota Aug 12, 2025
1600414
clang-format
hekota Aug 12, 2025
00341fa
Add note to tests
hekota Aug 12, 2025
150a452
Add test for multi-dimensional local resource array & add notes to tests
hekota Aug 12, 2025
f5ec2b8
Merge branch 'main' into resource-array-subscript-one-elem-codegen
hekota Aug 13, 2025
d1b3e3a
Update resource index calculation & fix formatting
hekota Aug 15, 2025
9b54bbd
code review feedback - use std::array, llvm_unreachanble, add test, f…
hekota Aug 18, 2025
afb8d3f
Merge branch 'users/hekota/pr152454-resource-array-subscript-one-elem…
hekota Aug 19, 2025
a732ff4
Merge branch 'main' of https://github.com/llvm/llvm-project into res-…
hekota Aug 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions clang/test/CodeGenHLSL/resources/res-array-local-multi-dim.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-compute -finclude-default-header \
// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s

// This test verifies handling of multi-dimensional local arrays of resources
// when used as a function argument and local variable.

// CHECK: @_ZL1A = internal global %"class.hlsl::RWBuffer" poison, align 4
// CHECK: @_ZL1B = internal global %"class.hlsl::RWBuffer" poison, align 4

RWBuffer<float> A : register(u10);
RWBuffer<float> B : register(u20);
RWStructuredBuffer<float> Out;

// NOTE: _ZN4hlsl8RWBufferIfEixEj is the subscript operator for RWBuffer<float> and
// _ZN4hlsl18RWStructuredBufferIfEixEj is the subscript operator for RWStructuredBuffer<float>

// CHECK: define {{.*}} float @_Z3fooA2_A2_N4hlsl8RWBufferIfEE(ptr noundef byval([2 x [2 x %"class.hlsl::RWBuffer"]]) align 4 %Arr)
// CHECK-NEXT: entry:
float foo(RWBuffer<float> Arr[2][2]) {
// CHECK-NEXT: %[[Arr_1_Ptr:.*]] = getelementptr inbounds [2 x [2 x %"class.hlsl::RWBuffer"]], ptr %Arr, i32 0, i32 1
// CHECK-NEXT: %[[Arr_1_1_Ptr:.*]] = getelementptr inbounds [2 x %"class.hlsl::RWBuffer"], ptr %[[Arr_1_Ptr]], i32 0, i32 1
// CHECK-NEXT: %[[BufPtr:.*]] = call {{.*}} ptr @_ZN4hlsl8RWBufferIfEixEj(ptr {{.*}} %[[Arr_1_1_Ptr]], i32 noundef 0)
// CHECK-NEXT: %[[Value:.*]] = load float, ptr %[[BufPtr]], align 4
// CHECK-NEXT: ret float %[[Value]]
return Arr[1][1][0];
}

// CHECK: define internal void @_Z4mainv()
// CHECK-NEXT: entry:
[numthreads(4,1,1)]
void main() {
// CHECK-NEXT: %L = alloca [2 x [2 x %"class.hlsl::RWBuffer"]], align 4
// CHECK-NEXT: %[[Tmp:.*]] = alloca [2 x [2 x %"class.hlsl::RWBuffer"]], align 4
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %L, ptr align 4 @_ZL1A, i32 4, i1 false)
// CHECK-NEXT: %[[Ptr1:.*]] = getelementptr inbounds %"class.hlsl::RWBuffer", ptr %L, i32 1
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %[[Ptr1]], ptr align 4 @_ZL1B, i32 4, i1 false)
// CHECK-NEXT: %[[Ptr2:.*]] = getelementptr inbounds [2 x %"class.hlsl::RWBuffer"], ptr %L, i32 1
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %[[Ptr2]], ptr align 4 @_ZL1A, i32 4, i1 false)
// CHECK-NEXT: %[[Ptr3:.*]] = getelementptr inbounds %"class.hlsl::RWBuffer", ptr %[[Ptr2]], i32 1
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %[[Ptr3]], ptr align 4 @_ZL1B, i32 4, i1 false)
RWBuffer<float> L[2][2] = { { A, B }, { A, B } };

// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %[[Tmp]], ptr align 4 %L, i32 16, i1 false)
// CHECK-NEXT: %[[ReturnedValue:.*]] = call {{.*}}float @_Z3fooA2_A2_N4hlsl8RWBufferIfEE(ptr noundef byval([2 x [2 x %"class.hlsl::RWBuffer"]]) align 4 %[[Tmp]])
// CHECK-NEXT: %[[OutBufPtr:.*]] = call {{.*}} ptr @_ZN4hlsl18RWStructuredBufferIfEixEj(ptr {{.*}} @_ZL3Out, i32 noundef 0)
// CHECK-NEXT: store float %[[ReturnedValue]], ptr %[[OutBufPtr]], align 4
// CHECK-NEXT: ret void
Out[0] = foo(L);
}
64 changes: 64 additions & 0 deletions clang/test/CodeGenHLSL/resources/res-array-local1.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-compute -finclude-default-header \
// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s

// This test verifies local arrays of resources in HLSL.

// CHECK: @_ZL1A = internal global %"class.hlsl::RWBuffer" poison, align 4
// CHECK: @_ZL1B = internal global %"class.hlsl::RWBuffer" poison, align 4
// CHECK: @_ZL1C = internal global %"class.hlsl::RWBuffer" poison, align 4

RWBuffer<float> A : register(u1);
RWBuffer<float> B : register(u2);
RWBuffer<float> C : register(u3);
RWStructuredBuffer<float> Out : register(u0);

// CHECK: define internal void @_Z4mainv()
// CHECK-NEXT: entry:
[numthreads(4,1,1)]
void main() {
// CHECK-NEXT: %First = alloca [3 x %"class.hlsl::RWBuffer"], align 4
// CHECK-NEXT: %Second = alloca [4 x %"class.hlsl::RWBuffer"], align 4
RWBuffer<float> First[3] = { A, B, C };
RWBuffer<float> Second[4];

// Verify initialization of First array from an initialization list
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %First, ptr align 4 @_ZL1A, i32 4, i1 false)
// CHECK-NEXT: %[[Ptr1:.*]] = getelementptr inbounds %"class.hlsl::RWBuffer", ptr %First, i32 1
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %[[Ptr1]], ptr align 4 @_ZL1B, i32 4, i1 false)
// CHECK-NEXT: %[[Ptr2:.*]] = getelementptr inbounds %"class.hlsl::RWBuffer", ptr %First, i32 2
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %[[Ptr2]], ptr align 4 @_ZL1C, i32 4, i1 false)

// Verify default initialization of Second array, which means there is a loop iterating
// over the array elements and calling the default constructor for each
// CHECK-NEXT: %[[ArrayBeginPtr:.*]] = getelementptr inbounds [4 x %"class.hlsl::RWBuffer"], ptr %Second, i32 0, i32 0
// CHECK-NEXT: %[[ArrayEndPtr:.*]] = getelementptr inbounds %"class.hlsl::RWBuffer", ptr %[[ArrayBeginPtr]], i32 4
// CHECK-NEXT: br label %[[ArrayInitLoop:.*]]
// CHECK: [[ArrayInitLoop]]:
// CHECK-NEXT: %[[ArrayCurPtr:.*]] = phi ptr [ %[[ArrayBeginPtr]], %entry ], [ %[[ArrayNextPtr:.*]], %[[ArrayInitLoop]] ]
// CHECK-NEXT: call void @_ZN4hlsl8RWBufferIfEC1Ev(ptr {{.*}} %[[ArrayCurPtr]])
// CHECK-NEXT: %[[ArrayNextPtr]] = getelementptr inbounds %"class.hlsl::RWBuffer", ptr %[[ArrayCurPtr]], i32 1
// CHECK-NEXT: %[[ArrayInitDone:.*]] = icmp eq ptr %[[ArrayNextPtr]], %[[ArrayEndPtr]]
// CHECK-NEXT: br i1 %[[ArrayInitDone]], label %[[AfterArrayInit:.*]], label %[[ArrayInitLoop]]
// CHECK: [[AfterArrayInit]]:

// Initialize First[2] with C
// CHECK: %[[Ptr3:.*]] = getelementptr inbounds [4 x %"class.hlsl::RWBuffer"], ptr %Second, i32 0, i32 2
// CHECK: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %[[Ptr3]], ptr align 4 @_ZL1C, i32 4, i1 false)
Second[2] = C;

// NOTE: _ZN4hlsl8RWBufferIfEixEj is the subscript operator for RWBuffer<float>

// get First[1][0] value
// CHECK: %[[First_1_Ptr:.*]] = getelementptr inbounds [3 x %"class.hlsl::RWBuffer"], ptr %First, i32 0, i32 1
// CHECK: %[[BufPtr1:.*]] = call {{.*}} ptr @_ZN4hlsl8RWBufferIfEixEj(ptr {{.*}} %[[First_1_Ptr]], i32 noundef 0)
// CHECK: %[[Value1:.*]] = load float, ptr %[[BufPtr1]], align 4

// get Second[2][0] value
// CHECK: %[[Second_2_Ptr:.*]] = getelementptr inbounds [4 x %"class.hlsl::RWBuffer"], ptr %Second, i32 0, i32 2
// CHECK: %[[BufPtr2:.*]] = call {{.*}} ptr @_ZN4hlsl8RWBufferIfEixEj(ptr {{.*}} %[[Second_2_Ptr]], i32 noundef 0)
// CHECK: %[[Value2:.*]] = load float, ptr %[[BufPtr2]], align 4

// add them
// CHECK: %{{.*}} = fadd {{.*}} float %[[Value1]], %[[Value2]]
Out[0] = First[1][0] + Second[2][0];
}
37 changes: 37 additions & 0 deletions clang/test/CodeGenHLSL/resources/res-array-local2.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-compute -finclude-default-header \
// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s

// This test verifies handling of local arrays of resources when used as a function argument.

// CHECK: @_ZL1A = internal global [3 x %"class.hlsl::RWBuffer"] poison, align 4

RWBuffer<float> A[3] : register(u0);
RWStructuredBuffer<float> Out : register(u0);

// NOTE: _ZN4hlsl8RWBufferIfEixEj is the subscript operator for RWBuffer<float> and
// _ZN4hlsl18RWStructuredBufferIfEixEj is the subscript operator for RWStructuredBuffer<float>

// CHECK: define {{.*}} float @_Z3fooA3_N4hlsl8RWBufferIfEE(ptr noundef byval([3 x %"class.hlsl::RWBuffer"]) align 4 %LocalA)
// CHECK-NEXT: entry:
float foo(RWBuffer<float> LocalA[3]) {
// CHECK-NEXT: %[[LocalA_2_Ptr:.*]] = getelementptr inbounds [3 x %"class.hlsl::RWBuffer"], ptr %LocalA, i32 0, i32 2
// CHECK-NEXT: %[[BufPtr:.*]] = call {{.*}} ptr @_ZN4hlsl8RWBufferIfEixEj(ptr {{.*}} %[[LocalA_2_Ptr]], i32 noundef 0)
// CHECK-NEXT: %[[Value:.*]] = load float, ptr %[[BufPtr]], align 4
// CHECK-NEXT: ret float %[[Value]]
return LocalA[2][0];
}

// CHECK: define internal void @_Z4mainv()
// CHECK-NEXT: entry:
[numthreads(4,1,1)]
void main() {
// Check that the `main` function calls `foo` with a local copy of the array
// CHECK-NEXT: %[[Tmp:.*]] = alloca [3 x %"class.hlsl::RWBuffer"], align 4
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %[[Tmp]], ptr align 4 @_ZL1A, i32 12, i1 false)

// CHECK-NEXT: %[[ReturnedValue:.*]] = call {{.*}} float @_Z3fooA3_N4hlsl8RWBufferIfEE(ptr noundef byval([3 x %"class.hlsl::RWBuffer"]) align 4 %[[Tmp]])
// CHECK-NEXT: %[[OutBufPtr:.*]] = call {{.*}} ptr @_ZN4hlsl18RWStructuredBufferIfEixEj(ptr {{.*}} @_ZL3Out, i32 noundef 0)
// CHECK-NEXT: store float %[[ReturnedValue]], ptr %[[OutBufPtr]], align 4
// CHECK-NEXT: ret void
Out[0] = foo(A);
}
62 changes: 62 additions & 0 deletions clang/test/CodeGenHLSL/resources/res-array-local3.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-compute -finclude-default-header \
// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s

// This test verifies handling of local arrays of resources when used
// as a function argument that is modified inside the function.

// CHECK: @_ZL1X = internal global %"class.hlsl::RWBuffer" poison, align 4
// CHECK: @_ZL1Y = internal global %"class.hlsl::RWBuffer" poison, align 4

RWBuffer<int> X : register(u0);
RWBuffer<int> Y : register(u1);

// CHECK: define {{.*}} @_Z6SomeFnA2_N4hlsl8RWBufferIiEEji(
// CHECK-SAME: ptr noundef byval([2 x %"class.hlsl::RWBuffer"]) align 4 %B, i32 noundef %Idx, i32 noundef %Val0)
// CHECK-NEXT: entry:
// CHECK-NEXT: %[[Idx_addr:.*]] = alloca i32, align 4
// CHECK-NEXT: %[[Val0_addr:.*]] = alloca i32, align 4
// CHECK-NEXT: store i32 %Idx, ptr %[[Idx_addr]], align 4
// CHECK-NEXT: store i32 %Val0, ptr %[[Val0_addr]], align 4
void SomeFn(RWBuffer<int> B[2], uint Idx, int Val0) {

// CHECK-NEXT: %[[B_0_Ptr:.*]] = getelementptr inbounds [2 x %"class.hlsl::RWBuffer"], ptr %B, i32 0, i32 0
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %[[B_0_Ptr]], ptr align 4 @_ZL1Y, i32 4, i1 false)
B[0] = Y;

// NOTE: _ZN4hlsl8RWBufferIiEixEj is the subscript operator for RWBuffer<int>

// CHECK-NEXT: %[[Val0:.*]] = load i32, ptr %[[Val0_addr]], align 4
// CHECK-NEXT: %[[B_0_Ptr:.*]] = getelementptr inbounds [2 x %"class.hlsl::RWBuffer"], ptr %B, i32 0, i32 0
// CHECK-NEXT: %[[Idx:.*]] = load i32, ptr %[[Idx_addr]], align 4
// CHECK-NEXT: %[[BufPtr:.*]] = call {{.*}} ptr @_ZN4hlsl8RWBufferIiEixEj(ptr {{.*}} %[[B_0_Ptr]], i32 noundef %[[Idx]])
// CHECK-NEXT: store i32 %[[Val0]], ptr %[[BufPtr]], align 4
B[0][Idx] = Val0;
}

// CHECK: define {{.*}} void @_Z4mainj(i32 noundef %GI)
// CHECK-NEXT: entry:
// CHECK-NEXT: %[[GI_addr:.*]] = alloca i32, align 4
[numthreads(4,1,1)]
void main(uint GI : SV_GroupIndex) {
// CHECK-NEXT: %A = alloca [2 x %"class.hlsl::RWBuffer"], align 4
// CHECK-NEXT: %[[Tmp:.*]] = alloca [2 x %"class.hlsl::RWBuffer"], align 4
// CHECK-NEXT: store i32 %GI, ptr %GI.addr, align 4

// Initialization of array A with resources X and Y
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %A, ptr align 4 @_ZL1X, i32 4, i1 false)
// CHECK-NEXT: %[[A_1_Ptr:.*]] = getelementptr inbounds %"class.hlsl::RWBuffer", ptr %A, i32 1
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %[[A_1_Ptr]], ptr align 4 @_ZL1Y, i32 4, i1 false)
RWBuffer<int> A[2] = {X, Y};

// Verify that SomeFn is called with a local copy of the array A
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %[[Tmp]], ptr align 4 %A, i32 8, i1 false)
// CHECK-NEXT: %[[GI:.*]] = load i32, ptr %[[GI_addr]], align 4
// CHECK-NEXT: call void @_Z6SomeFnA2_N4hlsl8RWBufferIiEEji(ptr noundef byval([2 x %"class.hlsl::RWBuffer"]) align 4 %[[Tmp]], i32 noundef %[[GI]], i32 noundef 1)
SomeFn(A, GI, 1);

// CHECK-NEXT: %[[A_0_Ptr:.*]] = getelementptr inbounds [2 x %"class.hlsl::RWBuffer"], ptr %A, i32 0, i32 0
// CHECK-NEXT: %[[GI:.*]] = load i32, ptr %[[GI_addr]], align 4
// CHECK-NEXT: %[[BufPtr:.*]] = call {{.*}} ptr @_ZN4hlsl8RWBufferIiEixEj(ptr {{.*}} %[[A_0_Ptr]], i32 noundef %[[GI]])
// CHECK-NEXT: store i32 2, ptr %[[BufPtr]], align 4
A[0][GI] = 2;
}