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
20using namespace clang;
21using namespace clang::CodeGen;
22using llvm::hlsl::CBufferRowSizeInBytes;
23
24namespace {
25
26// Creates a new array type with the same dimentions but with the new
27// element type.
28static llvm::Type *
29createArrayWithNewElementType(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
40static 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
52namespace clang {
53namespace 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.
69llvm::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.
175bool 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