xref: /freebsd/contrib/llvm-project/clang/lib/CodeGen/HLSLBufferLayoutBuilder.cpp (revision 770cf0a5f02dc8983a89c6568d741fbc25baa999)
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