diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index a591a25367301..711efbe727892 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -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">; diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index 3432810a7be44..86621795d81e6 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -5420,7 +5420,7 @@ bool Type::isHLSLResourceRecordArray() const { const Type *Ty = getUnqualifiedDesugaredType(); if (!Ty->isArrayType()) return false; - while (isa(Ty)) + while (isa(Ty)) Ty = Ty->getArrayElementTypeNoTypeQual(); return Ty->isHLSLResourceRecord(); } @@ -5433,7 +5433,7 @@ bool Type::isHLSLIntangibleType() const { return Ty->isHLSLBuiltinIntangibleType(); // unwrap arrays - while (isa(Ty)) + while (isa(Ty)) Ty = Ty->getArrayElementTypeNoTypeQual(); const RecordType *RT = diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 12bedae05f6f3..365ebb63b1559 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -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; @@ -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++. diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index 722e804632a61..fb8f131d1e11b 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -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(Ty)) - Ty = CAT->getArrayElementTypeNoTypeQual()->getUnqualifiedDesugaredType(); + while (const ArrayType *AT = dyn_cast(Ty)) + Ty = AT->getArrayElementTypeNoTypeQual()->getUnqualifiedDesugaredType(); return HLSLAttributedResourceType::findHandleTypeOnResource(Ty); } @@ -2022,9 +2022,11 @@ static bool DiagnoseHLSLRegisterAttribute(Sema &S, SourceLocation &ArgLoc, } void SemaHLSL::handleResourceBindingAttr(Decl *TheDecl, const ParsedAttr &AL) { - if (isa(TheDecl)) { - if (SemaRef.RequireCompleteType(TheDecl->getBeginLoc(), - cast(TheDecl)->getType(), + if (VarDecl *VD = dyn_cast(TheDecl)) { + QualType Ty = VD->getType(); + if (const auto *IAT = dyn_cast(Ty)) + Ty = IAT->getElementType(); + if (SemaRef.RequireCompleteType(TheDecl->getBeginLoc(), Ty, diag::err_incomplete_type)) return; } @@ -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(Ty); - Ty = CAT->getElementType()->getUnqualifiedDesugaredType(); + while (Ty->isArrayType()) { + const ArrayType *AT = cast(Ty); + Ty = AT->getElementType()->getUnqualifiedDesugaredType(); } // Resource (or array of resources) diff --git a/clang/test/CodeGenHLSL/resources/res-array-global-unbounded.hlsl b/clang/test/CodeGenHLSL/resources/res-array-global-unbounded.hlsl new file mode 100644 index 0000000000000..edf9ce01f72a6 --- /dev/null +++ b/clang/test/CodeGenHLSL/resources/res-array-global-unbounded.hlsl @@ -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 A[] : register(u10, space1); +RWBuffer B[][5][4]; + +RWStructuredBuffer Out; + +float foo(RWBuffer Arr[4], uint Index) { + return (float)Arr[Index][0]; +} + +// NOTE: +// - _ZN4hlsl8RWBufferIfEC1EjjijPKc is the constructor call for explicit binding for RWBuffer +// (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 +// (has "jijj" in the mangled name) and the arguments are (space, range_size, index, order_id, name). +// - _ZN4hlsl8RWBufferIfEixEj is the subscript operator on RWBuffer + +[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 constructor call with range -1 and index 100 + // 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[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; +} diff --git a/clang/test/SemaHLSL/unbounded_resource_arrays.hlsl b/clang/test/SemaHLSL/unbounded_resource_arrays.hlsl new file mode 100644 index 0000000000000..b65deeda9a4ff --- /dev/null +++ b/clang/test/SemaHLSL/unbounded_resource_arrays.hlsl @@ -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 unbounded_array[]; // no_error + +// expected-error@+1 {{incomplete resource array in a function parameter}} +void foo(RWBuffer array_arg[]) {} + +RWBuffer 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 res_local_array1[]; + + // expected-error@+1{{array initializer must be an initialzer list}} + RWBuffer res_local_array2[] = unbounded_array; + + // local incomplete resource array with initializer + RWBuffer res_local_array3[] = { A, B }; // no error +}