| 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, ArrayType: cast<const ConstantArrayType>(Val: ArrayElemType), NewElemType);
|
| 36 | return llvm::ArrayType::get(ElementType: NewElemType, NumElements: 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>(Val: 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(LayoutStructTy: 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(Elt: 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(Elt: 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(Elt: 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, Packoffset: PO))
|
| 114 | return nullptr;
|
| 115 | Layout.push_back(Elt: FieldOffset);
|
| 116 | LayoutElements.push_back(Elt: 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(Args&: FD, Args: 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(Elt: 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(Elements: LayoutElements, Name, isPacked: true);
|
| 155 |
|
| 156 | // create target layout type
|
| 157 | llvm::TargetExtType *NewLayoutTy = llvm::TargetExtType::get(
|
| 158 | Context&: CGM.getLLVMContext(), Name: LayoutTypeName, Types: {StructTy}, Ints: Layout);
|
| 159 | if (NewLayoutTy)
|
| 160 | CGM.getHLSLRuntime().addHLSLBufferLayoutType(LayoutStructTy: RT, LayoutTy: 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(Value: EndOffset, Align: 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(T: 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>(Val: createLayoutType(RT: Ty->getAs<RecordType>()));
|
| 207 | if (!NewTy)
|
| 208 | return false;
|
| 209 | assert(isa<llvm::TargetExtType>(NewTy) && "expected target type" );
|
| 210 | ElemSize = cast<llvm::TargetExtType>(Val: NewTy)->getIntParameter(i: 0);
|
| 211 | ElemLayoutTy = createArrayWithNewElementType(
|
| 212 | CGM, ArrayType: cast<ConstantArrayType>(Val: FieldTy.getTypePtr()), NewElemType: NewTy);
|
| 213 | } else {
|
| 214 | // Array of vectors or scalars
|
| 215 | ElemSize =
|
| 216 | getScalarOrVectorSizeInBytes(Ty: CGM.getTypes().ConvertTypeForMem(T: Ty));
|
| 217 | ElemLayoutTy = CGM.getTypes().ConvertTypeForMem(T: FieldTy);
|
| 218 | }
|
| 219 | ArrayStride = llvm::alignTo(Value: ElemSize, Align: 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(RT: cast<RecordType>(Val: FieldTy->getAs<RecordType>()));
|
| 226 | if (!ElemLayoutTy)
|
| 227 | return false;
|
| 228 | assert(isa<llvm::TargetExtType>(ElemLayoutTy) && "expected target type" );
|
| 229 | ElemSize = cast<llvm::TargetExtType>(Val: ElemLayoutTy)->getIntParameter(i: 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(T: FieldTy);
|
| 236 | if (ElemLayoutTy->isVectorTy()) {
|
| 237 | // align vectors by sub element size
|
| 238 | const llvm::FixedVectorType *FVT =
|
| 239 | cast<llvm::FixedVectorType>(Val: 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(Value: 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>(a: EndOffset, b: 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 | |