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