[tint][spir-v] Improve spir-v version robustness

Fix: 428041882

* The SPIR-V version enum is exposed via the options
* Improve the robustness of its usage by removing the specific values
    in the enum
* Add checks that only valid values can be used
* Move conversion to the version word into the printer
* Add validation environment settings to IRFuzzer validation

Change-Id: Ibe2f808f32e20128fda76ec566fe55a9dbf8aea3
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/250474
Auto-Submit: Alan Baker <alanbaker@google.com>
Reviewed-by: James Price <jrprice@google.com>
Reviewed-by: Ryan Harrison <rharrison@chromium.org>
Commit-Queue: Alan Baker <alanbaker@google.com>
Commit-Queue: James Price <jrprice@google.com>
diff --git a/src/tint/lang/spirv/writer/common/options.h b/src/tint/lang/spirv/writer/common/options.h
index 23d6dfd..ba8deb9e0 100644
--- a/src/tint/lang/spirv/writer/common/options.h
+++ b/src/tint/lang/spirv/writer/common/options.h
@@ -130,10 +130,16 @@
 };
 
 /// Supported SPIR-V binary versions.
+/// If a new version is added here, also add it to:
+/// * Writer::CanGenerate
+/// * Printer::Code
+/// Fully usable version will also need additions to:
+/// * --spir-version on the command line
+/// * Dawn in the Vulkan backend
 enum class SpvVersion : uint32_t {
-    kSpv13 = 0x10300u,  // SPIR-V 1.3
-    kSpv14 = 0x10400u,  // SPIR-V 1.4
-    kSpv15 = 0x10500u,  // SPIR-V 1.5, for testing purposes only
+    kSpv13,  // SPIR-V 1.3
+    kSpv14,  // SPIR-V 1.4
+    kSpv15,  // SPIR-V 1.5, for testing purposes only
 };
 
 /// Configuration options used for generating SPIR-V.
diff --git a/src/tint/lang/spirv/writer/printer/printer.cc b/src/tint/lang/spirv/writer/printer/printer.cc
index 2cdd29f..a8c5a30 100644
--- a/src/tint/lang/spirv/writer/printer/printer.cc
+++ b/src/tint/lang/spirv/writer/printer/printer.cc
@@ -215,10 +215,24 @@
             return res.Failure();
         }
 
+        uint32_t version = 0u;
+        switch (options_.spirv_version) {
+            case SpvVersion::kSpv13:
+                version = 0x10300u;
+                break;
+            case SpvVersion::kSpv14:
+                version = 0x10400u;
+                break;
+            case SpvVersion::kSpv15:
+                version = 0x10500u;
+                break;
+            default:
+                TINT_ICE() << "unsupported SPIR-V version";
+        }
+
         // Serialize the module into binary SPIR-V.
         BinaryWriter writer;
-        writer.WriteHeader(module_.IdBound(), kWriterVersion,
-                           static_cast<uint32_t>(options_.spirv_version));
+        writer.WriteHeader(module_.IdBound(), kWriterVersion, version);
         writer.WriteModule(module_);
 
         output_.spirv = std::move(writer.Result());
diff --git a/src/tint/lang/spirv/writer/writer.cc b/src/tint/lang/spirv/writer/writer.cc
index 50ef741..23e739a 100644
--- a/src/tint/lang/spirv/writer/writer.cc
+++ b/src/tint/lang/spirv/writer/writer.cc
@@ -42,6 +42,16 @@
 namespace tint::spirv::writer {
 
 Result<SuccessType> CanGenerate(const core::ir::Module& ir, const Options& options) {
+    // The enum is accessible in the API so ensure we have a valid value.
+    switch (options.spirv_version) {
+        case SpvVersion::kSpv13:
+        case SpvVersion::kSpv14:
+        case SpvVersion::kSpv15:
+            break;
+        default:
+            return Failure("unsupported SPIR-V version");
+    }
+
     // Check optionally supported types against their required options.
     for (auto* ty : ir.Types()) {
         if (ty->Is<core::type::SubgroupMatrix>()) {
diff --git a/src/tint/lang/spirv/writer/writer_fuzz.cc b/src/tint/lang/spirv/writer/writer_fuzz.cc
index d71f328..bce4dc8 100644
--- a/src/tint/lang/spirv/writer/writer_fuzz.cc
+++ b/src/tint/lang/spirv/writer/writer_fuzz.cc
@@ -49,8 +49,23 @@
                    << output.Failure().reason;
     }
 
+    spv_target_env target_env = SPV_ENV_VULKAN_1_1;
+    switch (options.spirv_version) {
+        case SpvVersion::kSpv13:
+            target_env = SPV_ENV_VULKAN_1_1;
+            break;
+        case SpvVersion::kSpv14:
+            target_env = SPV_ENV_VULKAN_1_1_SPIRV_1_4;
+            break;
+        case SpvVersion::kSpv15:
+            target_env = SPV_ENV_VULKAN_1_2;
+            break;
+        default:
+            TINT_ICE() << "unsupported SPIR-V version";
+    }
+
     auto& spirv = output->spirv;
-    if (auto res = validate::Validate(Slice(spirv.data(), spirv.size()), SPV_ENV_VULKAN_1_1);
+    if (auto res = validate::Validate(Slice(spirv.data(), spirv.size()), target_env);
         res != Success) {
         TINT_ICE() << "output of SPIR-V writer failed to validate with SPIR-V Tools\n"
                    << res.Failure() << "\n\n"