From 155728b2e9951b7d22c777c343d41ceed8343364 Mon Sep 17 00:00:00 2001 From: Steven Perron Date: Fri, 12 Jan 2024 14:45:17 -0500 Subject: [PATCH] Add preserver-interface option to spirv-opt (#5524) The optimizer is able to preserve the interface variables of the shaders, but that feature has not been exposed to the command line tool. This commit adds an option `--preserve-interface` to spirv-opt that will cause all calls to ADCE to leave the input and output variables, even if the variable is unused. It will apply regardless of where the option appears on the command line. Fixes #5522 --- include/spirv-tools/libspirv.h | 7 +++++ include/spirv-tools/optimizer.hpp | 15 ++++++---- source/opt/optimizer.cpp | 47 +++++++++++++++++++++++-------- tools/opt/opt.cpp | 10 ++++++- 4 files changed, 61 insertions(+), 18 deletions(-) diff --git a/include/spirv-tools/libspirv.h b/include/spirv-tools/libspirv.h index b70f084a8e..abdfc15d4f 100644 --- a/include/spirv-tools/libspirv.h +++ b/include/spirv-tools/libspirv.h @@ -966,9 +966,16 @@ SPIRV_TOOLS_EXPORT bool spvOptimizerRegisterPassFromFlag( spv_optimizer_t* optimizer, const char* flag); // Registers passes specified by length number of flags in an optimizer object. +// Passes may remove interface variables that are unused. SPIRV_TOOLS_EXPORT bool spvOptimizerRegisterPassesFromFlags( spv_optimizer_t* optimizer, const char** flags, const size_t flag_count); +// Registers passes specified by length number of flags in an optimizer object. +// Passes will not remove interface variables. +SPIRV_TOOLS_EXPORT bool +spvOptimizerRegisterPassesFromFlagsWhilePreservingTheInterface( + spv_optimizer_t* optimizer, const char** flags, const size_t flag_count); + // Optimizes the SPIR-V code of size |word_count| pointed to by |binary| and // returns an optimized spv_binary in |optimized_binary|. // diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp index 53ebc59f00..bc4f5bee89 100644 --- a/include/spirv-tools/optimizer.hpp +++ b/include/spirv-tools/optimizer.hpp @@ -100,8 +100,6 @@ class Optimizer { // // If |preserve_interface| is true, all non-io variables in the entry point // interface are considered live and are not eliminated. - // |preserve_interface| should be true if HLSL is generated - // from the SPIR-V bytecode. Optimizer& RegisterPerformancePasses(); Optimizer& RegisterPerformancePasses(bool preserve_interface); @@ -111,8 +109,6 @@ class Optimizer { // // If |preserve_interface| is true, all non-io variables in the entry point // interface are considered live and are not eliminated. - // |preserve_interface| should be true if HLSL is generated - // from the SPIR-V bytecode. Optimizer& RegisterSizePasses(); Optimizer& RegisterSizePasses(bool preserve_interface); @@ -127,8 +123,6 @@ class Optimizer { // // If |preserve_interface| is true, all non-io variables in the entry point // interface are considered live and are not eliminated. - // |preserve_interface| should be true if HLSL is generated - // from the SPIR-V bytecode. Optimizer& RegisterLegalizationPasses(); Optimizer& RegisterLegalizationPasses(bool preserve_interface); @@ -139,8 +133,13 @@ class Optimizer { // error message is emitted to the MessageConsumer object (use // Optimizer::SetMessageConsumer to define a message consumer, if needed). // + // If |preserve_interface| is true, all non-io variables in the entry point + // interface are considered live and are not eliminated. + // // If all the passes are registered successfully, it returns true. bool RegisterPassesFromFlags(const std::vector& flags); + bool RegisterPassesFromFlags(const std::vector& flags, + bool preserve_interface); // Registers the optimization pass associated with |flag|. This only accepts // |flag| values of the form "--pass_name[=pass_args]". If no such pass @@ -157,7 +156,11 @@ class Optimizer { // // --legalize-hlsl: Registers all passes that legalize SPIR-V generated by an // HLSL front-end. + // + // If |preserve_interface| is true, all non-io variables in the entry point + // interface are considered live and are not eliminated. bool RegisterPassFromFlag(const std::string& flag); + bool RegisterPassFromFlag(const std::string& flag, bool preserve_interface); // Validates that |flag| has a valid format. Strings accepted: // diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp index d865cf1d47..7cfc89f754 100644 --- a/source/opt/optimizer.cpp +++ b/source/opt/optimizer.cpp @@ -33,6 +33,15 @@ namespace spvtools { +std::vector GetVectorOfStrings(const char** strings, + const size_t string_count) { + std::vector result; + for (uint32_t i = 0; i < string_count; i++) { + result.emplace_back(strings[i]); + } + return result; +} + struct Optimizer::PassToken::Impl { Impl(std::unique_ptr p) : pass(std::move(p)) {} @@ -256,8 +265,13 @@ Optimizer& Optimizer::RegisterSizePasses(bool preserve_interface) { Optimizer& Optimizer::RegisterSizePasses() { return RegisterSizePasses(false); } bool Optimizer::RegisterPassesFromFlags(const std::vector& flags) { + return RegisterPassesFromFlags(flags, false); +} + +bool Optimizer::RegisterPassesFromFlags(const std::vector& flags, + bool preserve_interface) { for (const auto& flag : flags) { - if (!RegisterPassFromFlag(flag)) { + if (!RegisterPassFromFlag(flag, preserve_interface)) { return false; } } @@ -281,6 +295,11 @@ bool Optimizer::FlagHasValidForm(const std::string& flag) const { } bool Optimizer::RegisterPassFromFlag(const std::string& flag) { + return RegisterPassFromFlag(flag, false); +} + +bool Optimizer::RegisterPassFromFlag(const std::string& flag, + bool preserve_interface) { if (!FlagHasValidForm(flag)) { return false; } @@ -342,7 +361,7 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) { } else if (pass_name == "descriptor-scalar-replacement") { RegisterPass(CreateDescriptorScalarReplacementPass()); } else if (pass_name == "eliminate-dead-code-aggressive") { - RegisterPass(CreateAggressiveDCEPass()); + RegisterPass(CreateAggressiveDCEPass(preserve_interface)); } else if (pass_name == "eliminate-insert-extract") { RegisterPass(CreateInsertExtractElimPass()); } else if (pass_name == "eliminate-local-single-block") { @@ -513,11 +532,11 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) { } else if (pass_name == "fix-storage-class") { RegisterPass(CreateFixStorageClassPass()); } else if (pass_name == "O") { - RegisterPerformancePasses(); + RegisterPerformancePasses(preserve_interface); } else if (pass_name == "Os") { - RegisterSizePasses(); + RegisterSizePasses(preserve_interface); } else if (pass_name == "legalize-hlsl") { - RegisterLegalizationPasses(); + RegisterLegalizationPasses(preserve_interface); } else if (pass_name == "remove-unused-interface-variables") { RegisterPass(CreateRemoveUnusedInterfaceVariablesPass()); } else if (pass_name == "graphics-robust-access") { @@ -1170,13 +1189,19 @@ SPIRV_TOOLS_EXPORT bool spvOptimizerRegisterPassFromFlag( SPIRV_TOOLS_EXPORT bool spvOptimizerRegisterPassesFromFlags( spv_optimizer_t* optimizer, const char** flags, const size_t flag_count) { - std::vector opt_flags; - for (uint32_t i = 0; i < flag_count; i++) { - opt_flags.emplace_back(flags[i]); - } + std::vector opt_flags = + spvtools::GetVectorOfStrings(flags, flag_count); + return reinterpret_cast(optimizer) + ->RegisterPassesFromFlags(opt_flags, false); +} - return reinterpret_cast(optimizer)-> - RegisterPassesFromFlags(opt_flags); +SPIRV_TOOLS_EXPORT bool +spvOptimizerRegisterPassesFromFlagsWhilePreservingTheInterface( + spv_optimizer_t* optimizer, const char** flags, const size_t flag_count) { + std::vector opt_flags = + spvtools::GetVectorOfStrings(flags, flag_count); + return reinterpret_cast(optimizer) + ->RegisterPassesFromFlags(opt_flags, true); } SPIRV_TOOLS_EXPORT diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp index 3dfa021fdd..24724e59fa 100644 --- a/tools/opt/opt.cpp +++ b/tools/opt/opt.cpp @@ -392,6 +392,11 @@ Options (in lexicographical order):)", Ensure that the optimizer preserves all bindings declared within the module, even when those bindings are unused.)"); printf(R"( + --preserve-interface + Ensure that input and output variables are not removed from the + shader, even if they are unused. Note that this option applies to + all passes that will be run regardless of the order of the flags.)"); + printf(R"( --preserve-spec-constants Ensure that the optimizer preserves all specialization constants declared within the module, even when those constants are unused.)"); @@ -701,6 +706,7 @@ OptStatus ParseFlags(int argc, const char** argv, spvtools::ValidatorOptions* validator_options, spvtools::OptimizerOptions* optimizer_options) { std::vector pass_flags; + bool preserve_interface = true; for (int argi = 1; argi < argc; ++argi) { const char* cur_arg = argv[argi]; if ('-' == cur_arg[0]) { @@ -790,6 +796,8 @@ OptStatus ParseFlags(int argc, const char** argv, validator_options->SetSkipBlockLayout(true); } else if (0 == strcmp(cur_arg, "--relax-struct-store")) { validator_options->SetRelaxStructStore(true); + } else if (0 == strcmp(cur_arg, "--preserve-interface")) { + preserve_interface = true; } else { // Some passes used to accept the form '--pass arg', canonicalize them // to '--pass=arg'. @@ -812,7 +820,7 @@ OptStatus ParseFlags(int argc, const char** argv, } } - if (!optimizer->RegisterPassesFromFlags(pass_flags)) { + if (!optimizer->RegisterPassesFromFlags(pass_flags, preserve_interface)) { return {OPT_STOP, 1}; }