Browse Source

[SPIRV] Add option to preserve interface variables (#5400)

Add `-fspv-preserve-interface` CLI option to prevent DCE optimization
pass from compiling out interface variables. It happens if these
variables are unused.

The option may be useful when decompiling SPIR-V back to HLSL.
Personally, I need it to convert DX12 shaders to DX11 ones using
SPIRV-Cross as a tool for converting SPIR-V, produced by DXC, to the old
shader model HLSL.

SPIR-V Tools now have a parameter in `RegisterPerformancePasses()` and
`RegisterLegalizationPasses()` for this. This PR creates a new command
line option in DXC and passes it to the `spvtools::Optimizer`.

Closes #4567
Kirill Kozlov 2 years ago
parent
commit
38fcd6f741

+ 2 - 0
docs/SPIR-V.rst

@@ -4067,6 +4067,8 @@ codegen for Vulkan:
   to the HLSL entry point name.
   to the HLSL entry point name.
 - ``-fspv-use-legacy-buffer-matrix-order``: Assumes the legacy matrix order (row
 - ``-fspv-use-legacy-buffer-matrix-order``: Assumes the legacy matrix order (row
   major) when accessing raw buffers (e.g., ByteAdddressBuffer).
   major) when accessing raw buffers (e.g., ByteAdddressBuffer).
+- ``-fspv-preserve-interface``: Preserves all interface variables in the entry
+  point, even when those variables are unused.
 - ``-Wno-vk-ignored-features``: Does not emit warnings on ignored features
 - ``-Wno-vk-ignored-features``: Does not emit warnings on ignored features
   resulting from no Vulkan support, e.g., cbuffer member initializer.
   resulting from no Vulkan support, e.g., cbuffer member initializer.
 
 

+ 1 - 1
external/SPIRV-Headers

@@ -1 +1 @@
-Subproject commit 268a061764ee69f09a477a695bf6a11ffe311b8d
+Subproject commit d0006a3938d7acedffb26ab517fe3e95b5288cc6

+ 1 - 1
external/SPIRV-Tools

@@ -1 +1 @@
-Subproject commit 23cb9b96cc2acf93e55839136b2c9643cbef6df6
+Subproject commit e751c7e7db28998c3c151e6702343afcfef7b17d

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

@@ -394,6 +394,8 @@ def Oconfig : CommaJoined<["-"], "Oconfig=">, Group<spirv_Group>, Flags<[CoreOpt
   HelpText<"Specify a comma-separated list of SPIRV-Tools passes to customize optimization configuration (see http://khr.io/hlsl2spirv#optimization)">;
   HelpText<"Specify a comma-separated list of SPIRV-Tools passes to customize optimization configuration (see http://khr.io/hlsl2spirv#optimization)">;
 def fspv_preserve_bindings : Flag<["-"], "fspv-preserve-bindings">, Group<spirv_Group>, Flags<[CoreOption, DriverOption]>,
 def fspv_preserve_bindings : Flag<["-"], "fspv-preserve-bindings">, Group<spirv_Group>, Flags<[CoreOption, DriverOption]>,
   HelpText<"Preserves all bindings declared within the module, even when those bindings are unused">;
   HelpText<"Preserves all bindings declared within the module, even when those bindings are unused">;
+def fspv_preserve_interface : Flag<["-"], "fspv-preserve-interface">, Group<spirv_Group>, Flags<[CoreOption, DriverOption]>,
+  HelpText<"Preserves all interface variables in the entry point, even when those variables are unused">;
 // SPIRV Change Ends
 // SPIRV Change Ends
 
 
 //////////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////

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

@@ -56,6 +56,7 @@ struct SpirvCodeGenOptions {
   bool noWarnEmulatedFeatures;
   bool noWarnEmulatedFeatures;
   bool noWarnIgnoredFeatures;
   bool noWarnIgnoredFeatures;
   bool preserveBindings;
   bool preserveBindings;
+  bool preserveInterface;
   bool useDxLayout;
   bool useDxLayout;
   bool useGlLayout;
   bool useGlLayout;
   bool useLegacyBufferMatrixOrder;
   bool useLegacyBufferMatrixOrder;

+ 2 - 0
lib/DxcSupport/HLSLOptions.cpp

@@ -986,6 +986,8 @@ int ReadDxcOpts(const OptTable *optionTable, unsigned flagsToInclude,
       Args.hasFlag(OPT_ffinite_math_only, OPT_fno_finite_math_only, false);
       Args.hasFlag(OPT_ffinite_math_only, OPT_fno_finite_math_only, false);
   opts.SpirvOptions.preserveBindings =
   opts.SpirvOptions.preserveBindings =
       Args.hasFlag(OPT_fspv_preserve_bindings, OPT_INVALID, false);
       Args.hasFlag(OPT_fspv_preserve_bindings, OPT_INVALID, false);
+  opts.SpirvOptions.preserveInterface =
+      Args.hasFlag(OPT_fspv_preserve_interface, OPT_INVALID, false);
 
 
   if (!handleVkShiftArgs(Args, OPT_fvk_b_shift, "b", &opts.SpirvOptions.bShift, errors) ||
   if (!handleVkShiftArgs(Args, OPT_fvk_b_shift, "b", &opts.SpirvOptions.bShift, errors) ||
       !handleVkShiftArgs(Args, OPT_fvk_t_shift, "t", &opts.SpirvOptions.tShift, errors) ||
       !handleVkShiftArgs(Args, OPT_fvk_t_shift, "t", &opts.SpirvOptions.tShift, errors) ||

+ 10 - 6
tools/clang/lib/SPIRV/SpirvEmitter.cpp

@@ -13931,7 +13931,7 @@ bool SpirvEmitter::spirvToolsOptimize(std::vector<uint32_t> *mod,
 
 
   if (spirvOptions.optConfig.empty()) {
   if (spirvOptions.optConfig.empty()) {
     // Add performance passes.
     // Add performance passes.
-    optimizer.RegisterPerformancePasses();
+    optimizer.RegisterPerformancePasses(spirvOptions.preserveInterface);
 
 
     // Add propagation of volatile semantics passes.
     // Add propagation of volatile semantics passes.
     optimizer.RegisterPass(spvtools::CreateSpreadVolatileSemanticsPass());
     optimizer.RegisterPass(spvtools::CreateSpreadVolatileSemanticsPass());
@@ -13974,17 +13974,19 @@ bool SpirvEmitter::spirvToolsLegalize(std::vector<uint32_t> *mod,
     optimizer.RegisterPass(
     optimizer.RegisterPass(
         spvtools::CreateInterfaceVariableScalarReplacementPass());
         spvtools::CreateInterfaceVariableScalarReplacementPass());
   }
   }
-  optimizer.RegisterLegalizationPasses();
+  optimizer.RegisterLegalizationPasses(spirvOptions.preserveInterface);
   // Add flattening of resources if needed.
   // Add flattening of resources if needed.
   if (spirvOptions.flattenResourceArrays ||
   if (spirvOptions.flattenResourceArrays ||
       declIdMapper.requiresFlatteningCompositeResources()) {
       declIdMapper.requiresFlatteningCompositeResources()) {
     optimizer.RegisterPass(
     optimizer.RegisterPass(
         spvtools::CreateReplaceDescArrayAccessUsingVarIndexPass());
         spvtools::CreateReplaceDescArrayAccessUsingVarIndexPass());
-    optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass());
+    optimizer.RegisterPass(
+        spvtools::CreateAggressiveDCEPass(spirvOptions.preserveInterface));
     optimizer.RegisterPass(spvtools::CreateDescriptorScalarReplacementPass());
     optimizer.RegisterPass(spvtools::CreateDescriptorScalarReplacementPass());
     // ADCE should be run after desc_sroa in order to remove potentially
     // ADCE should be run after desc_sroa in order to remove potentially
     // illegal types such as structures containing opaque types.
     // illegal types such as structures containing opaque types.
-    optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass());
+    optimizer.RegisterPass(
+        spvtools::CreateAggressiveDCEPass(spirvOptions.preserveInterface));
   }
   }
   if (dsetbindingsToCombineImageSampler &&
   if (dsetbindingsToCombineImageSampler &&
       !dsetbindingsToCombineImageSampler->empty()) {
       !dsetbindingsToCombineImageSampler->empty()) {
@@ -13993,14 +13995,16 @@ bool SpirvEmitter::spirvToolsLegalize(std::vector<uint32_t> *mod,
     // ADCE should be run after combining images and samplers in order to
     // ADCE should be run after combining images and samplers in order to
     // remove potentially illegal types such as structures containing opaque
     // remove potentially illegal types such as structures containing opaque
     // types.
     // types.
-    optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass());
+    optimizer.RegisterPass(
+        spvtools::CreateAggressiveDCEPass(spirvOptions.preserveInterface));
   }
   }
   if (spirvOptions.reduceLoadSize) {
   if (spirvOptions.reduceLoadSize) {
     // The threshold must be bigger than 1.0 to reduce all possible loads.
     // The threshold must be bigger than 1.0 to reduce all possible loads.
     optimizer.RegisterPass(spvtools::CreateReduceLoadSizePass(1.1));
     optimizer.RegisterPass(spvtools::CreateReduceLoadSizePass(1.1));
     // ADCE should be run after reduce-load-size pass in order to remove
     // ADCE should be run after reduce-load-size pass in order to remove
     // dead instructions.
     // dead instructions.
-    optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass());
+    optimizer.RegisterPass(
+        spvtools::CreateAggressiveDCEPass(spirvOptions.preserveInterface));
   }
   }
   optimizer.RegisterPass(spvtools::CreateReplaceInvalidOpcodePass());
   optimizer.RegisterPass(spvtools::CreateReplaceInvalidOpcodePass());
   optimizer.RegisterPass(spvtools::CreateCompactIdsPass());
   optimizer.RegisterPass(spvtools::CreateCompactIdsPass());

+ 15 - 0
tools/clang/test/CodeGenSPIRV/spv.preserve-interface.hlsl

@@ -0,0 +1,15 @@
+// RUN: %dxc -T ps_6_0 -E main -O3 -fspv-preserve-interface
+
+float4 main(float2 a : A, /* unused */ float3 b : B, float2 c : C) : SV_Target {
+  return float4(a, c);
+}
+
+// CHECK: OpName %in_var_A "in.var.A"
+// CHECK: OpName %in_var_B "in.var.B"
+// CHECK: OpName %in_var_C "in.var.C"
+// CHECK: OpDecorate %in_var_A Location 0
+// CHECK: OpDecorate %in_var_B Location 1
+// CHECK: OpDecorate %in_var_C Location 2
+// CHECK: %in_var_A = OpVariable %_ptr_Input_v2float Input
+// CHECK: %in_var_B = OpVariable %_ptr_Input_v3float Input
+// CHECK: %in_var_C = OpVariable %_ptr_Input_v2float Input

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

@@ -1166,6 +1166,10 @@ TEST_F(FileTest, SpvUseLegacyMatrixBufferOrder) {
   runFileTest("spv.use-legacy-buffer-matrix-order.hlsl");
   runFileTest("spv.use-legacy-buffer-matrix-order.hlsl");
 }
 }
 
 
+TEST_F(FileTest, SpvPreserveInterface) {
+  runFileTest("spv.preserve-interface.hlsl");
+}
+
 TEST_F(FileTest, InitializeListRWByteAddressBuffer) {
 TEST_F(FileTest, InitializeListRWByteAddressBuffer) {
   runFileTest("initializelist.rwbyteaddressbuffer.hlsl", Expect::Success,
   runFileTest("initializelist.rwbyteaddressbuffer.hlsl", Expect::Success,
               /* runValidation */ false);
               /* runValidation */ false);