1//===- DXILPrettyPrinter.cpp - Print resources for textual DXIL -----------===//
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 "DXILPrettyPrinter.h"
10#include "DXILWriter/DXILDebugInfoMap.h"
11#include "DirectX.h"
12#include "llvm/ADT/DenseSet.h"
13#include "llvm/ADT/STLExtras.h"
14#include "llvm/ADT/SmallVector.h"
15#include "llvm/ADT/StringRef.h"
16#include "llvm/Analysis/DXILResource.h"
17#include "llvm/IR/AssemblyAnnotationWriter.h"
18#include "llvm/IR/DebugInfo.h"
19#include "llvm/IR/Metadata.h"
20#include "llvm/IR/Module.h"
21#include "llvm/IR/ModuleSlotTracker.h"
22#include "llvm/IR/PassManager.h"
23#include "llvm/InitializePasses.h"
24#include "llvm/Pass.h"
25#include "llvm/Support/FormatAdapters.h"
26#include "llvm/Support/FormatVariadic.h"
27#include "llvm/Support/FormattedStream.h"
28#include "llvm/Support/raw_ostream.h"
29
30using namespace llvm;
31using namespace llvm::dxil;
32
33static StringRef getRCName(dxil::ResourceClass RC) {
34 switch (RC) {
35 case dxil::ResourceClass::SRV:
36 return "texture";
37 case dxil::ResourceClass::UAV:
38 return "UAV";
39 case dxil::ResourceClass::CBuffer:
40 return "cbuffer";
41 case dxil::ResourceClass::Sampler:
42 return "sampler";
43 }
44 llvm_unreachable("covered switch");
45}
46
47static StringRef getRCPrefix(dxil::ResourceClass RC) {
48 switch (RC) {
49 case dxil::ResourceClass::SRV:
50 return "t";
51 case dxil::ResourceClass::UAV:
52 return "u";
53 case dxil::ResourceClass::CBuffer:
54 return "cb";
55 case dxil::ResourceClass::Sampler:
56 return "s";
57 }
58 llvm_unreachable("covered switch");
59}
60
61static StringRef getFormatName(const dxil::ResourceTypeInfo &RI) {
62 if (RI.isTyped()) {
63 switch (RI.getTyped().DXILStorageTy) {
64 case dxil::ElementType::I1:
65 return "i1";
66 case dxil::ElementType::I16:
67 return "i16";
68 case dxil::ElementType::U16:
69 return "u16";
70 case dxil::ElementType::I32:
71 return "i32";
72 case dxil::ElementType::U32:
73 return "u32";
74 case dxil::ElementType::I64:
75 return "i64";
76 case dxil::ElementType::U64:
77 return "u64";
78 case dxil::ElementType::F16:
79 return "f16";
80 case dxil::ElementType::F32:
81 return "f32";
82 case dxil::ElementType::F64:
83 return "f64";
84 case dxil::ElementType::SNormF16:
85 return "snorm_f16";
86 case dxil::ElementType::UNormF16:
87 return "unorm_f16";
88 case dxil::ElementType::SNormF32:
89 return "snorm_f32";
90 case dxil::ElementType::UNormF32:
91 return "unorm_f32";
92 case dxil::ElementType::SNormF64:
93 return "snorm_f64";
94 case dxil::ElementType::UNormF64:
95 return "unorm_f64";
96 case dxil::ElementType::PackedS8x32:
97 return "p32i8";
98 case dxil::ElementType::PackedU8x32:
99 return "p32u8";
100 case dxil::ElementType::Invalid:
101 llvm_unreachable("Invalid ElementType");
102 }
103 llvm_unreachable("Unhandled ElementType");
104 } else if (RI.isStruct())
105 return "struct";
106 else if (RI.isCBuffer() || RI.isSampler())
107 return "NA";
108 return "byte";
109}
110
111static StringRef getTextureDimName(dxil::ResourceKind RK) {
112 switch (RK) {
113 case dxil::ResourceKind::Texture1D:
114 return "1d";
115 case dxil::ResourceKind::Texture2D:
116 return "2d";
117 case dxil::ResourceKind::Texture3D:
118 return "3d";
119 case dxil::ResourceKind::TextureCube:
120 return "cube";
121 case dxil::ResourceKind::Texture1DArray:
122 return "1darray";
123 case dxil::ResourceKind::Texture2DArray:
124 return "2darray";
125 case dxil::ResourceKind::TextureCubeArray:
126 return "cubearray";
127 case dxil::ResourceKind::TBuffer:
128 return "tbuffer";
129 case dxil::ResourceKind::FeedbackTexture2D:
130 return "fbtex2d";
131 case dxil::ResourceKind::FeedbackTexture2DArray:
132 return "fbtex2darray";
133 case dxil::ResourceKind::Texture2DMS:
134 return "2dMS";
135 case dxil::ResourceKind::Texture2DMSArray:
136 return "2darrayMS";
137 case dxil::ResourceKind::Invalid:
138 case dxil::ResourceKind::NumEntries:
139 case dxil::ResourceKind::CBuffer:
140 case dxil::ResourceKind::RawBuffer:
141 case dxil::ResourceKind::Sampler:
142 case dxil::ResourceKind::StructuredBuffer:
143 case dxil::ResourceKind::TypedBuffer:
144 case dxil::ResourceKind::RTAccelerationStructure:
145 llvm_unreachable("Invalid ResourceKind for texture");
146 }
147 llvm_unreachable("Unhandled ResourceKind");
148}
149
150namespace {
151struct FormatResourceDimension
152 : public llvm::FormatAdapter<const dxil::ResourceTypeInfo &> {
153 FormatResourceDimension(const dxil::ResourceTypeInfo &RI, bool HasCounter)
154 : llvm::FormatAdapter<const dxil::ResourceTypeInfo &>(RI),
155 HasCounter(HasCounter) {}
156
157 bool HasCounter;
158
159 void format(llvm::raw_ostream &OS, StringRef Style) {
160 dxil::ResourceKind RK = Item.getResourceKind();
161 switch (RK) {
162 default: {
163 OS << getTextureDimName(RK);
164 if (Item.isMultiSample())
165 OS << Item.getMultiSampleCount();
166 break;
167 }
168 case dxil::ResourceKind::RawBuffer:
169 case dxil::ResourceKind::StructuredBuffer:
170 if (!Item.isUAV())
171 OS << "r/o";
172 else if (HasCounter)
173 OS << "r/w+cnt";
174 else
175 OS << "r/w";
176 break;
177 case dxil::ResourceKind::TypedBuffer:
178 OS << "buf";
179 break;
180 case dxil::ResourceKind::CBuffer:
181 OS << "NA";
182 break;
183 case dxil::ResourceKind::RTAccelerationStructure:
184 // TODO: dxc would print "ras" here. Can/should this happen?
185 llvm_unreachable("RTAccelerationStructure printing is not implemented");
186 }
187 }
188};
189
190struct FormatBindingID
191 : public llvm::FormatAdapter<const dxil::ResourceInfo &> {
192 dxil::ResourceClass RC;
193
194 explicit FormatBindingID(const dxil::ResourceInfo &RI,
195 const dxil::ResourceTypeInfo &RTI)
196 : llvm::FormatAdapter<const dxil::ResourceInfo &>(RI),
197 RC(RTI.getResourceClass()) {}
198
199 void format(llvm::raw_ostream &OS, StringRef Style) {
200 OS << getRCPrefix(RC).upper() << Item.getBinding().BindingID;
201 }
202};
203
204struct FormatBindingLocation
205 : public llvm::FormatAdapter<const dxil::ResourceInfo &> {
206 dxil::ResourceClass RC;
207
208 explicit FormatBindingLocation(const dxil::ResourceInfo &RI,
209 const dxil::ResourceTypeInfo &RTI)
210 : llvm::FormatAdapter<const dxil::ResourceInfo &>(RI),
211 RC(RTI.getResourceClass()) {}
212
213 void format(llvm::raw_ostream &OS, StringRef Style) {
214 const auto &Binding = Item.getBinding();
215 OS << getRCPrefix(RC) << Binding.LowerBound;
216 if (Binding.Space)
217 OS << ",space" << Binding.Space;
218 }
219};
220
221struct FormatBindingSize
222 : public llvm::FormatAdapter<const dxil::ResourceInfo &> {
223 explicit FormatBindingSize(const dxil::ResourceInfo &RI)
224 : llvm::FormatAdapter<const dxil::ResourceInfo &>(RI) {}
225
226 void format(llvm::raw_ostream &OS, StringRef Style) {
227 uint32_t Size = Item.getBinding().Size;
228 if (Size == 0)
229 OS << "unbounded";
230 else
231 OS << Size;
232 }
233};
234
235} // namespace
236
237static void prettyPrintResources(raw_ostream &OS, const DXILResourceMap &DRM,
238 DXILResourceTypeMap &DRTM) {
239 // Column widths are arbitrary but match the widths DXC uses.
240 OS << ";\n; Resource Bindings:\n;\n";
241 OS << formatv(Fmt: "; {0,-30} {1,10} {2,7} {3,11} {4,7} {5,14} {6,9}\n", Vals: "Name",
242 Vals: "Type", Vals: "Format", Vals: "Dim", Vals: "ID", Vals: "HLSL Bind", Vals: "Count");
243 OS << formatv(
244 Fmt: "; {0,-+30} {1,-+10} {2,-+7} {3,-+11} {4,-+7} {5,-+14} {6,-+9}\n", Vals: "", Vals: "",
245 Vals: "", Vals: "", Vals: "", Vals: "", Vals: "");
246
247 // TODO: Do we want to sort these by binding or something like that?
248 for (const dxil::ResourceInfo &RI : DRM) {
249 if (!RI.hasBinding())
250 continue;
251 const dxil::ResourceTypeInfo &RTI = DRTM[RI.getHandleTy()];
252
253 dxil::ResourceClass RC = RTI.getResourceClass();
254 StringRef Name(RI.getName());
255 StringRef Type(getRCName(RC));
256 StringRef Format(getFormatName(RI: RTI));
257 FormatResourceDimension Dim(RTI, RI.hasCounter());
258 FormatBindingID ID(RI, RTI);
259 FormatBindingLocation Bind(RI, RTI);
260 FormatBindingSize Count(RI);
261 OS << formatv(Fmt: "; {0,-30} {1,10} {2,7} {3,11} {4,7} {5,14} {6,9}\n", Vals&: Name,
262 Vals&: Type, Vals&: Format, Vals&: Dim, Vals&: ID, Vals&: Bind, Vals&: Count);
263 }
264 OS << ";\n";
265}
266
267namespace {
268class DXILModuleSlotTracker : public ModuleSlotTracker {
269public:
270 using ModuleSlotTracker::ModuleSlotTracker;
271 using ModuleSlotTracker::renumberMetadataForAssembly;
272};
273
274class DXILAssemblyAnnotationWriter : public llvm::AssemblyAnnotationWriter {
275private:
276 ModuleSlotTracker &MST;
277 AbstractSlotTrackerStorage &STS;
278 const DXILDebugInfoMap &DI;
279 DenseSet<const MDNode *> &EmittedMDNodes;
280
281public:
282 DXILAssemblyAnnotationWriter(ModuleSlotTracker &MST,
283 AbstractSlotTrackerStorage &STS,
284 const DXILDebugInfoMap &DI,
285 DenseSet<const MDNode *> &EmittedMDNodes)
286 : MST(MST), STS(STS), DI(DI), EmittedMDNodes(EmittedMDNodes) {}
287
288 void emitInstructionAnnot(const Instruction *OrigI,
289 formatted_raw_ostream &os) override {
290 if (const Instruction *I = &DI.getDXILInstruction(I: *OrigI); I != OrigI) {
291 os << "; DXIL: to be replaced with: ";
292 I->print(O&: os, MST);
293 os << "\n";
294 }
295 }
296
297 void emitMDNodeAnnot(const MDNode *N, formatted_raw_ostream &os) override {
298 EmittedMDNodes.insert(V: N);
299
300 if (const Metadata *NewMD = DI.MDReplace.lookup(Val: N)) {
301 if (const auto *NewN = dyn_cast<MDNode>(Val: NewMD))
302 STS.createMetadataSlot(NewN);
303
304 os << "; DXIL: ";
305 N->printAsOperand(OS&: os, MST);
306 os << ": to be replaced by: ";
307 NewMD->printAsOperand(OS&: os, MST);
308 os << "\n";
309 return;
310 }
311
312 if (const Metadata *ExtraMD = DI.MDExtra.lookup(Val: N)) {
313 if (const auto *ExtraN = dyn_cast<MDNode>(Val: ExtraMD))
314 STS.createMetadataSlot(ExtraN);
315
316 os << "; DXIL: ";
317 N->printAsOperand(OS&: os, MST);
318 os << ": additional data: ";
319 ExtraMD->printAsOperand(OS&: os, MST);
320 os << "\n";
321 return;
322 }
323 }
324};
325} // namespace
326
327static SmallVector<const MDNode *>
328collectAdditionalMetadata(Module &M, const DXILDebugInfoMap &DI) {
329 // Annotation metadata follows module metadata in the order its keys print.
330 // Follow replacement graphs to preserve that order in canonical output.
331 M.renumberMetadataForAssembly();
332
333 ModuleSlotTracker MST(&M);
334 AbstractSlotTrackerStorage *STS = nullptr;
335 MST.setProcessHook(
336 [&](AbstractSlotTrackerStorage *STS_, const Module *) { STS = STS_; });
337 MDNode::get(Context&: M.getContext(), MDs: {})->print(OS&: llvm::nulls(), MST);
338 assert(STS && "Slot tracker storage should have been initialised");
339
340 DenseSet<const Metadata *> ReplacementMetadata;
341 for (auto [_, Replacement] : DI.MDReplace)
342 ReplacementMetadata.insert(V: Replacement);
343
344 SmallVector<std::pair<unsigned, const MDNode *>> OriginalNodes;
345 DenseSet<const MDNode *> Queued;
346 auto AddOriginal = [&](const Metadata *MD) {
347 const auto *N = dyn_cast<MDNode>(Val: MD);
348 if (!N || ReplacementMetadata.contains(V: N) || !Queued.insert(V: N).second)
349 return;
350 OriginalNodes.emplace_back(Args: STS->getMetadataSlot(N), Args&: N);
351 };
352 for (auto [Original, _] : DI.MDReplace)
353 AddOriginal(Original);
354 for (auto [Original, _] : DI.MDExtra)
355 AddOriginal(Original);
356 llvm::sort(C&: OriginalNodes);
357
358 SmallVector<const MDNode *> Worklist;
359 for (auto [_, N] : OriginalNodes)
360 Worklist.push_back(Elt: N);
361
362 SmallVector<const MDNode *> AdditionalMetadata;
363 auto AddAdditional = [&](const Metadata *MD) {
364 const auto *Root = dyn_cast_or_null<MDNode>(Val: MD);
365 if (!Root || Queued.contains(V: Root))
366 return;
367
368 AdditionalMetadata.push_back(Elt: Root);
369 SmallVector<const MDNode *> Nodes = {Root};
370 while (!Nodes.empty()) {
371 const MDNode *N = Nodes.pop_back_val();
372 if (!Queued.insert(V: N).second)
373 continue;
374 Worklist.push_back(Elt: N);
375 for (const MDOperand &Op : llvm::reverse(C: N->operands()))
376 if (const auto *OpNode = dyn_cast_or_null<MDNode>(Val: Op.get()))
377 Nodes.push_back(Elt: OpNode);
378 }
379 };
380
381 for (size_t I = 0; I != Worklist.size(); ++I) {
382 const MDNode *N = Worklist[I];
383 if (const Metadata *Replacement = DI.MDReplace.lookup(Val: N)) {
384 AddAdditional(Replacement);
385 continue;
386 }
387 AddAdditional(DI.MDExtra.lookup(Val: N));
388 }
389 return AdditionalMetadata;
390}
391
392static void prettyPrint(raw_ostream &OS, Module &M, const DXILResourceMap &DRM,
393 DXILResourceTypeMap &DRTM) {
394 formatted_raw_ostream FOS(OS);
395
396 prettyPrintResources(OS&: FOS, DRM, DRTM);
397
398 const DXILDebugInfoMap DI = collectDXILDebugInfo(M);
399 SmallVector<const MDNode *> AdditionalMetadata =
400 collectAdditionalMetadata(M, DI);
401 DXILModuleSlotTracker MST(&M);
402 MST.renumberMetadataForAssembly(AdditionalMetadata);
403 AbstractSlotTrackerStorage *STS = nullptr;
404 MST.setProcessHook(
405 [&](AbstractSlotTrackerStorage *STS_, const Module *) { STS = STS_; });
406 // Force initialisation. ModuleSlotTracker does not have a dedicated function
407 // for this so trigger it through a dummy print.
408 MDNode::get(Context&: M.getContext(), MDs: {})->print(OS&: llvm::nulls(), MST);
409 assert(STS && "Slot tracker storage should have been initialised");
410
411 DenseSet<const MDNode *> EmittedMDNodes;
412 DXILAssemblyAnnotationWriter DAAW(MST, *STS, DI, EmittedMDNodes);
413 M.print(OS&: FOS, AAW: &DAAW);
414
415 ModuleSlotTracker::MachineMDNodeListType MDNodes;
416 MST.collectMDNodes(L&: MDNodes);
417 std::sort(first: MDNodes.begin(), last: MDNodes.end(),
418 comp: [](const std::pair<unsigned, const MDNode *> &A,
419 const std::pair<unsigned, const MDNode *> &B) {
420 return A.first < B.first;
421 });
422 for (auto [_, MDNode] : MDNodes) {
423 if (EmittedMDNodes.contains(V: MDNode))
424 continue;
425 DAAW.emitMDNodeAnnot(N: MDNode, os&: FOS);
426 MDNode->print(OS&: FOS, MST);
427 FOS << "\n";
428 }
429}
430
431PreservedAnalyses DXILPrettyPrinterPass::run(Module &M,
432 ModuleAnalysisManager &MAM) {
433 const DXILResourceMap &DRM = MAM.getResult<DXILResourceAnalysis>(IR&: M);
434 DXILResourceTypeMap &DRTM = MAM.getResult<DXILResourceTypeAnalysis>(IR&: M);
435 prettyPrint(OS, M, DRM, DRTM);
436 return PreservedAnalyses::all();
437}
438
439namespace {
440class DXILPrettyPrinterLegacy : public llvm::ModulePass {
441 raw_ostream &OS; // raw_ostream to print to.
442
443public:
444 static char ID;
445
446 explicit DXILPrettyPrinterLegacy(raw_ostream &O) : ModulePass(ID), OS(O) {}
447
448 StringRef getPassName() const override { return "DXIL Pretty Printer"; }
449
450 bool runOnModule(Module &M) override;
451 void getAnalysisUsage(AnalysisUsage &AU) const override {
452 AU.addRequired<DXILResourceTypeWrapperPass>();
453 AU.addRequired<DXILResourceWrapperPass>();
454 }
455};
456} // namespace
457
458char DXILPrettyPrinterLegacy::ID = 0;
459INITIALIZE_PASS_BEGIN(DXILPrettyPrinterLegacy, "dxil-pretty-printer",
460 "DXIL Pretty Printer", true, false)
461INITIALIZE_PASS_DEPENDENCY(DXILResourceTypeWrapperPass)
462INITIALIZE_PASS_DEPENDENCY(DXILResourceWrapperPass)
463INITIALIZE_PASS_END(DXILPrettyPrinterLegacy, "dxil-pretty-printer",
464 "DXIL Pretty Printer", true, false)
465
466bool DXILPrettyPrinterLegacy::runOnModule(Module &M) {
467 const DXILResourceMap &DRM =
468 getAnalysis<DXILResourceWrapperPass>().getResourceMap();
469 DXILResourceTypeMap &DRTM =
470 getAnalysis<DXILResourceTypeWrapperPass>().getResourceTypeMap();
471 prettyPrint(OS, M, DRM, DRTM);
472 return false;
473}
474
475ModulePass *llvm::createDXILPrettyPrinterLegacyPass(raw_ostream &OS) {
476 return new DXILPrettyPrinterLegacy(OS);
477}
478