瀏覽代碼

[spirv] Add support for ignoring unused resources (#875)

Added a new command line option -fvk-ignore-unused-resources
to avoid emitting SPIR-V code for resources defined but not statically
referenced by the call tree of the entry point in question.
Lei Zhang 7 年之前
父節點
當前提交
5de265fddb

+ 3 - 0
docs/SPIR-V.rst

@@ -2262,6 +2262,9 @@ codegen for Vulkan:
 - ``-fvk-t-shift N M``, similar to ``-fvk-b-shift``, but for t-type registers.
 - ``-fvk-s-shift N M``, similar to ``-fvk-b-shift``, but for s-type registers.
 - ``-fvk-u-shift N M``, similar to ``-fvk-b-shift``, but for u-type registers.
+- ``-fvk-ignore-unused-resources``: Avoids emitting SPIR-V code for resources
+  defined but not statically referenced by the call tree of the entry point
+  in question.
 - ``-fvk-stage-io-order={alpha|decl}``: Assigns the stage input/output variable
   location number according to alphabetical order or declaration order. See
   `HLSL semantic and Vulkan Location`_ for more details.

+ 1 - 0
include/dxc/Support/HLSLOptions.h

@@ -158,6 +158,7 @@ public:
   // SPIRV Change Starts
 #ifdef ENABLE_SPIRV_CODEGEN
   bool GenSPIRV; // OPT_spirv
+  bool VkIgnoreUnusedResources; // OPT_fvk_ignore_used_resources
   llvm::StringRef VkStageIoOrder; // OPT_fvk_stage_io_order
   llvm::SmallVector<uint32_t, 4> VkBShift; // OPT_fvk_b_shift
   llvm::SmallVector<uint32_t, 4> VkTShift; // OPT_fvk_t_shift

+ 2 - 0
include/dxc/Support/HLSLOptions.td

@@ -236,6 +236,8 @@ def ignore_line_directives : Flag<["-", "/"], "ignore-line-directives">, HelpTex
 // SPIRV Change Starts
 def spirv : Flag<["-"], "spirv">, Group<spirv_Group>, Flags<[CoreOption, DriverOption]>,
   HelpText<"Generate SPIR-V code">;
+def fvk_ignore_unused_resources : Flag<["-"], "fvk-ignore-unused-resources">, Group<spirv_Group>, Flags<[CoreOption, DriverOption]>,
+  HelpText<"Do not emit SPIR-V code for unused resources">;
 def fvk_stage_io_order_EQ : Joined<["-"], "fvk-stage-io-order=">, Group<spirv_Group>, Flags<[CoreOption, DriverOption, HelpHidden]>,
   HelpText<"Specify Vulkan stage I/O location assignment order">;
 def fvk_b_shift : MultiArg<["-"], "fvk-b-shift", 2>, MetaVarName<"<shift> <space>">, Group<spirv_Group>, Flags<[CoreOption, DriverOption]>,

+ 2 - 0
lib/DxcSupport/HLSLOptions.cpp

@@ -481,6 +481,7 @@ int ReadDxcOpts(const OptTable *optionTable, unsigned flagsToInclude,
   // SPIRV Change Starts
 #ifdef ENABLE_SPIRV_CODEGEN
   const bool genSpirv = opts.GenSPIRV = Args.hasFlag(OPT_spirv, OPT_INVALID, false);
+  opts.VkIgnoreUnusedResources = Args.hasFlag(OPT_fvk_ignore_unused_resources, OPT_INVALID, false);
 
   // Collects the arguments for -fvk-{b|s|t|u}-shift.
   const auto handleVkShiftArgs = [genSpirv, &Args, &errors](
@@ -518,6 +519,7 @@ int ReadDxcOpts(const OptTable *optionTable, unsigned flagsToInclude,
   }
 #else
   if (Args.hasFlag(OPT_spirv, OPT_INVALID, false) ||
+      Args.hasFlag(OPT_fvk_ignore_unused_resources, OPT_INVALID, false) ||
       !Args.getLastArgValue(OPT_fvk_stage_io_order_EQ).empty() ||
       !Args.getLastArgValue(OPT_fvk_b_shift).empty() ||
       !Args.getLastArgValue(OPT_fvk_t_shift).empty() ||

+ 1 - 0
tools/clang/include/clang/SPIRV/EmitSPIRVOptions.h

@@ -17,6 +17,7 @@ namespace clang {
 struct EmitSPIRVOptions {
   /// Disable legalization and optimization and emit raw SPIR-V
   bool codeGenHighLevel;
+  bool ignoreUnusedResources;
   llvm::StringRef stageIoOrder;
   llvm::SmallVector<uint32_t, 4> bShift;
   llvm::SmallVector<uint32_t, 4> tShift;

+ 7 - 3
tools/clang/lib/SPIRV/DeclResultIdMapper.cpp

@@ -209,7 +209,8 @@ DeclResultIdMapper::getDeclSpirvInfo(const NamedDecl *decl) const {
   return nullptr;
 }
 
-SpirvEvalInfo DeclResultIdMapper::getDeclResultId(const NamedDecl *decl) {
+SpirvEvalInfo DeclResultIdMapper::getDeclResultId(const NamedDecl *decl,
+                                                  bool checkRegistered) {
   if (const auto *info = getDeclSpirvInfo(decl))
     if (info->indexInCTBuffer >= 0) {
       // If this is a VarDecl inside a HLSLBufferDecl, we need to do an extra
@@ -234,8 +235,11 @@ SpirvEvalInfo DeclResultIdMapper::getDeclResultId(const NamedDecl *decl) {
       return *info;
     }
 
-  assert(false && "found unregistered decl");
-  decl->dump();
+  if (checkRegistered) {
+    emitFatalError("found unregistered decl", decl->getLocation())
+        << decl->getName();
+  }
+
   return 0;
 }
 

+ 16 - 3
tools/clang/lib/SPIRV/DeclResultIdMapper.h

@@ -249,10 +249,13 @@ private:
   const DeclSpirvInfo *getDeclSpirvInfo(const NamedDecl *decl) const;
 
 public:
-  /// \brief Returns the information for the given decl.
+  /// \brief Returns the information for the given decl. If the decl is not
+  /// registered previously, return an invalid SpirvEvalInfo.
   ///
-  /// This method will panic if the given decl is not registered.
-  SpirvEvalInfo getDeclResultId(const NamedDecl *decl);
+  /// This method will emit a fatal error if checkRegistered is true and the
+  /// decl is not registered.
+  SpirvEvalInfo getDeclResultId(const NamedDecl *decl,
+                                bool checkRegistered = true);
 
   /// \brief Returns the <result-id> for the given function if already
   /// registered; otherwise, treats the given function as a normal decl and
@@ -294,6 +297,16 @@ public:
   bool decorateResourceBindings();
 
 private:
+  /// \brief Wrapper method to create a fatal error message and report it
+  /// in the diagnostic engine associated with this consumer.
+  template <unsigned N>
+  DiagnosticBuilder emitFatalError(const char (&message)[N],
+                                   SourceLocation loc) {
+    const auto diagId =
+        diags.getCustomDiagID(clang::DiagnosticsEngine::Fatal, message);
+    return diags.Report(loc, diagId);
+  }
+
   /// \brief Wrapper method to create an error message and report it
   /// in the diagnostic engine associated with this consumer.
   template <unsigned N>

+ 42 - 6
tools/clang/lib/SPIRV/SPIRVEmitter.cpp

@@ -282,6 +282,18 @@ turnIntoElementPtr(SpirvEvalInfo &info, QualType elemType,
   return info.setResultId(builder.createAccessChain(ptrType, info, indices));
 }
 
+/// Returns the HLSLBufferDecl if the given VarDecl is inside a cbuffer/tbuffer.
+/// Returns nullptr otherwise, including varDecl is a ConstantBuffer or
+/// TextureBuffer itself.
+inline const HLSLBufferDecl *getCTBufferContext(const VarDecl *varDecl) {
+  if (const auto *bufferDecl =
+          dyn_cast<HLSLBufferDecl>(varDecl->getDeclContext()))
+    // Filter ConstantBuffer/TextureBuffer
+    if (!bufferDecl->isConstantBufferView())
+      return bufferDecl;
+  return nullptr;
+}
+
 } // namespace
 
 SPIRVEmitter::SPIRVEmitter(CompilerInstance &ci,
@@ -317,7 +329,11 @@ void SPIRVEmitter::HandleTranslationUnit(ASTContext &context) {
         patchConstFunc = funcDecl;
       }
     } else {
-      doDecl(decl);
+      // If ignoring unused resources, defer Decl handling inside
+      // TranslationUnit to the time of first referencing.
+      if (!spirvOptions.ignoreUnusedResources) {
+        doDecl(decl);
+      }
     }
   }
 
@@ -382,7 +398,14 @@ void SPIRVEmitter::doDecl(const Decl *decl) {
     return;
 
   if (const auto *varDecl = dyn_cast<VarDecl>(decl)) {
-    doVarDecl(varDecl);
+    // We can have VarDecls inside cbuffer/tbuffer. For those VarDecls, we need
+    // to emit their cbuffer/tbuffer as a whole and access each individual one
+    // using access chains.
+    if (const auto *bufferDecl = getCTBufferContext(varDecl)) {
+      doHLSLBufferDecl(bufferDecl);
+    } else {
+      doVarDecl(varDecl);
+    }
   } else if (const auto *funcDecl = dyn_cast<FunctionDecl>(decl)) {
     doFunctionDecl(funcDecl);
   } else if (const auto *bufferDecl = dyn_cast<HLSLBufferDecl>(decl)) {
@@ -437,11 +460,26 @@ void SPIRVEmitter::doStmt(const Stmt *stmt,
   }
 }
 
+SpirvEvalInfo SPIRVEmitter::doDeclRefExpr(const DeclRefExpr *expr) {
+  const auto *decl = expr->getDecl();
+  auto id = declIdMapper.getDeclResultId(decl, false);
+
+  if (spirvOptions.ignoreUnusedResources && !id) {
+    // First time referencing a Decl inside TranslationUnit. Register
+    // into DeclResultIdMapper and emit SPIR-V for it and then query
+    // again.
+    doDecl(decl);
+    id = declIdMapper.getDeclResultId(decl);
+  }
+
+  return id;
+}
+
 SpirvEvalInfo SPIRVEmitter::doExpr(const Expr *expr) {
   expr = expr->IgnoreParens();
 
   if (const auto *declRefExpr = dyn_cast<DeclRefExpr>(expr)) {
-    return declIdMapper.getDeclResultId(declRefExpr->getDecl());
+    return doDeclRefExpr(declRefExpr);
   }
 
   if (const auto *memberExpr = dyn_cast<MemberExpr>(expr)) {
@@ -2532,12 +2570,10 @@ SPIRVEmitter::processACSBufferAppendConsume(const CXXMemberCallExpr *expr) {
 
   const auto *object =
       expr->getImplicitObjectArgument()->IgnoreParenNoopCasts(astContext);
-  const auto *buffer = cast<DeclRefExpr>(object)->getDecl();
+  auto bufferInfo = doDeclRefExpr(cast<DeclRefExpr>(object));
 
   uint32_t index = incDecRWACSBufferCounter(expr, isAppend);
 
-  auto bufferInfo = declIdMapper.getDeclResultId(buffer);
-
   const auto bufferElemTy = hlsl::GetHLSLResourceResultType(object->getType());
 
   (void)turnIntoElementPtr(bufferInfo, bufferElemTy, {zero, index}, theBuilder,

+ 1 - 0
tools/clang/lib/SPIRV/SPIRVEmitter.h

@@ -99,6 +99,7 @@ private:
   SpirvEvalInfo doConditionalOperator(const ConditionalOperator *expr);
   SpirvEvalInfo doCXXMemberCallExpr(const CXXMemberCallExpr *expr);
   SpirvEvalInfo doCXXOperatorCallExpr(const CXXOperatorCallExpr *expr);
+  SpirvEvalInfo doDeclRefExpr(const DeclRefExpr *expr);
   SpirvEvalInfo doExtMatrixElementExpr(const ExtMatrixElementExpr *expr);
   SpirvEvalInfo doHLSLVectorElementExpr(const HLSLVectorElementExpr *expr);
   SpirvEvalInfo doInitListExpr(const InitListExpr *expr);

+ 73 - 0
tools/clang/test/CodeGenSPIRV/vk.cloption.ignore-unused-resources.hlsl

@@ -0,0 +1,73 @@
+// Run: %dxc -T ps_6_0 -E main -fvk-ignore-unused-resources
+
+SamplerState gSampler;
+
+// CHECK:      %gRWBuffer1 = OpVariable
+// CHECK-NOT:  %gRWBuffer2 = OpVariable
+// CHECK-NOT:  %gRWBuffer3 = OpVariable
+// CHECK:      %gRWBuffer4 = OpVariable
+RWBuffer<float4> gRWBuffer1;
+RWBuffer<float4> gRWBuffer2;
+RWBuffer<float4> gRWBuffer3;
+RWBuffer<float4> gRWBuffer4;
+
+// CHECK-NOT:  %gTex2D1 = OpVariable
+// CHECK:      %gTex2D2 = OpVariable
+// CHECK:      %gTex2D3 = OpVariable
+// CHECK-NOT:  %gTex2D4 = OpVariable
+Texture2D gTex2D1;
+Texture2D gTex2D2;
+Texture2D gTex2D3;
+Texture2D gTex2D4;
+
+// CHECK-NOT: %var_gCBuffer1 = OpVariable
+cbuffer gCBuffer1 {
+    float4 cb_f;
+};
+
+// CHECK:     %var_gTBuffer1 = OpVariable
+tbuffer gTBuffer1 {
+    float4 tb_f;
+};
+
+struct S {
+    float4 f;
+};
+
+// CHECK:     %gCBuffer2 = OpVariable
+// CHECK-NOT: %gTBuffer1 = OpVariable
+ConstantBuffer<S> gCBuffer2;
+TextureBuffer<S> gTBuffer2;
+
+// CHECK:     %gASBuffer1 = OpVariable
+// CHECK-NOT: %gASBuffer2 = OpVariable
+AppendStructuredBuffer<S> gASBuffer1;
+AppendStructuredBuffer<S> gASBuffer2;
+
+// CHECK-NOT: %gCSBuffer1 = OpVariable
+// CHECK:     %gCSBuffer2 = OpVariable
+ConsumeStructuredBuffer<S> gCSBuffer1;
+ConsumeStructuredBuffer<S> gCSBuffer2;
+
+float4 foo(float4 param) {
+    return param;
+}
+
+float4 bar(float4 param) {
+    return param;
+}
+
+float4 main() : SV_Target {
+    S val = {1., 2., 3., 4.};
+
+    float4 ret =
+        gRWBuffer1.Load(0) + gRWBuffer4[1] +
+        gTex2D2.Sample(gSampler, 2.) + gTex2D3[float2(3., 4.)] +
+        tb_f + gCBuffer2.f;
+
+    gASBuffer1.Append(val);
+
+    ret += gCSBuffer2.Consume().f;
+
+    return ret;
+}

+ 1 - 0
tools/clang/tools/dxcompiler/dxcompilerobj.cpp

@@ -465,6 +465,7 @@ public:
       else if (opts.GenSPIRV) {
           clang::EmitSPIRVOptions spirvOpts;
           spirvOpts.codeGenHighLevel = opts.CodeGenHighLevel;
+          spirvOpts.ignoreUnusedResources = opts.VkIgnoreUnusedResources;
           spirvOpts.stageIoOrder = opts.VkStageIoOrder;
           spirvOpts.bShift = opts.VkBShift;
           spirvOpts.tShift = opts.VkTShift;

+ 4 - 0
tools/clang/unittests/SPIRV/CodeGenSPIRVTest.cpp

@@ -966,6 +966,10 @@ TEST_F(FileTest, VulkanAttributeInvalidUsages) {
   runFileTest("vk.attribute.invalid.hlsl", FileTest::Expect::Failure);
 }
 
+TEST_F(FileTest, VulkanCLOptionIgnoreUnusedResources) {
+  runFileTest("vk.cloption.ignore-unused-resources.hlsl");
+}
+
 // Vulkan specific
 TEST_F(FileTest, VulkanLocation) { runFileTest("vk.location.hlsl"); }
 TEST_F(FileTest, VulkanLocationInputExplicitOutputImplicit) {