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 "TargetInfo.h"
13#include "clang/AST/Type.h"
14#include <climits>
15
16//===----------------------------------------------------------------------===//
17// Implementation of constant buffer layout common between DirectX and
18// SPIR/SPIR-V.
19//===----------------------------------------------------------------------===//
20
21using namespace clang;
22using namespace clang::CodeGen;
23
24static const CharUnits CBufferRowSize =
25 CharUnits::fromQuantity(Quantity: llvm::hlsl::CBufferRowSizeInBytes);
26
27namespace clang {
28namespace CodeGen {
29
30llvm::StructType *
31HLSLBufferLayoutBuilder::layOutStruct(const RecordType *RT,
32 const CGHLSLOffsetInfo &OffsetInfo) {
33
34 // check if we already have the layout type for this struct
35 // TODO: Do we need to check for matching OffsetInfo?
36 if (llvm::StructType *Ty = CGM.getHLSLRuntime().getHLSLBufferLayoutType(LayoutStructTy: RT))
37 return Ty;
38
39 // iterate over all fields of the record, including fields on base classes
40 llvm::SmallVector<CXXRecordDecl *> RecordDecls;
41 RecordDecls.push_back(Elt: RT->castAsCXXRecordDecl());
42 while (RecordDecls.back()->getNumBases()) {
43 CXXRecordDecl *D = RecordDecls.back();
44 assert(D->getNumBases() == 1 &&
45 "HLSL doesn't support multiple inheritance");
46 RecordDecls.push_back(Elt: D->bases_begin()->getType()->castAsCXXRecordDecl());
47 }
48
49 SmallVector<std::pair<const FieldDecl *, uint32_t>> FieldsWithOffset;
50 unsigned OffsetIdx = 0;
51 for (const CXXRecordDecl *RD : llvm::reverse(C&: RecordDecls))
52 for (const auto *FD : RD->fields())
53 FieldsWithOffset.emplace_back(Args&: FD, Args: OffsetInfo[OffsetIdx++]);
54
55 if (!OffsetInfo.empty())
56 llvm::stable_sort(Range&: FieldsWithOffset, C: [](const auto &LHS, const auto &RHS) {
57 return CGHLSLOffsetInfo::compareOffsets(LHS: LHS.second, RHS: RHS.second);
58 });
59
60 SmallVector<llvm::Type *> Layout;
61 CharUnits CurrentOffset = CharUnits::Zero();
62 for (auto &[FD, Offset] : FieldsWithOffset) {
63 llvm::Type *LayoutType = layOutType(Type: FD->getType());
64 if (!LayoutType)
65 continue;
66
67 const llvm::DataLayout &DL = CGM.getDataLayout();
68 CharUnits Size =
69 CharUnits::fromQuantity(Quantity: DL.getTypeSizeInBits(Ty: LayoutType) / 8);
70 CharUnits Align = CharUnits::fromQuantity(Quantity: DL.getABITypeAlign(Ty: LayoutType));
71
72 if (LayoutType->isAggregateType() ||
73 (CurrentOffset % CBufferRowSize) + Size > CBufferRowSize)
74 Align = Align.alignTo(Align: CBufferRowSize);
75
76 CharUnits NextOffset = CurrentOffset.alignTo(Align);
77
78 if (Offset != CGHLSLOffsetInfo::Unspecified) {
79 CharUnits PackOffset = CharUnits::fromQuantity(Quantity: Offset);
80 assert(PackOffset >= NextOffset &&
81 "Offset is invalid - would overlap with previous object");
82 NextOffset = PackOffset;
83 }
84
85 if (NextOffset > CurrentOffset) {
86 llvm::Type *Padding = CGM.getTargetCodeGenInfo().getHLSLPadding(
87 CGM, NumBytes: NextOffset - CurrentOffset);
88 assert(Padding && "No padding type for target?");
89 Layout.emplace_back(Args&: Padding);
90 CurrentOffset = NextOffset;
91 }
92 Layout.emplace_back(Args&: LayoutType);
93 CurrentOffset += Size;
94 }
95
96 // Create the layout struct type; anonymous structs have empty name but
97 // non-empty qualified name
98 const auto *Decl = RT->castAsCXXRecordDecl();
99 std::string Name =
100 Decl->getName().empty() ? "anon" : Decl->getQualifiedNameAsString();
101
102 llvm::StructType *NewTy = llvm::StructType::create(Elements: Layout, Name,
103 /*isPacked=*/true);
104 CGM.getHLSLRuntime().addHLSLBufferLayoutType(LayoutStructTy: RT, LayoutTy: NewTy);
105 return NewTy;
106}
107
108llvm::Type *HLSLBufferLayoutBuilder::padArrayElements(llvm::Type *EltTy,
109 uint64_t Count) {
110 CharUnits EltSize =
111 CharUnits::fromQuantity(Quantity: CGM.getDataLayout().getTypeSizeInBits(Ty: EltTy) / 8);
112 CharUnits Padding = EltSize.alignTo(Align: CBufferRowSize) - EltSize;
113
114 // If we don't have any padding between elements then we just need the array
115 // itself.
116 if (Count < 2 || Padding.isZero())
117 return llvm::ArrayType::get(ElementType: EltTy, NumElements: Count);
118
119 llvm::LLVMContext &Context = CGM.getLLVMContext();
120 llvm::Type *PaddingTy =
121 CGM.getTargetCodeGenInfo().getHLSLPadding(CGM, NumBytes: Padding);
122 assert(PaddingTy && "No padding type for target?");
123 auto *PaddedEltTy =
124 llvm::StructType::get(Context, Elements: {EltTy, PaddingTy}, /*isPacked=*/true);
125 return llvm::StructType::get(
126 Context, Elements: {llvm::ArrayType::get(ElementType: PaddedEltTy, NumElements: Count - 1), EltTy},
127 /*IsPacked=*/isPacked: true);
128}
129
130llvm::Type *HLSLBufferLayoutBuilder::layOutArray(const ConstantArrayType *AT) {
131 llvm::Type *EltTy = layOutType(Type: AT->getElementType());
132 uint64_t Count = AT->getZExtSize();
133 return padArrayElements(EltTy, Count);
134}
135
136llvm::Type *HLSLBufferLayoutBuilder::layOutMatrix(QualType Ty) {
137 // ConvertTypeForMem already handles row/column-major layout and bool
138 // promotion, producing [Count x <VecLen x EltTy>]. We just need to add
139 // cbuffer padding between the array elements. Pass the sugared QualType so
140 // that the `row_major`/`column_major` orientation attribute is preserved.
141 llvm::ArrayType *MemTy =
142 cast<llvm::ArrayType>(Val: CGM.getTypes().ConvertTypeForMem(T: Ty));
143 return padArrayElements(EltTy: MemTy->getElementType(), Count: MemTy->getNumElements());
144}
145
146llvm::Type *HLSLBufferLayoutBuilder::layOutType(QualType Ty) {
147 // HLSL resource types are not included in the buffer layout.
148 if (Ty->isHLSLResourceRecord() || Ty->isHLSLResourceRecordArray())
149 return nullptr;
150
151 if (const auto *AT = CGM.getContext().getAsConstantArrayType(T: Ty))
152 return layOutArray(AT);
153
154 if (Ty->isStructureOrClassType()) {
155 CGHLSLOffsetInfo EmptyOffsets;
156 return layOutStruct(RT: Ty->getAsCanonical<RecordType>(), OffsetInfo: EmptyOffsets);
157 }
158
159 if (Ty->isConstantMatrixType())
160 return layOutMatrix(Ty);
161
162 return CGM.getTypes().ConvertTypeForMem(T: Ty);
163}
164
165} // namespace CodeGen
166} // namespace clang
167