| //===- DXILShaderFlags.cpp - DXIL Shader Flags helper objects -------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| /// | |
| /// \file This file contains helper objects and APIs for working with DXIL | |
| /// Shader Flags. | |
| /// | |
| //===----------------------------------------------------------------------===// | |
| #include "DXILShaderFlags.h" | |
| #include "DirectX.h" | |
| #include "llvm/ADT/SCCIterator.h" | |
| #include "llvm/ADT/SmallVector.h" | |
| #include "llvm/Analysis/CallGraph.h" | |
| #include "llvm/Analysis/DXILResource.h" | |
| #include "llvm/IR/Attributes.h" | |
| #include "llvm/IR/DiagnosticInfo.h" | |
| #include "llvm/IR/Instruction.h" | |
| #include "llvm/IR/Instructions.h" | |
| #include "llvm/IR/IntrinsicInst.h" | |
| #include "llvm/IR/Intrinsics.h" | |
| #include "llvm/IR/IntrinsicsDirectX.h" | |
| #include "llvm/IR/Module.h" | |
| #include "llvm/InitializePasses.h" | |
| #include "llvm/Support/FormatVariadic.h" | |
| #include "llvm/Support/raw_ostream.h" | |
| using namespace llvm; | |
| using namespace llvm::dxil; | |
| static bool hasUAVsAtEveryStage(const DXILResourceMap &DRM, | |
| const ModuleMetadataInfo &MMDI) { | |
| if (DRM.uavs().empty()) | |
| return false; | |
| switch (MMDI.ShaderProfile) { | |
| default: | |
| return false; | |
| case Triple::EnvironmentType::Compute: | |
| case Triple::EnvironmentType::Pixel: | |
| return false; | |
| case Triple::EnvironmentType::Vertex: | |
| case Triple::EnvironmentType::Geometry: | |
| case Triple::EnvironmentType::Hull: | |
| case Triple::EnvironmentType::Domain: | |
| return true; | |
| case Triple::EnvironmentType::Library: | |
| case Triple::EnvironmentType::RayGeneration: | |
| case Triple::EnvironmentType::Intersection: | |
| case Triple::EnvironmentType::AnyHit: | |
| case Triple::EnvironmentType::ClosestHit: | |
| case Triple::EnvironmentType::Miss: | |
| case Triple::EnvironmentType::Callable: | |
| case Triple::EnvironmentType::Mesh: | |
| case Triple::EnvironmentType::Amplification: | |
| return MMDI.ValidatorVersion < VersionTuple(1, 8); | |
| } | |
| } | |
| static bool checkWaveOps(Intrinsic::ID IID) { | |
| // Currently unsupported intrinsics | |
| // case Intrinsic::dx_wave_getlanecount: | |
| // case Intrinsic::dx_wave_readfirst: | |
| // case Intrinsic::dx_wave_reduce.and: | |
| // case Intrinsic::dx_wave_reduce.or: | |
| // case Intrinsic::dx_wave_reduce.xor: | |
| // case Intrinsic::dx_wave_prefixop: | |
| // case Intrinsic::dx_quad.readat: | |
| // case Intrinsic::dx_quad.readacrossy: | |
| // case Intrinsic::dx_quad.readacrossdiagonal: | |
| // case Intrinsic::dx_wave_prefixballot: | |
| // case Intrinsic::dx_wave_match: | |
| // case Intrinsic::dx_wavemulti.*: | |
| // case Intrinsic::dx_wavemulti.ballot: | |
| // case Intrinsic::dx_quad.vote: | |
| switch (IID) { | |
| default: | |
| return false; | |
| case Intrinsic::dx_wave_is_first_lane: | |
| case Intrinsic::dx_wave_getlaneindex: | |
| case Intrinsic::dx_wave_any: | |
| case Intrinsic::dx_wave_all_equal: | |
| case Intrinsic::dx_wave_all: | |
| case Intrinsic::dx_wave_readlane: | |
| case Intrinsic::dx_wave_active_countbits: | |
| case Intrinsic::dx_wave_ballot: | |
| case Intrinsic::dx_wave_prefix_bit_count: | |
| // Wave Active Op Variants | |
| case Intrinsic::dx_wave_reduce_or: | |
| case Intrinsic::dx_wave_reduce_xor: | |
| case Intrinsic::dx_wave_reduce_and: | |
| case Intrinsic::dx_wave_reduce_sum: | |
| case Intrinsic::dx_wave_reduce_usum: | |
| case Intrinsic::dx_wave_product: | |
| case Intrinsic::dx_wave_uproduct: | |
| case Intrinsic::dx_wave_reduce_max: | |
| case Intrinsic::dx_wave_reduce_umax: | |
| case Intrinsic::dx_wave_reduce_min: | |
| case Intrinsic::dx_wave_reduce_umin: | |
| // Wave Prefix Op Variants | |
| case Intrinsic::dx_wave_prefix_sum: | |
| case Intrinsic::dx_wave_prefix_usum: | |
| case Intrinsic::dx_wave_prefix_product: | |
| case Intrinsic::dx_wave_prefix_uproduct: | |
| // Quad Op Variants | |
| case Intrinsic::dx_quad_read_across_x: | |
| case Intrinsic::dx_quad_read_across_y: | |
| return true; | |
| } | |
| } | |
| static bool checkDoubleExtensionOps(Intrinsic::ID IID) { | |
| switch (IID) { | |
| default: | |
| return false; | |
| case Intrinsic::fma: | |
| return true; | |
| } | |
| } | |
| static bool isOptimizationDisabled(const Module &M) { | |
| const StringRef Key = "dx.disable_optimizations"; | |
| if (auto *Flag = mdconst::extract_or_null<ConstantInt>(M.getModuleFlag(Key))) | |
| return Flag->getValue().getBoolValue(); | |
| return false; | |
| } | |
| // Checks to see if the status bit from a load with status | |
| // instruction is ever extracted. If it is, the module needs | |
| // to have the TiledResources shader flag set. | |
| bool checkIfStatusIsExtracted(const IntrinsicInst &II) { | |
| [[maybe_unused]] Intrinsic::ID IID = II.getIntrinsicID(); | |
| assert(IID == Intrinsic::dx_resource_load_typedbuffer || | |
| IID == Intrinsic::dx_resource_load_rawbuffer && | |
| "unexpected intrinsic ID"); | |
| for (const User *U : II.users()) { | |
| if (const ExtractValueInst *EVI = dyn_cast<ExtractValueInst>(U)) { | |
| // Resource load operations return a {result, status} pair. | |
| // Check if we extract the status | |
| if (EVI->getNumIndices() == 1 && EVI->getIndices()[0] == 1) | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| /// Update the shader flags mask based on the given instruction. | |
| /// \param CSF Shader flags mask to update. | |
| /// \param I Instruction to check. | |
| void ModuleShaderFlags::updateFunctionFlags(ComputedShaderFlags &CSF, | |
| const Instruction &I, | |
| DXILResourceTypeMap &DRTM, | |
| const ModuleMetadataInfo &MMDI) { | |
| if (!CSF.Doubles) | |
| CSF.Doubles = I.getType()->getScalarType()->isDoubleTy(); | |
| if (!CSF.Doubles) { | |
| for (const Value *Op : I.operands()) { | |
| if (Op->getType()->getScalarType()->isDoubleTy()) { | |
| CSF.Doubles = true; | |
| break; | |
| } | |
| } | |
| } | |
| if (CSF.Doubles) { | |
| switch (I.getOpcode()) { | |
| case Instruction::FDiv: | |
| case Instruction::UIToFP: | |
| case Instruction::SIToFP: | |
| case Instruction::FPToUI: | |
| case Instruction::FPToSI: | |
| CSF.DX11_1_DoubleExtensions = true; | |
| break; | |
| } | |
| } | |
| if (!CSF.LowPrecisionPresent) | |
| CSF.LowPrecisionPresent = I.getType()->getScalarType()->isIntegerTy(16) || | |
| I.getType()->getScalarType()->isHalfTy(); | |
| if (!CSF.LowPrecisionPresent) { | |
| for (const Value *Op : I.operands()) { | |
| if (Op->getType()->getScalarType()->isIntegerTy(16) || | |
| Op->getType()->getScalarType()->isHalfTy()) { | |
| CSF.LowPrecisionPresent = true; | |
| break; | |
| } | |
| } | |
| } | |
| if (CSF.LowPrecisionPresent) { | |
| if (CSF.NativeLowPrecisionMode) | |
| CSF.NativeLowPrecision = true; | |
| else | |
| CSF.MinimumPrecision = true; | |
| } | |
| if (!CSF.Int64Ops) | |
| CSF.Int64Ops = I.getType()->getScalarType()->isIntegerTy(64); | |
| if (!CSF.Int64Ops && !isa<LifetimeIntrinsic>(&I)) { | |
| for (const Value *Op : I.operands()) { | |
| if (Op->getType()->getScalarType()->isIntegerTy(64)) { | |
| CSF.Int64Ops = true; | |
| break; | |
| } | |
| } | |
| } | |
| if (const auto *II = dyn_cast<IntrinsicInst>(&I)) { | |
| switch (II->getIntrinsicID()) { | |
| default: | |
| break; | |
| case Intrinsic::dx_resource_handlefrombinding: { | |
| dxil::ResourceTypeInfo &RTI = DRTM[cast<TargetExtType>(II->getType())]; | |
| // Set ResMayNotAlias if DXIL validator version >= 1.8 and the function | |
| // uses UAVs | |
| if (!CSF.ResMayNotAlias && CanSetResMayNotAlias && | |
| MMDI.ValidatorVersion >= VersionTuple(1, 8) && RTI.isUAV()) | |
| CSF.ResMayNotAlias = true; | |
| switch (RTI.getResourceKind()) { | |
| case dxil::ResourceKind::StructuredBuffer: | |
| case dxil::ResourceKind::RawBuffer: | |
| CSF.EnableRawAndStructuredBuffers = true; | |
| break; | |
| default: | |
| break; | |
| } | |
| break; | |
| } | |
| case Intrinsic::dx_resource_load_typedbuffer: { | |
| dxil::ResourceTypeInfo &RTI = | |
| DRTM[cast<TargetExtType>(II->getArgOperand(0)->getType())]; | |
| if (RTI.isTyped() && RTI.isUAV()) | |
| CSF.TypedUAVLoadAdditionalFormats |= RTI.getTyped().ElementCount > 1; | |
| if (!CSF.TiledResources && checkIfStatusIsExtracted(*II)) | |
| CSF.TiledResources = true; | |
| break; | |
| } | |
| case Intrinsic::dx_resource_load_rawbuffer: { | |
| if (!CSF.TiledResources && checkIfStatusIsExtracted(*II)) | |
| CSF.TiledResources = true; | |
| break; | |
| } | |
| } | |
| } | |
| // Handle call instructions | |
| if (auto *CI = dyn_cast<CallInst>(&I)) { | |
| const Function *CF = CI->getCalledFunction(); | |
| // Merge-in shader flags mask of the called function in the current module | |
| if (FunctionFlags.contains(CF)) | |
| CSF.merge(FunctionFlags[CF]); | |
| CSF.DX11_1_DoubleExtensions |= | |
| checkDoubleExtensionOps(CI->getIntrinsicID()); | |
| CSF.WaveOps |= checkWaveOps(CI->getIntrinsicID()); | |
| } | |
| } | |
| /// Set shader flags that apply to all functions within the module | |
| ComputedShaderFlags | |
| ModuleShaderFlags::gatherGlobalModuleFlags(const Module &M, | |
| const DXILResourceMap &DRM, | |
| const ModuleMetadataInfo &MMDI) { | |
| ComputedShaderFlags CSF; | |
| CSF.DisableOptimizations = isOptimizationDisabled(M); | |
| CSF.UAVsAtEveryStage = hasUAVsAtEveryStage(DRM, MMDI); | |
| // Set the Max64UAVs flag if the number of UAVs is > 8 | |
| uint32_t NumUAVs = 0; | |
| for (auto &UAV : DRM.uavs()) | |
| if (MMDI.ValidatorVersion < VersionTuple(1, 6)) { | |
| NumUAVs++; | |
| } else { // MMDI.ValidatorVersion >= VersionTuple(1, 6) | |
| uint32_t Size = UAV.getBinding().Size; | |
| uint32_t NewNum = NumUAVs + (Size == 0 ? ~0U : Size); | |
| if (NewNum < NumUAVs) | |
| NewNum = ~0U; | |
| NumUAVs = NewNum; | |
| } | |
| if (NumUAVs > 8) | |
| CSF.Max64UAVs = true; | |
| // Set the module flag that enables native low-precision execution mode. | |
| // NativeLowPrecisionMode can only be set when the command line option | |
| // -enable-16bit-types is provided. This is indicated by the dx.nativelowprec | |
| // module flag being set | |
| // This flag is needed even if the module does not use 16-bit types because a | |
| // corresponding debug module may include 16-bit types, and tools that use the | |
| // debug module may expect it to have the same flags as the original | |
| if (auto *NativeLowPrec = mdconst::extract_or_null<ConstantInt>( | |
| M.getModuleFlag("dx.nativelowprec"))) | |
| if (MMDI.ShaderModelVersion >= VersionTuple(6, 2)) | |
| CSF.NativeLowPrecisionMode = NativeLowPrec->getValue().getBoolValue(); | |
| // Set ResMayNotAlias to true if DXIL validator version < 1.8 and there | |
| // are UAVs present globally. | |
| if (CanSetResMayNotAlias && MMDI.ValidatorVersion < VersionTuple(1, 8)) | |
| CSF.ResMayNotAlias = !DRM.uavs().empty(); | |
| // The command line option -all-resources-bound will set the | |
| // dx.allresourcesbound module flag to 1 | |
| if (auto *AllResourcesBound = mdconst::extract_or_null<ConstantInt>( | |
| M.getModuleFlag("dx.allresourcesbound"))) | |
| if (AllResourcesBound->getValue().getBoolValue()) | |
| CSF.AllResourcesBound = true; | |
| return CSF; | |
| } | |
| /// Construct ModuleShaderFlags for module Module M | |
| void ModuleShaderFlags::initialize(Module &M, DXILResourceTypeMap &DRTM, | |
| const DXILResourceMap &DRM, | |
| const ModuleMetadataInfo &MMDI) { | |
| CanSetResMayNotAlias = MMDI.DXILVersion >= VersionTuple(1, 7); | |
| // The command line option -res-may-alias will set the dx.resmayalias module | |
| // flag to 1, thereby disabling the ability to set the ResMayNotAlias flag | |
| if (auto *ResMayAlias = mdconst::extract_or_null<ConstantInt>( | |
| M.getModuleFlag("dx.resmayalias"))) | |
| if (ResMayAlias->getValue().getBoolValue()) | |
| CanSetResMayNotAlias = false; | |
| ComputedShaderFlags GlobalSFMask = gatherGlobalModuleFlags(M, DRM, MMDI); | |
| CallGraph CG(M); | |
| // Compute Shader Flags Mask for all functions using post-order visit of SCC | |
| // of the call graph. | |
| for (scc_iterator<CallGraph *> SCCI = scc_begin(&CG); !SCCI.isAtEnd(); | |
| ++SCCI) { | |
| const std::vector<CallGraphNode *> &CurSCC = *SCCI; | |
| // Union of shader masks of all functions in CurSCC | |
| ComputedShaderFlags SCCSF; | |
| // List of functions in CurSCC that are neither external nor declarations | |
| // and hence whose flags are collected | |
| SmallVector<Function *> CurSCCFuncs; | |
| for (CallGraphNode *CGN : CurSCC) { | |
| Function *F = CGN->getFunction(); | |
| if (!F) | |
| continue; | |
| if (F->isDeclaration()) { | |
| assert(!F->getName().starts_with("dx.op.") && | |
| "DXIL Shader Flag analysis should not be run post-lowering."); | |
| continue; | |
| } | |
| ComputedShaderFlags CSF = GlobalSFMask; | |
| for (const auto &BB : *F) | |
| for (const auto &I : BB) | |
| updateFunctionFlags(CSF, I, DRTM, MMDI); | |
| // Update combined shader flags mask for all functions in this SCC | |
| SCCSF.merge(CSF); | |
| CurSCCFuncs.push_back(F); | |
| } | |
| // Update combined shader flags mask for all functions of the module | |
| CombinedSFMask.merge(SCCSF); | |
| // Shader flags mask of each of the functions in an SCC of the call graph is | |
| // the union of all functions in the SCC. Update shader flags masks of | |
| // functions in CurSCC accordingly. This is trivially true if SCC contains | |
| // one function. | |
| for (Function *F : CurSCCFuncs) | |
| // Merge SCCSF with that of F | |
| FunctionFlags[F].merge(SCCSF); | |
| } | |
| } | |
| void ComputedShaderFlags::print(raw_ostream &OS) const { | |
| uint64_t FlagVal = (uint64_t) * this; | |
| OS << formatv("; Shader Flags Value: {0:x8}\n;\n", FlagVal); | |
| if (FlagVal == 0) | |
| return; | |
| OS << "; Note: shader requires additional functionality:\n"; | |
| #define SHADER_FEATURE_FLAG(FeatureBit, DxilModuleNum, FlagName, Str) \ | |
| if (FlagName) \ | |
| (OS << ";").indent(7) << Str << "\n"; | |
| #include "llvm/BinaryFormat/DXContainerConstants.def" | |
| OS << "; Note: extra DXIL module flags:\n"; | |
| #define DXIL_MODULE_FLAG(DxilModuleBit, FlagName, Str) \ | |
| if (FlagName) \ | |
| (OS << ";").indent(7) << Str << "\n"; | |
| #include "llvm/BinaryFormat/DXContainerConstants.def" | |
| OS << ";\n"; | |
| } | |
| /// Return the shader flags mask of the specified function Func. | |
| const ComputedShaderFlags & | |
| ModuleShaderFlags::getFunctionFlags(const Function *Func) const { | |
| auto Iter = FunctionFlags.find(Func); | |
| assert((Iter != FunctionFlags.end() && Iter->first == Func) && | |
| "Get Shader Flags : No Shader Flags Mask exists for function"); | |
| return Iter->second; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // ShaderFlagsAnalysis and ShaderFlagsAnalysisPrinterPass | |
| // Provide an explicit template instantiation for the static ID. | |
| AnalysisKey ShaderFlagsAnalysis::Key; | |
| ModuleShaderFlags ShaderFlagsAnalysis::run(Module &M, | |
| ModuleAnalysisManager &AM) { | |
| DXILResourceTypeMap &DRTM = AM.getResult<DXILResourceTypeAnalysis>(M); | |
| DXILResourceMap &DRM = AM.getResult<DXILResourceAnalysis>(M); | |
| const ModuleMetadataInfo MMDI = AM.getResult<DXILMetadataAnalysis>(M); | |
| ModuleShaderFlags MSFI; | |
| MSFI.initialize(M, DRTM, DRM, MMDI); | |
| return MSFI; | |
| } | |
| PreservedAnalyses ShaderFlagsAnalysisPrinter::run(Module &M, | |
| ModuleAnalysisManager &AM) { | |
| const ModuleShaderFlags &FlagsInfo = AM.getResult<ShaderFlagsAnalysis>(M); | |
| // Print description of combined shader flags for all module functions | |
| OS << "; Combined Shader Flags for Module\n"; | |
| FlagsInfo.getCombinedFlags().print(OS); | |
| // Print shader flags mask for each of the module functions | |
| OS << "; Shader Flags for Module Functions\n"; | |
| for (const auto &F : M.getFunctionList()) { | |
| if (F.isDeclaration()) | |
| continue; | |
| const ComputedShaderFlags &SFMask = FlagsInfo.getFunctionFlags(&F); | |
| OS << formatv("; Function {0} : {1:x8}\n;\n", F.getName(), | |
| (uint64_t)(SFMask)); | |
| } | |
| return PreservedAnalyses::all(); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // ShaderFlagsAnalysis and ShaderFlagsAnalysisPrinterPass | |
| bool ShaderFlagsAnalysisWrapper::runOnModule(Module &M) { | |
| DXILResourceTypeMap &DRTM = | |
| getAnalysis<DXILResourceTypeWrapperPass>().getResourceTypeMap(); | |
| DXILResourceMap &DRM = | |
| getAnalysis<DXILResourceWrapperPass>().getResourceMap(); | |
| const ModuleMetadataInfo MMDI = | |
| getAnalysis<DXILMetadataAnalysisWrapperPass>().getModuleMetadata(); | |
| MSFI.initialize(M, DRTM, DRM, MMDI); | |
| return false; | |
| } | |
| void ShaderFlagsAnalysisWrapper::getAnalysisUsage(AnalysisUsage &AU) const { | |
| AU.setPreservesAll(); | |
| AU.addRequiredTransitive<DXILResourceTypeWrapperPass>(); | |
| AU.addRequiredTransitive<DXILResourceWrapperPass>(); | |
| AU.addRequired<DXILMetadataAnalysisWrapperPass>(); | |
| } | |
| char ShaderFlagsAnalysisWrapper::ID = 0; | |
| INITIALIZE_PASS_BEGIN(ShaderFlagsAnalysisWrapper, "dx-shader-flag-analysis", | |
| "DXIL Shader Flag Analysis", true, true) | |
| INITIALIZE_PASS_DEPENDENCY(DXILResourceTypeWrapperPass) | |
| INITIALIZE_PASS_DEPENDENCY(DXILMetadataAnalysisWrapperPass) | |
| INITIALIZE_PASS_END(ShaderFlagsAnalysisWrapper, "dx-shader-flag-analysis", | |
| "DXIL Shader Flag Analysis", true, true) |