Skip to content

[HLSL] Tests for local resource arrays #153257

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: users/hekota/pr152454-resource-array-subscript-one-elem-codegen
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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;
}