1//===- DXILPostOptimizationValidation.cpp - Opt DXIL validation ----------===//
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 "DXILPostOptimizationValidation.h"
10#include "DXILRootSignature.h"
11#include "DXILShaderFlags.h"
12#include "DirectX.h"
13#include "llvm/ADT/SmallString.h"
14#include "llvm/Analysis/DXILMetadataAnalysis.h"
15#include "llvm/Analysis/DXILResource.h"
16#include "llvm/IR/DiagnosticInfo.h"
17#include "llvm/IR/Instructions.h"
18#include "llvm/IR/IntrinsicsDirectX.h"
19#include "llvm/IR/Module.h"
20#include "llvm/InitializePasses.h"
21#include "llvm/Support/DXILABI.h"
22
23#define DEBUG_TYPE "dxil-post-optimization-validation"
24
25using namespace llvm;
26using namespace llvm::dxil;
27
28static ResourceClass toResourceClass(dxbc::RootParameterType Type) {
29 using namespace dxbc;
30 switch (Type) {
31 case RootParameterType::Constants32Bit:
32 return ResourceClass::CBuffer;
33 case RootParameterType::SRV:
34 return ResourceClass::SRV;
35 case RootParameterType::UAV:
36 return ResourceClass::UAV;
37 case RootParameterType::CBV:
38 return ResourceClass::CBuffer;
39 case dxbc::RootParameterType::DescriptorTable:
40 llvm_unreachable("DescriptorTable is not convertible to ResourceClass");
41 }
42 llvm_unreachable("Unknown RootParameterType");
43}
44
45static void reportInvalidDirection(Module &M, DXILResourceMap &DRM) {
46 for (const auto &UAV : DRM.uavs()) {
47 if (UAV.CounterDirection != ResourceCounterDirection::Invalid)
48 continue;
49
50 CallInst *ResourceHandle = nullptr;
51 for (CallInst *MaybeHandle : DRM.calls()) {
52 if (*DRM.find(Key: MaybeHandle) == UAV) {
53 ResourceHandle = MaybeHandle;
54 break;
55 }
56 }
57
58 StringRef Message = "RWStructuredBuffers may increment or decrement their "
59 "counters, but not both.";
60 for (const auto &U : ResourceHandle->users()) {
61 const CallInst *CI = dyn_cast<CallInst>(Val: U);
62 if (!CI && CI->getIntrinsicID() != Intrinsic::dx_resource_updatecounter)
63 continue;
64
65 M.getContext().diagnose(DI: DiagnosticInfoGenericWithLoc(
66 Message, *CI->getFunction(), CI->getDebugLoc()));
67 }
68 }
69}
70
71static void reportOverlappingError(Module &M, ResourceInfo R1,
72 ResourceInfo R2) {
73 SmallString<128> Message;
74 raw_svector_ostream OS(Message);
75 OS << "resource " << R1.getName() << " at register "
76 << R1.getBinding().LowerBound << " overlaps with resource " << R2.getName()
77 << " at register " << R2.getBinding().LowerBound << " in space "
78 << R2.getBinding().Space;
79 M.getContext().diagnose(DI: DiagnosticInfoGeneric(Message));
80}
81
82static void reportOverlappingBinding(Module &M, DXILResourceMap &DRM) {
83 [[maybe_unused]] bool ErrorFound = false;
84 for (const auto &ResList :
85 {DRM.srvs(), DRM.uavs(), DRM.cbuffers(), DRM.samplers()}) {
86 if (ResList.empty())
87 continue;
88 const ResourceInfo *PrevRI = &*ResList.begin();
89 for (auto *I = ResList.begin() + 1; I != ResList.end(); ++I) {
90 const ResourceInfo *CurrentRI = &*I;
91 const ResourceInfo *RI = CurrentRI;
92 while (RI != ResList.end() &&
93 PrevRI->getBinding().overlapsWith(RHS: RI->getBinding())) {
94 reportOverlappingError(M, R1: *PrevRI, R2: *RI);
95 ErrorFound = true;
96 RI++;
97 }
98 PrevRI = CurrentRI;
99 }
100 }
101 assert(ErrorFound && "this function should be called only when if "
102 "DXILResourceBindingInfo::hasOverlapingBinding() is "
103 "true, yet no overlapping binding was found");
104}
105
106static void reportInvalidHandleTyError(Module &M, ResourceClass RC,
107 ResourceInfo::ResourceBinding Binding) {
108 SmallString<160> Message;
109 raw_svector_ostream OS(Message);
110 StringRef RCName = getResourceClassName(RC);
111 OS << RCName << " at register " << Binding.LowerBound << " and space "
112 << Binding.Space << " is bound to a texture or typed buffer. " << RCName
113 << " root descriptors can only be Raw or Structured buffers.";
114 M.getContext().diagnose(DI: DiagnosticInfoGeneric(Message));
115}
116
117static void reportOverlappingRegisters(Module &M, const llvm::hlsl::Binding &R1,
118 const llvm::hlsl::Binding &R2) {
119 SmallString<128> Message;
120
121 raw_svector_ostream OS(Message);
122 OS << "resource " << getResourceClassName(RC: R1.RC) << " (space=" << R1.Space
123 << ", registers=[" << R1.LowerBound << ", " << R1.UpperBound
124 << "]) overlaps with resource " << getResourceClassName(RC: R2.RC)
125 << " (space=" << R2.Space << ", registers=[" << R2.LowerBound << ", "
126 << R2.UpperBound << "])";
127 M.getContext().diagnose(DI: DiagnosticInfoGeneric(Message));
128}
129
130static void
131reportRegNotBound(Module &M, ResourceClass Class,
132 const llvm::dxil::ResourceInfo::ResourceBinding &Unbound) {
133 SmallString<128> Message;
134 raw_svector_ostream OS(Message);
135 OS << getResourceClassName(RC: Class) << " register " << Unbound.LowerBound
136 << " in space " << Unbound.Space
137 << " does not have a binding in the Root Signature";
138 M.getContext().diagnose(DI: DiagnosticInfoGeneric(Message));
139}
140
141static dxbc::ShaderVisibility
142tripleToVisibility(llvm::Triple::EnvironmentType ET) {
143 switch (ET) {
144 case Triple::Pixel:
145 return dxbc::ShaderVisibility::Pixel;
146 case Triple::Vertex:
147 return dxbc::ShaderVisibility::Vertex;
148 case Triple::Geometry:
149 return dxbc::ShaderVisibility::Geometry;
150 case Triple::Hull:
151 return dxbc::ShaderVisibility::Hull;
152 case Triple::Domain:
153 return dxbc::ShaderVisibility::Domain;
154 case Triple::Mesh:
155 return dxbc::ShaderVisibility::Mesh;
156 case Triple::Compute:
157 return dxbc::ShaderVisibility::All;
158 default:
159 llvm_unreachable("Invalid triple to shader stage conversion");
160 }
161}
162
163static void reportIfDeniedShaderStageAccess(Module &M,
164 const dxbc::RootFlags &Flags,
165 const dxbc::RootFlags &Mask) {
166 if ((Flags & Mask) != Mask)
167 return;
168
169 SmallString<128> Message;
170 raw_svector_ostream OS(Message);
171 OS << "Shader has root bindings but root signature uses a DENY flag to "
172 "disallow root binding access to the shader stage.";
173 M.getContext().diagnose(DI: DiagnosticInfoGeneric(Message));
174}
175
176static std::optional<dxbc::RootFlags>
177getEnvironmentDenyFlagMask(Triple::EnvironmentType ShaderProfile) {
178 switch (ShaderProfile) {
179 case Triple::Pixel:
180 return dxbc::RootFlags::DenyPixelShaderRootAccess;
181 case Triple::Vertex:
182 return dxbc::RootFlags::DenyVertexShaderRootAccess;
183 case Triple::Geometry:
184 return dxbc::RootFlags::DenyGeometryShaderRootAccess;
185 case Triple::Hull:
186 return dxbc::RootFlags::DenyHullShaderRootAccess;
187 case Triple::Domain:
188 return dxbc::RootFlags::DenyDomainShaderRootAccess;
189 case Triple::Mesh:
190 return dxbc::RootFlags::DenyMeshShaderRootAccess;
191 case Triple::Amplification:
192 return dxbc::RootFlags::DenyAmplificationShaderRootAccess;
193 default:
194 return std::nullopt;
195 }
196}
197
198static void validateRootSignature(Module &M,
199 const mcdxbc::RootSignatureDesc &RSD,
200 dxil::ModuleMetadataInfo &MMI,
201 DXILResourceMap &DRM,
202 DXILResourceTypeMap &DRTM) {
203
204 hlsl::BindingInfoBuilder Builder;
205 dxbc::ShaderVisibility Visibility = tripleToVisibility(ET: MMI.ShaderProfile);
206
207 for (const mcdxbc::RootParameterInfo &ParamInfo : RSD.ParametersContainer) {
208 dxbc::ShaderVisibility ParamVisibility =
209 dxbc::ShaderVisibility(ParamInfo.Visibility);
210 if (ParamVisibility != dxbc::ShaderVisibility::All &&
211 ParamVisibility != Visibility)
212 continue;
213 dxbc::RootParameterType ParamType = dxbc::RootParameterType(ParamInfo.Type);
214 switch (ParamType) {
215 case dxbc::RootParameterType::Constants32Bit: {
216 mcdxbc::RootConstants Const =
217 RSD.ParametersContainer.getConstant(Index: ParamInfo.Location);
218 Builder.trackBinding(RC: dxil::ResourceClass::CBuffer, Space: Const.RegisterSpace,
219 LowerBound: Const.ShaderRegister, UpperBound: Const.ShaderRegister,
220 Cookie: &ParamInfo);
221 break;
222 }
223
224 case dxbc::RootParameterType::SRV:
225 case dxbc::RootParameterType::UAV:
226 case dxbc::RootParameterType::CBV: {
227 mcdxbc::RootDescriptor Desc =
228 RSD.ParametersContainer.getRootDescriptor(Index: ParamInfo.Location);
229 Builder.trackBinding(RC: toResourceClass(Type: ParamInfo.Type), Space: Desc.RegisterSpace,
230 LowerBound: Desc.ShaderRegister, UpperBound: Desc.ShaderRegister,
231 Cookie: &ParamInfo);
232
233 break;
234 }
235 case dxbc::RootParameterType::DescriptorTable: {
236 const mcdxbc::DescriptorTable &Table =
237 RSD.ParametersContainer.getDescriptorTable(Index: ParamInfo.Location);
238
239 for (const mcdxbc::DescriptorRange &Range : Table.Ranges) {
240 uint32_t UpperBound =
241 Range.NumDescriptors == ~0U
242 ? Range.BaseShaderRegister
243 : Range.BaseShaderRegister + Range.NumDescriptors - 1;
244 Builder.trackBinding(RC: Range.RangeType, Space: Range.RegisterSpace,
245 LowerBound: Range.BaseShaderRegister, UpperBound, Cookie: &ParamInfo);
246 }
247 break;
248 }
249 }
250 }
251
252 for (const mcdxbc::StaticSampler &S : RSD.StaticSamplers)
253 Builder.trackBinding(RC: dxil::ResourceClass::Sampler, Space: S.RegisterSpace,
254 LowerBound: S.ShaderRegister, UpperBound: S.ShaderRegister, Cookie: &S);
255
256 Builder.calculateBindingInfo(
257 ReportOverlap: [&M](const llvm::hlsl::BindingInfoBuilder &Builder,
258 const llvm::hlsl::Binding &ReportedBinding) {
259 const llvm::hlsl::Binding &Overlaping =
260 Builder.findOverlapping(ReportedBinding);
261 reportOverlappingRegisters(M, R1: ReportedBinding, R2: Overlaping);
262 });
263
264 const hlsl::BoundRegs &BoundRegs = Builder.takeBoundRegs();
265 bool HasBindings = false;
266 for (const ResourceInfo &RI : DRM) {
267 const ResourceInfo::ResourceBinding &Binding = RI.getBinding();
268 const dxil::ResourceTypeInfo &RTI = DRTM[RI.getHandleTy()];
269 dxil::ResourceClass RC = RTI.getResourceClass();
270 dxil::ResourceKind RK = RTI.getResourceKind();
271
272 const llvm::hlsl::Binding *Reg =
273 BoundRegs.findBoundReg(RC, Space: Binding.Space, LowerBound: Binding.LowerBound,
274 UpperBound: Binding.LowerBound + Binding.Size - 1);
275
276 if (!Reg) {
277 reportRegNotBound(M, Class: RC, Unbound: Binding);
278 continue;
279 }
280
281 const auto *ParamInfo =
282 static_cast<const mcdxbc::RootParameterInfo *>(Reg->Cookie);
283
284 bool IsSRVOrUAV = RC == ResourceClass::SRV || RC == ResourceClass::UAV;
285 bool IsDescriptorTable =
286 ParamInfo->Type == dxbc::RootParameterType::DescriptorTable;
287 bool IsRawOrStructuredBuffer =
288 RK != ResourceKind::RawBuffer && RK != ResourceKind::StructuredBuffer;
289 if (IsSRVOrUAV && !IsDescriptorTable && IsRawOrStructuredBuffer) {
290 reportInvalidHandleTyError(M, RC, Binding);
291 continue;
292 }
293
294 HasBindings = true;
295 }
296
297 if (!HasBindings)
298 return;
299
300 if (std::optional<dxbc::RootFlags> Mask =
301 getEnvironmentDenyFlagMask(ShaderProfile: MMI.ShaderProfile))
302 reportIfDeniedShaderStageAccess(M, Flags: dxbc::RootFlags(RSD.Flags), Mask: *Mask);
303}
304
305static mcdxbc::RootSignatureDesc *
306getRootSignature(RootSignatureBindingInfo &RSBI,
307 dxil::ModuleMetadataInfo &MMI) {
308 if (MMI.EntryPropertyVec.size() == 0)
309 return nullptr;
310 return RSBI.getDescForFunction(F: MMI.EntryPropertyVec[0].Entry);
311}
312
313static void reportErrors(Module &M, DXILResourceMap &DRM,
314 DXILResourceBindingInfo &DRBI,
315 RootSignatureBindingInfo &RSBI,
316 dxil::ModuleMetadataInfo &MMI,
317 DXILResourceTypeMap &DRTM) {
318 if (DRM.hasInvalidCounterDirection())
319 reportInvalidDirection(M, DRM);
320
321 if (DRBI.hasOverlappingBinding())
322 reportOverlappingBinding(M, DRM);
323
324 assert(!DRBI.hasImplicitBinding() && "implicit bindings should be handled in "
325 "DXILResourceImplicitBinding pass");
326
327 if (mcdxbc::RootSignatureDesc *RSD = getRootSignature(RSBI, MMI))
328 validateRootSignature(M, RSD: *RSD, MMI, DRM, DRTM);
329}
330
331PreservedAnalyses
332DXILPostOptimizationValidation::run(Module &M, ModuleAnalysisManager &MAM) {
333 DXILResourceMap &DRM = MAM.getResult<DXILResourceAnalysis>(IR&: M);
334 DXILResourceBindingInfo &DRBI = MAM.getResult<DXILResourceBindingAnalysis>(IR&: M);
335 RootSignatureBindingInfo &RSBI = MAM.getResult<RootSignatureAnalysis>(IR&: M);
336 ModuleMetadataInfo &MMI = MAM.getResult<DXILMetadataAnalysis>(IR&: M);
337 DXILResourceTypeMap &DRTM = MAM.getResult<DXILResourceTypeAnalysis>(IR&: M);
338
339 reportErrors(M, DRM, DRBI, RSBI, MMI, DRTM);
340 return PreservedAnalyses::all();
341}
342
343namespace {
344class DXILPostOptimizationValidationLegacy : public ModulePass {
345public:
346 bool runOnModule(Module &M) override {
347 DXILResourceMap &DRM =
348 getAnalysis<DXILResourceWrapperPass>().getResourceMap();
349 DXILResourceBindingInfo &DRBI =
350 getAnalysis<DXILResourceBindingWrapperPass>().getBindingInfo();
351 RootSignatureBindingInfo &RSBI =
352 getAnalysis<RootSignatureAnalysisWrapper>().getRSInfo();
353 dxil::ModuleMetadataInfo &MMI =
354 getAnalysis<DXILMetadataAnalysisWrapperPass>().getModuleMetadata();
355 DXILResourceTypeMap &DRTM =
356 getAnalysis<DXILResourceTypeWrapperPass>().getResourceTypeMap();
357
358 reportErrors(M, DRM, DRBI, RSBI, MMI, DRTM);
359 return false;
360 }
361 StringRef getPassName() const override {
362 return "DXIL Post Optimization Validation";
363 }
364 DXILPostOptimizationValidationLegacy() : ModulePass(ID) {}
365
366 static char ID; // Pass identification.
367 void getAnalysisUsage(llvm::AnalysisUsage &AU) const override {
368 AU.addRequired<DXILResourceWrapperPass>();
369 AU.addRequired<DXILResourceBindingWrapperPass>();
370 AU.addRequired<DXILMetadataAnalysisWrapperPass>();
371 AU.addRequired<RootSignatureAnalysisWrapper>();
372 AU.addRequired<DXILResourceTypeWrapperPass>();
373 AU.addPreserved<DXILResourceWrapperPass>();
374 AU.addPreserved<DXILResourceBindingWrapperPass>();
375 AU.addPreserved<DXILMetadataAnalysisWrapperPass>();
376 AU.addPreserved<ShaderFlagsAnalysisWrapper>();
377 AU.addPreserved<RootSignatureAnalysisWrapper>();
378 }
379};
380char DXILPostOptimizationValidationLegacy::ID = 0;
381} // end anonymous namespace
382
383INITIALIZE_PASS_BEGIN(DXILPostOptimizationValidationLegacy, DEBUG_TYPE,
384 "DXIL Post Optimization Validation", false, false)
385INITIALIZE_PASS_DEPENDENCY(DXILResourceBindingWrapperPass)
386INITIALIZE_PASS_DEPENDENCY(DXILResourceTypeWrapperPass)
387INITIALIZE_PASS_DEPENDENCY(DXILResourceWrapperPass)
388INITIALIZE_PASS_DEPENDENCY(DXILMetadataAnalysisWrapperPass)
389INITIALIZE_PASS_DEPENDENCY(RootSignatureAnalysisWrapper)
390INITIALIZE_PASS_DEPENDENCY(DXILResourceTypeWrapperPass)
391INITIALIZE_PASS_END(DXILPostOptimizationValidationLegacy, DEBUG_TYPE,
392 "DXIL Post Optimization Validation", false, false)
393
394ModulePass *llvm::createDXILPostOptimizationValidationLegacyPass() {
395 return new DXILPostOptimizationValidationLegacy();
396}
397