Add --target-env flag to spirv-opt (#2216)

Fixes #2199
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index 8c6b396..f8769c9 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -148,6 +148,10 @@
   // returns false.
   bool FlagHasValidForm(const std::string& flag) const;
 
+  // Allows changing, after creation time, the target environment to be
+  // optimized for.  Should be called before calling Run().
+  void SetTargetEnv(const spv_target_env env);
+
   // Optimizes the given SPIR-V module |original_binary| and writes the
   // optimized binary into |optimized_binary|.
   // Returns true on successful optimization, whether or not the module is
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index d30528b..856ede7 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -56,7 +56,7 @@
 struct Optimizer::Impl {
   explicit Impl(spv_target_env env) : target_env(env), pass_manager() {}
 
-  const spv_target_env target_env;  // Target environment.
+  spv_target_env target_env;        // Target environment.
   opt::PassManager pass_manager;    // Internal implementation pass manager.
 };
 
@@ -450,6 +450,10 @@
   return true;
 }
 
+void Optimizer::SetTargetEnv(const spv_target_env env) {
+  impl_->target_env = env;
+}
+
 bool Optimizer::Run(const uint32_t* original_binary,
                     const size_t original_binary_size,
                     std::vector<uint32_t>* optimized_binary) const {
diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp
index b417dc2..0e8088f 100644
--- a/tools/opt/opt.cpp
+++ b/tools/opt/opt.cpp
@@ -23,6 +23,7 @@
 #include <vector>
 
 #include "source/opt/log.h"
+#include "source/spirv_target_env.h"
 #include "source/util/string_utils.h"
 #include "spirv-tools/libspirv.hpp"
 #include "spirv-tools/optimizer.hpp"
@@ -338,6 +339,11 @@
   --strip-reflect
                Remove all reflection information.  For now, this covers
                reflection information defined by SPV_GOOGLE_hlsl_functionality1.
+  --target-env=<env>
+               Set the target environment. Without this flag the target
+               enviroment defaults to spv1.3.
+               <env> must be one of vulkan1.0, vulkan1.1, opencl2.2, spv1.0,
+               spv1.1, spv1.2, spv1.3, or webgpu0.
   --time-report
                Print the resource utilization of each pass (e.g., CPU time,
                RSS) to standard error output. Currently it supports only Unix
@@ -566,6 +572,17 @@
         optimizer_options->set_max_id_bound(max_id_bound);
         validator_options->SetUniversalLimit(spv_validator_limit_max_id_bound,
                                              max_id_bound);
+      } else if (0 == strncmp(cur_arg,
+                              "--target-env=", sizeof("--target-env=") - 1)) {
+        const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
+        const auto target_env_str = split_flag.second.c_str();
+        spv_target_env target_env;
+        if (!spvParseTargetEnv(target_env_str, &target_env)) {
+          spvtools::Error(opt_diagnostic, nullptr, {},
+                          "Invalid value passed to --target-env");
+          return {OPT_STOP, 1};
+        }
+        optimizer->SetTargetEnv(target_env);
       } else {
         // Some passes used to accept the form '--pass arg', canonicalize them
         // to '--pass=arg'.