Add —preserve-bindings and —preserve-spec-constants (#2693)

Add optimizer options to for preservation of spec constants and variable with
binding decorations.  They are to be preserved even if they are unused.
diff --git a/include/spirv-tools/libspirv.h b/include/spirv-tools/libspirv.h
index acd641e..e21b058 100644
--- a/include/spirv-tools/libspirv.h
+++ b/include/spirv-tools/libspirv.h
@@ -574,6 +574,15 @@
 SPIRV_TOOLS_EXPORT void spvOptimizerOptionsSetMaxIdBound(
     spv_optimizer_options options, uint32_t val);
 
+// Records whether all bindings within the module should be preserved.
+SPIRV_TOOLS_EXPORT void spvOptimizerOptionsSetPreserveBindings(
+    spv_optimizer_options options, bool val);
+
+// Records whether all specialization constants within the module
+// should be preserved.
+SPIRV_TOOLS_EXPORT void spvOptimizerOptionsSetPreserveSpecConstants(
+    spv_optimizer_options options, bool val);
+
 // Creates a reducer options object with default options. Returns a valid
 // options object. The object remains valid until it is passed into
 // |spvReducerOptionsDestroy|.
diff --git a/include/spirv-tools/libspirv.hpp b/include/spirv-tools/libspirv.hpp
index a1db488..2da1152 100644
--- a/include/spirv-tools/libspirv.hpp
+++ b/include/spirv-tools/libspirv.hpp
@@ -161,6 +161,18 @@
     spvOptimizerOptionsSetMaxIdBound(options_, new_bound);
   }
 
+  // Records whether all bindings within the module should be preserved.
+  void set_preserve_bindings(bool preserve_bindings) {
+    spvOptimizerOptionsSetPreserveBindings(options_, preserve_bindings);
+  }
+
+  // Records whether all specialization constants within the module
+  // should be preserved.
+  void set_preserve_spec_constants(bool preserve_spec_constants) {
+    spvOptimizerOptionsSetPreserveSpecConstants(options_,
+                                                preserve_spec_constants);
+  }
+
  private:
   spv_optimizer_options options_;
 };
diff --git a/source/opt/aggressive_dead_code_elim_pass.cpp b/source/opt/aggressive_dead_code_elim_pass.cpp
index a4bef6c..bc7ec87 100644
--- a/source/opt/aggressive_dead_code_elim_pass.cpp
+++ b/source/opt/aggressive_dead_code_elim_pass.cpp
@@ -570,13 +570,28 @@
       AddToWorklist(&entry);
     }
   }
-  // Keep workgroup size.
   for (auto& anno : get_module()->annotations()) {
     if (anno.opcode() == SpvOpDecorate) {
+      // Keep workgroup size.
       if (anno.GetSingleWordInOperand(1u) == SpvDecorationBuiltIn &&
           anno.GetSingleWordInOperand(2u) == SpvBuiltInWorkgroupSize) {
         AddToWorklist(&anno);
       }
+
+      if (context()->preserve_bindings()) {
+        // Keep all bindings.
+        if ((anno.GetSingleWordInOperand(1u) == SpvDecorationDescriptorSet) ||
+            (anno.GetSingleWordInOperand(1u) == SpvDecorationBinding)) {
+          AddToWorklist(&anno);
+        }
+      }
+
+      if (context()->preserve_spec_constants()) {
+        // Keep all specialization constant instructions
+        if (anno.GetSingleWordInOperand(1u) == SpvDecorationSpecId) {
+          AddToWorklist(&anno);
+        }
+      }
     }
   }
 }
diff --git a/source/opt/ir_context.h b/source/opt/ir_context.h
index 32d5b17..37c6449 100644
--- a/source/opt/ir_context.h
+++ b/source/opt/ir_context.h
@@ -100,7 +100,9 @@
         constant_mgr_(nullptr),
         type_mgr_(nullptr),
         id_to_name_(nullptr),
-        max_id_bound_(kDefaultMaxIdBound) {
+        max_id_bound_(kDefaultMaxIdBound),
+        preserve_bindings_(false),
+        preserve_spec_constants_(false) {
     SetContextMessageConsumer(syntax_context_, consumer_);
     module_->SetContext(this);
   }
@@ -115,7 +117,9 @@
         valid_analyses_(kAnalysisNone),
         type_mgr_(nullptr),
         id_to_name_(nullptr),
-        max_id_bound_(kDefaultMaxIdBound) {
+        max_id_bound_(kDefaultMaxIdBound),
+        preserve_bindings_(false),
+        preserve_spec_constants_(false) {
     SetContextMessageConsumer(syntax_context_, consumer_);
     module_->SetContext(this);
     InitializeCombinators();
@@ -491,6 +495,16 @@
   uint32_t max_id_bound() const { return max_id_bound_; }
   void set_max_id_bound(uint32_t new_bound) { max_id_bound_ = new_bound; }
 
+  bool preserve_bindings() const { return preserve_bindings_; }
+  void set_preserve_bindings(bool should_preserve_bindings) {
+    preserve_bindings_ = should_preserve_bindings;
+  }
+
+  bool preserve_spec_constants() const { return preserve_spec_constants_; }
+  void set_preserve_spec_constants(bool should_preserve_spec_constants) {
+    preserve_spec_constants_ = should_preserve_spec_constants;
+  }
+
   // Return id of input variable only decorated with |builtin|, if in module.
   // Create variable and return its id otherwise. If builtin not currently
   // supported, return 0.
@@ -750,6 +764,13 @@
 
   // The maximum legal value for the id bound.
   uint32_t max_id_bound_;
+
+  // Whether all bindings within |module_| should be preserved.
+  bool preserve_bindings_;
+
+  // Whether all specialization constants within |module_|
+  // should be preserved.
+  bool preserve_spec_constants_;
 };
 
 inline IRContext::Analysis operator|(IRContext::Analysis lhs,
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index 62d886a..6206f64 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -528,6 +528,8 @@
   if (context == nullptr) return false;
 
   context->set_max_id_bound(opt_options->max_id_bound_);
+  context->set_preserve_bindings(opt_options->preserve_bindings_);
+  context->set_preserve_spec_constants(opt_options->preserve_spec_constants_);
 
   impl_->pass_manager.SetValidatorOptions(&opt_options->val_options_);
   impl_->pass_manager.SetTargetEnv(impl_->target_env);
diff --git a/source/spirv_optimizer_options.cpp b/source/spirv_optimizer_options.cpp
index 30db4e2..e92ffc0 100644
--- a/source/spirv_optimizer_options.cpp
+++ b/source/spirv_optimizer_options.cpp
@@ -39,3 +39,13 @@
     spv_optimizer_options options, uint32_t val) {
   options->max_id_bound_ = val;
 }
+
+SPIRV_TOOLS_EXPORT void spvOptimizerOptionsSetPreserveBindings(
+    spv_optimizer_options options, bool val) {
+  options->preserve_bindings_ = val;
+}
+
+SPIRV_TOOLS_EXPORT void spvOptimizerOptionsSetPreserveSpecConstants(
+    spv_optimizer_options options, bool val) {
+  options->preserve_spec_constants_ = val;
+}
diff --git a/source/spirv_optimizer_options.h b/source/spirv_optimizer_options.h
index 1eb4d3f..aa76d20 100644
--- a/source/spirv_optimizer_options.h
+++ b/source/spirv_optimizer_options.h
@@ -24,7 +24,9 @@
   spv_optimizer_options_t()
       : run_validator_(true),
         val_options_(),
-        max_id_bound_(kDefaultMaxIdBound) {}
+        max_id_bound_(kDefaultMaxIdBound),
+        preserve_bindings_(false),
+        preserve_spec_constants_(false) {}
 
   // When true the validator will be run before optimizations are run.
   bool run_validator_;
@@ -36,5 +38,12 @@
   // this value must be at least 0x3FFFFF, but implementations can allow for a
   // higher value.
   uint32_t max_id_bound_;
+
+  // When true, all binding declarations within the module should be preserved.
+  bool preserve_bindings_;
+
+  // When true, all specialization constants within the module should be
+  // preserved.
+  bool preserve_spec_constants_;
 };
 #endif  // SOURCE_SPIRV_OPTIMIZER_OPTIONS_H_
diff --git a/test/opt/aggressive_dead_code_elim_test.cpp b/test/opt/aggressive_dead_code_elim_test.cpp
index 4f89487..f323801 100644
--- a/test/opt/aggressive_dead_code_elim_test.cpp
+++ b/test/opt/aggressive_dead_code_elim_test.cpp
@@ -6496,6 +6496,70 @@
   SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
 }
 
+TEST_F(AggressiveDCETest, PreserveBindings) {
+  const std::string spirv = R"(
+; CHECK: OpDecorate %unusedSampler DescriptorSet 0
+; CHECK: OpDecorate %unusedSampler Binding 0
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpName %main "main"
+OpName %unusedSampler "unusedSampler"
+OpDecorate %unusedSampler DescriptorSet 0
+OpDecorate %unusedSampler Binding 0
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%7 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%8 = OpTypeSampledImage %7
+%_ptr_UniformConstant_8 = OpTypePointer UniformConstant %8
+%unusedSampler = OpVariable %_ptr_UniformConstant_8 UniformConstant
+%main = OpFunction %void None %5
+%10 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+
+  OptimizerOptions()->preserve_bindings_ = true;
+
+  SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
+}
+
+TEST_F(AggressiveDCETest, PreserveSpecConstants) {
+  const std::string spirv = R"(
+; CHECK: OpName %specConstant "specConstant"
+; CHECK: %specConstant = OpSpecConstant %int 0
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpName %main "main"
+OpName %specConstant "specConstant"
+OpDecorate %specConstant SpecId 0
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%specConstant = OpSpecConstant %int 0
+%main = OpFunction %void None %3
+%5 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+
+  OptimizerOptions()->preserve_spec_constants_ = true;
+
+  SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    Check that logical addressing required
diff --git a/test/opt/pass_fixture.h b/test/opt/pass_fixture.h
index 10e7c53..b7a0742 100644
--- a/test/opt/pass_fixture.h
+++ b/test/opt/pass_fixture.h
@@ -27,6 +27,7 @@
 #include "source/opt/build_module.h"
 #include "source/opt/pass_manager.h"
 #include "source/opt/passes.h"
+#include "source/spirv_optimizer_options.h"
 #include "source/spirv_validator_options.h"
 #include "source/util/make_unique.h"
 #include "spirv-tools/libspirv.hpp"
@@ -67,6 +68,10 @@
       return std::make_tuple(std::vector<uint32_t>(), Pass::Status::Failure);
     }
 
+    context()->set_preserve_bindings(OptimizerOptions()->preserve_bindings_);
+    context()->set_preserve_spec_constants(
+        OptimizerOptions()->preserve_spec_constants_);
+
     const auto status = pass->Run(context());
 
     std::vector<uint32_t> binary;
@@ -206,6 +211,10 @@
         std::move(BuildModule(env_, nullptr, original, assemble_options_));
     ASSERT_NE(nullptr, context());
 
+    context()->set_preserve_bindings(OptimizerOptions()->preserve_bindings_);
+    context()->set_preserve_spec_constants(
+        OptimizerOptions()->preserve_spec_constants_);
+
     manager_->Run(context());
 
     std::vector<uint32_t> binary;
@@ -232,6 +241,8 @@
     consumer_ = msg_consumer;
   }
 
+  spv_optimizer_options OptimizerOptions() { return &optimizer_options_; }
+
   spv_validator_options ValidatorOptions() { return &validator_options_; }
 
   void SetTargetEnv(spv_target_env env) { env_ = env; }
@@ -242,6 +253,7 @@
   std::unique_ptr<PassManager> manager_;  // The pass manager.
   uint32_t assemble_options_;
   uint32_t disassemble_options_;
+  spv_optimizer_options_t optimizer_options_;
   spv_validator_options_t validator_options_;
   spv_target_env env_;
 };
diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp
index af527e3..d29b8a0 100644
--- a/tools/opt/opt.cpp
+++ b/tools/opt/opt.cpp
@@ -357,6 +357,14 @@
                --merge-blocks followed by all the transformations implied by
                -O.)");
   printf(R"(
+  --preserve-bindings
+               Ensure that the optimizer preserves all bindings declared within
+               the module, even when those bindings are unused.)");
+  printf(R"(
+  --preserve-spec-constants
+               Ensure that the optimizer preserves all specialization constants declared
+               within the module, even when those constants are unused.)");
+  printf(R"(
   --print-all
                Print SPIR-V assembly to standard error output before each pass
                and after the last pass.)");
@@ -693,6 +701,10 @@
         optimizer_options->set_run_validator(false);
       } else if (0 == strcmp(cur_arg, "--print-all")) {
         optimizer->SetPrintAll(&std::cerr);
+      } else if (0 == strcmp(cur_arg, "--preserve-bindings")) {
+        optimizer_options->set_preserve_bindings(true);
+      } else if (0 == strcmp(cur_arg, "--preserve-spec-constants")) {
+        optimizer_options->set_preserve_spec_constants(true);
       } else if (0 == strcmp(cur_arg, "--time-report")) {
         optimizer->SetTimeReport(&std::cerr);
       } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {