Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
64 changes: 64 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,64 @@
// 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-54
// and implicit binding (space 0, order_id 0)
// 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
}