diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index 12dce309127e5..dfcf075e73312 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -2724,6 +2724,7 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase { bool isHLSLAttributedResourceType() const; bool isHLSLInlineSpirvType() const; bool isHLSLResourceRecord() const; + bool isHLSLResourceRecordArray() const; bool isHLSLIntangibleType() const; // Any HLSL intangible type (builtin, array, class) diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h index 085c9ed9f3ebd..0c215c6e10013 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::SmallVector &Args); }; } // namespace clang diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index 141edc881d683..03d7413cf95b4 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -5246,6 +5246,15 @@ bool Type::isHLSLResourceRecord() const { return HLSLAttributedResourceType::findHandleTypeOnResource(this) != nullptr; } +bool Type::isHLSLResourceRecordArray() const { + const Type *Ty = getUnqualifiedDesugaredType(); + if (!Ty->isArrayType()) + return false; + while (isa(Ty)) + Ty = Ty->getArrayElementTypeNoTypeQual(); + return Ty->isHLSLResourceRecord(); +} + bool Type::isHLSLIntangibleType() const { const Type *Ty = getUnqualifiedDesugaredType(); diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp index ed35a055d8a7f..8c34fb501a3b8 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" @@ -4532,6 +4533,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 f64ac20545fa3..8a362cc551687 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.cpp +++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp @@ -18,6 +18,7 @@ #include "CodeGenModule.h" #include "TargetInfo.h" #include "clang/AST/ASTContext.h" +#include "clang/AST/Attrs.inc" #include "clang/AST/Decl.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/Type.h" @@ -35,6 +36,7 @@ #include "llvm/Support/Alignment.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FormatVariadic.h" +#include using namespace clang; using namespace CodeGen; @@ -84,6 +86,124 @@ void addRootSignature(llvm::dxbc::RootSignatureVersion RootSigVer, RootSignatureValMD->addOperand(MDVals); } +// If the specified expr is a simple decay from an array to pointer, +// return the array subexpression. Otherwise, return nullptr. +static const Expr *getSubExprFromArrayDecayOperand(const Expr *E) { + const auto *CE = dyn_cast(E); + if (!CE || CE->getCastKind() != CK_ArrayToPointerDecay) + return nullptr; + return CE->getSubExpr(); +} + +// 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 = getSubExprFromArrayDecayOperand(ASE->getBase()); + 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(const clang::Type *Ty) { + assert(Ty->isArrayType() && "expected array type"); + if (Ty->isIncompleteArrayType()) + return -1; + int Size = 1; + while (const auto *CAT = dyn_cast(Ty)) { + Size *= CAT->getSExtSize(); + Ty = CAT->getArrayElementTypeNoTypeQual(); + } + return Size; +} + +// Find constructor decl for a specific resource record type and binding +// (implicit vs. explicit). The constructor has 6 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) { + std::string Str(BaseName); + std::string GlobalName(Str + ".str"); + return CGM.GetAddrOfConstantCString(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) { + 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 * @@ -103,13 +223,6 @@ llvm::Triple::ArchType CGHLSLRuntime::getArch() { return CGM.getTarget().getTriple().getArch(); } -// Returns true if the type is an HLSL resource class or an array of them -static bool isResourceRecordTypeOrArrayOf(const clang::Type *Ty) { - while (const ConstantArrayType *CAT = dyn_cast(Ty)) - Ty = CAT->getArrayElementTypeNoTypeQual(); - return Ty->isHLSLResourceRecord(); -} - // Emits constant global variables for buffer constants declarations // and creates metadata linking the constant globals with the buffer global. void CGHLSLRuntime::emitBufferGlobalsAndMetadata(const HLSLBufferDecl *BufDecl, @@ -146,7 +259,7 @@ void CGHLSLRuntime::emitBufferGlobalsAndMetadata(const HLSLBufferDecl *BufDecl, if (VDTy.getAddressSpace() != LangAS::hlsl_constant) { if (VD->getStorageClass() == SC_Static || VDTy.getAddressSpace() == LangAS::hlsl_groupshared || - isResourceRecordTypeOrArrayOf(VDTy.getTypePtr())) { + VDTy->isHLSLResourceRecord() || VDTy->isHLSLResourceRecordArray()) { // Emit static and groupshared variables and resource classes inside // cbuffer as regular globals CGM.EmitGlobal(VD); @@ -597,13 +710,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) { @@ -631,17 +737,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()); @@ -708,3 +810,93 @@ 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 subscrips + 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 total array size (= range size) + const Type *ResArrayTy = ArrayDecl->getType().getTypePtr(); + assert(ResArrayTy->isHLSLResourceRecordArray() && + "expected array of resource classes"); + llvm::Value *Range = + llvm::ConstantInt::get(CGM.IntTy, getTotalArraySize(ResArrayTy)); + + // 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( + getSubExprFromArrayDecayOperand(ASE->getBase())); + } + + // 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 + CXXConstructorDecl *CD = findResourceConstructorDecl( + ArrayDecl->getASTContext(), 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 (auto *Size = CGF.EmitLifetimeStart( + CGM.getDataLayout().getTypeAllocSize(TmpVar.getElementType()), + TmpVar.getPointer())) { + CGF.pushFullExprCleanup( + NormalEHLifetimeMarker, TmpVar, Size); + } + AggValueSlot ValueSlot = AggValueSlot::forAddr( + TmpVar, Qualifiers(), AggValueSlot::IsDestructed_t(true), + AggValueSlot::DoesNotNeedGCBarriers, AggValueSlot::IsAliased_t(false), + AggValueSlot::MayOverlap); + + Address ThisAddress = ValueSlot.getAddress(); + llvm::Value *ThisPtr = CGF.getAsNaturalPointerTo( + ThisAddress, CD->getThisType()->getPointeeType()); + + // 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 834b1c067d84c..1498c5d75fa53 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -5775,8 +5775,8 @@ void CodeGenModule::EmitGlobalVarDefinition(const VarDecl *D, if (D->getType()->isReferenceType()) T = D->getType(); - if (getLangOpts().HLSL && - D->getType().getTypePtr()->isHLSLResourceRecord()) { + if (getLangOpts().HLSL && (D->getType()->isHLSLResourceRecord() || + D->getType()->isHLSLResourceRecordArray())) { Init = llvm::PoisonValue::get(getTypes().ConvertType(ASTTy)); NeedsGlobalCtor = true; } else if (getLangOpts().CPlusPlus) { diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index 8536e04a129ee..5bfb8b9332cb7 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -71,6 +71,10 @@ static RegisterType getRegisterType(ResourceClass RC) { llvm_unreachable("unexpected ResourceClass value"); } +static RegisterType getRegisterType(const HLSLAttributedResourceType *ResTy) { + return getRegisterType(ResTy->getAttrs().ResourceClass); +} + // Converts the first letter of string Slot to RegisterType. // Returns false if the letter does not correspond to a valid register type. static bool convertToRegisterType(StringRef Slot, RegisterType *RT) { @@ -337,16 +341,28 @@ static bool isZeroSizedArray(const ConstantArrayType *CAT) { return CAT != nullptr; } -// Returns true if the record type is an HLSL resource class or an array of -// resource classes -static bool isResourceRecordTypeOrArrayOf(const Type *Ty) { - while (const ConstantArrayType *CAT = dyn_cast(Ty)) - Ty = CAT->getArrayElementTypeNoTypeQual(); - return HLSLAttributedResourceType::findHandleTypeOnResource(Ty) != nullptr; +static bool isResourceRecordTypeOrArrayOf(VarDecl *VD) { + const Type *Ty = VD->getType().getTypePtr(); + return Ty->isHLSLResourceRecord() || Ty->isHLSLResourceRecordArray(); } -static bool isResourceRecordTypeOrArrayOf(VarDecl *VD) { - return isResourceRecordTypeOrArrayOf(VD->getType().getTypePtr()); +static const HLSLAttributedResourceType * +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(); + } + return HLSLAttributedResourceType::findHandleTypeOnResource(Ty); +} + +// returns the element type of an array (including multi-dimensional array) +static QualType getArrayElementType(QualType Ty) { + assert(Ty->isArrayType() && "expected array type"); + while (const ArrayType *AT = dyn_cast(Ty.getTypePtr())) + Ty = AT->getElementType(); + return Ty; } // Returns true if the type is a leaf element type that is not valid to be @@ -355,7 +371,7 @@ static bool isResourceRecordTypeOrArrayOf(VarDecl *VD) { // type or if it is a record type that needs to be inspected further. static bool isInvalidConstantBufferLeafElementType(const Type *Ty) { Ty = Ty->getUnqualifiedDesugaredType(); - if (isResourceRecordTypeOrArrayOf(Ty)) + if (Ty->isHLSLResourceRecord() || Ty->isHLSLResourceRecordArray()) return true; if (Ty->isRecordType()) return Ty->getAsCXXRecordDecl()->isEmpty(); @@ -575,16 +591,13 @@ void createHostLayoutStructForBuffer(Sema &S, HLSLBufferDecl *BufDecl) { BufDecl->addLayoutStruct(LS); } -static void addImplicitBindingAttrToBuffer(Sema &S, HLSLBufferDecl *BufDecl, - uint32_t ImplicitBindingOrderID) { - RegisterType RT = - BufDecl->isCBuffer() ? RegisterType::CBuffer : RegisterType::SRV; +static void addImplicitBindingAttrToDecl(Sema &S, Decl *D, RegisterType RT, + uint32_t ImplicitBindingOrderID) { auto *Attr = HLSLResourceBindingAttr::CreateImplicit(S.getASTContext(), "", "0", {}); - std::optional RegSlot; - Attr->setBinding(RT, RegSlot, 0); + Attr->setBinding(RT, std::nullopt, 0); Attr->setImplicitBindingOrderID(ImplicitBindingOrderID); - BufDecl->addAttr(Attr); + D->addAttr(Attr); } // Handle end of cbuffer/tbuffer declaration @@ -607,7 +620,10 @@ void SemaHLSL::ActOnFinishBuffer(Decl *Dcl, SourceLocation RBrace) { if (RBA) RBA->setImplicitBindingOrderID(OrderID); else - addImplicitBindingAttrToBuffer(SemaRef, BufDecl, OrderID); + addImplicitBindingAttrToDecl(SemaRef, BufDecl, + BufDecl->isCBuffer() ? RegisterType::CBuffer + : RegisterType::SRV, + OrderID); } SemaRef.PopDeclContext(); @@ -2446,8 +2462,8 @@ void SemaHLSL::ActOnEndOfTranslationUnit(TranslationUnitDecl *TU) { HLSLBufferDecl *DefaultCBuffer = HLSLBufferDecl::CreateDefaultCBuffer( SemaRef.getASTContext(), SemaRef.getCurLexicalContext(), DefaultCBufferDecls); - addImplicitBindingAttrToBuffer(SemaRef, DefaultCBuffer, - getNextImplicitBindingOrderID()); + addImplicitBindingAttrToDecl(SemaRef, DefaultCBuffer, RegisterType::CBuffer, + getNextImplicitBindingOrderID()); SemaRef.getCurLexicalContext()->addDecl(DefaultCBuffer); createHostLayoutStructForBuffer(SemaRef, DefaultCBuffer); @@ -3597,7 +3613,7 @@ void SemaHLSL::deduceAddressSpace(VarDecl *Decl) { return; // Resource handles. - if (isResourceRecordTypeOrArrayOf(Type->getUnqualifiedDesugaredType())) + if (Type->isHLSLResourceRecord() || Type->isHLSLResourceRecordArray()) return; // Only static globals belong to the Private address space. @@ -3633,14 +3649,7 @@ void SemaHLSL::ActOnVariableDeclarator(VarDecl *VD) { DefaultCBufferDecls.push_back(VD); } - // find all resources bindings on decl - if (VD->getType()->isHLSLIntangibleType()) - collectResourceBindingsOnVarDecl(VD); - - const Type *VarType = VD->getType().getTypePtr(); - while (VarType->isArrayType()) - VarType = VarType->getArrayElementTypeNoTypeQual(); - if (VarType->isHLSLResourceRecord() || + if (isResourceRecordTypeOrArrayOf(VD) || VD->hasAttr()) { // Make the variable for resources static. The global externally visible // storage is accessed through the handle, which is a member. The variable @@ -3648,6 +3657,28 @@ void SemaHLSL::ActOnVariableDeclarator(VarDecl *VD) { VD->setStorageClass(StorageClass::SC_Static); } + if (VD->getType()->isHLSLResourceRecordArray()) { + // If the resource array does not have an explicit binding attribute, + // create an implicit one. It will be used to transfer implicit binding + // order_ID to codegen. + if (!VD->hasAttr()) { + HLSLResourceBindingAttr *RBA = VD->getAttr(); + if (!RBA || !RBA->hasRegisterSlot()) { + uint32_t OrderID = getNextImplicitBindingOrderID(); + if (RBA) + RBA->setImplicitBindingOrderID(OrderID); + else + addImplicitBindingAttrToDecl( + SemaRef, VD, getRegisterType(getResourceArrayHandleType(VD)), + OrderID); + } + } + } + + // find all resources bindings on decl + if (VD->getType()->isHLSLIntangibleType()) + collectResourceBindingsOnVarDecl(VD); + // process explicit bindings processExplicitBindingsOnDecl(VD); } @@ -3675,11 +3706,14 @@ 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::SmallVector &Args) { std::optional RegisterSlot; uint32_t SpaceNo = 0; - HLSLVkBindingAttr *VkBinding = VD->getAttr(); - HLSLResourceBindingAttr *RBA = VD->getAttr(); if (VkBinding) { RegisterSlot = VkBinding->getBinding(); SpaceNo = VkBinding->getSet(); @@ -3694,12 +3728,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()), @@ -3710,18 +3744,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 = getArrayElementType(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) { @@ -3730,17 +3803,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/AST/HLSL/resource_binding_attr.hlsl b/clang/test/AST/HLSL/resource_binding_attr.hlsl index c073cd4dc1476..05d5eb0d619d8 100644 --- a/clang/test/AST/HLSL/resource_binding_attr.hlsl +++ b/clang/test/AST/HLSL/resource_binding_attr.hlsl @@ -34,6 +34,10 @@ RWBuffer UAV1 : register(u2), UAV2 : register(u4); // CHECK: HLSLResourceBindingAttr {{.*}} "" "space5" RWBuffer UAV3 : register(space5); +// CHECK: VarDecl {{.*}} UAV_Array 'RWBuffer[10]' +// CHECK: HLSLResourceBindingAttr {{.*}} "u10" "space6" +RWBuffer UAV_Array[10] : register(u10, space6); + // // Default constants ($Globals) layout annotations @@ -56,3 +60,19 @@ struct S { // CHECK: VarDecl {{.*}} s 'hlsl_constant S' // CHECK: HLSLResourceBindingAttr {{.*}} "c10" "space0 S s : register(c10); + +// +// Implicit binding + +// Constant buffers should have implicit binding attribute added by SemaHLSL +// CHECK: HLSLBufferDecl {{.*}} line:[[# @LINE + 3]]:9 cbuffer CB2 +// CHECK-NEXT: HLSLResourceClassAttr {{.*}} Implicit CBuffer +// CHECK-NEXT: HLSLResourceBindingAttr {{.*}} Implicit "" "0" +cbuffer CB2 { + float4 c; +} + +// Resource arrays should have implicit binding attribute added by SemaHLSL +// CHECK: VarDecl {{.*}} SB 'StructuredBuffer[10]' +// CHECK: HLSLResourceBindingAttr {{.*}} Implicit "" "0" +StructuredBuffer SB[10]; 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..97df14769b132 --- /dev/null +++ b/clang/test/CodeGenHLSL/resources/res-array-global-multi-dim.hlsl @@ -0,0 +1,32 @@ +// 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); +RWBuffer D[10][5]; // 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 + + // Make sure that B[2][3] 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]]) + Out[0] = B[3][2][0] + (float)C[1][0][3][0] + (float)D[9][2][0]; +} 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..de36291e3fd4b --- /dev/null +++ b/clang/test/CodeGenHLSL/resources/res-array-global.hlsl @@ -0,0 +1,59 @@ +// 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,SPIRV + +// 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 + + // 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 range 10 and index 7 + // and DXIL for implicit binding in space0, order id 1 + // and SPIR-V explicit binding (binding 13, set 0), order id 0 + // 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/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 diff --git a/test2-multi-dim-arrays.hlsl b/test2-multi-dim-arrays.hlsl new file mode 100644 index 0000000000000..f6366cea77ecc --- /dev/null +++ b/test2-multi-dim-arrays.hlsl @@ -0,0 +1,64 @@ +RWBuffer B[4][4] : register(u2); +RWBuffer C[2][2][5] : register(u10, space1); + +RWStructuredBuffer Out; + +[numthreads(4,1,1)] +void main() { + Out[0] = B[3][2][0] + C[1][0][3][0]; +} + +// B -> 3 * 4 + 2 = 14 +// C -> 1 * 2 * 5 + 0 * 5 + 3 = 13 + +/* +; ModuleID = 'test2-multi-dim-arrays.hlsl' +source_filename = "test2-multi-dim-arrays.hlsl" +target datalayout = "e-m:e-p:32:32-i1:32-i8:8-i16:16-i32:32-i64:64-f16:16-f32:32-f64:64-n8:16:32:64" +target triple = "dxilv1.6-unknown-shadermodel6.6-compute" + +@.str = private unnamed_addr constant [2 x i8] c"B\00", align 1 +@.str.2 = private unnamed_addr constant [2 x i8] c"C\00", align 1 +@.str.4 = private unnamed_addr constant [4 x i8] c"Out\00", align 1 + +; Function Attrs: mustprogress nofree noinline norecurse nosync nounwind willreturn memory(readwrite, inaccessiblemem: none) +define void @main() local_unnamed_addr #0 { +entry: + %0 = tail call target("dx.TypedBuffer", float, 1, 0, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.TypedBuffer_f32_1_0_0t(i32 0, i32 0, i32 1, i32 0, i1 false, ptr nonnull @.str.4) + %1 = tail call target("dx.TypedBuffer", float, 1, 0, 0) @llvm.dx.resource.handlefrombinding.tdx.TypedBuffer_f32_1_0_0t(i32 0, i32 2, i32 16, i32 14, i1 false, ptr nonnull @.str) + %2 = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.TypedBuffer_f32_1_0_0t(target("dx.TypedBuffer", float, 1, 0, 0) %1, i32 0) + %3 = load float, ptr %2, align 4, !tbaa !4 + %4 = tail call target("dx.TypedBuffer", float, 1, 0, 0) @llvm.dx.resource.handlefrombinding.tdx.TypedBuffer_f32_1_0_0t(i32 1, i32 10, i32 20, i32 13, i1 false, ptr nonnull @.str.2) + %5 = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.TypedBuffer_f32_1_0_0t(target("dx.TypedBuffer", float, 1, 0, 0) %4, i32 0) + %6 = load float, ptr %5, align 4, !tbaa !4 + %add.i = fadd reassoc nnan ninf nsz arcp afn float %6, %3 + %7 = tail call noundef nonnull align 4 dereferenceable(4) ptr @llvm.dx.resource.getpointer.p0.tdx.TypedBuffer_f32_1_0_0t(target("dx.TypedBuffer", float, 1, 0, 0) %0, i32 0) + store float %add.i, ptr %7, align 4, !tbaa !4 + ret void +} + +; Function Attrs: mustprogress nocallback nofree nosync nounwind willreturn memory(none) +declare target("dx.TypedBuffer", float, 1, 0, 0) @llvm.dx.resource.handlefromimplicitbinding.tdx.TypedBuffer_f32_1_0_0t(i32, i32, i32, i32, i1, ptr) #1 + +; Function Attrs: mustprogress nocallback nofree nosync nounwind willreturn memory(none) +declare ptr @llvm.dx.resource.getpointer.p0.tdx.TypedBuffer_f32_1_0_0t(target("dx.TypedBuffer", float, 1, 0, 0), i32) #1 + +; Function Attrs: mustprogress nocallback nofree nosync nounwind willreturn memory(none) +declare target("dx.TypedBuffer", float, 1, 0, 0) @llvm.dx.resource.handlefrombinding.tdx.TypedBuffer_f32_1_0_0t(i32, i32, i32, i32, i1, ptr) #1 + +attributes #0 = { mustprogress nofree noinline norecurse nosync nounwind willreturn memory(readwrite, inaccessiblemem: none) "approx-func-fp-math"="true" "frame-pointer"="all" "hlsl.numthreads"="4,1,1" "hlsl.shader"="compute" "no-infs-fp-math"="true" "no-nans-fp-math"="true" "no-signed-zeros-fp-math"="true" "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { mustprogress nocallback nofree nosync nounwind willreturn memory(none) } + +!llvm.module.flags = !{!0, !1} +!dx.valver = !{!2} +!llvm.ident = !{!3} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{i32 7, !"frame-pointer", i32 2} +!2 = !{i32 1, i32 8} +!3 = !{!"clang version 21.0.0git (C:/llvm-project/clang 0c495ce9c4237f0f090b672efd94839e52cb5f18)"} +!4 = !{!5, !5, i64 0} +!5 = !{!"float", !6, i64 0} +!6 = !{!"omnipotent char", !7, i64 0} +!7 = !{!"Simple C++ TBAA"} +*/