Skip to content

Commit 9d97c5f

Browse files
authored
[HLSL] Enable unbounded resource arrays at global scope (llvm#155053)
Adds support for unbounded resource arrays declared at a global scope. Local unbounded resource array variables or incomplete resource arrays as function arguments are not going be supported in HLSL in Clang. See: - microsoft/hlsl-specs#141 - llvm/wg-hlsl#298 Closes llvm#145427
1 parent ba8773b commit 9d97c5f

File tree

6 files changed

+116
-13
lines changed

6 files changed

+116
-13
lines changed

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13205,6 +13205,9 @@ def err_hlsl_resource_range_overlap: Error<
1320513205
"%select{All|Vertex|Hull|Domain|Geometry|Pixel|Amplification|Mesh}9">;
1320613206
def note_hlsl_resource_range_here: Note<"overlapping resource range here">;
1320713207

13208+
def err_hlsl_incomplete_resource_array_in_function_param: Error<
13209+
"incomplete resource array in a function parameter">;
13210+
1320813211
// Layout randomization diagnostics.
1320913212
def err_non_designated_init_used : Error<
1321013213
"a randomized struct can only be initialized with a designated initializer">;

clang/lib/AST/Type.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5420,7 +5420,7 @@ bool Type::isHLSLResourceRecordArray() const {
54205420
const Type *Ty = getUnqualifiedDesugaredType();
54215421
if (!Ty->isArrayType())
54225422
return false;
5423-
while (isa<ConstantArrayType>(Ty))
5423+
while (isa<ArrayType>(Ty))
54245424
Ty = Ty->getArrayElementTypeNoTypeQual();
54255425
return Ty->isHLSLResourceRecord();
54265426
}
@@ -5433,7 +5433,7 @@ bool Type::isHLSLIntangibleType() const {
54335433
return Ty->isHLSLBuiltinIntangibleType();
54345434

54355435
// unwrap arrays
5436-
while (isa<ConstantArrayType>(Ty))
5436+
while (isa<ArrayType>(Ty))
54375437
Ty = Ty->getArrayElementTypeNoTypeQual();
54385438

54395439
const RecordType *RT =

clang/lib/Sema/SemaDecl.cpp

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14393,9 +14393,12 @@ void Sema::ActOnUninitializedDecl(Decl *RealDecl) {
1439314393
return;
1439414394
}
1439514395

14396-
// Provide a specific diagnostic for uninitialized variable
14397-
// definitions with incomplete array type.
14398-
if (Type->isIncompleteArrayType()) {
14396+
// Provide a specific diagnostic for uninitialized variable definitions
14397+
// with incomplete array type, unless it is a global unbounded HLSL resource
14398+
// array.
14399+
if (Type->isIncompleteArrayType() &&
14400+
!(getLangOpts().HLSL && Var->hasGlobalStorage() &&
14401+
Type->isHLSLResourceRecordArray())) {
1439914402
if (Var->isConstexpr())
1440014403
Diag(Var->getLocation(), diag::err_constexpr_var_requires_const_init)
1440114404
<< Var;
@@ -15476,6 +15479,14 @@ Decl *Sema::ActOnParamDeclarator(Scope *S, Declarator &D,
1547615479
}
1547715480
}
1547815481

15482+
// Incomplete resource arrays are not allowed as function parameters in HLSL
15483+
if (getLangOpts().HLSL && parmDeclType->isIncompleteArrayType() &&
15484+
parmDeclType->isHLSLResourceRecordArray()) {
15485+
Diag(D.getIdentifierLoc(),
15486+
diag::err_hlsl_incomplete_resource_array_in_function_param);
15487+
D.setInvalidType(true);
15488+
}
15489+
1547915490
// Temporarily put parameter variables in the translation unit, not
1548015491
// the enclosing context. This prevents them from accidentally
1548115492
// looking like class members in C++.

clang/lib/Sema/SemaHLSL.cpp

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -350,8 +350,8 @@ getResourceArrayHandleType(VarDecl *VD) {
350350
assert(VD->getType()->isHLSLResourceRecordArray() &&
351351
"expected array of resource records");
352352
const Type *Ty = VD->getType()->getUnqualifiedDesugaredType();
353-
while (const ConstantArrayType *CAT = dyn_cast<ConstantArrayType>(Ty))
354-
Ty = CAT->getArrayElementTypeNoTypeQual()->getUnqualifiedDesugaredType();
353+
while (const ArrayType *AT = dyn_cast<ArrayType>(Ty))
354+
Ty = AT->getArrayElementTypeNoTypeQual()->getUnqualifiedDesugaredType();
355355
return HLSLAttributedResourceType::findHandleTypeOnResource(Ty);
356356
}
357357

@@ -2022,9 +2022,11 @@ static bool DiagnoseHLSLRegisterAttribute(Sema &S, SourceLocation &ArgLoc,
20222022
}
20232023

20242024
void SemaHLSL::handleResourceBindingAttr(Decl *TheDecl, const ParsedAttr &AL) {
2025-
if (isa<VarDecl>(TheDecl)) {
2026-
if (SemaRef.RequireCompleteType(TheDecl->getBeginLoc(),
2027-
cast<ValueDecl>(TheDecl)->getType(),
2025+
if (VarDecl *VD = dyn_cast<VarDecl>(TheDecl)) {
2026+
QualType Ty = VD->getType();
2027+
if (const auto *IAT = dyn_cast<IncompleteArrayType>(Ty))
2028+
Ty = IAT->getElementType();
2029+
if (SemaRef.RequireCompleteType(TheDecl->getBeginLoc(), Ty,
20282030
diag::err_incomplete_type))
20292031
return;
20302032
}
@@ -3831,9 +3833,9 @@ void SemaHLSL::collectResourceBindingsOnVarDecl(VarDecl *VD) {
38313833
// Unwrap arrays
38323834
// FIXME: Calculate array size while unwrapping
38333835
const Type *Ty = VD->getType()->getUnqualifiedDesugaredType();
3834-
while (Ty->isConstantArrayType()) {
3835-
const ConstantArrayType *CAT = cast<ConstantArrayType>(Ty);
3836-
Ty = CAT->getElementType()->getUnqualifiedDesugaredType();
3836+
while (Ty->isArrayType()) {
3837+
const ArrayType *AT = cast<ArrayType>(Ty);
3838+
Ty = AT->getElementType()->getUnqualifiedDesugaredType();
38373839
}
38383840

38393841
// Resource (or array of resources)
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-compute -finclude-default-header \
2+
// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s -check-prefixes=CHECK,DXIL
3+
// RUN: %clang_cc1 -finclude-default-header -triple spirv-unknown-vulkan-compute \
4+
// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s -check-prefixes=CHECK,SPV
5+
6+
// CHECK: @[[BufA:.*]] = private unnamed_addr constant [2 x i8] c"A\00", align 1
7+
// CHECK: @[[BufB:.*]] = private unnamed_addr constant [2 x i8] c"B\00", align 1
8+
9+
RWBuffer<float> A[] : register(u10, space1);
10+
RWBuffer<int> B[][5][4];
11+
12+
RWStructuredBuffer<float> Out;
13+
14+
float foo(RWBuffer<int> Arr[4], uint Index) {
15+
return (float)Arr[Index][0];
16+
}
17+
18+
// NOTE:
19+
// - _ZN4hlsl8RWBufferIfEC1EjjijPKc is the constructor call for explicit binding for RWBuffer<float>
20+
// (has "jjij" in the mangled name) and the arguments are (register, space, range_size, index, name).
21+
// - _ZN4hlsl8RWBufferIiEC1EjijjPKc is the constructor call for implicit binding for RWBuffer<int>
22+
// (has "jijj" in the mangled name) and the arguments are (space, range_size, index, order_id, name).
23+
// - _ZN4hlsl8RWBufferIfEixEj is the subscript operator on RWBuffer<float>
24+
25+
[numthreads(4,1,1)]
26+
void main(uint GI : SV_GroupIndex) {
27+
// CHECK: define internal {{.*}}void @_Z4mainj(i32 noundef %GI)
28+
// CHECK: %[[GI_alloca:.*]] = alloca i32, align 4
29+
// CHECK-NEXT: %a = alloca float, align 4
30+
// CHECK-NEXT: %[[Tmp0:.*]] = alloca %"class.hlsl::RWBuffer
31+
// CHECK-NEXT: %b = alloca float, align 4
32+
// CHECK-NEXT: %[[Tmp1:.*]] = alloca [4 x %"class.hlsl::RWBuffer"]
33+
// CHECK-NEXT: %[[Tmp2:.*]] = alloca [4 x %"class.hlsl::RWBuffer"]
34+
// CHECK-NEXT: store i32 %GI, ptr %[[GI_alloca]], align 4
35+
36+
// Make sure A[100] is translated to a RWBuffer<float> constructor call with range -1 and index 100
37+
// and explicit binding (u10, space1)
38+
// CHECK: call void @_ZN4hlsl8RWBufferIfEC1EjjijPKc(ptr {{.*}} %[[Tmp0]], i32 noundef 10, i32 noundef 1, i32 noundef -1, i32 noundef 100, ptr noundef @A.str)
39+
// CHECK-NEXT: %[[BufPtr:.*]] = call {{.*}} ptr{{.*}} @_ZN4hlsl8RWBufferIfEixEj(ptr {{.*}} %[[Tmp0]], i32 noundef 0)
40+
// CHECK-NEXT: %[[Value1:.*]] = load float, ptr{{.*}} %[[BufPtr]], align 4
41+
// CHECK-NEXT: store float %[[Value1]], ptr %a, align 4
42+
float a = A[100][0];
43+
44+
// Make sure B[2][3] is translated to a local RWBuffer<int>[4] array where each array element
45+
// is initialized by a constructor call with range -1 and index 52-55 and implicit binding
46+
// (space 0, order_id 0)
47+
// The first index is calculated from the array dimensions (unbounded x 5 x 4) and indices (2, 3)
48+
// as 2 * 5 * 4 + 3 * 4 = 52 and the following indices are sequential.
49+
// CHECK-NEXT: %[[Ptr_Tmp2_0:.*]] = getelementptr [4 x %"class.hlsl::RWBuffer"], ptr %[[Tmp2]], i32 0, i32 0
50+
// CHECK-NEXT: call void @_ZN4hlsl8RWBufferIiEC1EjijjPKc(ptr {{.*}} %[[Ptr_Tmp2_0]], i32 noundef 0, i32 noundef -1, i32 noundef 52, i32 noundef 0, ptr noundef @B.str)
51+
// CHECK-NEXT: %[[Ptr_Tmp2_1:.*]] = getelementptr [4 x %"class.hlsl::RWBuffer"], ptr %[[Tmp2]], i32 0, i32 1
52+
// CHECK-NEXT: call void @_ZN4hlsl8RWBufferIiEC1EjijjPKc(ptr {{.*}} %[[Ptr_Tmp2_1]], i32 noundef 0, i32 noundef -1, i32 noundef 53, i32 noundef 0, ptr noundef @B.str)
53+
// CHECK-NEXT: %[[Ptr_Tmp2_2:.*]] = getelementptr [4 x %"class.hlsl::RWBuffer"], ptr %[[Tmp2]], i32 0, i32 2
54+
// CHECK-NEXT: call void @_ZN4hlsl8RWBufferIiEC1EjijjPKc(ptr {{.*}} %[[Ptr_Tmp2_2]], i32 noundef 0, i32 noundef -1, i32 noundef 54, i32 noundef 0, ptr noundef @B.str)
55+
// CHECK-NEXT: %[[Ptr_Tmp2_3:.*]] = getelementptr [4 x %"class.hlsl::RWBuffer"], ptr %[[Tmp2]], i32 0, i32 3
56+
// CHECK-NEXT: call void @_ZN4hlsl8RWBufferIiEC1EjijjPKc(ptr {{.*}} %[[Ptr_Tmp2_3]], i32 noundef 0, i32 noundef -1, i32 noundef 55, i32 noundef 0, ptr noundef @B.str)
57+
// DXIL-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 %[[Tmp1]], ptr align 4 %[[Tmp2]], i32 16, i1 false)
58+
// SPV-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %[[Tmp1]], ptr align 8 %[[Tmp2]], i64 32, i1 false)
59+
// CHECK-NEXT: %[[GI:.*]] = load i32, ptr %[[GI_alloca]], align 4
60+
// DXIL-NEXT: %[[Value2:.*]] = call {{.*}} float @_Z3fooA4_N4hlsl8RWBufferIiEEj(ptr noundef byval([4 x %"class.hlsl::RWBuffer"]) align 4 %[[Tmp1]], i32 noundef %[[GI]])
61+
// SPV-NEXT: %[[Value2:.*]] = call {{.*}} float @_Z3fooA4_N4hlsl8RWBufferIiEEj(ptr noundef byval([4 x %"class.hlsl::RWBuffer"]) align 8 %[[Tmp1]], i32 noundef %[[GI]])
62+
// CHECK-NEXT: store float %[[Value2]], ptr %b, align 4
63+
float b = foo(B[2][3], GI);
64+
65+
Out[0] = a + b;
66+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// RUN: not %clang_cc1 -triple dxil-pc-shadermodel6.3-compute -finclude-default-header --o - %s -verify
2+
3+
// unbounded resource array at a global scope
4+
RWBuffer<float> unbounded_array[]; // no_error
5+
6+
// expected-error@+1 {{incomplete resource array in a function parameter}}
7+
void foo(RWBuffer<float> array_arg[]) {}
8+
9+
RWBuffer<float> A, B;
10+
11+
[numthreads(4,1,1)]
12+
void main() {
13+
// expected-error@+1{{definition of variable with array type needs an explicit size or an initializer}}
14+
RWBuffer<float> res_local_array1[];
15+
16+
// expected-error@+1{{array initializer must be an initialzer list}}
17+
RWBuffer<float> res_local_array2[] = unbounded_array;
18+
19+
// local incomplete resource array with initializer
20+
RWBuffer<float> res_local_array3[] = { A, B }; // no error
21+
}

0 commit comments

Comments
 (0)