1 //===- HLSLBufferLayoutBuilder.cpp ----------------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "HLSLBufferLayoutBuilder.h" 10 #include "CGHLSLRuntime.h" 11 #include "CodeGenModule.h" 12 #include "clang/AST/Type.h" 13 #include <climits> 14 15 //===----------------------------------------------------------------------===// 16 // Implementation of constant buffer layout common between DirectX and 17 // SPIR/SPIR-V. 18 //===----------------------------------------------------------------------===// 19 20 using namespace clang; 21 using namespace clang::CodeGen; 22 using llvm::hlsl::CBufferRowSizeInBytes; 23 24 namespace { 25 26 // Creates a new array type with the same dimentions but with the new 27 // element type. 28 static llvm::Type * 29 createArrayWithNewElementType(CodeGenModule &CGM, 30 const ConstantArrayType *ArrayType, 31 llvm::Type *NewElemType) { 32 const clang::Type *ArrayElemType = ArrayType->getArrayElementTypeNoTypeQual(); 33 if (ArrayElemType->isConstantArrayType()) 34 NewElemType = createArrayWithNewElementType( 35 CGM, cast<const ConstantArrayType>(ArrayElemType), NewElemType); 36 return llvm::ArrayType::get(NewElemType, ArrayType->getSExtSize()); 37 } 38 39 // Returns the size of a scalar or vector in bytes 40 static unsigned getScalarOrVectorSizeInBytes(llvm::Type *Ty) { 41 assert(Ty->isVectorTy() || Ty->isIntegerTy() || Ty->isFloatingPointTy()); 42 if (Ty->isVectorTy()) { 43 llvm::FixedVectorType *FVT = cast<llvm::FixedVectorType>(Ty); 44 return FVT->getNumElements() * 45 (FVT->getElementType()->getScalarSizeInBits() / 8); 46 } 47 return Ty->getScalarSizeInBits() / 8; 48 } 49 50 } // namespace 51 52 namespace clang { 53 namespace CodeGen { 54 55 // Creates a layout type for given struct or class with HLSL constant buffer 56 // layout taking into account PackOffsets, if provided. 57 // Previously created layout types are cached by CGHLSLRuntime. 58 // 59 // The function iterates over all fields of the record type (including base 60 // classes) and calls layoutField to converts each field to its corresponding 61 // LLVM type and to calculate its HLSL constant buffer layout. Any embedded 62 // structs (or arrays of structs) are converted to target layout types as well. 63 // 64 // When PackOffsets are specified the elements will be placed based on the 65 // user-specified offsets. Not all elements must have a packoffset/register(c#) 66 // annotation though. For those that don't, the PackOffsets array will contain 67 // -1 value instead. These elements must be placed at the end of the layout 68 // after all of the elements with specific offset. 69 llvm::TargetExtType *HLSLBufferLayoutBuilder::createLayoutType( 70 const RecordType *RT, const llvm::SmallVector<int32_t> *PackOffsets) { 71 72 // check if we already have the layout type for this struct 73 if (llvm::TargetExtType *Ty = 74 CGM.getHLSLRuntime().getHLSLBufferLayoutType(RT)) 75 return Ty; 76 77 SmallVector<unsigned> Layout; 78 SmallVector<llvm::Type *> LayoutElements; 79 unsigned Index = 0; // packoffset index 80 unsigned EndOffset = 0; 81 82 SmallVector<std::pair<const FieldDecl *, unsigned>> DelayLayoutFields; 83 84 // reserve first spot in the layout vector for buffer size 85 Layout.push_back(0); 86 87 // iterate over all fields of the record, including fields on base classes 88 llvm::SmallVector<const RecordType *> RecordTypes; 89 RecordTypes.push_back(RT); 90 while (RecordTypes.back()->getAsCXXRecordDecl()->getNumBases()) { 91 CXXRecordDecl *D = RecordTypes.back()->getAsCXXRecordDecl(); 92 assert(D->getNumBases() == 1 && 93 "HLSL doesn't support multiple inheritance"); 94 RecordTypes.push_back(D->bases_begin()->getType()->getAs<RecordType>()); 95 } 96 97 unsigned FieldOffset; 98 llvm::Type *FieldType; 99 100 while (!RecordTypes.empty()) { 101 const RecordType *RT = RecordTypes.back(); 102 RecordTypes.pop_back(); 103 104 for (const auto *FD : RT->getDecl()->fields()) { 105 assert((!PackOffsets || Index < PackOffsets->size()) && 106 "number of elements in layout struct does not match number of " 107 "packoffset annotations"); 108 109 // No PackOffset info at all, or have a valid packoffset/register(c#) 110 // annotations value -> layout the field. 111 const int PO = PackOffsets ? (*PackOffsets)[Index++] : -1; 112 if (!PackOffsets || PO != -1) { 113 if (!layoutField(FD, EndOffset, FieldOffset, FieldType, PO)) 114 return nullptr; 115 Layout.push_back(FieldOffset); 116 LayoutElements.push_back(FieldType); 117 continue; 118 } 119 // Have PackOffset info, but there is no packoffset/register(cX) 120 // annotation on this field. Delay the layout until after all of the 121 // other elements with packoffsets/register(cX) are processed. 122 DelayLayoutFields.emplace_back(FD, LayoutElements.size()); 123 // reserve space for this field in the layout vector and elements list 124 Layout.push_back(UINT_MAX); 125 LayoutElements.push_back(nullptr); 126 } 127 } 128 129 // process delayed layouts 130 for (auto I : DelayLayoutFields) { 131 const FieldDecl *FD = I.first; 132 const unsigned IndexInLayoutElements = I.second; 133 // the first item in layout vector is size, so we need to offset the index 134 // by 1 135 const unsigned IndexInLayout = IndexInLayoutElements + 1; 136 assert(Layout[IndexInLayout] == UINT_MAX && 137 LayoutElements[IndexInLayoutElements] == nullptr); 138 139 if (!layoutField(FD, EndOffset, FieldOffset, FieldType)) 140 return nullptr; 141 Layout[IndexInLayout] = FieldOffset; 142 LayoutElements[IndexInLayoutElements] = FieldType; 143 } 144 145 // set the size of the buffer 146 Layout[0] = EndOffset; 147 148 // create the layout struct type; anonymous struct have empty name but 149 // non-empty qualified name 150 const CXXRecordDecl *Decl = RT->getAsCXXRecordDecl(); 151 std::string Name = 152 Decl->getName().empty() ? "anon" : Decl->getQualifiedNameAsString(); 153 llvm::StructType *StructTy = 154 llvm::StructType::create(LayoutElements, Name, true); 155 156 // create target layout type 157 llvm::TargetExtType *NewLayoutTy = llvm::TargetExtType::get( 158 CGM.getLLVMContext(), LayoutTypeName, {StructTy}, Layout); 159 if (NewLayoutTy) 160 CGM.getHLSLRuntime().addHLSLBufferLayoutType(RT, NewLayoutTy); 161 return NewLayoutTy; 162 } 163 164 // The function converts a single field of HLSL Buffer to its corresponding 165 // LLVM type and calculates it's layout. Any embedded structs (or 166 // arrays of structs) are converted to target layout types as well. 167 // The converted type is set to the FieldType parameter, the element 168 // offset is set to the FieldOffset parameter. The EndOffset (=size of the 169 // buffer) is also updated accordingly to the offset just after the placed 170 // element, unless the incoming EndOffset already larger (may happen in case 171 // of unsorted packoffset annotations). 172 // Returns true if the conversion was successful. 173 // The packoffset parameter contains the field's layout offset provided by the 174 // user or -1 if there was no packoffset (or register(cX)) annotation. 175 bool HLSLBufferLayoutBuilder::layoutField(const FieldDecl *FD, 176 unsigned &EndOffset, 177 unsigned &FieldOffset, 178 llvm::Type *&FieldType, 179 int Packoffset) { 180 181 // Size of element; for arrays this is a size of a single element in the 182 // array. Total array size of calculated as (ArrayCount-1) * ArrayStride + 183 // ElemSize. 184 unsigned ElemSize = 0; 185 unsigned ElemOffset = 0; 186 unsigned ArrayCount = 1; 187 unsigned ArrayStride = 0; 188 189 unsigned NextRowOffset = llvm::alignTo(EndOffset, CBufferRowSizeInBytes); 190 191 llvm::Type *ElemLayoutTy = nullptr; 192 QualType FieldTy = FD->getType(); 193 194 if (FieldTy->isConstantArrayType()) { 195 // Unwrap array to find the element type and get combined array size. 196 QualType Ty = FieldTy; 197 while (Ty->isConstantArrayType()) { 198 auto *ArrayTy = CGM.getContext().getAsConstantArrayType(Ty); 199 ArrayCount *= ArrayTy->getSExtSize(); 200 Ty = ArrayTy->getElementType(); 201 } 202 // For array of structures, create a new array with a layout type 203 // instead of the structure type. 204 if (Ty->isStructureOrClassType()) { 205 llvm::Type *NewTy = 206 cast<llvm::TargetExtType>(createLayoutType(Ty->getAs<RecordType>())); 207 if (!NewTy) 208 return false; 209 assert(isa<llvm::TargetExtType>(NewTy) && "expected target type"); 210 ElemSize = cast<llvm::TargetExtType>(NewTy)->getIntParameter(0); 211 ElemLayoutTy = createArrayWithNewElementType( 212 CGM, cast<ConstantArrayType>(FieldTy.getTypePtr()), NewTy); 213 } else { 214 // Array of vectors or scalars 215 ElemSize = 216 getScalarOrVectorSizeInBytes(CGM.getTypes().ConvertTypeForMem(Ty)); 217 ElemLayoutTy = CGM.getTypes().ConvertTypeForMem(FieldTy); 218 } 219 ArrayStride = llvm::alignTo(ElemSize, CBufferRowSizeInBytes); 220 ElemOffset = (Packoffset != -1) ? Packoffset : NextRowOffset; 221 222 } else if (FieldTy->isStructureOrClassType()) { 223 // Create a layout type for the structure 224 ElemLayoutTy = 225 createLayoutType(cast<RecordType>(FieldTy->getAs<RecordType>())); 226 if (!ElemLayoutTy) 227 return false; 228 assert(isa<llvm::TargetExtType>(ElemLayoutTy) && "expected target type"); 229 ElemSize = cast<llvm::TargetExtType>(ElemLayoutTy)->getIntParameter(0); 230 ElemOffset = (Packoffset != -1) ? Packoffset : NextRowOffset; 231 232 } else { 233 // scalar or vector - find element size and alignment 234 unsigned Align = 0; 235 ElemLayoutTy = CGM.getTypes().ConvertTypeForMem(FieldTy); 236 if (ElemLayoutTy->isVectorTy()) { 237 // align vectors by sub element size 238 const llvm::FixedVectorType *FVT = 239 cast<llvm::FixedVectorType>(ElemLayoutTy); 240 unsigned SubElemSize = FVT->getElementType()->getScalarSizeInBits() / 8; 241 ElemSize = FVT->getNumElements() * SubElemSize; 242 Align = SubElemSize; 243 } else { 244 assert(ElemLayoutTy->isIntegerTy() || ElemLayoutTy->isFloatingPointTy()); 245 ElemSize = ElemLayoutTy->getScalarSizeInBits() / 8; 246 Align = ElemSize; 247 } 248 249 // calculate or get element offset for the vector or scalar 250 if (Packoffset != -1) { 251 ElemOffset = Packoffset; 252 } else { 253 ElemOffset = llvm::alignTo(EndOffset, Align); 254 // if the element does not fit, move it to the next row 255 if (ElemOffset + ElemSize > NextRowOffset) 256 ElemOffset = NextRowOffset; 257 } 258 } 259 260 // Update end offset of the layout; do not update it if the EndOffset 261 // is already bigger than the new value (which may happen with unordered 262 // packoffset annotations) 263 unsigned NewEndOffset = 264 ElemOffset + (ArrayCount - 1) * ArrayStride + ElemSize; 265 EndOffset = std::max<unsigned>(EndOffset, NewEndOffset); 266 267 // add the layout element and offset to the lists 268 FieldOffset = ElemOffset; 269 FieldType = ElemLayoutTy; 270 return true; 271 } 272 273 } // namespace CodeGen 274 } // namespace clang 275