1//===- DXILRootSignature.cpp - DXIL Root Signature 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 and APIs for working with DXIL
10/// Root Signatures.
11///
12//===----------------------------------------------------------------------===//
13#include "DXILRootSignature.h"
14#include "DirectX.h"
15#include "llvm/ADT/StringSwitch.h"
16#include "llvm/ADT/Twine.h"
17#include "llvm/Analysis/DXILMetadataAnalysis.h"
18#include "llvm/BinaryFormat/DXContainer.h"
19#include "llvm/Frontend/HLSL/RootSignatureMetadata.h"
20#include "llvm/Frontend/HLSL/RootSignatureValidations.h"
21#include "llvm/IR/Constants.h"
22#include "llvm/IR/DiagnosticInfo.h"
23#include "llvm/IR/Function.h"
24#include "llvm/IR/LLVMContext.h"
25#include "llvm/IR/Metadata.h"
26#include "llvm/IR/Module.h"
27#include "llvm/InitializePasses.h"
28#include "llvm/MC/DXContainerRootSignature.h"
29#include "llvm/Pass.h"
30#include "llvm/Support/Error.h"
31#include "llvm/Support/ErrorHandling.h"
32#include "llvm/Support/ScopedPrinter.h"
33#include "llvm/Support/raw_ostream.h"
34#include <cstdint>
35
36using namespace llvm;
37using namespace llvm::dxil;
38
39static std::optional<uint32_t> extractMdIntValue(MDNode *Node,
40 unsigned int OpId) {
41 if (auto *CI =
42 mdconst::dyn_extract<ConstantInt>(MD: Node->getOperand(I: OpId).get()))
43 return CI->getZExtValue();
44 return std::nullopt;
45}
46
47static bool reportError(LLVMContext *Ctx, Twine Message,
48 DiagnosticSeverity Severity = DS_Error) {
49 Ctx->diagnose(DI: DiagnosticInfoGeneric(Message, Severity));
50 return true;
51}
52
53static SmallDenseMap<const Function *, mcdxbc::RootSignatureDesc>
54analyzeModule(Module &M) {
55
56 /** Root Signature are specified as following in the metadata:
57
58 !dx.rootsignatures = !{!2} ; list of function/root signature pairs
59 !2 = !{ ptr @main, !3 } ; function, root signature
60 !3 = !{ !4, !5, !6, !7 } ; list of root signature elements
61
62 So for each MDNode inside dx.rootsignatures NamedMDNode
63 (the Root parameter of this function), the parsing process needs
64 to loop through each of its operands and process the function,
65 signature pair.
66 */
67
68 LLVMContext *Ctx = &M.getContext();
69
70 SmallDenseMap<const Function *, mcdxbc::RootSignatureDesc> RSDMap;
71
72 NamedMDNode *RootSignatureNode = M.getNamedMetadata(Name: "dx.rootsignatures");
73 if (RootSignatureNode == nullptr)
74 return RSDMap;
75
76 bool AllowNullFunctions = false;
77 if (M.getTargetTriple().getEnvironment() ==
78 Triple::EnvironmentType::RootSignature) {
79 assert(RootSignatureNode->getNumOperands() == 1);
80 AllowNullFunctions = true;
81 }
82
83 for (const auto &RSDefNode : RootSignatureNode->operands()) {
84 if (RSDefNode->getNumOperands() != 3) {
85 reportError(Ctx, Message: "Invalid Root Signature metadata - expected function, "
86 "signature, and version.");
87 continue;
88 }
89
90 // Function was pruned during compilation.
91 Function *F = nullptr;
92
93 if (!AllowNullFunctions) {
94 const MDOperand &FunctionPointerMdNode = RSDefNode->getOperand(I: 0);
95 if (FunctionPointerMdNode == nullptr) {
96 reportError(
97 Ctx, Message: "Function associated with Root Signature definition is null.");
98 continue;
99 }
100
101 ValueAsMetadata *VAM =
102 llvm::dyn_cast<ValueAsMetadata>(Val: FunctionPointerMdNode.get());
103 if (VAM == nullptr) {
104 reportError(Ctx, Message: "First element of root signature is not a Value");
105 continue;
106 }
107
108 F = dyn_cast<Function>(Val: VAM->getValue());
109 if (F == nullptr) {
110 reportError(Ctx, Message: "First element of root signature is not a Function");
111 continue;
112 }
113 }
114
115 Metadata *RootElementListOperand = RSDefNode->getOperand(I: 1).get();
116
117 if (RootElementListOperand == nullptr) {
118 reportError(Ctx, Message: "Root Element mdnode is null.");
119 continue;
120 }
121
122 MDNode *RootElementListNode = dyn_cast<MDNode>(Val: RootElementListOperand);
123 if (RootElementListNode == nullptr) {
124 reportError(Ctx, Message: "Root Element is not a metadata node.");
125 continue;
126 }
127 std::optional<uint32_t> V = extractMdIntValue(Node: RSDefNode, OpId: 2);
128 if (!V.has_value()) {
129 reportError(Ctx, Message: "Invalid RSDefNode value, expected constant int");
130 continue;
131 }
132
133 if (!hlsl::rootsig::verifyVersion(Version: *V)) {
134 reportError(Ctx, Message: "Invalid Root Signature Version: " + Twine(*V));
135 continue;
136 }
137
138 llvm::hlsl::rootsig::MetadataParser MDParser(RootElementListNode);
139 llvm::Expected<mcdxbc::RootSignatureDesc> RSDOrErr =
140 MDParser.ParseRootSignature(Version: V.value());
141
142 if (!RSDOrErr) {
143 handleAllErrors(E: RSDOrErr.takeError(), Handlers: [&](ErrorInfoBase &EIB) {
144 Ctx->emitError(ErrorStr: EIB.message());
145 });
146 continue;
147 }
148
149 auto &RSD = *RSDOrErr;
150
151 // Clang emits the root signature data in dxcontainer following a specific
152 // sequence. First the header, then the root parameters. So the header
153 // offset will always equal to the header size.
154 RSD.RootParameterOffset = sizeof(dxbc::RTS0::v1::RootSignatureHeader);
155
156 // static sampler offset is calculated when writting dxcontainer.
157 RSD.StaticSamplersOffset = 0u;
158
159 RSDMap.insert(KV: std::make_pair(x&: F, y&: RSD));
160 }
161
162 return RSDMap;
163}
164
165AnalysisKey RootSignatureAnalysis::Key;
166
167RootSignatureAnalysis::Result
168RootSignatureAnalysis::run(Module &M, ModuleAnalysisManager &AM) {
169 return RootSignatureBindingInfo(analyzeModule(M));
170}
171
172//===----------------------------------------------------------------------===//
173
174PreservedAnalyses RootSignatureAnalysisPrinter::run(Module &M,
175 ModuleAnalysisManager &AM) {
176
177 RootSignatureBindingInfo &RSDMap = AM.getResult<RootSignatureAnalysis>(IR&: M);
178 if (RSDMap.empty())
179 return PreservedAnalyses::all();
180
181 OS << "Root Signature Definitions"
182 << "\n";
183 for (const Function &F : M) {
184 auto It = RSDMap.find(F: &F);
185 if (It == RSDMap.end())
186 continue;
187 const auto &RS = It->second;
188 OS << "Definition for '" << F.getName() << "':\n";
189 // start root signature header
190 OS << "Flags: " << format_hex(N: RS.Flags, Width: 8) << "\n"
191 << "Version: " << RS.Version << "\n"
192 << "RootParametersOffset: " << RS.RootParameterOffset << "\n"
193 << "NumParameters: " << RS.ParametersContainer.size() << "\n";
194 for (size_t I = 0; I < RS.ParametersContainer.size(); I++) {
195 const mcdxbc::RootParameterInfo &Info = RS.ParametersContainer.getInfo(Location: I);
196
197 OS << "- Parameter Type: "
198 << dxbc::getRootParameterTypes().toString(Value: Info.Type) << "\n"
199 << " Shader Visibility: "
200 << dxbc::getShaderVisibility().toString(Value: Info.Visibility) << "\n";
201 switch (Info.Type) {
202 case dxbc::RootParameterType::Constants32Bit: {
203 const mcdxbc::RootConstants &Constants =
204 RS.ParametersContainer.getConstant(Index: Info.Location);
205 OS << " Register Space: " << Constants.RegisterSpace << "\n"
206 << " Shader Register: " << Constants.ShaderRegister << "\n"
207 << " Num 32 Bit Values: " << Constants.Num32BitValues << "\n";
208 break;
209 }
210 case dxbc::RootParameterType::CBV:
211 case dxbc::RootParameterType::UAV:
212 case dxbc::RootParameterType::SRV: {
213 const mcdxbc::RootDescriptor &Descriptor =
214 RS.ParametersContainer.getRootDescriptor(Index: Info.Location);
215 OS << " Register Space: " << Descriptor.RegisterSpace << "\n"
216 << " Shader Register: " << Descriptor.ShaderRegister << "\n";
217 if (RS.Version > 1)
218 OS << " Flags: " << Descriptor.Flags << "\n";
219 break;
220 }
221 case dxbc::RootParameterType::DescriptorTable: {
222 const mcdxbc::DescriptorTable &Table =
223 RS.ParametersContainer.getDescriptorTable(Index: Info.Location);
224 OS << " NumRanges: " << Table.Ranges.size() << "\n";
225
226 for (const mcdxbc::DescriptorRange &Range : Table) {
227 OS << " - Range Type: "
228 << dxil::getResourceClassName(RC: Range.RangeType) << "\n"
229 << " Register Space: " << Range.RegisterSpace << "\n"
230 << " Base Shader Register: " << Range.BaseShaderRegister << "\n"
231 << " Num Descriptors: " << Range.NumDescriptors << "\n"
232 << " Offset In Descriptors From Table Start: "
233 << Range.OffsetInDescriptorsFromTableStart << "\n";
234 if (RS.Version > 1)
235 OS << " Flags: " << Range.Flags << "\n";
236 }
237 break;
238 }
239 }
240 }
241 OS << "NumStaticSamplers: " << 0 << "\n";
242 OS << "StaticSamplersOffset: " << RS.StaticSamplersOffset << "\n";
243 }
244 return PreservedAnalyses::all();
245}
246
247//===----------------------------------------------------------------------===//
248bool RootSignatureAnalysisWrapper::runOnModule(Module &M) {
249 FuncToRsMap = std::make_unique<RootSignatureBindingInfo>(
250 args: RootSignatureBindingInfo(analyzeModule(M)));
251 return false;
252}
253
254void RootSignatureAnalysisWrapper::getAnalysisUsage(AnalysisUsage &AU) const {
255 AU.setPreservesAll();
256 AU.addPreserved<DXILMetadataAnalysisWrapperPass>();
257}
258
259char RootSignatureAnalysisWrapper::ID = 0;
260
261INITIALIZE_PASS_BEGIN(RootSignatureAnalysisWrapper,
262 "dxil-root-signature-analysis",
263 "DXIL Root Signature Analysis", true, true)
264INITIALIZE_PASS_END(RootSignatureAnalysisWrapper,
265 "dxil-root-signature-analysis",
266 "DXIL Root Signature Analysis", true, true)
267