[spirv-reader] Add PIMPL parser sources

Validates the incoming SPIR-V binary and emits an empty IR module.

Add the target SPIR-V environment as a parameter to the SPIR-V
validation helper, as we validate against different versions in the
reader and writer.

Bug: tint:1907
Change-Id: I2052cf51c92a3e21aec226c7cef1f06b36e214f6
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/165303
Reviewed-by: David Neto <dneto@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/lang/spirv/reader/BUILD.bazel b/src/tint/lang/spirv/reader/BUILD.bazel
index 8be26f4..f7662cf 100644
--- a/src/tint/lang/spirv/reader/BUILD.bazel
+++ b/src/tint/lang/spirv/reader/BUILD.bazel
@@ -73,6 +73,7 @@
   ] + select({
     ":tint_build_spv_reader": [
       "//src/tint/lang/spirv/reader/ast_parser",
+      "//src/tint/lang/spirv/reader/parser",
     ],
     "//conditions:default": [],
   }),
diff --git a/src/tint/lang/spirv/reader/BUILD.cmake b/src/tint/lang/spirv/reader/BUILD.cmake
index bda6ba0..6e0d5ac 100644
--- a/src/tint/lang/spirv/reader/BUILD.cmake
+++ b/src/tint/lang/spirv/reader/BUILD.cmake
@@ -37,6 +37,7 @@
 include(lang/spirv/reader/ast_lower/BUILD.cmake)
 include(lang/spirv/reader/ast_parser/BUILD.cmake)
 include(lang/spirv/reader/common/BUILD.cmake)
+include(lang/spirv/reader/parser/BUILD.cmake)
 
 if(TINT_BUILD_SPV_READER)
 ################################################################################
@@ -80,6 +81,7 @@
 if(TINT_BUILD_SPV_READER)
   tint_target_add_dependencies(tint_lang_spirv_reader lib
     tint_lang_spirv_reader_ast_parser
+    tint_lang_spirv_reader_parser
   )
 endif(TINT_BUILD_SPV_READER)
 
diff --git a/src/tint/lang/spirv/reader/BUILD.gn b/src/tint/lang/spirv/reader/BUILD.gn
index 5075295..82b4314 100644
--- a/src/tint/lang/spirv/reader/BUILD.gn
+++ b/src/tint/lang/spirv/reader/BUILD.gn
@@ -72,7 +72,10 @@
     ]
 
     if (tint_build_spv_reader) {
-      deps += [ "${tint_src_dir}/lang/spirv/reader/ast_parser" ]
+      deps += [
+        "${tint_src_dir}/lang/spirv/reader/ast_parser",
+        "${tint_src_dir}/lang/spirv/reader/parser",
+      ]
     }
   }
 }
diff --git a/src/tint/lang/spirv/reader/parser/BUILD.bazel b/src/tint/lang/spirv/reader/parser/BUILD.bazel
new file mode 100644
index 0000000..44f0586
--- /dev/null
+++ b/src/tint/lang/spirv/reader/parser/BUILD.bazel
@@ -0,0 +1,95 @@
+# Copyright 2023 The Dawn & Tint Authors
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions and the following disclaimer in the documentation
+#    and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its
+#    contributors may be used to endorse or promote products derived from
+#    this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+################################################################################
+# File generated by 'tools/src/cmd/gen' using the template:
+#   tools/src/cmd/gen/build/BUILD.bazel.tmpl
+#
+# To regenerate run: './tools/run gen'
+#
+#                       Do not modify this file directly
+################################################################################
+
+load("//src/tint:flags.bzl", "COPTS")
+load("@bazel_skylib//lib:selects.bzl", "selects")
+cc_library(
+  name = "parser",
+  srcs = [
+    "parser.cc",
+  ],
+  hdrs = [
+    "parser.h",
+  ],
+  deps = [
+    "//src/tint/api/common",
+    "//src/tint/lang/core",
+    "//src/tint/lang/core/constant",
+    "//src/tint/lang/core/intrinsic",
+    "//src/tint/lang/core/ir",
+    "//src/tint/lang/core/type",
+    "//src/tint/utils/containers",
+    "//src/tint/utils/diagnostic",
+    "//src/tint/utils/ice",
+    "//src/tint/utils/id",
+    "//src/tint/utils/macros",
+    "//src/tint/utils/math",
+    "//src/tint/utils/memory",
+    "//src/tint/utils/reflection",
+    "//src/tint/utils/result",
+    "//src/tint/utils/rtti",
+    "//src/tint/utils/symbol",
+    "//src/tint/utils/text",
+    "//src/tint/utils/traits",
+  ] + select({
+    ":tint_build_spv_reader_or_tint_build_spv_writer": [
+      "//src/tint/lang/spirv/validate",
+      "@spirv_tools",
+    ],
+    "//conditions:default": [],
+  }),
+  copts = COPTS,
+  visibility = ["//visibility:public"],
+)
+
+alias(
+  name = "tint_build_spv_reader",
+  actual = "//src/tint:tint_build_spv_reader_true",
+)
+
+alias(
+  name = "tint_build_spv_writer",
+  actual = "//src/tint:tint_build_spv_writer_true",
+)
+
+selects.config_setting_group(
+    name = "tint_build_spv_reader_or_tint_build_spv_writer",
+    match_any = [
+        "tint_build_spv_reader",
+        "tint_build_spv_writer",
+    ],
+)
+
diff --git a/src/tint/lang/spirv/reader/parser/BUILD.cfg b/src/tint/lang/spirv/reader/parser/BUILD.cfg
new file mode 100644
index 0000000..a460fd5
--- /dev/null
+++ b/src/tint/lang/spirv/reader/parser/BUILD.cfg
@@ -0,0 +1,3 @@
+{
+    "condition": "tint_build_spv_reader"
+}
diff --git a/src/tint/lang/spirv/reader/parser/BUILD.cmake b/src/tint/lang/spirv/reader/parser/BUILD.cmake
new file mode 100644
index 0000000..0aee0a6
--- /dev/null
+++ b/src/tint/lang/spirv/reader/parser/BUILD.cmake
@@ -0,0 +1,79 @@
+# Copyright 2023 The Dawn & Tint Authors
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions and the following disclaimer in the documentation
+#    and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its
+#    contributors may be used to endorse or promote products derived from
+#    this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+################################################################################
+# File generated by 'tools/src/cmd/gen' using the template:
+#   tools/src/cmd/gen/build/BUILD.cmake.tmpl
+#
+# To regenerate run: './tools/run gen'
+#
+#                       Do not modify this file directly
+################################################################################
+
+if(TINT_BUILD_SPV_READER)
+################################################################################
+# Target:    tint_lang_spirv_reader_parser
+# Kind:      lib
+# Condition: TINT_BUILD_SPV_READER
+################################################################################
+tint_add_target(tint_lang_spirv_reader_parser lib
+  lang/spirv/reader/parser/parser.cc
+  lang/spirv/reader/parser/parser.h
+)
+
+tint_target_add_dependencies(tint_lang_spirv_reader_parser lib
+  tint_api_common
+  tint_lang_core
+  tint_lang_core_constant
+  tint_lang_core_intrinsic
+  tint_lang_core_ir
+  tint_lang_core_type
+  tint_utils_containers
+  tint_utils_diagnostic
+  tint_utils_ice
+  tint_utils_id
+  tint_utils_macros
+  tint_utils_math
+  tint_utils_memory
+  tint_utils_reflection
+  tint_utils_result
+  tint_utils_rtti
+  tint_utils_symbol
+  tint_utils_text
+  tint_utils_traits
+)
+
+if(TINT_BUILD_SPV_READER OR TINT_BUILD_SPV_WRITER)
+  tint_target_add_dependencies(tint_lang_spirv_reader_parser lib
+    tint_lang_spirv_validate
+  )
+  tint_target_add_external_dependencies(tint_lang_spirv_reader_parser lib
+    "spirv-tools"
+  )
+endif(TINT_BUILD_SPV_READER OR TINT_BUILD_SPV_WRITER)
+
+endif(TINT_BUILD_SPV_READER)
\ No newline at end of file
diff --git a/src/tint/lang/spirv/reader/parser/BUILD.gn b/src/tint/lang/spirv/reader/parser/BUILD.gn
new file mode 100644
index 0000000..ccce1da
--- /dev/null
+++ b/src/tint/lang/spirv/reader/parser/BUILD.gn
@@ -0,0 +1,76 @@
+# Copyright 2023 The Dawn & Tint Authors
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions and the following disclaimer in the documentation
+#    and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its
+#    contributors may be used to endorse or promote products derived from
+#    this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+################################################################################
+# File generated by 'tools/src/cmd/gen' using the template:
+#   tools/src/cmd/gen/build/BUILD.gn.tmpl
+#
+# To regenerate run: './tools/run gen'
+#
+#                       Do not modify this file directly
+################################################################################
+
+import("../../../../../../scripts/tint_overrides_with_defaults.gni")
+
+import("${tint_src_dir}/tint.gni")
+if (tint_build_spv_reader) {
+  libtint_source_set("parser") {
+    sources = [
+      "parser.cc",
+      "parser.h",
+    ]
+    deps = [
+      "${tint_src_dir}/api/common",
+      "${tint_src_dir}/lang/core",
+      "${tint_src_dir}/lang/core/constant",
+      "${tint_src_dir}/lang/core/intrinsic",
+      "${tint_src_dir}/lang/core/ir",
+      "${tint_src_dir}/lang/core/type",
+      "${tint_src_dir}/utils/containers",
+      "${tint_src_dir}/utils/diagnostic",
+      "${tint_src_dir}/utils/ice",
+      "${tint_src_dir}/utils/id",
+      "${tint_src_dir}/utils/macros",
+      "${tint_src_dir}/utils/math",
+      "${tint_src_dir}/utils/memory",
+      "${tint_src_dir}/utils/reflection",
+      "${tint_src_dir}/utils/result",
+      "${tint_src_dir}/utils/rtti",
+      "${tint_src_dir}/utils/symbol",
+      "${tint_src_dir}/utils/text",
+      "${tint_src_dir}/utils/traits",
+    ]
+
+    if (tint_build_spv_reader || tint_build_spv_writer) {
+      deps += [
+        "${tint_spirv_tools_dir}:spvtools_headers",
+        "${tint_spirv_tools_dir}:spvtools_val",
+        "${tint_src_dir}/lang/spirv/validate",
+      ]
+    }
+  }
+}
diff --git a/src/tint/lang/spirv/reader/parser/parser.cc b/src/tint/lang/spirv/reader/parser/parser.cc
new file mode 100644
index 0000000..11ad5b2
--- /dev/null
+++ b/src/tint/lang/spirv/reader/parser/parser.cc
@@ -0,0 +1,72 @@
+// Copyright 2023 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/spirv/reader/parser/parser.h"
+
+#include <utility>
+#include <vector>
+
+#include "src/tint/lang/core/ir/builder.h"
+#include "src/tint/lang/core/ir/module.h"
+#include "src/tint/lang/spirv/validate/validate.h"
+
+namespace tint::spirv::reader {
+
+namespace {
+
+/// The SPIR-V environment that we validate against.
+constexpr auto kTargetEnv = SPV_ENV_VULKAN_1_1;
+
+/// PIMPL class for SPIR-V parser.
+class Parser {
+  public:
+    /// @param spirv the SPIR-V binary data
+    /// @returns the generated SPIR-V IR module on success, or failure
+    Result<core::ir::Module> Run(Slice<const uint32_t> spirv) {
+        // Validate the incoming SPIR-V binary.
+        auto result = validate::Validate(spirv, kTargetEnv);
+        if (!result) {
+            return result.Failure();
+        }
+
+        // TODO(crbug.com/tint/1907): Parse the module.
+
+        return std::move(ir_);
+    }
+
+  private:
+    /// The generated IR module.
+    core::ir::Module ir_;
+};
+
+}  // namespace
+
+Result<core::ir::Module> Parse(Slice<const uint32_t> spirv) {
+    return Parser{}.Run(spirv);
+}
+
+}  // namespace tint::spirv::reader
diff --git a/src/tint/lang/spirv/reader/parser/parser.h b/src/tint/lang/spirv/reader/parser/parser.h
new file mode 100644
index 0000000..17612cf
--- /dev/null
+++ b/src/tint/lang/spirv/reader/parser/parser.h
@@ -0,0 +1,49 @@
+// Copyright 2020 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_TINT_LANG_SPIRV_READER_PARSER_PARSER_H_
+#define SRC_TINT_LANG_SPIRV_READER_PARSER_PARSER_H_
+
+#include <vector>
+
+#include "src/tint/utils/result/result.h"
+
+// Forward declarations
+namespace tint::core::ir {
+class Module;
+}  // namespace tint::core::ir
+
+namespace tint::spirv::reader {
+
+/// Parse a SPIR-V binary to produce a SPIR-V IR module.
+/// @param spirv the SPIR-V binary data
+/// @returns the SPIR-V IR module on success, or failure
+Result<core::ir::Module> Parse(Slice<const uint32_t> spirv);
+
+}  // namespace tint::spirv::reader
+
+#endif  // SRC_TINT_LANG_SPIRV_READER_PARSER_PARSER_H_
diff --git a/src/tint/lang/spirv/reader/reader.cc b/src/tint/lang/spirv/reader/reader.cc
index 3e7ce9a..1930b97 100644
--- a/src/tint/lang/spirv/reader/reader.cc
+++ b/src/tint/lang/spirv/reader/reader.cc
@@ -31,12 +31,19 @@
 
 #include "src/tint/lang/core/ir/module.h"
 #include "src/tint/lang/spirv/reader/ast_parser/parse.h"
+#include "src/tint/lang/spirv/reader/parser/parser.h"
 
 namespace tint::spirv::reader {
 
 Result<core::ir::Module> ReadIR(const std::vector<uint32_t>& input) {
-    (void)input;
-    return Failure("SPIR-V to IR reader is unimplemented");
+    auto mod = Parse(Slice(input.data(), input.size()));
+    if (!mod) {
+        return mod.Failure();
+    }
+
+    // TODO(crbug.com/tint/1907): Lower the module to core dialect.
+
+    return mod;
 }
 
 Program Read(const std::vector<uint32_t>& input, const Options& options) {
diff --git a/src/tint/lang/spirv/validate/BUILD.bazel b/src/tint/lang/spirv/validate/BUILD.bazel
index bc1d952..dd47510 100644
--- a/src/tint/lang/spirv/validate/BUILD.bazel
+++ b/src/tint/lang/spirv/validate/BUILD.bazel
@@ -85,6 +85,7 @@
   ] + select({
     ":tint_build_spv_reader_or_tint_build_spv_writer": [
       "//src/tint/lang/spirv/validate",
+      "@spirv_tools",
     ],
     "//conditions:default": [],
   }),
diff --git a/src/tint/lang/spirv/validate/BUILD.cmake b/src/tint/lang/spirv/validate/BUILD.cmake
index e50d0e9..726f9cf 100644
--- a/src/tint/lang/spirv/validate/BUILD.cmake
+++ b/src/tint/lang/spirv/validate/BUILD.cmake
@@ -96,6 +96,9 @@
   tint_target_add_dependencies(tint_lang_spirv_validate_test test
     tint_lang_spirv_validate
   )
+  tint_target_add_external_dependencies(tint_lang_spirv_validate_test test
+    "spirv-tools"
+  )
 endif(TINT_BUILD_SPV_READER OR TINT_BUILD_SPV_WRITER)
 
 endif(TINT_BUILD_SPV_READER OR TINT_BUILD_SPV_WRITER)
\ No newline at end of file
diff --git a/src/tint/lang/spirv/validate/BUILD.gn b/src/tint/lang/spirv/validate/BUILD.gn
index 99890f7..2f79a37 100644
--- a/src/tint/lang/spirv/validate/BUILD.gn
+++ b/src/tint/lang/spirv/validate/BUILD.gn
@@ -87,7 +87,11 @@
       ]
 
       if (tint_build_spv_reader || tint_build_spv_writer) {
-        deps += [ "${tint_src_dir}/lang/spirv/validate" ]
+        deps += [
+          "${tint_spirv_tools_dir}:spvtools_headers",
+          "${tint_spirv_tools_dir}:spvtools_val",
+          "${tint_src_dir}/lang/spirv/validate",
+        ]
       }
     }
   }
diff --git a/src/tint/lang/spirv/validate/validate.cc b/src/tint/lang/spirv/validate/validate.cc
index 13344b7..8d85413 100644
--- a/src/tint/lang/spirv/validate/validate.cc
+++ b/src/tint/lang/spirv/validate/validate.cc
@@ -35,11 +35,11 @@
 
 namespace tint::spirv::validate {
 
-Result<SuccessType> Validate(Slice<const uint32_t> spirv) {
+Result<SuccessType> Validate(Slice<const uint32_t> spirv, spv_target_env target_env) {
     Vector<diag::Diagnostic, 4> diags;
     diags.Push(diag::Diagnostic{});  // Filled in on error
 
-    spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_3);
+    spvtools::SpirvTools tools(target_env);
     tools.SetMessageConsumer(
         [&](spv_message_level_t level, const char*, const spv_position_t& pos, const char* msg) {
             diag::Diagnostic diag;
diff --git a/src/tint/lang/spirv/validate/validate.h b/src/tint/lang/spirv/validate/validate.h
index 840d90f..1e09477 100644
--- a/src/tint/lang/spirv/validate/validate.h
+++ b/src/tint/lang/spirv/validate/validate.h
@@ -28,6 +28,7 @@
 #ifndef SRC_TINT_LANG_SPIRV_VALIDATE_VALIDATE_H_
 #define SRC_TINT_LANG_SPIRV_VALIDATE_VALIDATE_H_
 
+#include "spirv-tools/libspirv.hpp"
 #include "src/tint/utils/result/result.h"
 
 // Forward declarations
@@ -38,8 +39,10 @@
 namespace tint::spirv::validate {
 
 /// Validate checks that the provided SPIR-V passes validation.
+/// @param spirv the SPIR-V binary data
+/// @param target_env the target environment to validate against
 /// @return success or failure(s)
-Result<SuccessType> Validate(Slice<const uint32_t> spirv);
+Result<SuccessType> Validate(Slice<const uint32_t> spirv, spv_target_env target_env);
 
 }  // namespace tint::spirv::validate
 
diff --git a/src/tint/lang/spirv/validate/validate_test.cc b/src/tint/lang/spirv/validate/validate_test.cc
index 96ba63e..14dde40 100644
--- a/src/tint/lang/spirv/validate/validate_test.cc
+++ b/src/tint/lang/spirv/validate/validate_test.cc
@@ -48,7 +48,7 @@
         0x0000000e, 0x00000002, 0x0000000b, 0x0004003d, 0x00000006, 0x0000000f, 0x0000000e,
         0x00050051, 0x00000005, 0x00000010, 0x0000000f, 0x00000001, 0x000100fd, 0x00010038,
     };
-    auto res = Validate(spirv);
+    auto res = Validate(spirv, SPV_ENV_VULKAN_1_3);
     EXPECT_TRUE(res) << res;
 }
 
@@ -68,7 +68,7 @@
         0x0000000e, 0x00000002, 0x0000000b, 0x0004003d, 0x00000006, 0x0000000f, 0x0000000e,
         0x00050051, 0x00000005, 0x00000010, 0x0000000f, 0x00000001, 0x000100fd, 0x00010038,
     };
-    auto res = Validate(spirv);
+    auto res = Validate(spirv, SPV_ENV_VULKAN_1_3);
     ASSERT_FALSE(res);
     EXPECT_EQ(res.Failure().reason.str(), R"(spirv error: SPIR-V failed validation.
 
diff --git a/src/tint/lang/spirv/writer/BUILD.cmake b/src/tint/lang/spirv/writer/BUILD.cmake
index 0720114..3700f48 100644
--- a/src/tint/lang/spirv/writer/BUILD.cmake
+++ b/src/tint/lang/spirv/writer/BUILD.cmake
@@ -267,6 +267,9 @@
   tint_target_add_dependencies(tint_lang_spirv_writer_fuzz fuzz
     tint_lang_spirv_validate
   )
+  tint_target_add_external_dependencies(tint_lang_spirv_writer_fuzz fuzz
+    "spirv-tools"
+  )
 endif(TINT_BUILD_SPV_READER OR TINT_BUILD_SPV_WRITER)
 
 if(TINT_BUILD_SPV_WRITER)
diff --git a/src/tint/lang/spirv/writer/BUILD.gn b/src/tint/lang/spirv/writer/BUILD.gn
index 8045c04..120b4f4 100644
--- a/src/tint/lang/spirv/writer/BUILD.gn
+++ b/src/tint/lang/spirv/writer/BUILD.gn
@@ -235,7 +235,11 @@
     ]
 
     if (tint_build_spv_reader || tint_build_spv_writer) {
-      deps += [ "${tint_src_dir}/lang/spirv/validate" ]
+      deps += [
+        "${tint_spirv_tools_dir}:spvtools_headers",
+        "${tint_spirv_tools_dir}:spvtools_val",
+        "${tint_src_dir}/lang/spirv/validate",
+      ]
     }
 
     if (tint_build_spv_writer) {
diff --git a/src/tint/lang/spirv/writer/ast_writer_fuzz.cc b/src/tint/lang/spirv/writer/ast_writer_fuzz.cc
index 7ce83db..c70990c 100644
--- a/src/tint/lang/spirv/writer/ast_writer_fuzz.cc
+++ b/src/tint/lang/spirv/writer/ast_writer_fuzz.cc
@@ -47,7 +47,8 @@
         return;
     }
     auto& spirv = output->spirv;
-    if (auto res = validate::Validate(Slice(spirv.data(), spirv.size())); !res) {
+    if (auto res = validate::Validate(Slice(spirv.data(), spirv.size()), SPV_ENV_VULKAN_1_1);
+        !res) {
         TINT_ICE() << "Output of SPIR-V writer failed to validate with SPIR-V Tools\n"
                    << res.Failure();
     }
diff --git a/src/tint/lang/spirv/writer/writer_fuzz.cc b/src/tint/lang/spirv/writer/writer_fuzz.cc
index ff37ab7..5fdb191 100644
--- a/src/tint/lang/spirv/writer/writer_fuzz.cc
+++ b/src/tint/lang/spirv/writer/writer_fuzz.cc
@@ -41,7 +41,8 @@
         return;
     }
     auto& spirv = output->spirv;
-    if (auto res = validate::Validate(Slice(spirv.data(), spirv.size())); !res) {
+    if (auto res = validate::Validate(Slice(spirv.data(), spirv.size()), SPV_ENV_VULKAN_1_1);
+        !res) {
         TINT_ICE() << "Output of SPIR-V writer failed to validate with SPIR-V Tools\n"
                    << res.Failure();
     }