1 | //===- DXILResource.cpp - DXIL Resource helper objects --------------------===// |
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 | /// \file This file contains helper objects for working with DXIL Resources. |
10 | /// |
11 | //===----------------------------------------------------------------------===// |
12 | |
13 | #include "DXILResource.h" |
14 | #include "CBufferDataLayout.h" |
15 | #include "llvm/ADT/StringSwitch.h" |
16 | #include "llvm/IR/IRBuilder.h" |
17 | #include "llvm/IR/Metadata.h" |
18 | #include "llvm/IR/Module.h" |
19 | #include "llvm/Support/Debug.h" |
20 | #include "llvm/Support/Format.h" |
21 | |
22 | using namespace llvm; |
23 | using namespace llvm::dxil; |
24 | |
25 | template <typename T> void ResourceTable<T>::collect(Module &M) { |
26 | NamedMDNode *Entry = M.getNamedMetadata(Name: MDName); |
27 | if (!Entry || Entry->getNumOperands() == 0) |
28 | return; |
29 | |
30 | uint32_t Counter = 0; |
31 | for (auto *Res : Entry->operands()) { |
32 | Data.push_back(T(Counter++, hlsl::FrontendResource(cast<MDNode>(Val: Res)))); |
33 | } |
34 | } |
35 | |
36 | template <> void ResourceTable<ConstantBuffer>::collect(Module &M) { |
37 | NamedMDNode *Entry = M.getNamedMetadata(Name: MDName); |
38 | if (!Entry || Entry->getNumOperands() == 0) |
39 | return; |
40 | |
41 | uint32_t Counter = 0; |
42 | for (auto *Res : Entry->operands()) { |
43 | Data.push_back( |
44 | Elt: ConstantBuffer(Counter++, hlsl::FrontendResource(cast<MDNode>(Val: Res)))); |
45 | } |
46 | // FIXME: share CBufferDataLayout with CBuffer load lowering. |
47 | // See https://github.com/llvm/llvm-project/issues/58381 |
48 | CBufferDataLayout CBDL(M.getDataLayout(), /*IsLegacy*/ true); |
49 | for (auto &CB : Data) |
50 | CB.setSize(CBDL); |
51 | } |
52 | |
53 | void Resources::collect(Module &M) { |
54 | UAVs.collect(M); |
55 | CBuffers.collect(M); |
56 | } |
57 | |
58 | ResourceBase::ResourceBase(uint32_t I, hlsl::FrontendResource R) |
59 | : ID(I), GV(R.getGlobalVariable()), Name("" ), Space(R.getSpace()), |
60 | LowerBound(R.getResourceIndex()), RangeSize(1) { |
61 | if (auto *ArrTy = dyn_cast<ArrayType>(Val: GV->getValueType())) |
62 | RangeSize = ArrTy->getNumElements(); |
63 | } |
64 | |
65 | StringRef ResourceBase::getElementTypeName(ElementType ElTy) { |
66 | switch (ElTy) { |
67 | case ElementType::Invalid: |
68 | return "invalid" ; |
69 | case ElementType::I1: |
70 | return "i1" ; |
71 | case ElementType::I16: |
72 | return "i16" ; |
73 | case ElementType::U16: |
74 | return "u16" ; |
75 | case ElementType::I32: |
76 | return "i32" ; |
77 | case ElementType::U32: |
78 | return "u32" ; |
79 | case ElementType::I64: |
80 | return "i64" ; |
81 | case ElementType::U64: |
82 | return "u64" ; |
83 | case ElementType::F16: |
84 | return "f16" ; |
85 | case ElementType::F32: |
86 | return "f32" ; |
87 | case ElementType::F64: |
88 | return "f64" ; |
89 | case ElementType::SNormF16: |
90 | return "snorm_f16" ; |
91 | case ElementType::UNormF16: |
92 | return "unorm_f16" ; |
93 | case ElementType::SNormF32: |
94 | return "snorm_f32" ; |
95 | case ElementType::UNormF32: |
96 | return "unorm_f32" ; |
97 | case ElementType::SNormF64: |
98 | return "snorm_f64" ; |
99 | case ElementType::UNormF64: |
100 | return "unorm_f64" ; |
101 | case ElementType::PackedS8x32: |
102 | return "p32i8" ; |
103 | case ElementType::PackedU8x32: |
104 | return "p32u8" ; |
105 | } |
106 | llvm_unreachable("All ElementType enums are handled in switch" ); |
107 | } |
108 | |
109 | void ResourceBase::printElementType(ResourceKind Kind, ElementType ElTy, |
110 | unsigned Alignment, raw_ostream &OS) { |
111 | switch (Kind) { |
112 | default: |
113 | // TODO: add vector size. |
114 | OS << right_justify(Str: getElementTypeName(ElTy), Width: Alignment); |
115 | break; |
116 | case ResourceKind::RawBuffer: |
117 | OS << right_justify(Str: "byte" , Width: Alignment); |
118 | break; |
119 | case ResourceKind::StructuredBuffer: |
120 | OS << right_justify(Str: "struct" , Width: Alignment); |
121 | break; |
122 | case ResourceKind::CBuffer: |
123 | case ResourceKind::Sampler: |
124 | OS << right_justify(Str: "NA" , Width: Alignment); |
125 | break; |
126 | case ResourceKind::Invalid: |
127 | case ResourceKind::NumEntries: |
128 | break; |
129 | } |
130 | } |
131 | |
132 | StringRef ResourceBase::getKindName(ResourceKind Kind) { |
133 | switch (Kind) { |
134 | case ResourceKind::NumEntries: |
135 | case ResourceKind::Invalid: |
136 | return "invalid" ; |
137 | case ResourceKind::Texture1D: |
138 | return "1d" ; |
139 | case ResourceKind::Texture2D: |
140 | return "2d" ; |
141 | case ResourceKind::Texture2DMS: |
142 | return "2dMS" ; |
143 | case ResourceKind::Texture3D: |
144 | return "3d" ; |
145 | case ResourceKind::TextureCube: |
146 | return "cube" ; |
147 | case ResourceKind::Texture1DArray: |
148 | return "1darray" ; |
149 | case ResourceKind::Texture2DArray: |
150 | return "2darray" ; |
151 | case ResourceKind::Texture2DMSArray: |
152 | return "2darrayMS" ; |
153 | case ResourceKind::TextureCubeArray: |
154 | return "cubearray" ; |
155 | case ResourceKind::TypedBuffer: |
156 | return "buf" ; |
157 | case ResourceKind::RawBuffer: |
158 | return "rawbuf" ; |
159 | case ResourceKind::StructuredBuffer: |
160 | return "structbuf" ; |
161 | case ResourceKind::CBuffer: |
162 | return "cbuffer" ; |
163 | case ResourceKind::Sampler: |
164 | return "sampler" ; |
165 | case ResourceKind::TBuffer: |
166 | return "tbuffer" ; |
167 | case ResourceKind::RTAccelerationStructure: |
168 | return "ras" ; |
169 | case ResourceKind::FeedbackTexture2D: |
170 | return "fbtex2d" ; |
171 | case ResourceKind::FeedbackTexture2DArray: |
172 | return "fbtex2darray" ; |
173 | } |
174 | llvm_unreachable("All ResourceKind enums are handled in switch" ); |
175 | } |
176 | |
177 | void ResourceBase::printKind(ResourceKind Kind, unsigned Alignment, |
178 | raw_ostream &OS, bool SRV, bool HasCounter, |
179 | uint32_t SampleCount) { |
180 | switch (Kind) { |
181 | default: |
182 | OS << right_justify(Str: getKindName(Kind), Width: Alignment); |
183 | break; |
184 | |
185 | case ResourceKind::RawBuffer: |
186 | case ResourceKind::StructuredBuffer: |
187 | if (SRV) |
188 | OS << right_justify(Str: "r/o" , Width: Alignment); |
189 | else { |
190 | if (!HasCounter) |
191 | OS << right_justify(Str: "r/w" , Width: Alignment); |
192 | else |
193 | OS << right_justify(Str: "r/w+cnt" , Width: Alignment); |
194 | } |
195 | break; |
196 | case ResourceKind::TypedBuffer: |
197 | OS << right_justify(Str: "buf" , Width: Alignment); |
198 | break; |
199 | case ResourceKind::Texture2DMS: |
200 | case ResourceKind::Texture2DMSArray: { |
201 | std::string DimName = getKindName(Kind).str(); |
202 | if (SampleCount) |
203 | DimName += std::to_string(val: SampleCount); |
204 | OS << right_justify(Str: DimName, Width: Alignment); |
205 | } break; |
206 | case ResourceKind::CBuffer: |
207 | case ResourceKind::Sampler: |
208 | OS << right_justify(Str: "NA" , Width: Alignment); |
209 | break; |
210 | case ResourceKind::Invalid: |
211 | case ResourceKind::NumEntries: |
212 | break; |
213 | } |
214 | } |
215 | |
216 | void ResourceBase::print(raw_ostream &OS, StringRef IDPrefix, |
217 | StringRef BindingPrefix) const { |
218 | std::string ResID = IDPrefix.str(); |
219 | ResID += std::to_string(val: ID); |
220 | OS << right_justify(Str: ResID, Width: 8); |
221 | |
222 | std::string Bind = BindingPrefix.str(); |
223 | Bind += std::to_string(val: LowerBound); |
224 | if (Space) |
225 | Bind += ",space" + std::to_string(val: Space); |
226 | |
227 | OS << right_justify(Str: Bind, Width: 15); |
228 | if (RangeSize != UINT_MAX) |
229 | OS << right_justify(Str: std::to_string(val: RangeSize), Width: 6) << "\n" ; |
230 | else |
231 | OS << right_justify(Str: "unbounded" , Width: 6) << "\n" ; |
232 | } |
233 | |
234 | void UAVResource::print(raw_ostream &OS) const { |
235 | OS << "; " << left_justify(Str: Name, Width: 31); |
236 | |
237 | OS << right_justify(Str: "UAV" , Width: 10); |
238 | |
239 | printElementType(Kind: Shape, ElTy: ExtProps.ElementType.value_or(u: ElementType::Invalid), |
240 | Alignment: 8, OS); |
241 | |
242 | // FIXME: support SampleCount. |
243 | // See https://github.com/llvm/llvm-project/issues/58175 |
244 | printKind(Kind: Shape, Alignment: 12, OS, /*SRV*/ false, HasCounter); |
245 | // Print the binding part. |
246 | ResourceBase::print(OS, IDPrefix: "U" , BindingPrefix: "u" ); |
247 | } |
248 | |
249 | ConstantBuffer::ConstantBuffer(uint32_t I, hlsl::FrontendResource R) |
250 | : ResourceBase(I, R) {} |
251 | |
252 | void ConstantBuffer::setSize(CBufferDataLayout &DL) { |
253 | CBufferSizeInBytes = DL.getTypeAllocSizeInBytes(Ty: GV->getValueType()); |
254 | } |
255 | |
256 | void ConstantBuffer::print(raw_ostream &OS) const { |
257 | OS << "; " << left_justify(Str: Name, Width: 31); |
258 | |
259 | OS << right_justify(Str: "cbuffer" , Width: 10); |
260 | |
261 | printElementType(Kind: ResourceKind::CBuffer, ElTy: ElementType::Invalid, Alignment: 8, OS); |
262 | |
263 | printKind(Kind: ResourceKind::CBuffer, Alignment: 12, OS, /*SRV*/ false, /*HasCounter*/ false); |
264 | // Print the binding part. |
265 | ResourceBase::print(OS, IDPrefix: "CB" , BindingPrefix: "cb" ); |
266 | } |
267 | |
268 | template <typename T> void ResourceTable<T>::print(raw_ostream &OS) const { |
269 | for (auto &Res : Data) |
270 | Res.print(OS); |
271 | } |
272 | |
273 | MDNode *ResourceBase::ExtendedProperties::write(LLVMContext &Ctx) const { |
274 | IRBuilder<> B(Ctx); |
275 | SmallVector<Metadata *> Entries; |
276 | if (ElementType) { |
277 | Entries.emplace_back( |
278 | Args: ConstantAsMetadata::get(C: B.getInt32(C: TypedBufferElementType))); |
279 | Entries.emplace_back(Args: ConstantAsMetadata::get( |
280 | C: B.getInt32(C: static_cast<uint32_t>(*ElementType)))); |
281 | } |
282 | if (Entries.empty()) |
283 | return nullptr; |
284 | return MDNode::get(Context&: Ctx, MDs: Entries); |
285 | } |
286 | |
287 | void ResourceBase::write(LLVMContext &Ctx, |
288 | MutableArrayRef<Metadata *> Entries) const { |
289 | IRBuilder<> B(Ctx); |
290 | Entries[0] = ConstantAsMetadata::get(C: B.getInt32(C: ID)); |
291 | Entries[1] = ConstantAsMetadata::get(C: GV); |
292 | Entries[2] = MDString::get(Context&: Ctx, Str: Name); |
293 | Entries[3] = ConstantAsMetadata::get(C: B.getInt32(C: Space)); |
294 | Entries[4] = ConstantAsMetadata::get(C: B.getInt32(C: LowerBound)); |
295 | Entries[5] = ConstantAsMetadata::get(C: B.getInt32(C: RangeSize)); |
296 | } |
297 | |
298 | MDNode *UAVResource::write() const { |
299 | auto &Ctx = GV->getContext(); |
300 | IRBuilder<> B(Ctx); |
301 | Metadata *Entries[11]; |
302 | ResourceBase::write(Ctx, Entries); |
303 | Entries[6] = |
304 | ConstantAsMetadata::get(C: B.getInt32(C: static_cast<uint32_t>(Shape))); |
305 | Entries[7] = ConstantAsMetadata::get(C: B.getInt1(V: GloballyCoherent)); |
306 | Entries[8] = ConstantAsMetadata::get(C: B.getInt1(V: HasCounter)); |
307 | Entries[9] = ConstantAsMetadata::get(C: B.getInt1(V: IsROV)); |
308 | Entries[10] = ExtProps.write(Ctx); |
309 | return MDNode::get(Context&: Ctx, MDs: Entries); |
310 | } |
311 | |
312 | MDNode *ConstantBuffer::write() const { |
313 | auto &Ctx = GV->getContext(); |
314 | IRBuilder<> B(Ctx); |
315 | Metadata *Entries[7]; |
316 | ResourceBase::write(Ctx, Entries); |
317 | |
318 | Entries[6] = ConstantAsMetadata::get(C: B.getInt32(C: CBufferSizeInBytes)); |
319 | return MDNode::get(Context&: Ctx, MDs: Entries); |
320 | } |
321 | |
322 | template <typename T> MDNode *ResourceTable<T>::write(Module &M) const { |
323 | if (Data.empty()) |
324 | return nullptr; |
325 | SmallVector<Metadata *> MDs; |
326 | for (auto &Res : Data) |
327 | MDs.emplace_back(Res.write()); |
328 | |
329 | NamedMDNode *Entry = M.getNamedMetadata(Name: MDName); |
330 | if (Entry) |
331 | Entry->eraseFromParent(); |
332 | |
333 | return MDNode::get(Context&: M.getContext(), MDs); |
334 | } |
335 | |
336 | void Resources::write(Module &M) const { |
337 | Metadata *ResourceMDs[4] = {nullptr, nullptr, nullptr, nullptr}; |
338 | |
339 | ResourceMDs[1] = UAVs.write(M); |
340 | |
341 | ResourceMDs[2] = CBuffers.write(M); |
342 | |
343 | bool HasResource = ResourceMDs[0] != nullptr || ResourceMDs[1] != nullptr || |
344 | ResourceMDs[2] != nullptr || ResourceMDs[3] != nullptr; |
345 | |
346 | if (HasResource) { |
347 | NamedMDNode *DXResMD = M.getOrInsertNamedMetadata(Name: "dx.resources" ); |
348 | DXResMD->addOperand(M: MDNode::get(Context&: M.getContext(), MDs: ResourceMDs)); |
349 | } |
350 | |
351 | NamedMDNode *Entry = M.getNamedMetadata(Name: "hlsl.uavs" ); |
352 | if (Entry) |
353 | Entry->eraseFromParent(); |
354 | } |
355 | |
356 | void Resources::print(raw_ostream &O) const { |
357 | O << ";\n" |
358 | << "; Resource Bindings:\n" |
359 | << ";\n" |
360 | << "; Name Type Format Dim " |
361 | "ID HLSL Bind Count\n" |
362 | << "; ------------------------------ ---------- ------- ----------- " |
363 | "------- -------------- ------\n" ; |
364 | |
365 | CBuffers.print(OS&: O); |
366 | UAVs.print(OS&: O); |
367 | } |
368 | |
369 | void Resources::dump() const { print(O&: dbgs()); } |
370 | |