diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h index 085c9ed9f3ebd..016456f241eed 100644 --- a/clang/include/clang/Sema/SemaHLSL.h +++ b/clang/include/clang/Sema/SemaHLSL.h @@ -229,10 +229,17 @@ class SemaHLSL : public SemaBase { void diagnoseAvailabilityViolations(TranslationUnitDecl *TU); - bool initGlobalResourceDecl(VarDecl *VD); uint32_t getNextImplicitBindingOrderID() { return ImplicitBindingNextOrderID++; } + + bool initGlobalResourceDecl(VarDecl *VD); + bool initGlobalResourceArrayDecl(VarDecl *VD); + void createResourceRecordCtorArgs(const Type *ResourceTy, StringRef VarName, + HLSLResourceBindingAttr *RBA, + HLSLVkBindingAttr *VkBinding, + uint32_t ArrayIndex, + llvm::SmallVectorImpl &Args); }; } // namespace clang diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp index d5df6dd3e303c..d229d81d6b934 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -16,6 +16,7 @@ #include "CGCall.h" #include "CGCleanup.h" #include "CGDebugInfo.h" +#include "CGHLSLRuntime.h" #include "CGObjCRuntime.h" #include "CGOpenMPRuntime.h" #include "CGRecordLayout.h" @@ -4534,6 +4535,15 @@ LValue CodeGenFunction::EmitArraySubscriptExpr(const ArraySubscriptExpr *E, LHS.getBaseInfo(), TBAAAccessInfo()); } + // The HLSL runtime handle the subscript expression on global resource arrays. + if (getLangOpts().HLSL && (E->getType()->isHLSLResourceRecord() || + E->getType()->isHLSLResourceRecordArray())) { + std::optional LV = + CGM.getHLSLRuntime().emitResourceArraySubscriptExpr(E, *this); + if (LV.has_value()) + return *LV; + } + // All the other cases basically behave like simple offsetting. // Handle the extvector case we ignored above. diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp index 9b3ef6aafd05f..1c947f36347ec 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.cpp +++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp @@ -22,6 +22,7 @@ #include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/Type.h" #include "clang/Basic/TargetOptions.h" +#include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Frontend/HLSL/RootSignatureMetadata.h" #include "llvm/IR/Constants.h" @@ -84,6 +85,111 @@ void addRootSignature(llvm::dxbc::RootSignatureVersion RootSigVer, RootSignatureValMD->addOperand(MDVals); } +// Find array variable declaration from nested array subscript AST nodes +static const ValueDecl *getArrayDecl(const ArraySubscriptExpr *ASE) { + const Expr *E = nullptr; + while (ASE != nullptr) { + E = ASE->getBase()->IgnoreImpCasts(); + if (!E) + return nullptr; + ASE = dyn_cast(E); + } + if (const DeclRefExpr *DRE = dyn_cast_or_null(E)) + return DRE->getDecl(); + return nullptr; +} + +// Get the total size of the array, or -1 if the array is unbounded. +static int getTotalArraySize(ASTContext &AST, const clang::Type *Ty) { + Ty = Ty->getUnqualifiedDesugaredType(); + assert(Ty->isArrayType() && "expected array type"); + if (Ty->isIncompleteArrayType()) + return -1; + return AST.getConstantArrayElementCount(cast(Ty)); +} + +// Find constructor decl for a specific resource record type and binding +// (implicit vs. explicit). The constructor has 5 parameters. +// For explicit binding the signature is: +// void(unsigned, unsigned, int, unsigned, const char *). +// For implicit binding the signature is: +// void(unsigned, int, unsigned, unsigned, const char *). +static CXXConstructorDecl *findResourceConstructorDecl(ASTContext &AST, + QualType ResTy, + bool ExplicitBinding) { + SmallVector ExpParmTypes = { + AST.UnsignedIntTy, AST.UnsignedIntTy, AST.UnsignedIntTy, + AST.UnsignedIntTy, AST.getPointerType(AST.CharTy.withConst())}; + ExpParmTypes[ExplicitBinding ? 2 : 1] = AST.IntTy; + + CXXRecordDecl *ResDecl = ResTy->getAsCXXRecordDecl(); + for (auto *Ctor : ResDecl->ctors()) { + if (Ctor->getNumParams() != ExpParmTypes.size()) + continue; + ParmVarDecl **ParmIt = Ctor->param_begin(); + QualType *ExpTyIt = ExpParmTypes.begin(); + for (; ParmIt != Ctor->param_end() && ExpTyIt != ExpParmTypes.end(); + ++ParmIt, ++ExpTyIt) { + if ((*ParmIt)->getType() != *ExpTyIt) + break; + } + if (ParmIt == Ctor->param_end()) + return Ctor; + } + llvm_unreachable("did not find constructor for resource class"); +} + +static Value *buildNameForResource(llvm::StringRef BaseName, + CodeGenModule &CGM) { + llvm::SmallString<64> GlobalName = {BaseName, ".str"}; + return CGM.GetAddrOfConstantCString(BaseName.str(), GlobalName.c_str()) + .getPointer(); +} + +static void createResourceCtorArgs(CodeGenModule &CGM, CXXConstructorDecl *CD, + llvm::Value *ThisPtr, llvm::Value *Range, + llvm::Value *Index, StringRef Name, + HLSLResourceBindingAttr *RBA, + HLSLVkBindingAttr *VkBinding, + CallArgList &Args) { + assert((VkBinding || RBA) && "at least one a binding attribute expected"); + + std::optional RegisterSlot; + uint32_t SpaceNo = 0; + if (VkBinding) { + RegisterSlot = VkBinding->getBinding(); + SpaceNo = VkBinding->getSet(); + } else { + if (RBA->hasRegisterSlot()) + RegisterSlot = RBA->getSlotNumber(); + SpaceNo = RBA->getSpaceNumber(); + } + + ASTContext &AST = CD->getASTContext(); + Value *NameStr = buildNameForResource(Name, CGM); + Value *Space = llvm::ConstantInt::get(CGM.IntTy, SpaceNo); + + Args.add(RValue::get(ThisPtr), CD->getThisType()); + if (RegisterSlot.has_value()) { + // explicit binding + auto *RegSlot = llvm::ConstantInt::get(CGM.IntTy, RegisterSlot.value()); + Args.add(RValue::get(RegSlot), AST.UnsignedIntTy); + Args.add(RValue::get(Space), AST.UnsignedIntTy); + Args.add(RValue::get(Range), AST.IntTy); + Args.add(RValue::get(Index), AST.UnsignedIntTy); + + } else { + // implicit binding + auto *OrderID = + llvm::ConstantInt::get(CGM.IntTy, RBA->getImplicitBindingOrderID()); + Args.add(RValue::get(Space), AST.UnsignedIntTy); + Args.add(RValue::get(Range), AST.IntTy); + Args.add(RValue::get(Index), AST.UnsignedIntTy); + Args.add(RValue::get(OrderID), AST.UnsignedIntTy); + } + Args.add(RValue::get(NameStr), AST.getPointerType(AST.CharTy.withConst())); +} + } // namespace llvm::Type * @@ -589,13 +695,6 @@ static void initializeBuffer(CodeGenModule &CGM, llvm::GlobalVariable *GV, CGM.AddCXXGlobalInit(InitResFunc); } -static Value *buildNameForResource(llvm::StringRef BaseName, - CodeGenModule &CGM) { - std::string Str(BaseName); - std::string GlobalName(Str + ".str"); - return CGM.GetAddrOfConstantCString(Str, GlobalName.c_str()).getPointer(); -} - void CGHLSLRuntime::initializeBufferFromBinding(const HLSLBufferDecl *BufDecl, llvm::GlobalVariable *GV, HLSLVkBindingAttr *VkBinding) { @@ -623,17 +722,13 @@ void CGHLSLRuntime::initializeBufferFromBinding(const HLSLBufferDecl *BufDecl, auto *Index = llvm::ConstantInt::get(CGM.IntTy, 0); auto *RangeSize = llvm::ConstantInt::get(CGM.IntTy, 1); auto *Space = llvm::ConstantInt::get(CGM.IntTy, RBA->getSpaceNumber()); - Value *Name = nullptr; + Value *Name = buildNameForResource(BufDecl->getName(), CGM); llvm::Intrinsic::ID IntrinsicID = RBA->hasRegisterSlot() ? CGM.getHLSLRuntime().getCreateHandleFromBindingIntrinsic() : CGM.getHLSLRuntime().getCreateHandleFromImplicitBindingIntrinsic(); - std::string Str(BufDecl->getName()); - std::string GlobalName(Str + ".str"); - Name = CGM.GetAddrOfConstantCString(Str, GlobalName.c_str()).getPointer(); - // buffer with explicit binding if (RBA->hasRegisterSlot()) { auto *RegSlot = llvm::ConstantInt::get(CGM.IntTy, RBA->getSlotNumber()); @@ -700,3 +795,94 @@ void CGHLSLRuntime::emitInitListOpaqueValues(CodeGenFunction &CGF, } } } + +std::optional CGHLSLRuntime::emitResourceArraySubscriptExpr( + const ArraySubscriptExpr *ArraySubsExpr, CodeGenFunction &CGF) { + assert(ArraySubsExpr->getType()->isHLSLResourceRecord() || + ArraySubsExpr->getType()->isHLSLResourceRecordArray() && + "expected resource array subscript expression"); + + // let clang codegen handle local resource array subscripts + const VarDecl *ArrayDecl = dyn_cast(getArrayDecl(ArraySubsExpr)); + if (!ArrayDecl || !ArrayDecl->hasGlobalStorage()) + return std::nullopt; + + // FIXME: this is not yet implemented (llvm/llvm-project#145426) + assert(!ArraySubsExpr->getType()->isArrayType() && + "indexing of array subsets it not supported yet"); + + // get the resource array type + const Type *ResArrayTy = ArrayDecl->getType().getTypePtr(); + assert(ResArrayTy->isHLSLResourceRecordArray() && + "expected array of resource classes"); + + // Iterate through all nested array subscript expressions to calculate + // the index in the flattened resource array (if this is a multi- + // dimensional array). The index is calculated as a sum of all indices + // multiplied by the total size of the array at that level. + Value *Index = nullptr; + Value *Multiplier = nullptr; + const ArraySubscriptExpr *ASE = ArraySubsExpr; + while (ASE != nullptr) { + Value *SubIndex = CGF.EmitScalarExpr(ASE->getIdx()); + if (const auto *ArrayTy = + dyn_cast(ASE->getType().getTypePtr())) { + Value *SubMultiplier = + llvm::ConstantInt::get(CGM.IntTy, ArrayTy->getSExtSize()); + Multiplier = Multiplier ? CGF.Builder.CreateMul(Multiplier, SubMultiplier) + : SubMultiplier; + SubIndex = CGF.Builder.CreateMul(SubIndex, Multiplier); + } + + Index = Index ? CGF.Builder.CreateAdd(Index, SubIndex) : SubIndex; + ASE = dyn_cast(ASE->getBase()->IgnoreParenImpCasts()); + } + + // find binding info for the resource array + // (for implicit binding an HLSLResourceBindingAttr should have been added by + // SemaHLSL) + QualType ResourceTy = ArraySubsExpr->getType(); + HLSLVkBindingAttr *VkBinding = ArrayDecl->getAttr(); + HLSLResourceBindingAttr *RBA = ArrayDecl->getAttr(); + assert((VkBinding || RBA) && "resource array must have a binding attribute"); + + // lookup the resource class constructor based on the resource type and + // binding + ASTContext &AST = ArrayDecl->getASTContext(); + CXXConstructorDecl *CD = findResourceConstructorDecl( + AST, ResourceTy, VkBinding || RBA->hasRegisterSlot()); + + // create a temporary variable for the resource class instance (we need to + // return an LValue) + RawAddress TmpVar = CGF.CreateMemTemp(ResourceTy); + if (CGF.EmitLifetimeStart(TmpVar.getPointer())) { + CGF.pushFullExprCleanup( + NormalEHLifetimeMarker, TmpVar); + } + AggValueSlot ValueSlot = AggValueSlot::forAddr( + TmpVar, Qualifiers(), AggValueSlot::IsDestructed_t(true), + AggValueSlot::DoesNotNeedGCBarriers, AggValueSlot::IsAliased_t(false), + AggValueSlot::DoesNotOverlap); + + Address ThisAddress = ValueSlot.getAddress(); + llvm::Value *ThisPtr = CGF.getAsNaturalPointerTo( + ThisAddress, CD->getThisType()->getPointeeType()); + + // get total array size (= range size) + llvm::Value *Range = + llvm::ConstantInt::get(CGM.IntTy, getTotalArraySize(AST, ResArrayTy)); + + // assemble the constructor parameters + CallArgList Args; + createResourceCtorArgs(CGM, CD, ThisPtr, Range, Index, ArrayDecl->getName(), + RBA, VkBinding, Args); + + // call the constructor + CGF.EmitCXXConstructorCall(CD, Ctor_Complete, false, false, ThisAddress, Args, + ValueSlot.mayOverlap(), + ArraySubsExpr->getExprLoc(), + ValueSlot.isSanitizerChecked()); + + return CGF.MakeAddrLValue(TmpVar, ArraySubsExpr->getType(), + AlignmentSource::Decl); +} diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h index 31d1728da9c56..b872f9ef0e9b6 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.h +++ b/clang/lib/CodeGen/CGHLSLRuntime.h @@ -68,6 +68,7 @@ class Type; class RecordType; class DeclContext; class HLSLPackOffsetAttr; +class ArraySubscriptExpr; class FunctionDecl; @@ -75,6 +76,7 @@ namespace CodeGen { class CodeGenModule; class CodeGenFunction; +class LValue; class CGHLSLRuntime { public: @@ -164,6 +166,10 @@ class CGHLSLRuntime { llvm::TargetExtType *LayoutTy); void emitInitListOpaqueValues(CodeGenFunction &CGF, InitListExpr *E); + std::optional + emitResourceArraySubscriptExpr(const ArraySubscriptExpr *E, + CodeGenFunction &CGF); + private: void emitBufferGlobalsAndMetadata(const HLSLBufferDecl *BufDecl, llvm::GlobalVariable *BufGV); diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 2541a441c7d59..590705508b7e8 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -5752,11 +5752,16 @@ void CodeGenModule::EmitGlobalVarDefinition(const VarDecl *D, (D->getType()->isCUDADeviceBuiltinSurfaceType() || D->getType()->isCUDADeviceBuiltinTextureType()); if (getLangOpts().CUDA && - (IsCUDASharedVar || IsCUDAShadowVar || IsCUDADeviceShadowVar)) + (IsCUDASharedVar || IsCUDAShadowVar || IsCUDADeviceShadowVar)) { Init = llvm::UndefValue::get(getTypes().ConvertTypeForMem(ASTTy)); - else if (D->hasAttr()) + } else if (getLangOpts().HLSL && + (D->getType()->isHLSLResourceRecord() || + D->getType()->isHLSLResourceRecordArray())) { + Init = llvm::PoisonValue::get(getTypes().ConvertType(ASTTy)); + NeedsGlobalCtor = D->getType()->isHLSLResourceRecord(); + } else if (D->hasAttr()) { Init = llvm::UndefValue::get(getTypes().ConvertTypeForMem(ASTTy)); - else if (!InitExpr) { + } else if (!InitExpr) { // This is a tentative definition; tentative definitions are // implicitly initialized with { 0 }. // @@ -5777,11 +5782,7 @@ void CodeGenModule::EmitGlobalVarDefinition(const VarDecl *D, if (D->getType()->isReferenceType()) T = D->getType(); - if (getLangOpts().HLSL && - D->getType().getTypePtr()->isHLSLResourceRecord()) { - Init = llvm::PoisonValue::get(getTypes().ConvertType(ASTTy)); - NeedsGlobalCtor = true; - } else if (getLangOpts().CPlusPlus) { + if (getLangOpts().CPlusPlus) { Init = EmitNullConstant(T); if (!IsDefinitionAvailableExternally) NeedsGlobalCtor = true; diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index 47a4874721db8..f87715950c74c 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -3697,11 +3697,12 @@ static bool initVarDeclWithCtor(Sema &S, VarDecl *VD, return true; } -bool SemaHLSL::initGlobalResourceDecl(VarDecl *VD) { +void SemaHLSL::createResourceRecordCtorArgs( + const Type *ResourceTy, StringRef VarName, HLSLResourceBindingAttr *RBA, + HLSLVkBindingAttr *VkBinding, uint32_t ArrayIndex, + llvm::SmallVectorImpl &Args) { std::optional RegisterSlot; uint32_t SpaceNo = 0; - HLSLVkBindingAttr *VkBinding = VD->getAttr(); - HLSLResourceBindingAttr *RBA = VD->getAttr(); if (VkBinding) { RegisterSlot = VkBinding->getBinding(); SpaceNo = VkBinding->getSet(); @@ -3716,12 +3717,12 @@ bool SemaHLSL::initGlobalResourceDecl(VarDecl *VD) { uint64_t IntTySize = AST.getTypeSize(AST.IntTy); IntegerLiteral *RangeSize = IntegerLiteral::Create( AST, llvm::APInt(IntTySize, 1), AST.IntTy, SourceLocation()); - IntegerLiteral *Index = IntegerLiteral::Create( - AST, llvm::APInt(UIntTySize, 0), AST.UnsignedIntTy, SourceLocation()); + IntegerLiteral *Index = + IntegerLiteral::Create(AST, llvm::APInt(UIntTySize, ArrayIndex), + AST.UnsignedIntTy, SourceLocation()); IntegerLiteral *Space = IntegerLiteral::Create(AST, llvm::APInt(UIntTySize, SpaceNo), AST.UnsignedIntTy, SourceLocation()); - StringRef VarName = VD->getName(); StringLiteral *Name = StringLiteral::Create( AST, VarName, StringLiteralKind::Ordinary, false, AST.getStringLiteralArrayType(AST.CharTy.withConst(), VarName.size()), @@ -3732,18 +3733,57 @@ bool SemaHLSL::initGlobalResourceDecl(VarDecl *VD) { IntegerLiteral *RegSlot = IntegerLiteral::Create( AST, llvm::APInt(UIntTySize, RegisterSlot.value()), AST.UnsignedIntTy, SourceLocation()); - Expr *Args[] = {RegSlot, Space, RangeSize, Index, Name}; - return initVarDeclWithCtor(SemaRef, VD, Args); + Args.append({RegSlot, Space, RangeSize, Index, Name}); + } else { + // resource with implicit binding + uint32_t OrderID = (RBA && RBA->hasImplicitBindingOrderID()) + ? RBA->getImplicitBindingOrderID() + : getNextImplicitBindingOrderID(); + IntegerLiteral *OrderId = + IntegerLiteral::Create(AST, llvm::APInt(UIntTySize, OrderID), + AST.UnsignedIntTy, SourceLocation()); + Args.append({Space, RangeSize, Index, OrderId, Name}); } +} - // resource with implicit binding - IntegerLiteral *OrderId = IntegerLiteral::Create( - AST, llvm::APInt(UIntTySize, getNextImplicitBindingOrderID()), - AST.UnsignedIntTy, SourceLocation()); - Expr *Args[] = {Space, RangeSize, Index, OrderId, Name}; +bool SemaHLSL::initGlobalResourceDecl(VarDecl *VD) { + SmallVector Args; + createResourceRecordCtorArgs(VD->getType().getTypePtr(), VD->getName(), + VD->getAttr(), + VD->getAttr(), 0, Args); return initVarDeclWithCtor(SemaRef, VD, Args); } +bool SemaHLSL::initGlobalResourceArrayDecl(VarDecl *VD) { + assert(VD->getType()->isHLSLResourceRecordArray() && + "expected array of resource records"); + + // Individual resources in a resource array are not initialized here. They + // are initialized later on during codegen when the individual resources are + // accessed. Codegen will emit a call to the resource constructor with the + // specified array index. We need to make sure though that the constructor + // for the specific resource type is instantiated, so codegen can emit a call + // to it when the array element is accessed. + SmallVector Args; + QualType ResElementTy = VD->getASTContext().getBaseElementType(VD->getType()); + createResourceRecordCtorArgs(ResElementTy.getTypePtr(), VD->getName(), + VD->getAttr(), + VD->getAttr(), 0, Args); + + SourceLocation Loc = VD->getLocation(); + InitializedEntity Entity = + InitializedEntity::InitializeTemporary(ResElementTy); + InitializationKind Kind = InitializationKind::CreateDirect(Loc, Loc, Loc); + InitializationSequence InitSeq(SemaRef, Entity, Kind, Args); + if (InitSeq.Failed()) + return false; + + // This takes care of instantiating and emitting of the constructor that will + // be called from codegen when the array is accessed. + ExprResult OneResInit = InitSeq.Perform(SemaRef, Entity, Kind, Args); + return !OneResInit.isInvalid(); +} + // Returns true if the initialization has been handled. // Returns false to use default initialization. bool SemaHLSL::ActOnUninitializedVarDecl(VarDecl *VD) { @@ -3752,17 +3792,14 @@ bool SemaHLSL::ActOnUninitializedVarDecl(VarDecl *VD) { if (VD->getType().getAddressSpace() == LangAS::hlsl_constant) return true; - // Initialize resources - if (!isResourceRecordTypeOrArrayOf(VD)) - return false; - - // FIXME: We currectly support only simple resources - no arrays of resources - // or resources in user defined structs. - // (llvm/llvm-project#133835, llvm/llvm-project#133837) // Initialize resources at the global scope - if (VD->hasGlobalStorage() && VD->getType()->isHLSLResourceRecord()) - return initGlobalResourceDecl(VD); - + if (VD->hasGlobalStorage()) { + const Type *Ty = VD->getType().getTypePtr(); + if (Ty->isHLSLResourceRecord()) + return initGlobalResourceDecl(VD); + if (Ty->isHLSLResourceRecordArray()) + return initGlobalResourceArrayDecl(VD); + } return false; } diff --git a/clang/test/CodeGenHLSL/convergence/global_array.hlsl b/clang/test/CodeGenHLSL/convergence/global_array.hlsl index 030ba48a5f170..c594e3a3e62ae 100644 --- a/clang/test/CodeGenHLSL/convergence/global_array.hlsl +++ b/clang/test/CodeGenHLSL/convergence/global_array.hlsl @@ -6,9 +6,15 @@ // CHECK: [[loop_entry]]: // CHECK: [[loop_token:%.*]] = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token [[entry_token]]) ] -// CHECK: call void {{.*}} [ "convergencectrl"(token [[loop_token]]) ] +// CHECK: call spir_func void {{.*}} [ "convergencectrl"(token [[loop_token]]) ] // CHECK: br i1 {{%.*}} label {{%.*}} label %[[loop_entry]] -RWBuffer e[2]; + +struct S { + int i; + S() { i = 10; } +}; + +static S s[2]; [numthreads(4,1,1)] void main() { diff --git a/clang/test/CodeGenHLSL/resources/res-array-global-dyn-index.hlsl b/clang/test/CodeGenHLSL/resources/res-array-global-dyn-index.hlsl new file mode 100644 index 0000000000000..5b62452ef9844 --- /dev/null +++ b/clang/test/CodeGenHLSL/resources/res-array-global-dyn-index.hlsl @@ -0,0 +1,29 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-compute -finclude-default-header \ +// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s + +// CHECK: @[[BufA:.*]] = private unnamed_addr constant [2 x i8] c"A\00", align 1 + +RWBuffer A[4][3] : register(u2); +RWStructuredBuffer Out; + +// Make sure A[GI.x][GI.y] is translated to a RWBuffer constructor call with range 12 and dynamically calculated index + +// NOTE: +// Constructor call for explicit binding has "jjij" in the mangled name and the arguments are (register, space, range_size, index, name). + +// CHECK: define internal void @_Z4mainDv3_j(<3 x i32> noundef %GI) +// CHECK: %[[GI_alloca:.*]] = alloca <3 x i32>, align 16 +// CHECK: %[[Tmp0:.*]] = alloca %"class.hlsl::RWBuffer +// CHECK: store <3 x i32> %GI, ptr %[[GI_alloca]] + +// CHECK: %[[GI:.*]] = load <3 x i32>, ptr %[[GI_alloca]], align 16 +// CHECK: %[[GI_y:.*]] = extractelement <3 x i32> %[[GI]], i32 1 +// CHECK: %[[GI:.*]] = load <3 x i32>, ptr %[[GI_alloca]], align 16 +// CHECK: %[[GI_x:.*]] = extractelement <3 x i32> %[[GI]], i32 0 +// CHECK: %[[Tmp1:.*]] = mul i32 %[[GI_x]], 3 +// CHECK: %[[Index:.*]] = add i32 %[[GI_y]], %[[Tmp1]] +// CHECK: call void @_ZN4hlsl8RWBufferIfEC1EjjijPKc(ptr {{.*}} %[[Tmp0]], i32 noundef 2, i32 noundef 0, i32 noundef 12, i32 noundef %[[Index]], ptr noundef @A.str) +[numthreads(4,1,1)] +void main(uint3 GI : SV_GroupThreadID) { + Out[0] = A[GI.x][GI.y][0]; +} diff --git a/clang/test/CodeGenHLSL/resources/res-array-global-multi-dim.hlsl b/clang/test/CodeGenHLSL/resources/res-array-global-multi-dim.hlsl new file mode 100644 index 0000000000000..8d664dad58153 --- /dev/null +++ b/clang/test/CodeGenHLSL/resources/res-array-global-multi-dim.hlsl @@ -0,0 +1,46 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-compute -finclude-default-header \ +// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s +// RUN: %clang_cc1 -finclude-default-header -triple spirv-unknown-vulkan-compute \ +// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s + +// CHECK: @[[BufB:.*]] = private unnamed_addr constant [2 x i8] c"B\00", align 1 +// CHECK: @[[BufC:.*]] = private unnamed_addr constant [2 x i8] c"C\00", align 1 +// CHECK: @[[BufD:.*]] = private unnamed_addr constant [2 x i8] c"D\00", align 1 + +RWBuffer B[4][4] : register(u2); +RWBuffer C[2][2][5] : register(u10, space1); + +typedef RWBuffer RWBufferArrayTenByFive[10][5]; // test typedef for the resource array type +RWBufferArrayTenByFive D; // implicit binding -> u18, space0 + +RWStructuredBuffer Out; + +[numthreads(4,1,1)] +void main() { + // CHECK: define internal{{.*}} void @_Z4mainv() + // CHECK: %[[Tmp0:.*]] = alloca %"class.hlsl::RWBuffer + // CHECK: %[[Tmp1:.*]] = alloca %"class.hlsl::RWBuffer + // CHECK: %[[Tmp2:.*]] = alloca %"class.hlsl::RWBuffer + // CHECK: %[[Tmp3:.*]] = alloca %"class.hlsl::RWBuffer + + // NOTE: + // Constructor call for explicit binding has "jjij" in the mangled name and the arguments are (register, space, range_size, index, name). + // For implicit binding the constructor has "jijj" in the mangled name and the arguments are (space, range_size, index, order_id, name). + // The range_size can be -1 for unbounded arrays, and that is the only signed int in the signature. + // The order_id argument is a sequential number that is assigned to resources with implicit binding and corresponds to the order in which + // the resources were declared. It is needed because implicit bindings are assigned later on in an LLVM pass that needs to know the order + // of the resource declarations. + + // Make sure that B[3][2] is translated to a RWBuffer constructor call for explicit binding (u2, space0) with range 16 and index 14 + // CHECK: call void @_ZN4hlsl8RWBufferIfEC1EjjijPKc(ptr {{.*}} %[[Tmp0]], i32 noundef 2, i32 noundef 0, i32 noundef 16, i32 noundef 14, ptr noundef @[[BufB]]) + + // Make sure that C[1][0][3] is translated to a RWBuffer constructor call for explicit binding (u10, space1) with range 20 and index 13 + // CHECK: call void @_ZN4hlsl8RWBufferIiEC1EjjijPKc(ptr {{.*}} %[[Tmp1]], i32 noundef 10, i32 noundef 1, i32 noundef 20, i32 noundef 13, ptr noundef @[[BufC]]) + + // Make sure that D[9][2] is translated to a RWBuffer constructor call for implicit binding (u18, space0) with range 50 and index 47 + // CHECK: call void @_ZN4hlsl8RWBufferIjEC1EjijjPKc(ptr {{.*}} %[[Tmp2]], i32 noundef 0, i32 noundef 50, i32 noundef 47, i32 noundef 0, ptr noundef @[[BufD]]) + + // Make sure that the second B[3][2] is translated to the same a RWBuffer constructor call as the first B[3][2] subscript + // CHECK: call void @_ZN4hlsl8RWBufferIfEC1EjjijPKc(ptr {{.*}} %[[Tmp3]], i32 noundef 2, i32 noundef 0, i32 noundef 16, i32 noundef 14, ptr noundef @[[BufB]]) + Out[0] = B[3][2][0] + (float)C[1][0][3][0] + (float)D[9][2][0] + B[3][2][1]; +} diff --git a/clang/test/CodeGenHLSL/resources/res-array-global.hlsl b/clang/test/CodeGenHLSL/resources/res-array-global.hlsl new file mode 100644 index 0000000000000..98aed10cb57f0 --- /dev/null +++ b/clang/test/CodeGenHLSL/resources/res-array-global.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 +// CHECK: @[[BufC:.*]] = private unnamed_addr constant [2 x i8] c"C\00", align 1 +// CHECK: @[[BufD:.*]] = private unnamed_addr constant [2 x i8] c"D\00", align 1 + +// different explicit binding for DXIL and SPIR-V +[[vk::binding(12, 2)]] +RWBuffer A[4] : register(u10, space1); + +[[vk::binding(13)]] // SPIR-V explicit binding 13, set 0 +RWBuffer B[5]; // DXIL implicit binding in space0 + +// same explicit binding for both DXIL and SPIR-V +// (SPIR-V takes the binding from register annotation if there is no vk::binding attribute)) +RWBuffer C[3] : register(u2); + +// implicit binding for both DXIL and SPIR-V in space/set 0 +RWBuffer D[10]; + +RWStructuredBuffer Out; + +[numthreads(4,1,1)] +void main() { + // CHECK: define internal{{.*}} void @_Z4mainv() + // CHECK: %[[Tmp0:.*]] = alloca %"class.hlsl::RWBuffer + // CHECK: %[[Tmp1:.*]] = alloca %"class.hlsl::RWBuffer + // CHECK: %[[Tmp2:.*]] = alloca %"class.hlsl::RWBuffer + // CHECK: %[[Tmp3:.*]] = alloca %"class.hlsl::RWBuffer + + // NOTE: + // Constructor call for explicit binding has "jjij" in the mangled name and the arguments are (register, space, range_size, index, name). + // For implicit binding the constructor has "jijj" in the mangled name and the arguments are (space, range_size, index, order_id, name). + // The range_size can be -1 for unbounded arrays, and that is the only signed int in the signature. + // The order_id argument is a sequential number that is assigned to resources with implicit binding and corresponds to the order in which + // the resources were declared. It is needed because implicit bindings are assigned later on in an LLVM pass that needs to know the order + // of the resource declarations. + + // Make sure A[2] is translated to a RWBuffer constructor call with range 4 and index 2 + // and DXIL explicit binding (u10, space1) + // and SPIR-V explicit binding (binding 12, set 2) + // DXIL: call void @_ZN4hlsl8RWBufferIfEC1EjjijPKc(ptr {{.*}} %[[Tmp0]], i32 noundef 10, i32 noundef 1, i32 noundef 4, i32 noundef 2, ptr noundef @[[BufA]]) + // SPV: call void @_ZN4hlsl8RWBufferIfEC1EjjijPKc(ptr {{.*}} %[[Tmp0]], i32 noundef 12, i32 noundef 2, i32 noundef 4, i32 noundef 2, ptr noundef @[[BufA]]) + + // Make sure B[3] is translated to a RWBuffer constructor call with range 5 and index 3 + // and DXIL for implicit binding in space0, order id 0 + // and SPIR-V explicit binding (binding 13, set 0) + // DXIL: call void @_ZN4hlsl8RWBufferIiEC1EjijjPKc(ptr {{.*}} %[[Tmp1]], i32 noundef 0, i32 noundef 5, i32 noundef 3, i32 noundef 0, ptr noundef @[[BufB]]) + // SPV: call void @_ZN4hlsl8RWBufferIiEC1EjjijPKc(ptr {{.*}} %[[Tmp1]], i32 noundef 13, i32 noundef 0, i32 noundef 5, i32 noundef 3, ptr noundef @[[BufB]]) + + // Make sure C[1] is translated to a RWBuffer constructor call with range 3 and index 1 + // and DXIL explicit binding (u2, space0) + // and SPIR-V explicit binding (binding 2, set 0) + // DXIL: call void @_ZN4hlsl8RWBufferIiEC1EjjijPKc(ptr {{.*}} %[[Tmp2]], i32 noundef 2, i32 noundef 0, i32 noundef 3, i32 noundef 1, ptr noundef @[[BufC]]) + // SPV: call void @_ZN4hlsl8RWBufferIiEC1EjjijPKc(ptr {{.*}} %[[Tmp2]], i32 noundef 2, i32 noundef 0, i32 noundef 3, i32 noundef 1, ptr noundef @[[BufC]]) + + // Make sure D[7] is translated to a RWBuffer constructor call with implicit binding + // for both DXIL and SPIR-V + // DXIL: call void @_ZN4hlsl8RWBufferIdEC1EjijjPKc(ptr {{.*}} %[[Tmp3]], i32 noundef 0, i32 noundef 10, i32 noundef 7, i32 noundef 1, ptr noundef @[[BufD]]) + // SPV: call void @_ZN4hlsl8RWBufferIdEC1EjijjPKc(ptr {{.*}} %[[Tmp3]], i32 noundef 0, i32 noundef 10, i32 noundef 7, i32 noundef 0, ptr noundef @[[BufD]]) + Out[0] = A[2][0] + (float)B[3][0] + (float)C[1][0] + (float)D[7][0]; +} diff --git a/clang/test/CodeGenHLSL/resources/resource-bindings.hlsl b/clang/test/CodeGenHLSL/resources/resource-bindings.hlsl index 0a301346e55be..2d49753e546ca 100644 --- a/clang/test/CodeGenHLSL/resources/resource-bindings.hlsl +++ b/clang/test/CodeGenHLSL/resources/resource-bindings.hlsl @@ -4,11 +4,13 @@ // CHECK: %"class.hlsl::RWBuffer.0" = type { target("dx.TypedBuffer", float, 1, 0, 0) } // CHECK: %"class.hlsl::StructuredBuffer" = type { target("dx.RawBuffer", i32, 0, 0) } // CHECK: %"class.hlsl::RWStructuredBuffer" = type { target("dx.RawBuffer", %struct.S, 1, 0) } +// CHECK: %"class.hlsl::RWBuffer.1" = type { target("dx.TypedBuffer", double, 1, 0, 0) } // CHECK: @_ZL4U0S0 = internal global %"class.hlsl::RWBuffer" poison, align 4 // CHECK: @_ZL4U5S3 = internal global %"class.hlsl::RWBuffer.0" poison, align 4 // CHECK: @_ZL4T2S2 = internal global %"class.hlsl::StructuredBuffer" poison, align 4 // CHECK: @_ZL4T3S0 = internal global %"class.hlsl::RWStructuredBuffer" poison, align 4 +// CHECK: @_ZL5Array = internal global [10 x %"class.hlsl::RWBuffer.1"] poison, align 4 // CHECK: %[[HANDLE:.*]] = call target("dx.TypedBuffer", <4 x float>, 1, 0, 0) // CHECK-SAME: @llvm.dx.resource.handlefrombinding.tdx.TypedBuffer_v4f32_1_0_0t( @@ -42,5 +44,15 @@ struct S { }; RWStructuredBuffer T3S0 : register(u3); +// Resource array elements are initialized on access; make sure there is not call +// to initialize RWBuffer. +// CHECK-NOT: call target("dx.TypedBuffer", double, 1, 0, 0) @llvm.dx.resource.handlefrombinding.tdx.TypedBuffer_f64_1_0_0t( +RWBuffer Array[10] : register(u4, space0); + [numthreads(4,1,1)] -void main() {} +void main() { + // Reference Array to ensure it is emitted and we can test that it is initialized + // to poison, but do not index it. + // Non-array resources are always emitted because they have a constructor initializer. + (void)Array; +} diff --git a/clang/test/CodeGenHLSL/static-local-ctor.hlsl b/clang/test/CodeGenHLSL/static-local-ctor.hlsl index 87f49b8bf7ac7..9a4bf66f030ed 100644 --- a/clang/test/CodeGenHLSL/static-local-ctor.hlsl +++ b/clang/test/CodeGenHLSL/static-local-ctor.hlsl @@ -2,7 +2,7 @@ // Verify that no per variable _Init_thread instructions are emitted for non-trivial static locals // These would normally be emitted by the MicrosoftCXXABI, but the DirectX backend should exlude them -// Instead, check for the guardvar oparations that should protect the constructor initialization should +// Instead, check for the guardvar operations that should protect the constructor initialization should // only take place once. RWBuffer buf[10]; @@ -15,13 +15,14 @@ void InitBuf(RWBuffer buf) { // CHECK-NOT: _Init_thread_epoch // CHECK: define internal void @_Z4mainv // CHECK-NEXT: entry: +// CHECK-NEXT: [[Tmp0:%.*]] = alloca %"class.hlsl::RWBuffer" // CHECK-NEXT: [[Tmp1:%.*]] = alloca %"class.hlsl::RWBuffer" // CHECK-NEXT: [[Tmp2:%.*]] = load i8, ptr @_ZGVZ4mainvE5mybuf // CHECK-NEXT: [[Tmp3:%.*]] = icmp eq i8 [[Tmp2]], 0 // CHECK-NEXT: br i1 [[Tmp3]] // CHECK-NOT: _Init_thread_header // CHECK: init.check: -// CHECK-NEXT: call void @_ZN4hlsl8RWBufferIiEC1Ejijj +// CHECK-NEXT: call void @_ZN4hlsl8RWBufferIiEC1EjijjPKc( // CHECK-NEXT: store i8 1, ptr @_ZGVZ4mainvE5mybuf // CHECK-NOT: _Init_thread_footer