| 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 | |
| 36 | using namespace llvm; |
| 37 | using namespace llvm::dxil; |
| 38 | |
| 39 | static std::optional<uint32_t> (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 | |
| 47 | static bool reportError(LLVMContext *Ctx, Twine Message, |
| 48 | DiagnosticSeverity Severity = DS_Error) { |
| 49 | Ctx->diagnose(DI: DiagnosticInfoGeneric(Message, Severity)); |
| 50 | return true; |
| 51 | } |
| 52 | |
| 53 | static SmallDenseMap<const Function *, mcdxbc::RootSignatureDesc> |
| 54 | analyzeModule(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 | |
| 165 | AnalysisKey RootSignatureAnalysis::Key; |
| 166 | |
| 167 | RootSignatureAnalysis::Result |
| 168 | RootSignatureAnalysis::run(Module &M, ModuleAnalysisManager &AM) { |
| 169 | return RootSignatureBindingInfo(analyzeModule(M)); |
| 170 | } |
| 171 | |
| 172 | //===----------------------------------------------------------------------===// |
| 173 | |
| 174 | PreservedAnalyses 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 | //===----------------------------------------------------------------------===// |
| 248 | bool RootSignatureAnalysisWrapper::runOnModule(Module &M) { |
| 249 | FuncToRsMap = std::make_unique<RootSignatureBindingInfo>( |
| 250 | args: RootSignatureBindingInfo(analyzeModule(M))); |
| 251 | return false; |
| 252 | } |
| 253 | |
| 254 | void RootSignatureAnalysisWrapper::getAnalysisUsage(AnalysisUsage &AU) const { |
| 255 | AU.setPreservesAll(); |
| 256 | AU.addPreserved<DXILMetadataAnalysisWrapperPass>(); |
| 257 | } |
| 258 | |
| 259 | char RootSignatureAnalysisWrapper::ID = 0; |
| 260 | |
| 261 | INITIALIZE_PASS_BEGIN(RootSignatureAnalysisWrapper, |
| 262 | "dxil-root-signature-analysis" , |
| 263 | "DXIL Root Signature Analysis" , true, true) |
| 264 | INITIALIZE_PASS_END(RootSignatureAnalysisWrapper, |
| 265 | "dxil-root-signature-analysis" , |
| 266 | "DXIL Root Signature Analysis" , true, true) |
| 267 | |