Skip to content
Merged
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
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -13205,6 +13205,9 @@ def err_hlsl_resource_range_overlap: Error<
"%select{All|Vertex|Hull|Domain|Geometry|Pixel|Amplification|Mesh}9">;
def note_hlsl_resource_range_here: Note<"overlapping resource range here">;

def err_hlsl_incomplete_resource_array_in_function_param: Error<
"incomplete resource array in a function parameter">;

// Layout randomization diagnostics.
def err_non_designated_init_used : Error<
"a randomized struct can only be initialized with a designated initializer">;
Expand Down
4 changes: 2 additions & 2 deletions clang/lib/AST/Type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5420,7 +5420,7 @@ bool Type::isHLSLResourceRecordArray() const {
const Type *Ty = getUnqualifiedDesugaredType();
if (!Ty->isArrayType())
return false;
while (isa<ConstantArrayType>(Ty))
while (isa<ArrayType>(Ty))
Ty = Ty->getArrayElementTypeNoTypeQual();
return Ty->isHLSLResourceRecord();
}
Expand All @@ -5433,7 +5433,7 @@ bool Type::isHLSLIntangibleType() const {
return Ty->isHLSLBuiltinIntangibleType();

// unwrap arrays
while (isa<ConstantArrayType>(Ty))
while (isa<ArrayType>(Ty))
Ty = Ty->getArrayElementTypeNoTypeQual();

const RecordType *RT =
Expand Down
17 changes: 14 additions & 3 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14393,9 +14393,12 @@ void Sema::ActOnUninitializedDecl(Decl *RealDecl) {
return;
}

// Provide a specific diagnostic for uninitialized variable
// definitions with incomplete array type.
if (Type->isIncompleteArrayType()) {
// Provide a specific diagnostic for uninitialized variable definitions
// with incomplete array type, unless it is a global unbounded HLSL resource
// array.
if (Type->isIncompleteArrayType() &&
!(getLangOpts().HLSL && Var->hasGlobalStorage() &&
Type->isHLSLResourceRecordArray())) {
if (Var->isConstexpr())
Diag(Var->getLocation(), diag::err_constexpr_var_requires_const_init)
<< Var;
Expand Down Expand Up @@ -15476,6 +15479,14 @@ Decl *Sema::ActOnParamDeclarator(Scope *S, Declarator &D,
}
}

// Incomplete resource arrays are not allowed as function parameters in HLSL
if (getLangOpts().HLSL && parmDeclType->isIncompleteArrayType() &&
parmDeclType->isHLSLResourceRecordArray()) {
Diag(D.getIdentifierLoc(),
diag::err_hlsl_incomplete_resource_array_in_function_param);
D.setInvalidType(true);
}

// Temporarily put parameter variables in the translation unit, not
// the enclosing context. This prevents them from accidentally
// looking like class members in C++.
Expand Down
18 changes: 10 additions & 8 deletions clang/lib/Sema/SemaHLSL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -350,8 +350,8 @@ getResourceArrayHandleType(VarDecl *VD) {
assert(VD->getType()->isHLSLResourceRecordArray() &&
"expected array of resource records");
const Type *Ty = VD->getType()->getUnqualifiedDesugaredType();
while (const ConstantArrayType *CAT = dyn_cast<ConstantArrayType>(Ty))
Ty = CAT->getArrayElementTypeNoTypeQual()->getUnqualifiedDesugaredType();
while (const ArrayType *AT = dyn_cast<ArrayType>(Ty))
Ty = AT->getArrayElementTypeNoTypeQual()->getUnqualifiedDesugaredType();
return HLSLAttributedResourceType::findHandleTypeOnResource(Ty);
}

Expand Down Expand Up @@ -2022,9 +2022,11 @@ static bool DiagnoseHLSLRegisterAttribute(Sema &S, SourceLocation &ArgLoc,
}

void SemaHLSL::handleResourceBindingAttr(Decl *TheDecl, const ParsedAttr &AL) {
if (isa<VarDecl>(TheDecl)) {
if (SemaRef.RequireCompleteType(TheDecl->getBeginLoc(),
cast<ValueDecl>(TheDecl)->getType(),
if (VarDecl *VD = dyn_cast<VarDecl>(TheDecl)) {
QualType Ty = VD->getType();
if (const auto *IAT = dyn_cast<IncompleteArrayType>(Ty))
Ty = IAT->getElementType();
if (SemaRef.RequireCompleteType(TheDecl->getBeginLoc(), Ty,
diag::err_incomplete_type))
return;
}
Expand Down Expand Up @@ -3831,9 +3833,9 @@ void SemaHLSL::collectResourceBindingsOnVarDecl(VarDecl *VD) {
// Unwrap arrays
// FIXME: Calculate array size while unwrapping
const Type *Ty = VD->getType()->getUnqualifiedDesugaredType();
while (Ty->isConstantArrayType()) {
const ConstantArrayType *CAT = cast<ConstantArrayType>(Ty);
Ty = CAT->getElementType()->getUnqualifiedDesugaredType();
while (Ty->isArrayType()) {
const ArrayType *AT = cast<ArrayType>(Ty);
Ty = AT->getElementType()->getUnqualifiedDesugaredType();
}

// Resource (or array of resources)
Expand Down
66 changes: 66 additions & 0 deletions clang/test/CodeGenHLSL/resources/res-array-global-unbounded.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-compute -finclude-default-header \
// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s -check-prefixes=CHECK,DXIL
// RUN: %clang_cc1 -finclude-default-header -triple spirv-unknown-vulkan-compute \
// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s -check-prefixes=CHECK,SPV

// CHECK: @[[BufA:.*]] = private unnamed_addr constant [2 x i8] c"A\00", align 1
// CHECK: @[[BufB:.*]] = private unnamed_addr constant [2 x i8] c"B\00", align 1

RWBuffer<float> A[] : register(u10, space1);
RWBuffer<int> B[][5][4];

RWStructuredBuffer<float> Out;

float foo(RWBuffer<int> Arr[4], uint Index) {
return (float)Arr[Index][0];
}

// NOTE:
// - _ZN4hlsl8RWBufferIfEC1EjjijPKc is the constructor call for explicit binding for RWBuffer<float>
// (has "jjij" in the mangled name) and the arguments are (register, space, range_size, index, name).
// - _ZN4hlsl8RWBufferIiEC1EjijjPKc is the constructor call for implicit binding for RWBuffer<int>
// (has "jijj" in the mangled name) and the arguments are (space, range_size, index, order_id, name).
// - _ZN4hlsl8RWBufferIfEixEj is the subscript operator on RWBuffer<float>
Comment on lines +19 to +23
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we/should we pipe to llvm-cxxfilt in the RUN lines so this explanation isn't necessary? I see some precedent for that in other clang/test/CodeGen* tests...

Copy link
Member Author

@hekota hekota Sep 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea! If it's ok with you, I would add the llvm-cxxfilt filter as part of the change to use static create methods because that change will be removing these lines anyway.


[numthreads(4,1,1)]
void main(uint GI : SV_GroupIndex) {
// CHECK: define internal {{.*}}void @_Z4mainj(i32 noundef %GI)
// CHECK: %[[GI_alloca:.*]] = alloca i32, align 4
// CHECK-NEXT: %a = alloca float, align 4
// CHECK-NEXT: %[[Tmp0:.*]] = alloca %"class.hlsl::RWBuffer
// CHECK-NEXT: %b = alloca float, align 4
// CHECK-NEXT: %[[Tmp1:.*]] = alloca [4 x %"class.hlsl::RWBuffer"]
// CHECK-NEXT: %[[Tmp2:.*]] = alloca [4 x %"class.hlsl::RWBuffer"]
// CHECK-NEXT: store i32 %GI, ptr %[[GI_alloca]], align 4

// Make sure A[100] is translated to a RWBuffer<float> constructor call with range -1 and index 100
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using -1 for the range might require special casing in the SPIR-V backend. I'll follow up on that later.

// and explicit binding (u10, space1)
// CHECK: call void @_ZN4hlsl8RWBufferIfEC1EjjijPKc(ptr {{.*}} %[[Tmp0]], i32 noundef 10, i32 noundef 1, i32 noundef -1, i32 noundef 100, ptr noundef @A.str)
// CHECK-NEXT: %[[BufPtr:.*]] = call {{.*}} ptr{{.*}} @_ZN4hlsl8RWBufferIfEixEj(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
// (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.
// CHECK-NEXT: %[[Ptr_Tmp2_0:.*]] = getelementptr [4 x %"class.hlsl::RWBuffer"], ptr %[[Tmp2]], i32 0, i32 0
// 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)
// CHECK-NEXT: %[[Ptr_Tmp2_1:.*]] = getelementptr [4 x %"class.hlsl::RWBuffer"], ptr %[[Tmp2]], i32 0, i32 1
// 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)
// CHECK-NEXT: %[[Ptr_Tmp2_2:.*]] = getelementptr [4 x %"class.hlsl::RWBuffer"], ptr %[[Tmp2]], i32 0, i32 2
// 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)
// CHECK-NEXT: %[[Ptr_Tmp2_3:.*]] = getelementptr [4 x %"class.hlsl::RWBuffer"], ptr %[[Tmp2]], i32 0, i32 3
// 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)
// 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)
// CHECK-NEXT: %[[GI:.*]] = load i32, ptr %[[GI_alloca]], align 4
// DXIL-NEXT: %[[Value2:.*]] = call {{.*}} float @_Z3fooA4_N4hlsl8RWBufferIiEEj(ptr noundef byval([4 x %"class.hlsl::RWBuffer"]) align 4 %[[Tmp1]], i32 noundef %[[GI]])
// SPV-NEXT: %[[Value2:.*]] = call {{.*}} float @_Z3fooA4_N4hlsl8RWBufferIiEEj(ptr noundef byval([4 x %"class.hlsl::RWBuffer"]) align 8 %[[Tmp1]], i32 noundef %[[GI]])
// CHECK-NEXT: store float %[[Value2]], ptr %b, align 4
float b = foo(B[2][3], GI);

Out[0] = a + b;
}
21 changes: 21 additions & 0 deletions clang/test/SemaHLSL/unbounded_resource_arrays.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// RUN: not %clang_cc1 -triple dxil-pc-shadermodel6.3-compute -finclude-default-header --o - %s -verify

// unbounded resource array at a global scope
RWBuffer<float> unbounded_array[]; // no_error

// expected-error@+1 {{incomplete resource array in a function parameter}}
void foo(RWBuffer<float> array_arg[]) {}

RWBuffer<float> A, B;

[numthreads(4,1,1)]
void main() {
// expected-error@+1{{definition of variable with array type needs an explicit size or an initializer}}
RWBuffer<float> res_local_array1[];

// expected-error@+1{{array initializer must be an initialzer list}}
RWBuffer<float> res_local_array2[] = unbounded_array;

// local incomplete resource array with initializer
RWBuffer<float> res_local_array3[] = { A, B }; // no error
}