spirv-reader: use spirv.hpp11

In preparation for SPIRV-Tools change where its internals
use the C++11 headers.

This patch works with SPIRV-Tools using the old C header
and using the C++11 header.

This patch includes some complex machinery inside "three_sided_patch"
namespaces that can be removed after third_party/vulkan-deps/spirv-tools has
fully transitioned into using the C++11 headers.

Change-Id: I36f358fe3edcc5e613625708017fb8d7919c40c6
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/108780
Reviewed-by: Alan Baker <alanbaker@google.com>
Commit-Queue: David Neto <dneto@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/reader/spirv/enum_converter.cc b/src/tint/reader/spirv/enum_converter.cc
index 923f250..d590507 100644
--- a/src/tint/reader/spirv/enum_converter.cc
+++ b/src/tint/reader/spirv/enum_converter.cc
@@ -20,13 +20,13 @@
 
 EnumConverter::~EnumConverter() = default;
 
-ast::PipelineStage EnumConverter::ToPipelineStage(SpvExecutionModel model) {
+ast::PipelineStage EnumConverter::ToPipelineStage(spv::ExecutionModel model) {
     switch (model) {
-        case SpvExecutionModelVertex:
+        case spv::ExecutionModel::Vertex:
             return ast::PipelineStage::kVertex;
-        case SpvExecutionModelFragment:
+        case spv::ExecutionModel::Fragment:
             return ast::PipelineStage::kFragment;
-        case SpvExecutionModelGLCompute:
+        case spv::ExecutionModel::GLCompute:
             return ast::PipelineStage::kCompute;
         default:
             break;
@@ -36,23 +36,23 @@
     return ast::PipelineStage::kNone;
 }
 
-ast::AddressSpace EnumConverter::ToAddressSpace(const SpvStorageClass sc) {
+ast::AddressSpace EnumConverter::ToAddressSpace(const spv::StorageClass sc) {
     switch (sc) {
-        case SpvStorageClassInput:
+        case spv::StorageClass::Input:
             return ast::AddressSpace::kIn;
-        case SpvStorageClassOutput:
+        case spv::StorageClass::Output:
             return ast::AddressSpace::kOut;
-        case SpvStorageClassUniform:
+        case spv::StorageClass::Uniform:
             return ast::AddressSpace::kUniform;
-        case SpvStorageClassWorkgroup:
+        case spv::StorageClass::Workgroup:
             return ast::AddressSpace::kWorkgroup;
-        case SpvStorageClassUniformConstant:
+        case spv::StorageClass::UniformConstant:
             return ast::AddressSpace::kNone;
-        case SpvStorageClassStorageBuffer:
+        case spv::StorageClass::StorageBuffer:
             return ast::AddressSpace::kStorage;
-        case SpvStorageClassPrivate:
+        case spv::StorageClass::Private:
             return ast::AddressSpace::kPrivate;
-        case SpvStorageClassFunction:
+        case spv::StorageClass::Function:
             return ast::AddressSpace::kFunction;
         default:
             break;
@@ -62,31 +62,31 @@
     return ast::AddressSpace::kUndefined;
 }
 
-ast::BuiltinValue EnumConverter::ToBuiltin(SpvBuiltIn b) {
+ast::BuiltinValue EnumConverter::ToBuiltin(spv::BuiltIn b) {
     switch (b) {
-        case SpvBuiltInPosition:
+        case spv::BuiltIn::Position:
             return ast::BuiltinValue::kPosition;
-        case SpvBuiltInVertexIndex:
+        case spv::BuiltIn::VertexIndex:
             return ast::BuiltinValue::kVertexIndex;
-        case SpvBuiltInInstanceIndex:
+        case spv::BuiltIn::InstanceIndex:
             return ast::BuiltinValue::kInstanceIndex;
-        case SpvBuiltInFrontFacing:
+        case spv::BuiltIn::FrontFacing:
             return ast::BuiltinValue::kFrontFacing;
-        case SpvBuiltInFragCoord:
+        case spv::BuiltIn::FragCoord:
             return ast::BuiltinValue::kPosition;
-        case SpvBuiltInFragDepth:
+        case spv::BuiltIn::FragDepth:
             return ast::BuiltinValue::kFragDepth;
-        case SpvBuiltInLocalInvocationId:
+        case spv::BuiltIn::LocalInvocationId:
             return ast::BuiltinValue::kLocalInvocationId;
-        case SpvBuiltInLocalInvocationIndex:
+        case spv::BuiltIn::LocalInvocationIndex:
             return ast::BuiltinValue::kLocalInvocationIndex;
-        case SpvBuiltInGlobalInvocationId:
+        case spv::BuiltIn::GlobalInvocationId:
             return ast::BuiltinValue::kGlobalInvocationId;
-        case SpvBuiltInWorkgroupId:
+        case spv::BuiltIn::WorkgroupId:
             return ast::BuiltinValue::kWorkgroupId;
-        case SpvBuiltInSampleId:
+        case spv::BuiltIn::SampleId:
             return ast::BuiltinValue::kSampleIndex;
-        case SpvBuiltInSampleMask:
+        case spv::BuiltIn::SampleMask:
             return ast::BuiltinValue::kSampleMask;
         default:
             break;
@@ -96,12 +96,12 @@
     return ast::BuiltinValue::kUndefined;
 }
 
-ast::TextureDimension EnumConverter::ToDim(SpvDim dim, bool arrayed) {
+ast::TextureDimension EnumConverter::ToDim(spv::Dim dim, bool arrayed) {
     if (arrayed) {
         switch (dim) {
-            case SpvDim2D:
+            case spv::Dim::Dim2D:
                 return ast::TextureDimension::k2dArray;
-            case SpvDimCube:
+            case spv::Dim::Cube:
                 return ast::TextureDimension::kCubeArray;
             default:
                 break;
@@ -111,13 +111,13 @@
     }
     // Assume non-arrayed
     switch (dim) {
-        case SpvDim1D:
+        case spv::Dim::Dim1D:
             return ast::TextureDimension::k1d;
-        case SpvDim2D:
+        case spv::Dim::Dim2D:
             return ast::TextureDimension::k2d;
-        case SpvDim3D:
+        case spv::Dim::Dim3D:
             return ast::TextureDimension::k3d;
-        case SpvDimCube:
+        case spv::Dim::Cube:
             return ast::TextureDimension::kCube;
         default:
             break;
@@ -126,47 +126,47 @@
     return ast::TextureDimension::kNone;
 }
 
-ast::TexelFormat EnumConverter::ToTexelFormat(SpvImageFormat fmt) {
+ast::TexelFormat EnumConverter::ToTexelFormat(spv::ImageFormat fmt) {
     switch (fmt) {
-        case SpvImageFormatUnknown:
+        case spv::ImageFormat::Unknown:
             return ast::TexelFormat::kUndefined;
 
         // 8 bit channels
-        case SpvImageFormatRgba8:
+        case spv::ImageFormat::Rgba8:
             return ast::TexelFormat::kRgba8Unorm;
-        case SpvImageFormatRgba8Snorm:
+        case spv::ImageFormat::Rgba8Snorm:
             return ast::TexelFormat::kRgba8Snorm;
-        case SpvImageFormatRgba8ui:
+        case spv::ImageFormat::Rgba8ui:
             return ast::TexelFormat::kRgba8Uint;
-        case SpvImageFormatRgba8i:
+        case spv::ImageFormat::Rgba8i:
             return ast::TexelFormat::kRgba8Sint;
 
         // 16 bit channels
-        case SpvImageFormatRgba16ui:
+        case spv::ImageFormat::Rgba16ui:
             return ast::TexelFormat::kRgba16Uint;
-        case SpvImageFormatRgba16i:
+        case spv::ImageFormat::Rgba16i:
             return ast::TexelFormat::kRgba16Sint;
-        case SpvImageFormatRgba16f:
+        case spv::ImageFormat::Rgba16f:
             return ast::TexelFormat::kRgba16Float;
 
         // 32 bit channels
-        case SpvImageFormatR32ui:
+        case spv::ImageFormat::R32ui:
             return ast::TexelFormat::kR32Uint;
-        case SpvImageFormatR32i:
+        case spv::ImageFormat::R32i:
             return ast::TexelFormat::kR32Sint;
-        case SpvImageFormatR32f:
+        case spv::ImageFormat::R32f:
             return ast::TexelFormat::kR32Float;
-        case SpvImageFormatRg32ui:
+        case spv::ImageFormat::Rg32ui:
             return ast::TexelFormat::kRg32Uint;
-        case SpvImageFormatRg32i:
+        case spv::ImageFormat::Rg32i:
             return ast::TexelFormat::kRg32Sint;
-        case SpvImageFormatRg32f:
+        case spv::ImageFormat::Rg32f:
             return ast::TexelFormat::kRg32Float;
-        case SpvImageFormatRgba32ui:
+        case spv::ImageFormat::Rgba32ui:
             return ast::TexelFormat::kRgba32Uint;
-        case SpvImageFormatRgba32i:
+        case spv::ImageFormat::Rgba32i:
             return ast::TexelFormat::kRgba32Sint;
-        case SpvImageFormatRgba32f:
+        case spv::ImageFormat::Rgba32f:
             return ast::TexelFormat::kRgba32Float;
         default:
             break;
diff --git a/src/tint/reader/spirv/enum_converter.h b/src/tint/reader/spirv/enum_converter.h
index 1ebea36..0082499 100644
--- a/src/tint/reader/spirv/enum_converter.h
+++ b/src/tint/reader/spirv/enum_converter.h
@@ -16,6 +16,7 @@
 #define SRC_TINT_READER_SPIRV_ENUM_CONVERTER_H_
 
 #include "spirv/unified1/spirv.h"
+#include "spirv/unified1/spirv.hpp11"
 #include "src/tint/ast/address_space.h"
 #include "src/tint/ast/builtin_value.h"
 #include "src/tint/ast/pipeline_stage.h"
@@ -37,32 +38,49 @@
     /// On failure, logs an error and returns kNone
     /// @param model the SPIR-V entry point execution model
     /// @returns a Tint AST pipeline stage
-    ast::PipelineStage ToPipelineStage(SpvExecutionModel model);
+    ast::PipelineStage ToPipelineStage(spv::ExecutionModel model);
 
     /// Converts a SPIR-V storage class to a Tint address space.
     /// On failure, logs an error and returns kNone
     /// @param sc the SPIR-V storage class
     /// @returns a Tint AST address space
-    ast::AddressSpace ToAddressSpace(const SpvStorageClass sc);
+    ast::AddressSpace ToAddressSpace(const spv::StorageClass sc);
 
     /// Converts a SPIR-V Builtin value a Tint Builtin.
     /// On failure, logs an error and returns kNone
     /// @param b the SPIR-V builtin
     /// @returns a Tint AST builtin
-    ast::BuiltinValue ToBuiltin(SpvBuiltIn b);
+    ast::BuiltinValue ToBuiltin(spv::BuiltIn b);
 
     /// Converts a possibly arrayed SPIR-V Dim to a Tint texture dimension.
     /// On failure, logs an error and returns kNone
     /// @param dim the SPIR-V Dim value
     /// @param arrayed true if the texture is arrayed
     /// @returns a Tint AST texture dimension
-    ast::TextureDimension ToDim(SpvDim dim, bool arrayed);
+    ast::TextureDimension ToDim(spv::Dim dim, bool arrayed);
+
+    /// Converts a possibly arrayed SPIR-V Dim to a Tint texture dimension.
+    /// On failure, logs an error and returns kNone
+    /// @param dim the SPIR-V Dim value
+    /// @param arrayed true if the texture is arrayed
+    /// @returns a Tint AST texture dimension
+    ast::TextureDimension ToDim(SpvDim dim, bool arrayed) {
+        return ToDim(static_cast<spv::Dim>(dim), arrayed);
+    }
 
     /// Converts a SPIR-V Image Format to a TexelFormat
     /// On failure, logs an error and returns kNone
     /// @param fmt the SPIR-V format
     /// @returns a Tint AST format
-    ast::TexelFormat ToTexelFormat(SpvImageFormat fmt);
+    ast::TexelFormat ToTexelFormat(spv::ImageFormat fmt);
+
+    /// Converts a SPIR-V Image Format to a TexelFormat
+    /// On failure, logs an error and returns kNone
+    /// @param fmt the SPIR-V format
+    /// @returns a Tint AST format
+    ast::TexelFormat ToTexelFormat(SpvImageFormat fmt) {
+        return ToTexelFormat(static_cast<spv::ImageFormat>(fmt));
+    }
 
   private:
     /// Registers a failure and returns a stream for log diagnostics.
diff --git a/src/tint/reader/spirv/enum_converter_test.cc b/src/tint/reader/spirv/enum_converter_test.cc
index 112dc4a..b366ebd 100644
--- a/src/tint/reader/spirv/enum_converter_test.cc
+++ b/src/tint/reader/spirv/enum_converter_test.cc
@@ -24,12 +24,12 @@
 // Pipeline stage
 
 struct PipelineStageCase {
-    SpvExecutionModel model;
+    spv::ExecutionModel model;
     bool expect_success;
     ast::PipelineStage expected;
 };
 inline std::ostream& operator<<(std::ostream& out, PipelineStageCase psc) {
-    out << "PipelineStageCase{ SpvExecutionModel:" << int(psc.model)
+    out << "PipelineStageCase{ spv::ExecutionModel:::" << int(psc.model)
         << " expect_success?:" << int(psc.expect_success) << " expected:" << int(psc.expected)
         << "}";
     return out;
@@ -65,29 +65,29 @@
 
 INSTANTIATE_TEST_SUITE_P(EnumConverterGood,
                          SpvPipelineStageTest,
-                         testing::Values(PipelineStageCase{SpvExecutionModelVertex, true,
+                         testing::Values(PipelineStageCase{spv::ExecutionModel::Vertex, true,
                                                            ast::PipelineStage::kVertex},
-                                         PipelineStageCase{SpvExecutionModelFragment, true,
+                                         PipelineStageCase{spv::ExecutionModel::Fragment, true,
                                                            ast::PipelineStage::kFragment},
-                                         PipelineStageCase{SpvExecutionModelGLCompute, true,
+                                         PipelineStageCase{spv::ExecutionModel::GLCompute, true,
                                                            ast::PipelineStage::kCompute}));
 
 INSTANTIATE_TEST_SUITE_P(EnumConverterBad,
                          SpvPipelineStageTest,
-                         testing::Values(PipelineStageCase{static_cast<SpvExecutionModel>(9999),
+                         testing::Values(PipelineStageCase{static_cast<spv::ExecutionModel>(9999),
                                                            false, ast::PipelineStage::kNone},
-                                         PipelineStageCase{SpvExecutionModelTessellationControl,
+                                         PipelineStageCase{spv::ExecutionModel::TessellationControl,
                                                            false, ast::PipelineStage::kNone}));
 
 // Storage class
 
 struct StorageClassCase {
-    SpvStorageClass sc;
+    spv::StorageClass sc;
     bool expect_success;
     ast::AddressSpace expected;
 };
 inline std::ostream& operator<<(std::ostream& out, StorageClassCase scc) {
-    out << "StorageClassCase{ SpvStorageClass:" << int(scc.sc)
+    out << "StorageClassCase{ spv::StorageClass:::" << int(scc.sc)
         << " expect_success?:" << int(scc.expect_success) << " expected:" << int(scc.expected)
         << "}";
     return out;
@@ -125,29 +125,29 @@
     EnumConverterGood,
     SpvStorageClassTest,
     testing::Values(
-        StorageClassCase{SpvStorageClassInput, true, ast::AddressSpace::kIn},
-        StorageClassCase{SpvStorageClassOutput, true, ast::AddressSpace::kOut},
-        StorageClassCase{SpvStorageClassUniform, true, ast::AddressSpace::kUniform},
-        StorageClassCase{SpvStorageClassWorkgroup, true, ast::AddressSpace::kWorkgroup},
-        StorageClassCase{SpvStorageClassUniformConstant, true, ast::AddressSpace::kNone},
-        StorageClassCase{SpvStorageClassStorageBuffer, true, ast::AddressSpace::kStorage},
-        StorageClassCase{SpvStorageClassPrivate, true, ast::AddressSpace::kPrivate},
-        StorageClassCase{SpvStorageClassFunction, true, ast::AddressSpace::kFunction}));
+        StorageClassCase{spv::StorageClass::Input, true, ast::AddressSpace::kIn},
+        StorageClassCase{spv::StorageClass::Output, true, ast::AddressSpace::kOut},
+        StorageClassCase{spv::StorageClass::Uniform, true, ast::AddressSpace::kUniform},
+        StorageClassCase{spv::StorageClass::Workgroup, true, ast::AddressSpace::kWorkgroup},
+        StorageClassCase{spv::StorageClass::UniformConstant, true, ast::AddressSpace::kNone},
+        StorageClassCase{spv::StorageClass::StorageBuffer, true, ast::AddressSpace::kStorage},
+        StorageClassCase{spv::StorageClass::Private, true, ast::AddressSpace::kPrivate},
+        StorageClassCase{spv::StorageClass::Function, true, ast::AddressSpace::kFunction}));
 
 INSTANTIATE_TEST_SUITE_P(EnumConverterBad,
                          SpvStorageClassTest,
-                         testing::Values(StorageClassCase{static_cast<SpvStorageClass>(9999), false,
-                                                          ast::AddressSpace::kUndefined}));
+                         testing::Values(StorageClassCase{static_cast<spv::StorageClass>(9999),
+                                                          false, ast::AddressSpace::kUndefined}));
 
 // Builtin
 
 struct BuiltinCase {
-    SpvBuiltIn builtin;
+    spv::BuiltIn builtin;
     bool expect_success;
     ast::BuiltinValue expected;
 };
 inline std::ostream& operator<<(std::ostream& out, BuiltinCase bc) {
-    out << "BuiltinCase{ SpvBuiltIn:" << int(bc.builtin)
+    out << "BuiltinCase{ spv::BuiltIn::" << int(bc.builtin)
         << " expect_success?:" << int(bc.expect_success) << " expected:" << int(bc.expected) << "}";
     return out;
 }
@@ -184,43 +184,44 @@
     EnumConverterGood_Input,
     SpvBuiltinTest,
     testing::Values(
-        BuiltinCase{SpvBuiltInPosition, true, ast::BuiltinValue::kPosition},
-        BuiltinCase{SpvBuiltInInstanceIndex, true, ast::BuiltinValue::kInstanceIndex},
-        BuiltinCase{SpvBuiltInFrontFacing, true, ast::BuiltinValue::kFrontFacing},
-        BuiltinCase{SpvBuiltInFragCoord, true, ast::BuiltinValue::kPosition},
-        BuiltinCase{SpvBuiltInLocalInvocationId, true, ast::BuiltinValue::kLocalInvocationId},
-        BuiltinCase{SpvBuiltInLocalInvocationIndex, true, ast::BuiltinValue::kLocalInvocationIndex},
-        BuiltinCase{SpvBuiltInGlobalInvocationId, true, ast::BuiltinValue::kGlobalInvocationId},
-        BuiltinCase{SpvBuiltInWorkgroupId, true, ast::BuiltinValue::kWorkgroupId},
-        BuiltinCase{SpvBuiltInSampleId, true, ast::BuiltinValue::kSampleIndex},
-        BuiltinCase{SpvBuiltInSampleMask, true, ast::BuiltinValue::kSampleMask}));
+        BuiltinCase{spv::BuiltIn::Position, true, ast::BuiltinValue::kPosition},
+        BuiltinCase{spv::BuiltIn::InstanceIndex, true, ast::BuiltinValue::kInstanceIndex},
+        BuiltinCase{spv::BuiltIn::FrontFacing, true, ast::BuiltinValue::kFrontFacing},
+        BuiltinCase{spv::BuiltIn::FragCoord, true, ast::BuiltinValue::kPosition},
+        BuiltinCase{spv::BuiltIn::LocalInvocationId, true, ast::BuiltinValue::kLocalInvocationId},
+        BuiltinCase{spv::BuiltIn::LocalInvocationIndex, true,
+                    ast::BuiltinValue::kLocalInvocationIndex},
+        BuiltinCase{spv::BuiltIn::GlobalInvocationId, true, ast::BuiltinValue::kGlobalInvocationId},
+        BuiltinCase{spv::BuiltIn::WorkgroupId, true, ast::BuiltinValue::kWorkgroupId},
+        BuiltinCase{spv::BuiltIn::SampleId, true, ast::BuiltinValue::kSampleIndex},
+        BuiltinCase{spv::BuiltIn::SampleMask, true, ast::BuiltinValue::kSampleMask}));
 
 INSTANTIATE_TEST_SUITE_P(
     EnumConverterGood_Output,
     SpvBuiltinTest,
-    testing::Values(BuiltinCase{SpvBuiltInPosition, true, ast::BuiltinValue::kPosition},
-                    BuiltinCase{SpvBuiltInFragDepth, true, ast::BuiltinValue::kFragDepth},
-                    BuiltinCase{SpvBuiltInSampleMask, true, ast::BuiltinValue::kSampleMask}));
+    testing::Values(BuiltinCase{spv::BuiltIn::Position, true, ast::BuiltinValue::kPosition},
+                    BuiltinCase{spv::BuiltIn::FragDepth, true, ast::BuiltinValue::kFragDepth},
+                    BuiltinCase{spv::BuiltIn::SampleMask, true, ast::BuiltinValue::kSampleMask}));
 
 INSTANTIATE_TEST_SUITE_P(EnumConverterBad,
                          SpvBuiltinTest,
-                         testing::Values(BuiltinCase{static_cast<SpvBuiltIn>(9999), false,
+                         testing::Values(BuiltinCase{static_cast<spv::BuiltIn>(9999), false,
                                                      ast::BuiltinValue::kUndefined},
-                                         BuiltinCase{static_cast<SpvBuiltIn>(9999), false,
+                                         BuiltinCase{static_cast<spv::BuiltIn>(9999), false,
                                                      ast::BuiltinValue::kUndefined},
-                                         BuiltinCase{SpvBuiltInNumWorkgroups, false,
+                                         BuiltinCase{spv::BuiltIn::NumWorkgroups, false,
                                                      ast::BuiltinValue::kUndefined}));
 
 // Dim
 
 struct DimCase {
-    SpvDim dim;
+    spv::Dim dim;
     bool arrayed;
     bool expect_success;
     ast::TextureDimension expected;
 };
 inline std::ostream& operator<<(std::ostream& out, DimCase dc) {
-    out << "DimCase{ SpvDim:" << int(dc.dim) << " arrayed?:" << int(dc.arrayed)
+    out << "DimCase{ spv::Dim:::" << int(dc.dim) << " arrayed?:" << int(dc.arrayed)
         << " expect_success?:" << int(dc.expect_success) << " expected:" << int(dc.expected) << "}";
     return out;
 }
@@ -256,40 +257,41 @@
                          SpvDimTest,
                          testing::Values(
                              // Non-arrayed
-                             DimCase{SpvDim1D, false, true, ast::TextureDimension::k1d},
-                             DimCase{SpvDim2D, false, true, ast::TextureDimension::k2d},
-                             DimCase{SpvDim3D, false, true, ast::TextureDimension::k3d},
-                             DimCase{SpvDimCube, false, true, ast::TextureDimension::kCube},
+                             DimCase{spv::Dim::Dim1D, false, true, ast::TextureDimension::k1d},
+                             DimCase{spv::Dim::Dim2D, false, true, ast::TextureDimension::k2d},
+                             DimCase{spv::Dim::Dim3D, false, true, ast::TextureDimension::k3d},
+                             DimCase{spv::Dim::Cube, false, true, ast::TextureDimension::kCube},
                              // Arrayed
-                             DimCase{SpvDim2D, true, true, ast::TextureDimension::k2dArray},
-                             DimCase{SpvDimCube, true, true, ast::TextureDimension::kCubeArray}));
+                             DimCase{spv::Dim::Dim2D, true, true, ast::TextureDimension::k2dArray},
+                             DimCase{spv::Dim::Cube, true, true,
+                                     ast::TextureDimension::kCubeArray}));
 
-INSTANTIATE_TEST_SUITE_P(EnumConverterBad,
-                         SpvDimTest,
-                         testing::Values(
-                             // Invalid SPIR-V dimensionality.
-                             DimCase{SpvDimMax, false, false, ast::TextureDimension::kNone},
-                             DimCase{SpvDimMax, true, false, ast::TextureDimension::kNone},
-                             // Vulkan non-arrayed dimensionalities not supported by WGSL.
-                             DimCase{SpvDimRect, false, false, ast::TextureDimension::kNone},
-                             DimCase{SpvDimBuffer, false, false, ast::TextureDimension::kNone},
-                             DimCase{SpvDimSubpassData, false, false, ast::TextureDimension::kNone},
-                             // Arrayed dimensionalities not supported by WGSL
-                             DimCase{SpvDim3D, true, false, ast::TextureDimension::kNone},
-                             DimCase{SpvDimRect, true, false, ast::TextureDimension::kNone},
-                             DimCase{SpvDimBuffer, true, false, ast::TextureDimension::kNone},
-                             DimCase{SpvDimSubpassData, true, false,
-                                     ast::TextureDimension::kNone}));
+INSTANTIATE_TEST_SUITE_P(
+    EnumConverterBad,
+    SpvDimTest,
+    testing::Values(
+        // Invalid SPIR-V dimensionality.
+        DimCase{spv::Dim::Max, false, false, ast::TextureDimension::kNone},
+        DimCase{spv::Dim::Max, true, false, ast::TextureDimension::kNone},
+        // Vulkan non-arrayed dimensionalities not supported by WGSL.
+        DimCase{spv::Dim::Rect, false, false, ast::TextureDimension::kNone},
+        DimCase{spv::Dim::Buffer, false, false, ast::TextureDimension::kNone},
+        DimCase{spv::Dim::SubpassData, false, false, ast::TextureDimension::kNone},
+        // Arrayed dimensionalities not supported by WGSL
+        DimCase{spv::Dim::Dim3D, true, false, ast::TextureDimension::kNone},
+        DimCase{spv::Dim::Rect, true, false, ast::TextureDimension::kNone},
+        DimCase{spv::Dim::Buffer, true, false, ast::TextureDimension::kNone},
+        DimCase{spv::Dim::SubpassData, true, false, ast::TextureDimension::kNone}));
 
 // TexelFormat
 
 struct TexelFormatCase {
-    SpvImageFormat format;
+    spv::ImageFormat format;
     bool expect_success;
     ast::TexelFormat expected;
 };
 inline std::ostream& operator<<(std::ostream& out, TexelFormatCase ifc) {
-    out << "TexelFormatCase{ SpvImageFormat:" << int(ifc.format)
+    out << "TexelFormatCase{ spv::ImageFormat:::" << int(ifc.format)
         << " expect_success?:" << int(ifc.expect_success) << " expected:" << int(ifc.expected)
         << "}";
     return out;
@@ -328,52 +330,52 @@
     SpvImageFormatTest,
     testing::Values(
         // Unknown.  This is used for sampled images.
-        TexelFormatCase{SpvImageFormatUnknown, true, ast::TexelFormat::kUndefined},
+        TexelFormatCase{spv::ImageFormat::Unknown, true, ast::TexelFormat::kUndefined},
         // 8 bit channels
-        TexelFormatCase{SpvImageFormatRgba8, true, ast::TexelFormat::kRgba8Unorm},
-        TexelFormatCase{SpvImageFormatRgba8Snorm, true, ast::TexelFormat::kRgba8Snorm},
-        TexelFormatCase{SpvImageFormatRgba8ui, true, ast::TexelFormat::kRgba8Uint},
-        TexelFormatCase{SpvImageFormatRgba8i, true, ast::TexelFormat::kRgba8Sint},
+        TexelFormatCase{spv::ImageFormat::Rgba8, true, ast::TexelFormat::kRgba8Unorm},
+        TexelFormatCase{spv::ImageFormat::Rgba8Snorm, true, ast::TexelFormat::kRgba8Snorm},
+        TexelFormatCase{spv::ImageFormat::Rgba8ui, true, ast::TexelFormat::kRgba8Uint},
+        TexelFormatCase{spv::ImageFormat::Rgba8i, true, ast::TexelFormat::kRgba8Sint},
         // 16 bit channels
-        TexelFormatCase{SpvImageFormatRgba16ui, true, ast::TexelFormat::kRgba16Uint},
-        TexelFormatCase{SpvImageFormatRgba16i, true, ast::TexelFormat::kRgba16Sint},
-        TexelFormatCase{SpvImageFormatRgba16f, true, ast::TexelFormat::kRgba16Float},
+        TexelFormatCase{spv::ImageFormat::Rgba16ui, true, ast::TexelFormat::kRgba16Uint},
+        TexelFormatCase{spv::ImageFormat::Rgba16i, true, ast::TexelFormat::kRgba16Sint},
+        TexelFormatCase{spv::ImageFormat::Rgba16f, true, ast::TexelFormat::kRgba16Float},
         // 32 bit channels
         // ... 1 channel
-        TexelFormatCase{SpvImageFormatR32ui, true, ast::TexelFormat::kR32Uint},
-        TexelFormatCase{SpvImageFormatR32i, true, ast::TexelFormat::kR32Sint},
-        TexelFormatCase{SpvImageFormatR32f, true, ast::TexelFormat::kR32Float},
+        TexelFormatCase{spv::ImageFormat::R32ui, true, ast::TexelFormat::kR32Uint},
+        TexelFormatCase{spv::ImageFormat::R32i, true, ast::TexelFormat::kR32Sint},
+        TexelFormatCase{spv::ImageFormat::R32f, true, ast::TexelFormat::kR32Float},
         // ... 2 channels
-        TexelFormatCase{SpvImageFormatRg32ui, true, ast::TexelFormat::kRg32Uint},
-        TexelFormatCase{SpvImageFormatRg32i, true, ast::TexelFormat::kRg32Sint},
-        TexelFormatCase{SpvImageFormatRg32f, true, ast::TexelFormat::kRg32Float},
+        TexelFormatCase{spv::ImageFormat::Rg32ui, true, ast::TexelFormat::kRg32Uint},
+        TexelFormatCase{spv::ImageFormat::Rg32i, true, ast::TexelFormat::kRg32Sint},
+        TexelFormatCase{spv::ImageFormat::Rg32f, true, ast::TexelFormat::kRg32Float},
         // ... 4 channels
-        TexelFormatCase{SpvImageFormatRgba32ui, true, ast::TexelFormat::kRgba32Uint},
-        TexelFormatCase{SpvImageFormatRgba32i, true, ast::TexelFormat::kRgba32Sint},
-        TexelFormatCase{SpvImageFormatRgba32f, true, ast::TexelFormat::kRgba32Float}));
+        TexelFormatCase{spv::ImageFormat::Rgba32ui, true, ast::TexelFormat::kRgba32Uint},
+        TexelFormatCase{spv::ImageFormat::Rgba32i, true, ast::TexelFormat::kRgba32Sint},
+        TexelFormatCase{spv::ImageFormat::Rgba32f, true, ast::TexelFormat::kRgba32Float}));
 
 INSTANTIATE_TEST_SUITE_P(
     EnumConverterBad,
     SpvImageFormatTest,
     testing::Values(
         // Scanning in order from the SPIR-V spec.
-        TexelFormatCase{SpvImageFormatRg16f, false, ast::TexelFormat::kUndefined},
-        TexelFormatCase{SpvImageFormatR11fG11fB10f, false, ast::TexelFormat::kUndefined},
-        TexelFormatCase{SpvImageFormatR16f, false, ast::TexelFormat::kUndefined},
-        TexelFormatCase{SpvImageFormatRgb10A2, false, ast::TexelFormat::kUndefined},
-        TexelFormatCase{SpvImageFormatRg16, false, ast::TexelFormat::kUndefined},
-        TexelFormatCase{SpvImageFormatRg8, false, ast::TexelFormat::kUndefined},
-        TexelFormatCase{SpvImageFormatR16, false, ast::TexelFormat::kUndefined},
-        TexelFormatCase{SpvImageFormatR8, false, ast::TexelFormat::kUndefined},
-        TexelFormatCase{SpvImageFormatRgba16Snorm, false, ast::TexelFormat::kUndefined},
-        TexelFormatCase{SpvImageFormatRg16Snorm, false, ast::TexelFormat::kUndefined},
-        TexelFormatCase{SpvImageFormatRg8Snorm, false, ast::TexelFormat::kUndefined},
-        TexelFormatCase{SpvImageFormatRg16i, false, ast::TexelFormat::kUndefined},
-        TexelFormatCase{SpvImageFormatRg8i, false, ast::TexelFormat::kUndefined},
-        TexelFormatCase{SpvImageFormatR8i, false, ast::TexelFormat::kUndefined},
-        TexelFormatCase{SpvImageFormatRgb10a2ui, false, ast::TexelFormat::kUndefined},
-        TexelFormatCase{SpvImageFormatRg16ui, false, ast::TexelFormat::kUndefined},
-        TexelFormatCase{SpvImageFormatRg8ui, false, ast::TexelFormat::kUndefined}));
+        TexelFormatCase{spv::ImageFormat::Rg16f, false, ast::TexelFormat::kUndefined},
+        TexelFormatCase{spv::ImageFormat::R11fG11fB10f, false, ast::TexelFormat::kUndefined},
+        TexelFormatCase{spv::ImageFormat::R16f, false, ast::TexelFormat::kUndefined},
+        TexelFormatCase{spv::ImageFormat::Rgb10A2, false, ast::TexelFormat::kUndefined},
+        TexelFormatCase{spv::ImageFormat::Rg16, false, ast::TexelFormat::kUndefined},
+        TexelFormatCase{spv::ImageFormat::Rg8, false, ast::TexelFormat::kUndefined},
+        TexelFormatCase{spv::ImageFormat::R16, false, ast::TexelFormat::kUndefined},
+        TexelFormatCase{spv::ImageFormat::R8, false, ast::TexelFormat::kUndefined},
+        TexelFormatCase{spv::ImageFormat::Rgba16Snorm, false, ast::TexelFormat::kUndefined},
+        TexelFormatCase{spv::ImageFormat::Rg16Snorm, false, ast::TexelFormat::kUndefined},
+        TexelFormatCase{spv::ImageFormat::Rg8Snorm, false, ast::TexelFormat::kUndefined},
+        TexelFormatCase{spv::ImageFormat::Rg16i, false, ast::TexelFormat::kUndefined},
+        TexelFormatCase{spv::ImageFormat::Rg8i, false, ast::TexelFormat::kUndefined},
+        TexelFormatCase{spv::ImageFormat::R8i, false, ast::TexelFormat::kUndefined},
+        TexelFormatCase{spv::ImageFormat::Rgb10a2ui, false, ast::TexelFormat::kUndefined},
+        TexelFormatCase{spv::ImageFormat::Rg16ui, false, ast::TexelFormat::kUndefined},
+        TexelFormatCase{spv::ImageFormat::Rg8ui, false, ast::TexelFormat::kUndefined}));
 
 }  // namespace
 }  // namespace tint::reader::spirv
diff --git a/src/tint/reader/spirv/function.cc b/src/tint/reader/spirv/function.cc
index d3febf2..0aafdf9 100644
--- a/src/tint/reader/spirv/function.cc
+++ b/src/tint/reader/spirv/function.cc
@@ -144,24 +144,99 @@
 
 namespace tint::reader::spirv {
 
+namespace three_sided_patch_function_cc {
+// This machinery is only used while SPIRV-Tools is in transition before it fully
+// uses the C++11 header spirv.hpp11
+
+/// Typedef for pointer to member function while the API call uses
+/// SpvStorageClass for its second argument.
+typedef uint32_t (
+    spvtools::opt::analysis::TypeManager::*PointerFinderSpvStorageClass)(uint32_t, SpvStorageClass);
+/// Typedef for pointer to member function while the API call uses
+/// spv::StorageClass for its second argument.
+typedef uint32_t (spvtools::opt::analysis::TypeManager::*PointerFinderSpvStorageClassCpp11)(
+    uint32_t,
+    spv::StorageClass);
+
+/// @param type_manager the SPIRV-Tools optimizer's type manager
+/// @param finder a pointer to member function in the type manager that does the
+/// actual lookup
+/// @param pointee_type_id the ID of the pointee type
+/// @param sc the storage class.  SC can be SpvStorageClass or spv::StorageClass
+/// @returns the ID for a SPIR-V pointer to pointee_type_id in storage class sc
+template <typename FinderType, typename SC>
+uint32_t FindPointerToType(spvtools::opt::analysis::TypeManager* type_manager,
+                           FinderType finder,
+                           uint32_t pointee_type_id,
+                           SC sc);
+
+template <>
+uint32_t FindPointerToType(spvtools::opt::analysis::TypeManager* type_mgr,
+                           PointerFinderSpvStorageClass finder,
+                           uint32_t pointee_type_id,
+                           SpvStorageClass sc) {
+    return (type_mgr->*finder)(pointee_type_id, sc);
+}
+
+template <>
+uint32_t FindPointerToType(spvtools::opt::analysis::TypeManager* type_mgr,
+                           PointerFinderSpvStorageClass finder,
+                           uint32_t pointee_type_id,
+                           spv::StorageClass sc) {
+    return (type_mgr->*finder)(pointee_type_id, static_cast<SpvStorageClass>(sc));
+}
+
+template <>
+uint32_t FindPointerToType(spvtools::opt::analysis::TypeManager* type_mgr,
+                           PointerFinderSpvStorageClassCpp11 finder,
+                           uint32_t pointee_type_id,
+                           SpvStorageClass sc) {
+    return (type_mgr->*finder)(pointee_type_id, static_cast<spv::StorageClass>(sc));
+}
+
+template <>
+uint32_t FindPointerToType(spvtools::opt::analysis::TypeManager* type_mgr,
+                           PointerFinderSpvStorageClassCpp11 finder,
+                           uint32_t pointee_type_id,
+                           spv::StorageClass sc) {
+    return (type_mgr->*finder)(pointee_type_id, sc);
+}
+}  // namespace three_sided_patch_function_cc
+
 namespace {
 
 constexpr uint32_t kMaxVectorLen = 4;
 
+template <typename FromOpcodeType>
+spv::Op ToOpcode(FromOpcodeType oc) {
+    return static_cast<spv::Op>(oc);
+}
+
+/// @param inst a SPIR-V instruction
+/// @returns Returns the opcode for an instruciton
+inline spv::Op opcode(const spvtools::opt::Instruction& inst) {
+    return ToOpcode(inst.opcode());
+}
+/// @param inst a SPIR-V instruction pointer
+/// @returns Returns the opcode for an instruciton
+inline spv::Op opcode(const spvtools::opt::Instruction* inst) {
+    return ToOpcode(inst->opcode());
+}
+
 // Gets the AST unary opcode for the given SPIR-V opcode, if any
 // @param opcode SPIR-V opcode
 // @param ast_unary_op return parameter
 // @returns true if it was a unary operation
-bool GetUnaryOp(SpvOp opcode, ast::UnaryOp* ast_unary_op) {
+bool GetUnaryOp(spv::Op opcode, ast::UnaryOp* ast_unary_op) {
     switch (opcode) {
-        case SpvOpSNegate:
-        case SpvOpFNegate:
+        case spv::Op::OpSNegate:
+        case spv::Op::OpFNegate:
             *ast_unary_op = ast::UnaryOp::kNegation;
             return true;
-        case SpvOpLogicalNot:
+        case spv::Op::OpLogicalNot:
             *ast_unary_op = ast::UnaryOp::kNot;
             return true;
-        case SpvOpNot:
+        case spv::Op::OpNot:
             *ast_unary_op = ast::UnaryOp::kComplement;
             return true;
         default:
@@ -173,17 +248,17 @@
 /// Converts a SPIR-V opcode for a WGSL builtin function, if there is a
 /// direct translation. Returns nullptr otherwise.
 /// @returns the WGSL builtin function name for the given opcode, or nullptr.
-const char* GetUnaryBuiltInFunctionName(SpvOp opcode) {
+const char* GetUnaryBuiltInFunctionName(spv::Op opcode) {
     switch (opcode) {
-        case SpvOpAny:
+        case spv::Op::OpAny:
             return "any";
-        case SpvOpAll:
+        case spv::Op::OpAll:
             return "all";
-        case SpvOpIsNan:
+        case spv::Op::OpIsNan:
             return "isNan";
-        case SpvOpIsInf:
+        case spv::Op::OpIsInf:
             return "isInf";
-        case SpvOpTranspose:
+        case spv::Op::OpTranspose:
             return "transpose";
         default:
             break;
@@ -194,63 +269,63 @@
 // Converts a SPIR-V opcode to its corresponding AST binary opcode, if any
 // @param opcode SPIR-V opcode
 // @returns the AST binary op for the given opcode, or kNone
-ast::BinaryOp ConvertBinaryOp(SpvOp opcode) {
+ast::BinaryOp ConvertBinaryOp(spv::Op opcode) {
     switch (opcode) {
-        case SpvOpIAdd:
-        case SpvOpFAdd:
+        case spv::Op::OpIAdd:
+        case spv::Op::OpFAdd:
             return ast::BinaryOp::kAdd;
-        case SpvOpISub:
-        case SpvOpFSub:
+        case spv::Op::OpISub:
+        case spv::Op::OpFSub:
             return ast::BinaryOp::kSubtract;
-        case SpvOpIMul:
-        case SpvOpFMul:
-        case SpvOpVectorTimesScalar:
-        case SpvOpMatrixTimesScalar:
-        case SpvOpVectorTimesMatrix:
-        case SpvOpMatrixTimesVector:
-        case SpvOpMatrixTimesMatrix:
+        case spv::Op::OpIMul:
+        case spv::Op::OpFMul:
+        case spv::Op::OpVectorTimesScalar:
+        case spv::Op::OpMatrixTimesScalar:
+        case spv::Op::OpVectorTimesMatrix:
+        case spv::Op::OpMatrixTimesVector:
+        case spv::Op::OpMatrixTimesMatrix:
             return ast::BinaryOp::kMultiply;
-        case SpvOpUDiv:
-        case SpvOpSDiv:
-        case SpvOpFDiv:
+        case spv::Op::OpUDiv:
+        case spv::Op::OpSDiv:
+        case spv::Op::OpFDiv:
             return ast::BinaryOp::kDivide;
-        case SpvOpUMod:
-        case SpvOpSMod:
-        case SpvOpFRem:
+        case spv::Op::OpUMod:
+        case spv::Op::OpSMod:
+        case spv::Op::OpFRem:
             return ast::BinaryOp::kModulo;
-        case SpvOpLogicalEqual:
-        case SpvOpIEqual:
-        case SpvOpFOrdEqual:
+        case spv::Op::OpLogicalEqual:
+        case spv::Op::OpIEqual:
+        case spv::Op::OpFOrdEqual:
             return ast::BinaryOp::kEqual;
-        case SpvOpLogicalNotEqual:
-        case SpvOpINotEqual:
-        case SpvOpFOrdNotEqual:
+        case spv::Op::OpLogicalNotEqual:
+        case spv::Op::OpINotEqual:
+        case spv::Op::OpFOrdNotEqual:
             return ast::BinaryOp::kNotEqual;
-        case SpvOpBitwiseAnd:
+        case spv::Op::OpBitwiseAnd:
             return ast::BinaryOp::kAnd;
-        case SpvOpBitwiseOr:
+        case spv::Op::OpBitwiseOr:
             return ast::BinaryOp::kOr;
-        case SpvOpBitwiseXor:
+        case spv::Op::OpBitwiseXor:
             return ast::BinaryOp::kXor;
-        case SpvOpLogicalAnd:
+        case spv::Op::OpLogicalAnd:
             return ast::BinaryOp::kAnd;
-        case SpvOpLogicalOr:
+        case spv::Op::OpLogicalOr:
             return ast::BinaryOp::kOr;
-        case SpvOpUGreaterThan:
-        case SpvOpSGreaterThan:
-        case SpvOpFOrdGreaterThan:
+        case spv::Op::OpUGreaterThan:
+        case spv::Op::OpSGreaterThan:
+        case spv::Op::OpFOrdGreaterThan:
             return ast::BinaryOp::kGreaterThan;
-        case SpvOpUGreaterThanEqual:
-        case SpvOpSGreaterThanEqual:
-        case SpvOpFOrdGreaterThanEqual:
+        case spv::Op::OpUGreaterThanEqual:
+        case spv::Op::OpSGreaterThanEqual:
+        case spv::Op::OpFOrdGreaterThanEqual:
             return ast::BinaryOp::kGreaterThanEqual;
-        case SpvOpULessThan:
-        case SpvOpSLessThan:
-        case SpvOpFOrdLessThan:
+        case spv::Op::OpULessThan:
+        case spv::Op::OpSLessThan:
+        case spv::Op::OpFOrdLessThan:
             return ast::BinaryOp::kLessThan;
-        case SpvOpULessThanEqual:
-        case SpvOpSLessThanEqual:
-        case SpvOpFOrdLessThanEqual:
+        case spv::Op::OpULessThanEqual:
+        case spv::Op::OpSLessThanEqual:
+        case spv::Op::OpFOrdLessThanEqual:
             return ast::BinaryOp::kLessThanEqual;
         default:
             break;
@@ -265,19 +340,19 @@
 // Othewrise returns BinaryOp::kNone.
 // @param opcode SPIR-V opcode
 // @returns operation corresponding to negated version of the SPIR-V opcode
-ast::BinaryOp NegatedFloatCompare(SpvOp opcode) {
+ast::BinaryOp NegatedFloatCompare(spv::Op opcode) {
     switch (opcode) {
-        case SpvOpFUnordEqual:
+        case spv::Op::OpFUnordEqual:
             return ast::BinaryOp::kNotEqual;
-        case SpvOpFUnordNotEqual:
+        case spv::Op::OpFUnordNotEqual:
             return ast::BinaryOp::kEqual;
-        case SpvOpFUnordLessThan:
+        case spv::Op::OpFUnordLessThan:
             return ast::BinaryOp::kGreaterThanEqual;
-        case SpvOpFUnordLessThanEqual:
+        case spv::Op::OpFUnordLessThanEqual:
             return ast::BinaryOp::kGreaterThan;
-        case SpvOpFUnordGreaterThan:
+        case spv::Op::OpFUnordGreaterThan:
             return ast::BinaryOp::kLessThanEqual;
-        case SpvOpFUnordGreaterThanEqual:
+        case spv::Op::OpFUnordGreaterThanEqual:
             return ast::BinaryOp::kLessThan;
         default:
             break;
@@ -443,36 +518,36 @@
 
 // Returns the WGSL standard library function builtin for the
 // given instruction, or sem::BuiltinType::kNone
-sem::BuiltinType GetBuiltin(SpvOp opcode) {
+sem::BuiltinType GetBuiltin(spv::Op opcode) {
     switch (opcode) {
-        case SpvOpBitCount:
+        case spv::Op::OpBitCount:
             return sem::BuiltinType::kCountOneBits;
-        case SpvOpBitFieldInsert:
+        case spv::Op::OpBitFieldInsert:
             return sem::BuiltinType::kInsertBits;
-        case SpvOpBitFieldSExtract:
-        case SpvOpBitFieldUExtract:
+        case spv::Op::OpBitFieldSExtract:
+        case spv::Op::OpBitFieldUExtract:
             return sem::BuiltinType::kExtractBits;
-        case SpvOpBitReverse:
+        case spv::Op::OpBitReverse:
             return sem::BuiltinType::kReverseBits;
-        case SpvOpDot:
+        case spv::Op::OpDot:
             return sem::BuiltinType::kDot;
-        case SpvOpDPdx:
+        case spv::Op::OpDPdx:
             return sem::BuiltinType::kDpdx;
-        case SpvOpDPdy:
+        case spv::Op::OpDPdy:
             return sem::BuiltinType::kDpdy;
-        case SpvOpFwidth:
+        case spv::Op::OpFwidth:
             return sem::BuiltinType::kFwidth;
-        case SpvOpDPdxFine:
+        case spv::Op::OpDPdxFine:
             return sem::BuiltinType::kDpdxFine;
-        case SpvOpDPdyFine:
+        case spv::Op::OpDPdyFine:
             return sem::BuiltinType::kDpdyFine;
-        case SpvOpFwidthFine:
+        case spv::Op::OpFwidthFine:
             return sem::BuiltinType::kFwidthFine;
-        case SpvOpDPdxCoarse:
+        case spv::Op::OpDPdxCoarse:
             return sem::BuiltinType::kDpdxCoarse;
-        case SpvOpDPdyCoarse:
+        case spv::Op::OpDPdyCoarse:
             return sem::BuiltinType::kDpdyCoarse;
-        case SpvOpFwidthCoarse:
+        case spv::Op::OpFwidthCoarse:
             return sem::BuiltinType::kFwidthCoarse;
         default:
             break;
@@ -483,20 +558,20 @@
 // @param opcode a SPIR-V opcode
 // @returns true if the given instruction is an image access instruction
 // whose first input operand is an OpSampledImage value.
-bool IsSampledImageAccess(SpvOp opcode) {
+bool IsSampledImageAccess(spv::Op opcode) {
     switch (opcode) {
-        case SpvOpImageSampleImplicitLod:
-        case SpvOpImageSampleExplicitLod:
-        case SpvOpImageSampleDrefImplicitLod:
-        case SpvOpImageSampleDrefExplicitLod:
+        case spv::Op::OpImageSampleImplicitLod:
+        case spv::Op::OpImageSampleExplicitLod:
+        case spv::Op::OpImageSampleDrefImplicitLod:
+        case spv::Op::OpImageSampleDrefExplicitLod:
         // WGSL doesn't have *Proj* texturing; spirv reader emulates it.
-        case SpvOpImageSampleProjImplicitLod:
-        case SpvOpImageSampleProjExplicitLod:
-        case SpvOpImageSampleProjDrefImplicitLod:
-        case SpvOpImageSampleProjDrefExplicitLod:
-        case SpvOpImageGather:
-        case SpvOpImageDrefGather:
-        case SpvOpImageQueryLod:
+        case spv::Op::OpImageSampleProjImplicitLod:
+        case spv::Op::OpImageSampleProjExplicitLod:
+        case spv::Op::OpImageSampleProjDrefImplicitLod:
+        case spv::Op::OpImageSampleProjDrefExplicitLod:
+        case spv::Op::OpImageGather:
+        case spv::Op::OpImageDrefGather:
+        case spv::Op::OpImageQueryLod:
             return true;
         default:
             break;
@@ -506,29 +581,29 @@
 
 // @param opcode a SPIR-V opcode
 // @returns true if the given instruction is an atomic operation.
-bool IsAtomicOp(SpvOp opcode) {
+bool IsAtomicOp(spv::Op opcode) {
     switch (opcode) {
-        case SpvOpAtomicLoad:
-        case SpvOpAtomicStore:
-        case SpvOpAtomicExchange:
-        case SpvOpAtomicCompareExchange:
-        case SpvOpAtomicCompareExchangeWeak:
-        case SpvOpAtomicIIncrement:
-        case SpvOpAtomicIDecrement:
-        case SpvOpAtomicIAdd:
-        case SpvOpAtomicISub:
-        case SpvOpAtomicSMin:
-        case SpvOpAtomicUMin:
-        case SpvOpAtomicSMax:
-        case SpvOpAtomicUMax:
-        case SpvOpAtomicAnd:
-        case SpvOpAtomicOr:
-        case SpvOpAtomicXor:
-        case SpvOpAtomicFlagTestAndSet:
-        case SpvOpAtomicFlagClear:
-        case SpvOpAtomicFMinEXT:
-        case SpvOpAtomicFMaxEXT:
-        case SpvOpAtomicFAddEXT:
+        case spv::Op::OpAtomicLoad:
+        case spv::Op::OpAtomicStore:
+        case spv::Op::OpAtomicExchange:
+        case spv::Op::OpAtomicCompareExchange:
+        case spv::Op::OpAtomicCompareExchangeWeak:
+        case spv::Op::OpAtomicIIncrement:
+        case spv::Op::OpAtomicIDecrement:
+        case spv::Op::OpAtomicIAdd:
+        case spv::Op::OpAtomicISub:
+        case spv::Op::OpAtomicSMin:
+        case spv::Op::OpAtomicUMin:
+        case spv::Op::OpAtomicSMax:
+        case spv::Op::OpAtomicUMax:
+        case spv::Op::OpAtomicAnd:
+        case spv::Op::OpAtomicOr:
+        case spv::Op::OpAtomicXor:
+        case spv::Op::OpAtomicFlagTestAndSet:
+        case spv::Op::OpAtomicFlagClear:
+        case spv::Op::OpAtomicFMinEXT:
+        case spv::Op::OpAtomicFMaxEXT:
+        case spv::Op::OpAtomicFAddEXT:
             return true;
         default:
             break;
@@ -539,19 +614,19 @@
 // @param opcode a SPIR-V opcode
 // @returns true if the given instruction is an image sampling, gather,
 // or gather-compare operation.
-bool IsImageSamplingOrGatherOrDrefGather(SpvOp opcode) {
+bool IsImageSamplingOrGatherOrDrefGather(spv::Op opcode) {
     switch (opcode) {
-        case SpvOpImageSampleImplicitLod:
-        case SpvOpImageSampleExplicitLod:
-        case SpvOpImageSampleDrefImplicitLod:
-        case SpvOpImageSampleDrefExplicitLod:
+        case spv::Op::OpImageSampleImplicitLod:
+        case spv::Op::OpImageSampleExplicitLod:
+        case spv::Op::OpImageSampleDrefImplicitLod:
+        case spv::Op::OpImageSampleDrefExplicitLod:
             // WGSL doesn't have *Proj* texturing; spirv reader emulates it.
-        case SpvOpImageSampleProjImplicitLod:
-        case SpvOpImageSampleProjExplicitLod:
-        case SpvOpImageSampleProjDrefImplicitLod:
-        case SpvOpImageSampleProjDrefExplicitLod:
-        case SpvOpImageGather:
-        case SpvOpImageDrefGather:
+        case spv::Op::OpImageSampleProjImplicitLod:
+        case spv::Op::OpImageSampleProjExplicitLod:
+        case spv::Op::OpImageSampleProjDrefImplicitLod:
+        case spv::Op::OpImageSampleProjDrefExplicitLod:
+        case spv::Op::OpImageGather:
+        case spv::Op::OpImageDrefGather:
             return true;
         default:
             break;
@@ -562,11 +637,11 @@
 // @param opcode a SPIR-V opcode
 // @returns true if the given instruction is an image access instruction
 // whose first input operand is an OpImage value.
-bool IsRawImageAccess(SpvOp opcode) {
+bool IsRawImageAccess(spv::Op opcode) {
     switch (opcode) {
-        case SpvOpImageRead:
-        case SpvOpImageWrite:
-        case SpvOpImageFetch:
+        case spv::Op::OpImageRead:
+        case spv::Op::OpImageWrite:
+        case spv::Op::OpImageFetch:
             return true;
         default:
             break;
@@ -576,13 +651,13 @@
 
 // @param opcode a SPIR-V opcode
 // @returns true if the given instruction is an image query instruction
-bool IsImageQuery(SpvOp opcode) {
+bool IsImageQuery(spv::Op opcode) {
     switch (opcode) {
-        case SpvOpImageQuerySize:
-        case SpvOpImageQuerySizeLod:
-        case SpvOpImageQueryLevels:
-        case SpvOpImageQuerySamples:
-        case SpvOpImageQueryLod:
+        case spv::Op::OpImageQuerySize:
+        case spv::Op::OpImageQuerySizeLod:
+        case spv::Op::OpImageQueryLevels:
+        case spv::Op::OpImageQuerySamples:
+        case spv::Op::OpImageQueryLod:
             return true;
         default:
             break;
@@ -653,15 +728,15 @@
         // Visit successors. We will naturally skip the continue target and merge
         // blocks.
         auto* terminator = bb->terminator();
-        auto opcode = terminator->opcode();
-        if (opcode == SpvOpBranchConditional) {
+        auto opcode = ToOpcode(terminator->opcode());
+        if (opcode == spv::Op::OpBranchConditional) {
             // Visit the false branch, then the true branch, to make them come
             // out in the natural order for an "if".
             VisitBackward(terminator->GetSingleWordInOperand(2));
             VisitBackward(terminator->GetSingleWordInOperand(1));
-        } else if (opcode == SpvOpBranch) {
+        } else if (opcode == spv::Op::OpBranch) {
             VisitBackward(terminator->GetSingleWordInOperand(0));
-        } else if (opcode == SpvOpSwitch) {
+        } else if (opcode == spv::Op::OpSwitch) {
             // TODO(dneto): Consider visiting the labels in literal-value order.
             utils::Vector<uint32_t, 32> successors;
             bb->ForEachSuccessorLabel(
@@ -1265,7 +1340,7 @@
     for (uint32_t var_id : ep_info_->inputs) {
         const auto* var = def_use_mgr_->GetDef(var_id);
         TINT_ASSERT(Reader, var != nullptr);
-        TINT_ASSERT(Reader, var->opcode() == SpvOpVariable);
+        TINT_ASSERT(Reader, opcode(var) == spv::Op::OpVariable);
         auto* store_type = GetVariableStoreType(*var);
         auto* forced_param_type = store_type;
         AttributeList param_decos;
@@ -1344,7 +1419,7 @@
             } else {
                 const auto* var = def_use_mgr_->GetDef(var_id);
                 TINT_ASSERT(Reader, var != nullptr);
-                TINT_ASSERT(Reader, var->opcode() == SpvOpVariable);
+                TINT_ASSERT(Reader, opcode(var) == spv::Op::OpVariable);
                 const Type* store_type = GetVariableStoreType(*var);
                 const Type* forced_member_type = store_type;
                 AttributeList out_decos;
@@ -1570,19 +1645,19 @@
         }
 
         if (const auto* inst = block.GetMergeInst()) {
-            auto terminator_opcode = block.terminator()->opcode();
-            switch (inst->opcode()) {
-                case SpvOpSelectionMerge:
-                    if ((terminator_opcode != SpvOpBranchConditional) &&
-                        (terminator_opcode != SpvOpSwitch)) {
+            auto terminator_opcode = opcode(block.terminator());
+            switch (opcode(inst)) {
+                case spv::Op::OpSelectionMerge:
+                    if ((terminator_opcode != spv::Op::OpBranchConditional) &&
+                        (terminator_opcode != spv::Op::OpSwitch)) {
                         return Fail() << "Selection header " << block_id
                                       << " does not end in an OpBranchConditional or "
                                          "OpSwitch instruction";
                     }
                     break;
-                case SpvOpLoopMerge:
-                    if ((terminator_opcode != SpvOpBranchConditional) &&
-                        (terminator_opcode != SpvOpBranch)) {
+                case spv::Op::OpLoopMerge:
+                    if ((terminator_opcode != spv::Op::OpBranchConditional) &&
+                        (terminator_opcode != spv::Op::OpBranch)) {
                         return Fail() << "Loop header " << block_id
                                       << " does not end in an OpBranch or "
                                          "OpBranchConditional instruction";
@@ -1612,7 +1687,7 @@
             merge_info->header_for_merge = header;
             header_info->merge_for_header = merge;
 
-            if (inst->opcode() == SpvOpLoopMerge) {
+            if (opcode(inst) == spv::Op::OpLoopMerge) {
                 if (header == entry_id) {
                     return Fail() << "Function entry block " << entry_id
                                   << " cannot be a loop header";
@@ -1867,8 +1942,8 @@
                 // From the interval rule, the selection construct consists of blocks
                 // in the block order, starting at the header, until just before the
                 // merge block.
-                const auto branch_opcode = header_info->basic_block->terminator()->opcode();
-                const auto kind = (branch_opcode == SpvOpBranchConditional)
+                const auto branch_opcode = opcode(header_info->basic_block->terminator());
+                const auto kind = (branch_opcode == spv::Op::OpBranchConditional)
                                       ? Construct::kIfSelection
                                       : Construct::kSwitchSelection;
                 top = push_construct(depth, kind, header, merge);
@@ -2485,7 +2560,7 @@
         return false;
     }
     for (auto& inst : *function_.entry()) {
-        if (inst.opcode() != SpvOpVariable) {
+        if (opcode(inst) != spv::Op::OpVariable) {
             continue;
         }
         auto* var_store_type = GetVariableStoreType(inst);
@@ -2586,8 +2661,8 @@
         Fail() << "ID " << id << " does not have a defining SPIR-V instruction";
         return {};
     }
-    switch (inst->opcode()) {
-        case SpvOpVariable: {
+    switch (opcode(inst)) {
+        case spv::Op::OpVariable: {
             // This occurs for module-scope variables.
             auto name = namer_.Name(id);
             // Construct the reference type, mapping storage class correctly.
@@ -2596,7 +2671,7 @@
             return TypedExpression{type, create<ast::IdentifierExpression>(
                                              Source{}, builder_.Symbols().Register(name))};
         }
-        case SpvOpUndef:
+        case spv::Op::OpUndef:
             // Substitute a null value for undef.
             // This case occurs when OpUndef appears at module scope, as if it were
             // a constant.
@@ -3102,11 +3177,11 @@
 
 bool FunctionEmitter::EmitNormalTerminator(const BlockInfo& block_info) {
     const auto& terminator = *(block_info.basic_block->terminator());
-    switch (terminator.opcode()) {
-        case SpvOpReturn:
+    switch (opcode(terminator)) {
+        case spv::Op::OpReturn:
             AddStatement(create<ast::ReturnStatement>(Source{}));
             return true;
-        case SpvOpReturnValue: {
+        case spv::Op::OpReturnValue: {
             auto value = MakeExpression(terminator.GetSingleWordInOperand(0));
             if (!value) {
                 return false;
@@ -3114,12 +3189,12 @@
             AddStatement(create<ast::ReturnStatement>(Source{}, value.expr));
         }
             return true;
-        case SpvOpKill:
+        case spv::Op::OpKill:
             // For now, assume SPIR-V OpKill has same semantics as WGSL discard.
             // TODO(dneto): https://github.com/gpuweb/gpuweb/issues/676
             AddStatement(create<ast::DiscardStatement>(Source{}));
             return true;
-        case SpvOpUnreachable:
+        case spv::Op::OpUnreachable:
             // Translate as if it's a return. This avoids the problem where WGSL
             // requires a return statement at the end of the function body.
             {
@@ -3133,12 +3208,12 @@
                 }
             }
             return true;
-        case SpvOpBranch: {
+        case spv::Op::OpBranch: {
             const auto dest_id = terminator.GetSingleWordInOperand(0);
             AddStatement(MakeBranch(block_info, *GetBlockInfo(dest_id)));
             return true;
         }
-        case SpvOpBranchConditional: {
+        case spv::Op::OpBranchConditional: {
             // If both destinations are the same, then do the same as we would
             // for an unconditional branch (OpBranch).
             const auto true_dest = terminator.GetSingleWordInOperand(1);
@@ -3191,7 +3266,7 @@
             }
             return true;
         }
-        case SpvOpSwitch:
+        case spv::Op::OpSwitch:
             // An OpSelectionMerge must precede an OpSwitch.  That is clarified
             // in the resolution to Khronos-internal SPIR-V issue 115.
             // A new enough version of the SPIR-V validator checks this case.
@@ -3377,8 +3452,8 @@
     const auto* terminator = bb.terminator();
     const auto* merge = bb.GetMergeInst();  // Might be nullptr
     for (auto& inst : bb) {
-        if (&inst == terminator || &inst == merge || inst.opcode() == SpvOpLabel ||
-            inst.opcode() == SpvOpVariable) {
+        if (&inst == terminator || &inst == merge || opcode(inst) == spv::Op::OpLabel ||
+            opcode(inst) == spv::Op::OpVariable) {
             continue;
         }
         if (!EmitStatement(inst)) {
@@ -3546,23 +3621,23 @@
         return false;
     }
 
-    if (IsImageQuery(inst.opcode())) {
+    if (IsImageQuery(opcode(inst))) {
         return EmitImageQuery(inst);
     }
 
-    if (IsSampledImageAccess(inst.opcode()) || IsRawImageAccess(inst.opcode())) {
+    if (IsSampledImageAccess(opcode(inst)) || IsRawImageAccess(opcode(inst))) {
         return EmitImageAccess(inst);
     }
 
-    if (IsAtomicOp(inst.opcode())) {
+    if (IsAtomicOp(opcode(inst))) {
         return EmitAtomicOp(inst);
     }
 
-    switch (inst.opcode()) {
-        case SpvOpNop:
+    switch (opcode(inst)) {
+        case spv::Op::OpNop:
             return true;
 
-        case SpvOpStore: {
+        case spv::Op::OpStore: {
             auto ptr_id = inst.GetSingleWordInOperand(0);
             const auto value_id = inst.GetSingleWordInOperand(1);
 
@@ -3633,7 +3708,7 @@
             return success();
         }
 
-        case SpvOpLoad: {
+        case spv::Op::OpLoad: {
             // Memory accesses must be issued in SPIR-V program order.
             // So represent a load by a new const definition.
             const auto ptr_id = inst.GetSingleWordInOperand(0);
@@ -3684,7 +3759,7 @@
             return EmitConstDefOrWriteToHoistedVar(inst, expr);
         }
 
-        case SpvOpCopyMemory: {
+        case spv::Op::OpCopyMemory: {
             // Generate an assignment.
             auto lhs = MakeOperand(inst, 0);
             auto rhs = MakeOperand(inst, 1);
@@ -3713,7 +3788,7 @@
             return success();
         }
 
-        case SpvOpCopyObject: {
+        case spv::Op::OpCopyObject: {
             // Arguably, OpCopyObject is purely combinatorial. On the other hand,
             // it exists to make a new name for something. So we choose to make
             // a new named constant definition.
@@ -3732,39 +3807,39 @@
             return EmitConstDefOrWriteToHoistedVar(inst, expr);
         }
 
-        case SpvOpPhi: {
+        case spv::Op::OpPhi: {
             // The value will be in scope, available for reading from the phi ID.
             return true;
         }
 
-        case SpvOpOuterProduct:
+        case spv::Op::OpOuterProduct:
             // Synthesize an outer product expression in its own statement.
             return EmitConstDefOrWriteToHoistedVar(inst, MakeOuterProduct(inst));
 
-        case SpvOpVectorInsertDynamic:
+        case spv::Op::OpVectorInsertDynamic:
             // Synthesize a vector insertion in its own statements.
             return MakeVectorInsertDynamic(inst);
 
-        case SpvOpCompositeInsert:
+        case spv::Op::OpCompositeInsert:
             // Synthesize a composite insertion in its own statements.
             return MakeCompositeInsert(inst);
 
-        case SpvOpFunctionCall:
+        case spv::Op::OpFunctionCall:
             return EmitFunctionCall(inst);
 
-        case SpvOpControlBarrier:
+        case spv::Op::OpControlBarrier:
             return EmitControlBarrier(inst);
 
-        case SpvOpExtInst:
+        case spv::Op::OpExtInst:
             if (parser_impl_.IsIgnoredExtendedInstruction(inst)) {
                 return true;
             }
             break;
 
-        case SpvOpIAddCarry:
-        case SpvOpISubBorrow:
-        case SpvOpUMulExtended:
-        case SpvOpSMulExtended:
+        case spv::Op::OpIAddCarry:
+        case spv::Op::OpISubBorrow:
+        case spv::Op::OpUMulExtended:
+        case spv::Op::OpSMulExtended:
             return Fail() << "extended arithmetic is not finalized for WGSL: "
                              "https://github.com/gpuweb/gpuweb/issues/1565: "
                           << inst.PrettyPrint();
@@ -3772,7 +3847,7 @@
         default:
             break;
     }
-    return Fail() << "unhandled instruction with opcode " << inst.opcode() << ": "
+    return Fail() << "unhandled instruction with opcode " << uint32_t(opcode(inst)) << ": "
                   << inst.PrettyPrint();
 }
 
@@ -3791,7 +3866,7 @@
         return {};
     }
 
-    const auto opcode = inst.opcode();
+    const auto op = opcode(inst);
 
     const Type* ast_type = nullptr;
     if (inst.type_id()) {
@@ -3802,7 +3877,7 @@
         }
     }
 
-    auto binary_op = ConvertBinaryOp(opcode);
+    auto binary_op = ConvertBinaryOp(op);
     if (binary_op != ast::BinaryOp::kNone) {
         auto arg0 = MakeOperand(inst, 0);
         auto arg1 =
@@ -3817,14 +3892,14 @@
     }
 
     auto unary_op = ast::UnaryOp::kNegation;
-    if (GetUnaryOp(opcode, &unary_op)) {
+    if (GetUnaryOp(op, &unary_op)) {
         auto arg0 = MakeOperand(inst, 0);
         auto* unary_expr = create<ast::UnaryOpExpression>(Source{}, unary_op, arg0.expr);
         TypedExpression result{ast_type, unary_expr};
         return parser_impl_.RectifyForcedResultType(result, inst, arg0.type);
     }
 
-    const char* unary_builtin_name = GetUnaryBuiltInFunctionName(opcode);
+    const char* unary_builtin_name = GetUnaryBuiltInFunctionName(op);
     if (unary_builtin_name != nullptr) {
         ExpressionList params;
         params.Push(MakeOperand(inst, 0).expr);
@@ -3835,40 +3910,40 @@
                               std::move(params))};
     }
 
-    const auto builtin = GetBuiltin(opcode);
+    const auto builtin = GetBuiltin(op);
     if (builtin != sem::BuiltinType::kNone) {
         return MakeBuiltinCall(inst);
     }
 
-    if (opcode == SpvOpFMod) {
+    if (op == spv::Op::OpFMod) {
         return MakeFMod(inst);
     }
 
-    if (opcode == SpvOpAccessChain || opcode == SpvOpInBoundsAccessChain) {
+    if (op == spv::Op::OpAccessChain || op == spv::Op::OpInBoundsAccessChain) {
         return MakeAccessChain(inst);
     }
 
-    if (opcode == SpvOpBitcast) {
+    if (op == spv::Op::OpBitcast) {
         return {ast_type, create<ast::BitcastExpression>(Source{}, ast_type->Build(builder_),
                                                          MakeOperand(inst, 0).expr)};
     }
 
-    if (opcode == SpvOpShiftLeftLogical || opcode == SpvOpShiftRightLogical ||
-        opcode == SpvOpShiftRightArithmetic) {
+    if (op == spv::Op::OpShiftLeftLogical || op == spv::Op::OpShiftRightLogical ||
+        op == spv::Op::OpShiftRightArithmetic) {
         auto arg0 = MakeOperand(inst, 0);
         // The second operand must be unsigned. It's ok to wrap the shift amount
         // since the shift is modulo the bit width of the first operand.
         auto arg1 = parser_impl_.AsUnsigned(MakeOperand(inst, 1));
 
-        switch (opcode) {
-            case SpvOpShiftLeftLogical:
+        switch (op) {
+            case spv::Op::OpShiftLeftLogical:
                 binary_op = ast::BinaryOp::kShiftLeft;
                 break;
-            case SpvOpShiftRightLogical:
+            case spv::Op::OpShiftRightLogical:
                 arg0 = parser_impl_.AsUnsigned(arg0);
                 binary_op = ast::BinaryOp::kShiftRight;
                 break;
-            case SpvOpShiftRightArithmetic:
+            case spv::Op::OpShiftRightArithmetic:
                 arg0 = parser_impl_.AsSigned(arg0);
                 binary_op = ast::BinaryOp::kShiftRight;
                 break;
@@ -3880,7 +3955,7 @@
         return parser_impl_.RectifyForcedResultType(result, inst, arg0.type);
     }
 
-    auto negated_op = NegatedFloatCompare(opcode);
+    auto negated_op = NegatedFloatCompare(op);
     if (negated_op != ast::BinaryOp::kNone) {
         auto arg0 = MakeOperand(inst, 0);
         auto arg1 = MakeOperand(inst, 1);
@@ -3891,7 +3966,7 @@
         return {ast_type, negated_expr};
     }
 
-    if (opcode == SpvOpExtInst) {
+    if (op == spv::Op::OpExtInst) {
         if (parser_impl_.IsIgnoredExtendedInstruction(inst)) {
             // Ignore it but don't error out.
             return {};
@@ -3904,7 +3979,7 @@
         return EmitGlslStd450ExtInst(inst);
     }
 
-    if (opcode == SpvOpCompositeConstruct) {
+    if (op == spv::Op::OpCompositeConstruct) {
         ExpressionList operands;
         for (uint32_t iarg = 0; iarg < inst.NumInOperands(); ++iarg) {
             operands.Push(MakeOperand(inst, iarg).expr);
@@ -3913,34 +3988,34 @@
                 builder_.Construct(Source{}, ast_type->Build(builder_), std::move(operands))};
     }
 
-    if (opcode == SpvOpCompositeExtract) {
+    if (op == spv::Op::OpCompositeExtract) {
         return MakeCompositeExtract(inst);
     }
 
-    if (opcode == SpvOpVectorShuffle) {
+    if (op == spv::Op::OpVectorShuffle) {
         return MakeVectorShuffle(inst);
     }
 
-    if (opcode == SpvOpVectorExtractDynamic) {
+    if (op == spv::Op::OpVectorExtractDynamic) {
         return {ast_type, create<ast::IndexAccessorExpression>(Source{}, MakeOperand(inst, 0).expr,
                                                                MakeOperand(inst, 1).expr)};
     }
 
-    if (opcode == SpvOpConvertSToF || opcode == SpvOpConvertUToF || opcode == SpvOpConvertFToS ||
-        opcode == SpvOpConvertFToU) {
+    if (op == spv::Op::OpConvertSToF || op == spv::Op::OpConvertUToF ||
+        op == spv::Op::OpConvertFToS || op == spv::Op::OpConvertFToU) {
         return MakeNumericConversion(inst);
     }
 
-    if (opcode == SpvOpUndef) {
+    if (op == spv::Op::OpUndef) {
         // Replace undef with the null value.
         return parser_impl_.MakeNullExpression(ast_type);
     }
 
-    if (opcode == SpvOpSelect) {
+    if (op == spv::Op::OpSelect) {
         return MakeSimpleSelect(inst);
     }
 
-    if (opcode == SpvOpArrayLength) {
+    if (op == spv::Op::OpArrayLength) {
         return MakeArrayLength(inst);
     }
 
@@ -4424,12 +4499,12 @@
     const auto constants = constant_mgr_->GetOperandConstants(&inst);
 
     const auto* ptr_type_inst = def_use_mgr_->GetDef(ptr_ty_id);
-    if (!ptr_type_inst || (ptr_type_inst->opcode() != SpvOpTypePointer)) {
+    if (!ptr_type_inst || (opcode(ptr_type_inst) != spv::Op::OpTypePointer)) {
         Fail() << "Access chain %" << inst.result_id() << " base pointer is not of pointer type";
         return {};
     }
-    SpvStorageClass address_space =
-        static_cast<SpvStorageClass>(ptr_type_inst->GetSingleWordInOperand(0));
+    spv::StorageClass address_space =
+        static_cast<spv::StorageClass>(ptr_type_inst->GetSingleWordInOperand(0));
     uint32_t pointee_type_id = ptr_type_inst->GetSingleWordInOperand(1);
 
     // Build up a nested expression for the access chain by walking down the type
@@ -4446,8 +4521,8 @@
                    << (index - first_index) << " indices: " << inst.PrettyPrint();
             return {};
         }
-        switch (pointee_type_inst->opcode()) {
-            case SpvOpTypeVector:
+        switch (opcode(pointee_type_inst)) {
+            case spv::Op::OpTypeVector:
                 if (index_const) {
                     // Try generating a MemberAccessor expression
                     const auto num_elems = pointee_type_inst->GetSingleWordInOperand(1);
@@ -4473,24 +4548,24 @@
                 // Sink pointers to vector components.
                 sink_pointer = true;
                 break;
-            case SpvOpTypeMatrix:
+            case spv::Op::OpTypeMatrix:
                 // Use array syntax.
                 next_expr = create<ast::IndexAccessorExpression>(Source{}, current_expr.expr,
                                                                  MakeOperand(inst, index).expr);
                 // All matrix components are the same type.
                 pointee_type_id = pointee_type_inst->GetSingleWordInOperand(0);
                 break;
-            case SpvOpTypeArray:
+            case spv::Op::OpTypeArray:
                 next_expr = create<ast::IndexAccessorExpression>(Source{}, current_expr.expr,
                                                                  MakeOperand(inst, index).expr);
                 pointee_type_id = pointee_type_inst->GetSingleWordInOperand(0);
                 break;
-            case SpvOpTypeRuntimeArray:
+            case spv::Op::OpTypeRuntimeArray:
                 next_expr = create<ast::IndexAccessorExpression>(Source{}, current_expr.expr,
                                                                  MakeOperand(inst, index).expr);
                 pointee_type_id = pointee_type_inst->GetSingleWordInOperand(0);
                 break;
-            case SpvOpTypeStruct: {
+            case spv::Op::OpTypeStruct: {
                 if (!index_const) {
                     Fail() << "Access chain %" << inst.result_id() << " index %"
                            << inst.GetSingleWordInOperand(index)
@@ -4519,7 +4594,9 @@
                        << ": " << pointee_type_inst->PrettyPrint();
                 return {};
         }
-        const auto pointer_type_id = type_mgr_->FindPointerToType(pointee_type_id, address_space);
+        const auto pointer_type_id = three_sided_patch_function_cc::FindPointerToType(
+            type_mgr_, &spvtools::opt::analysis::TypeManager::FindPointerToType, pointee_type_id,
+            address_space);
         auto* type = parser_impl_.ConvertType(pointer_type_id, PtrAs::Ref);
         TINT_ASSERT(Reader, type && type->Is<Reference>());
         current_expr = TypedExpression{type, next_expr};
@@ -4594,11 +4671,11 @@
             return {};
         }
         const char* operation_name = nullptr;
-        switch (inst.opcode()) {
-            case SpvOpCompositeExtract:
+        switch (opcode(inst)) {
+            case spv::Op::OpCompositeExtract:
                 operation_name = "OpCompositeExtract";
                 break;
-            case SpvOpCompositeInsert:
+            case spv::Op::OpCompositeInsert:
                 operation_name = "OpCompositeInsert";
                 break;
             default:
@@ -4606,8 +4683,8 @@
                 return {};
         }
         const ast::Expression* next_expr = nullptr;
-        switch (current_type_inst->opcode()) {
-            case SpvOpTypeVector: {
+        switch (opcode(current_type_inst)) {
+            case spv::Op::OpTypeVector: {
                 // Try generating a MemberAccessor expression. That result in something
                 // like  "foo.z", which is more idiomatic than "foo[2]".
                 const auto num_elems = current_type_inst->GetSingleWordInOperand(1);
@@ -4628,7 +4705,7 @@
                 current_type_id = current_type_inst->GetSingleWordInOperand(0);
                 break;
             }
-            case SpvOpTypeMatrix: {
+            case spv::Op::OpTypeMatrix: {
                 // Check bounds
                 const auto num_elems = current_type_inst->GetSingleWordInOperand(1);
                 if (num_elems <= index_val) {
@@ -4648,7 +4725,7 @@
                 current_type_id = current_type_inst->GetSingleWordInOperand(0);
                 break;
             }
-            case SpvOpTypeArray:
+            case spv::Op::OpTypeArray:
                 // The array size could be a spec constant, and so it's not always
                 // statically checkable.  Instead, rely on a runtime index clamp
                 // or runtime check to keep this safe.
@@ -4656,11 +4733,11 @@
                                                                  make_index(index_val));
                 current_type_id = current_type_inst->GetSingleWordInOperand(0);
                 break;
-            case SpvOpTypeRuntimeArray:
+            case spv::Op::OpTypeRuntimeArray:
                 Fail() << "can't do " << operation_name
                        << " on a runtime array: " << inst.PrettyPrint();
                 return {};
-            case SpvOpTypeStruct: {
+            case spv::Op::OpTypeStruct: {
                 const auto num_members = current_type_inst->NumInOperands();
                 if (num_members <= index_val) {
                     Fail() << operation_name << " %" << inst.result_id() << " index value "
@@ -4751,14 +4828,14 @@
         auto& def = def_info_[id];
         // Builtins are always defined outside the function.
         switch (builtin) {
-            case SpvBuiltInPointSize:
+            case spv::BuiltIn::PointSize:
                 def->skip = SkipReason::kPointSizeBuiltinPointer;
                 break;
-            case SpvBuiltInSampleMask: {
+            case spv::BuiltIn::SampleMask: {
                 // Distinguish between input and output variable.
                 const auto storage_class =
-                    static_cast<SpvStorageClass>(var->GetSingleWordInOperand(0));
-                if (storage_class == SpvStorageClassInput) {
+                    static_cast<spv::StorageClass>(var->GetSingleWordInOperand(0));
+                if (storage_class == spv::StorageClass::Input) {
                     sample_mask_in_id = id;
                     def->skip = SkipReason::kSampleMaskInBuiltinPointer;
                 } else {
@@ -4767,14 +4844,14 @@
                 }
                 break;
             }
-            case SpvBuiltInSampleId:
-            case SpvBuiltInInstanceIndex:
-            case SpvBuiltInVertexIndex:
-            case SpvBuiltInLocalInvocationIndex:
-            case SpvBuiltInLocalInvocationId:
-            case SpvBuiltInGlobalInvocationId:
-            case SpvBuiltInWorkgroupId:
-            case SpvBuiltInNumWorkgroups:
+            case spv::BuiltIn::SampleId:
+            case spv::BuiltIn::InstanceIndex:
+            case spv::BuiltIn::VertexIndex:
+            case spv::BuiltIn::LocalInvocationIndex:
+            case spv::BuiltIn::LocalInvocationId:
+            case spv::BuiltIn::GlobalInvocationId:
+            case spv::BuiltIn::WorkgroupId:
+            case spv::BuiltIn::NumWorkgroups:
                 break;
             default:
                 return Fail() << "unrecognized special builtin: " << int(builtin);
@@ -4791,7 +4868,7 @@
         const auto block_pos = block_info->pos;
         for (const auto& inst : *(block_info->basic_block)) {
             const auto result_id = inst.result_id();
-            if ((result_id == 0) || inst.opcode() == SpvOpLabel) {
+            if ((result_id == 0) || opcode(inst) == spv::Op::OpLabel) {
                 continue;
             }
             def_info_[result_id] = std::make_unique<DefInfo>(index, inst, block_pos);
@@ -4807,15 +4884,15 @@
                 // pointer definitions are SSA values, and their definitions must be
                 // visited before their uses.
                 if (type->AsPointer()) {
-                    switch (inst.opcode()) {
-                        case SpvOpUndef:
+                    switch (opcode(inst)) {
+                        case spv::Op::OpUndef:
                             return Fail() << "undef pointer is not valid: " << inst.PrettyPrint();
-                        case SpvOpVariable:
+                        case spv::Op::OpVariable:
                             info->pointer = GetPointerInfo(result_id);
                             break;
-                        case SpvOpAccessChain:
-                        case SpvOpInBoundsAccessChain:
-                        case SpvOpCopyObject:
+                        case spv::Op::OpAccessChain:
+                        case spv::Op::OpInBoundsAccessChain:
+                        case spv::Op::OpCopyObject:
                             // Inherit from the first operand. We need this so we can pick up
                             // a remapped storage buffer.
                             info->pointer = GetPointerInfo(inst.GetSingleWordInOperand(0));
@@ -4846,8 +4923,8 @@
         [&](const spvtools::opt::Instruction& inst) -> DefInfo::Pointer {
         // WGSL root identifiers (or SPIR-V "memory object declarations") are
         // either variables or function parameters.
-        switch (inst.opcode()) {
-            case SpvOpVariable: {
+        switch (opcode(inst)) {
+            case spv::Op::OpVariable: {
                 if (const auto* module_var = parser_impl_.GetModuleVariable(id)) {
                     return DefInfo::Pointer{module_var->declared_address_space,
                                             module_var->declared_access};
@@ -4856,7 +4933,7 @@
                 // access mode.
                 return DefInfo::Pointer{ast::AddressSpace::kFunction, ast::Access::kUndefined};
             }
-            case SpvOpFunctionParameter: {
+            case spv::Op::OpFunctionParameter: {
                 const auto* type = As<Pointer>(parser_impl_.ConvertType(inst.type_id()));
                 // For access mode, kUndefined is ok for now, since the
                 // only non-default access mode on a pointer would be for a storage
@@ -4879,7 +4956,7 @@
     auto where = def_info_.find(id);
     if (where != def_info_.end()) {
         const auto& info = where->second;
-        if (info->inst.opcode() == SpvOpVariable) {
+        if (opcode(info->inst) == spv::Op::OpVariable) {
             // Ignore the cache in this case and compute it from scratch.
             // That's because for a function-scope OpVariable is a
             // locally-defined value.  So its cache entry has been created
@@ -4925,8 +5002,8 @@
     };
     for (auto& id_def_info_pair : def_info_) {
         const auto& inst = id_def_info_pair.second->inst;
-        const auto opcode = inst.opcode();
-        if ((opcode == SpvOpVectorShuffle) || (opcode == SpvOpOuterProduct)) {
+        const auto op = opcode(inst);
+        if ((op == spv::Op::OpVectorShuffle) || (op == spv::Op::OpOuterProduct)) {
             // We might access the vector operands multiple times. Make sure they
             // are evaluated only once.
             require_named_const_def(inst, 0);
@@ -4979,7 +5056,7 @@
         const auto* block_info = GetBlockInfo(block_id);
         for (const auto& inst : *(block_info->basic_block)) {
             // Update bookkeeping for locally-defined IDs used by this instruction.
-            if (inst.opcode() == SpvOpPhi) {
+            if (opcode(inst) == spv::Op::OpPhi) {
                 // For an OpPhi defining value P, an incoming value V from parent block B is
                 // counted as being "used" at block B, not at the block containing the Phi.
                 // That's because we will create a variable PHI_P to hold the phi value, and
@@ -5128,7 +5205,7 @@
 }
 
 TypedExpression FunctionEmitter::MakeNumericConversion(const spvtools::opt::Instruction& inst) {
-    const auto opcode = inst.opcode();
+    const auto op = opcode(inst);
     auto* requested_type = parser_impl_.ConvertType(inst.type_id());
     auto arg_expr = MakeOperand(inst, 0);
     if (!arg_expr) {
@@ -5137,7 +5214,7 @@
     arg_expr.type = arg_expr.type->UnwrapRef();
 
     const Type* expr_type = nullptr;
-    if ((opcode == SpvOpConvertSToF) || (opcode == SpvOpConvertUToF)) {
+    if ((op == spv::Op::OpConvertSToF) || (op == spv::Op::OpConvertUToF)) {
         if (arg_expr.type->IsIntegerScalarOrVector()) {
             expr_type = requested_type;
         } else {
@@ -5145,7 +5222,7 @@
                       "scalar or vector: "
                    << inst.PrettyPrint();
         }
-    } else if (inst.opcode() == SpvOpConvertFToU) {
+    } else if (op == spv::Op::OpConvertFToU) {
         if (arg_expr.type->IsFloatScalarOrVector()) {
             expr_type = parser_impl_.GetUnsignedIntMatchingShape(arg_expr.type);
         } else {
@@ -5153,7 +5230,7 @@
                       "point scalar or vector: "
                    << inst.PrettyPrint();
         }
-    } else if (inst.opcode() == SpvOpConvertFToS) {
+    } else if (op == spv::Op::OpConvertFToS) {
         if (arg_expr.type->IsFloatScalarOrVector()) {
             expr_type = parser_impl_.GetSignedIntMatchingShape(arg_expr.type);
         } else {
@@ -5228,28 +5305,28 @@
     uint32_t memory = operands[1];
     uint32_t semantics = operands[2];
 
-    if (execution != SpvScopeWorkgroup) {
+    if (execution != uint32_t(spv::Scope::Workgroup)) {
         return Fail() << "unsupported control barrier execution scope: "
                       << "expected Workgroup (2), got: " << execution;
     }
-    if (semantics & SpvMemorySemanticsAcquireReleaseMask) {
-        semantics &= ~static_cast<uint32_t>(SpvMemorySemanticsAcquireReleaseMask);
+    if (semantics & uint32_t(spv::MemorySemanticsMask::AcquireRelease)) {
+        semantics &= ~static_cast<uint32_t>(spv::MemorySemanticsMask::AcquireRelease);
     } else {
         return Fail() << "control barrier semantics requires acquire and release";
     }
-    if (semantics & SpvMemorySemanticsWorkgroupMemoryMask) {
-        if (memory != SpvScopeWorkgroup) {
+    if (semantics & uint32_t(spv::MemorySemanticsMask::WorkgroupMemory)) {
+        if (memory != uint32_t(spv::Scope::Workgroup)) {
             return Fail() << "workgroupBarrier requires workgroup memory scope";
         }
         AddStatement(create<ast::CallStatement>(builder_.Call("workgroupBarrier")));
-        semantics &= ~static_cast<uint32_t>(SpvMemorySemanticsWorkgroupMemoryMask);
+        semantics &= ~static_cast<uint32_t>(spv::MemorySemanticsMask::WorkgroupMemory);
     }
-    if (semantics & SpvMemorySemanticsUniformMemoryMask) {
-        if (memory != SpvScopeDevice) {
+    if (semantics & uint32_t(spv::MemorySemanticsMask::UniformMemory)) {
+        if (memory != uint32_t(spv::Scope::Device)) {
             return Fail() << "storageBarrier requires device memory scope";
         }
         AddStatement(create<ast::CallStatement>(builder_.Call("storageBarrier")));
-        semantics &= ~static_cast<uint32_t>(SpvMemorySemanticsUniformMemoryMask);
+        semantics &= ~static_cast<uint32_t>(spv::MemorySemanticsMask::UniformMemory);
     }
     if (semantics) {
         return Fail() << "unsupported control barrier semantics: " << semantics;
@@ -5258,7 +5335,7 @@
 }
 
 TypedExpression FunctionEmitter::MakeBuiltinCall(const spvtools::opt::Instruction& inst) {
-    const auto builtin = GetBuiltin(inst.opcode());
+    const auto builtin = GetBuiltin(opcode(inst));
     auto* name = sem::str(builtin);
     auto* ident = create<ast::IdentifierExpression>(Source{}, builder_.Symbols().Register(name));
 
@@ -5374,7 +5451,7 @@
 
 bool FunctionEmitter::EmitImageAccess(const spvtools::opt::Instruction& inst) {
     ExpressionList args;
-    const auto opcode = inst.opcode();
+    const auto op = opcode(inst);
 
     // Form the texture operand.
     const spvtools::opt::Instruction* image = GetImage(inst);
@@ -5384,7 +5461,7 @@
     args.Push(GetImageExpression(inst));
 
     // Form the sampler operand, if needed.
-    if (IsSampledImageAccess(opcode)) {
+    if (IsSampledImageAccess(op)) {
         // Form the sampler operand.
         if (auto* sampler = GetSamplerExpression(inst)) {
             args.Push(sampler);
@@ -5438,25 +5515,25 @@
     bool is_dref_sample = false;
     bool is_gather_or_dref_gather = false;
     bool is_non_dref_sample = false;
-    switch (opcode) {
-        case SpvOpImageSampleImplicitLod:
-        case SpvOpImageSampleExplicitLod:
-        case SpvOpImageSampleProjImplicitLod:
-        case SpvOpImageSampleProjExplicitLod:
+    switch (op) {
+        case spv::Op::OpImageSampleImplicitLod:
+        case spv::Op::OpImageSampleExplicitLod:
+        case spv::Op::OpImageSampleProjImplicitLod:
+        case spv::Op::OpImageSampleProjExplicitLod:
             is_non_dref_sample = true;
             builtin_name = "textureSample";
             break;
-        case SpvOpImageSampleDrefImplicitLod:
-        case SpvOpImageSampleDrefExplicitLod:
-        case SpvOpImageSampleProjDrefImplicitLod:
-        case SpvOpImageSampleProjDrefExplicitLod:
+        case spv::Op::OpImageSampleDrefImplicitLod:
+        case spv::Op::OpImageSampleDrefExplicitLod:
+        case spv::Op::OpImageSampleProjDrefImplicitLod:
+        case spv::Op::OpImageSampleProjDrefExplicitLod:
             is_dref_sample = true;
             builtin_name = "textureSampleCompare";
             if (!consume_dref()) {
                 return false;
             }
             break;
-        case SpvOpImageGather:
+        case spv::Op::OpImageGather:
             is_gather_or_dref_gather = true;
             builtin_name = "textureGather";
             if (!texture_type->Is<DepthTexture>()) {
@@ -5471,20 +5548,20 @@
             // Skip over the component operand, even for depth textures.
             arg_index++;
             break;
-        case SpvOpImageDrefGather:
+        case spv::Op::OpImageDrefGather:
             is_gather_or_dref_gather = true;
             builtin_name = "textureGatherCompare";
             if (!consume_dref()) {
                 return false;
             }
             break;
-        case SpvOpImageFetch:
-        case SpvOpImageRead:
+        case spv::Op::OpImageFetch:
+        case spv::Op::OpImageRead:
             // Read a single texel from a sampled or storage image.
             builtin_name = "textureLoad";
             use_level_of_detail_suffix = false;
             break;
-        case SpvOpImageWrite:
+        case spv::Op::OpImageWrite:
             builtin_name = "textureStore";
             use_level_of_detail_suffix = false;
             if (arg_index < num_args) {
@@ -5511,7 +5588,7 @@
         image_operands_mask = inst.GetSingleWordInOperand(arg_index);
         arg_index++;
     }
-    if (arg_index < num_args && (image_operands_mask & SpvImageOperandsBiasMask)) {
+    if (arg_index < num_args && (image_operands_mask & uint32_t(spv::ImageOperandsMask::Bias))) {
         if (is_dref_sample) {
             return Fail() << "WGSL does not support depth-reference sampling with "
                              "level-of-detail bias: "
@@ -5524,10 +5601,10 @@
         }
         builtin_name += "Bias";
         args.Push(MakeOperand(inst, arg_index).expr);
-        image_operands_mask ^= SpvImageOperandsBiasMask;
+        image_operands_mask ^= uint32_t(spv::ImageOperandsMask::Bias);
         arg_index++;
     }
-    if (arg_index < num_args && (image_operands_mask & SpvImageOperandsLodMask)) {
+    if (arg_index < num_args && (image_operands_mask & uint32_t(spv::ImageOperandsMask::Lod))) {
         if (use_level_of_detail_suffix) {
             builtin_name += "Level";
         }
@@ -5553,15 +5630,16 @@
             args.Push(lod.expr);
         }
 
-        image_operands_mask ^= SpvImageOperandsLodMask;
+        image_operands_mask ^= uint32_t(spv::ImageOperandsMask::Lod);
         arg_index++;
-    } else if ((opcode == SpvOpImageFetch || opcode == SpvOpImageRead) &&
+    } else if ((op == spv::Op::OpImageFetch || op == spv::Op::OpImageRead) &&
                !texture_type->IsAnyOf<DepthMultisampledTexture, MultisampledTexture>()) {
         // textureLoad requires an explicit level-of-detail parameter for
         // non-multisampled texture types.
         args.Push(parser_impl_.MakeNullValue(ty_.I32()));
     }
-    if (arg_index + 1 < num_args && (image_operands_mask & SpvImageOperandsGradMask)) {
+    if (arg_index + 1 < num_args &&
+        (image_operands_mask & uint32_t(spv::ImageOperandsMask::Grad))) {
         if (is_dref_sample) {
             return Fail() << "WGSL does not support depth-reference sampling with "
                              "explicit gradient: "
@@ -5575,11 +5653,12 @@
         builtin_name += "Grad";
         args.Push(MakeOperand(inst, arg_index).expr);
         args.Push(MakeOperand(inst, arg_index + 1).expr);
-        image_operands_mask ^= SpvImageOperandsGradMask;
+        image_operands_mask ^= uint32_t(spv::ImageOperandsMask::Grad);
         arg_index += 2;
     }
-    if (arg_index < num_args && (image_operands_mask & SpvImageOperandsConstOffsetMask)) {
-        if (!IsImageSamplingOrGatherOrDrefGather(opcode)) {
+    if (arg_index < num_args &&
+        (image_operands_mask & uint32_t(spv::ImageOperandsMask::ConstOffset))) {
+        if (!IsImageSamplingOrGatherOrDrefGather(op)) {
             return Fail() << "ConstOffset is only permitted for sampling, gather, or "
                              "depth-reference gather operations: "
                           << inst.PrettyPrint();
@@ -5596,13 +5675,13 @@
         }
 
         args.Push(ToSignedIfUnsigned(MakeOperand(inst, arg_index)).expr);
-        image_operands_mask ^= SpvImageOperandsConstOffsetMask;
+        image_operands_mask ^= uint32_t(spv::ImageOperandsMask::ConstOffset);
         arg_index++;
     }
-    if (arg_index < num_args && (image_operands_mask & SpvImageOperandsSampleMask)) {
+    if (arg_index < num_args && (image_operands_mask & uint32_t(spv::ImageOperandsMask::Sample))) {
         // TODO(dneto): only permitted with ImageFetch
         args.Push(ToI32(MakeOperand(inst, arg_index)).expr);
-        image_operands_mask ^= SpvImageOperandsSampleMask;
+        image_operands_mask ^= uint32_t(spv::ImageOperandsMask::Sample);
         arg_index++;
     }
     if (image_operands_mask) {
@@ -5642,7 +5721,7 @@
         // Construct a 4-element vector with the result from the builtin in the
         // first component.
         if (texture_type->IsAnyOf<DepthTexture, DepthMultisampledTexture>()) {
-            if (is_non_dref_sample || (opcode == SpvOpImageFetch)) {
+            if (is_non_dref_sample || (op == spv::Op::OpImageFetch)) {
                 value = builder_.Construct(Source{},
                                            result_type->Build(builder_),  // a vec4
                                            utils::Vector{
@@ -5658,7 +5737,7 @@
         // result type. Compare the SPIR-V image's sampled component type with the
         // component of the result type of the SPIR-V instruction.
         auto* spirv_image_type = parser_impl_.GetSpirvTypeForHandleMemoryObjectDeclaration(*image);
-        if (!spirv_image_type || (spirv_image_type->opcode() != SpvOpTypeImage)) {
+        if (!spirv_image_type || (opcode(spirv_image_type) != spv::Op::OpTypeImage)) {
             return Fail() << "invalid image type for image memory object declaration "
                           << image->PrettyPrint();
         }
@@ -5670,7 +5749,7 @@
             value =
                 create<ast::BitcastExpression>(Source{}, result_type->Build(builder_), call_expr);
         }
-        if (!expected_component_type->Is<F32>() && IsSampledImageAccess(opcode)) {
+        if (!expected_component_type->Is<F32>() && IsSampledImageAccess(op)) {
             // WGSL permits sampled image access only on float textures.
             // Reject this case in the SPIR-V reader, at least until SPIR-V validation
             // catches up with this rule and can reject it earlier in the workflow.
@@ -5697,10 +5776,10 @@
         return false;
     }
 
-    const auto opcode = inst.opcode();
-    switch (opcode) {
-        case SpvOpImageQuerySize:
-        case SpvOpImageQuerySizeLod: {
+    const auto op = opcode(inst);
+    switch (op) {
+        case spv::Op::OpImageQuerySize:
+        case spv::Op::OpImageQuerySizeLod: {
             ExpressionList exprs;
             // Invoke textureDimensions.
             // If the texture is arrayed, combine with the result from
@@ -5708,7 +5787,7 @@
             auto* dims_ident = create<ast::IdentifierExpression>(
                 Source{}, builder_.Symbols().Register("textureDimensions"));
             ExpressionList dims_args{GetImageExpression(inst)};
-            if (opcode == SpvOpImageQuerySizeLod) {
+            if (op == spv::Op::OpImageQuerySizeLod) {
                 dims_args.Push(MakeOperand(inst, 1).expr);
             }
             const ast::Expression* dims_call =
@@ -5745,13 +5824,13 @@
 
             return EmitConstDefOrWriteToHoistedVar(inst, expr);
         }
-        case SpvOpImageQueryLod:
+        case spv::Op::OpImageQueryLod:
             return Fail() << "WGSL does not support querying the level of detail of an image: "
                           << inst.PrettyPrint();
-        case SpvOpImageQueryLevels:
-        case SpvOpImageQuerySamples: {
+        case spv::Op::OpImageQueryLevels:
+        case spv::Op::OpImageQuerySamples: {
             const auto* name =
-                (opcode == SpvOpImageQueryLevels) ? "textureNumLevels" : "textureNumSamples";
+                (op == spv::Op::OpImageQueryLevels) ? "textureNumLevels" : "textureNumSamples";
             auto* levels_ident =
                 create<ast::IdentifierExpression>(Source{}, builder_.Symbols().Register(name));
             const ast::Expression* ast_expr = create<ast::CallExpression>(
@@ -5834,46 +5913,46 @@
         return {};
     };
 
-    switch (inst.opcode()) {
-        case SpvOpAtomicLoad:
+    switch (opcode(inst)) {
+        case spv::Op::OpAtomicLoad:
             return emit_atomic(sem::BuiltinType::kAtomicLoad, {oper(/*ptr*/ 0)});
-        case SpvOpAtomicStore:
+        case spv::Op::OpAtomicStore:
             return emit_atomic(sem::BuiltinType::kAtomicStore,
                                {oper(/*ptr*/ 0), oper(/*value*/ 3)});
-        case SpvOpAtomicExchange:
+        case spv::Op::OpAtomicExchange:
             return emit_atomic(sem::BuiltinType::kAtomicExchange,
                                {oper(/*ptr*/ 0), oper(/*value*/ 3)});
-        case SpvOpAtomicCompareExchange:
-        case SpvOpAtomicCompareExchangeWeak:
+        case spv::Op::OpAtomicCompareExchange:
+        case spv::Op::OpAtomicCompareExchangeWeak:
             return emit_atomic(sem::BuiltinType::kAtomicCompareExchangeWeak,
                                {oper(/*ptr*/ 0), /*value*/ oper(5), /*comparator*/ oper(4)});
-        case SpvOpAtomicIIncrement:
+        case spv::Op::OpAtomicIIncrement:
             return emit_atomic(sem::BuiltinType::kAtomicAdd, {oper(/*ptr*/ 0), lit(1)});
-        case SpvOpAtomicIDecrement:
+        case spv::Op::OpAtomicIDecrement:
             return emit_atomic(sem::BuiltinType::kAtomicSub, {oper(/*ptr*/ 0), lit(1)});
-        case SpvOpAtomicIAdd:
+        case spv::Op::OpAtomicIAdd:
             return emit_atomic(sem::BuiltinType::kAtomicAdd, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
-        case SpvOpAtomicISub:
+        case spv::Op::OpAtomicISub:
             return emit_atomic(sem::BuiltinType::kAtomicSub, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
-        case SpvOpAtomicSMin:
+        case spv::Op::OpAtomicSMin:
             return emit_atomic(sem::BuiltinType::kAtomicMin, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
-        case SpvOpAtomicUMin:
+        case spv::Op::OpAtomicUMin:
             return emit_atomic(sem::BuiltinType::kAtomicMin, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
-        case SpvOpAtomicSMax:
+        case spv::Op::OpAtomicSMax:
             return emit_atomic(sem::BuiltinType::kAtomicMax, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
-        case SpvOpAtomicUMax:
+        case spv::Op::OpAtomicUMax:
             return emit_atomic(sem::BuiltinType::kAtomicMax, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
-        case SpvOpAtomicAnd:
+        case spv::Op::OpAtomicAnd:
             return emit_atomic(sem::BuiltinType::kAtomicAnd, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
-        case SpvOpAtomicOr:
+        case spv::Op::OpAtomicOr:
             return emit_atomic(sem::BuiltinType::kAtomicOr, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
-        case SpvOpAtomicXor:
+        case spv::Op::OpAtomicXor:
             return emit_atomic(sem::BuiltinType::kAtomicXor, {oper(/*ptr*/ 0), oper(/*value*/ 3)});
-        case SpvOpAtomicFlagTestAndSet:
-        case SpvOpAtomicFlagClear:
-        case SpvOpAtomicFMinEXT:
-        case SpvOpAtomicFMaxEXT:
-        case SpvOpAtomicFAddEXT:
+        case spv::Op::OpAtomicFlagTestAndSet:
+        case spv::Op::OpAtomicFlagClear:
+        case spv::Op::OpAtomicFMinEXT:
+        case spv::Op::OpAtomicFMaxEXT:
+        case spv::Op::OpAtomicFAddEXT:
             return Fail() << "unsupported atomic op: " << inst.PrettyPrint();
 
         default:
@@ -5926,11 +6005,11 @@
                << " prompted by " << inst.PrettyPrint();
     }
     bool is_proj = false;
-    switch (inst.opcode()) {
-        case SpvOpImageSampleProjImplicitLod:
-        case SpvOpImageSampleProjExplicitLod:
-        case SpvOpImageSampleProjDrefImplicitLod:
-        case SpvOpImageSampleProjDrefExplicitLod:
+    switch (opcode(inst)) {
+        case spv::Op::OpImageSampleProjImplicitLod:
+        case spv::Op::OpImageSampleProjExplicitLod:
+        case spv::Op::OpImageSampleProjDrefImplicitLod:
+        case spv::Op::OpImageSampleProjDrefExplicitLod:
             is_proj = true;
             break;
         default:
diff --git a/src/tint/reader/spirv/parser_impl.cc b/src/tint/reader/spirv/parser_impl.cc
index 13b4132..f055f4c 100644
--- a/src/tint/reader/spirv/parser_impl.cc
+++ b/src/tint/reader/spirv/parser_impl.cc
@@ -34,6 +34,65 @@
 
 namespace tint::reader::spirv {
 
+namespace three_sided_patch {
+// This machinery is only used while SPIRV-Tools is in transition before it fully
+// uses the C++11 header spirv.hpp11
+
+/// Typedef for pointer to member function while the API call uses
+/// SpvStorageClass for its second argument.
+typedef uint32_t (
+    spvtools::opt::analysis::TypeManager::*PointerFinderSpvStorageClass)(uint32_t, SpvStorageClass);
+/// Typedef for pointer to member function while the API call uses
+/// spv::StorageClass for its second argument.
+typedef uint32_t (spvtools::opt::analysis::TypeManager::*PointerFinderSpvStorageClassCpp11)(
+    uint32_t,
+    spv::StorageClass);
+
+/// @param type_manager the SPIRV-Tools optimizer's type manager
+/// @param finder a pointer to member function in the type manager that does the
+/// actual lookup
+/// @param pointee_type_id the ID of the pointee type
+/// @param sc the storage class.  SC can be SpvStorageClass or spv::StorageClass
+/// @returns the ID for a SPIR-V pointer to pointee_type_id in storage class sc
+template <typename FinderType, typename SC>
+uint32_t FindPointerToType(spvtools::opt::analysis::TypeManager* type_manager,
+                           FinderType finder,
+                           uint32_t pointee_type_id,
+                           SC sc);
+
+template <>
+uint32_t FindPointerToType(spvtools::opt::analysis::TypeManager* type_mgr,
+                           PointerFinderSpvStorageClass finder,
+                           uint32_t pointee_type_id,
+                           SpvStorageClass sc) {
+    return (type_mgr->*finder)(pointee_type_id, sc);
+}
+
+template <>
+uint32_t FindPointerToType(spvtools::opt::analysis::TypeManager* type_mgr,
+                           PointerFinderSpvStorageClass finder,
+                           uint32_t pointee_type_id,
+                           spv::StorageClass sc) {
+    return (type_mgr->*finder)(pointee_type_id, static_cast<SpvStorageClass>(sc));
+}
+
+template <>
+uint32_t FindPointerToType(spvtools::opt::analysis::TypeManager* type_mgr,
+                           PointerFinderSpvStorageClassCpp11 finder,
+                           uint32_t pointee_type_id,
+                           SpvStorageClass sc) {
+    return (type_mgr->*finder)(pointee_type_id, static_cast<spv::StorageClass>(sc));
+}
+
+template <>
+uint32_t FindPointerToType(spvtools::opt::analysis::TypeManager* type_mgr,
+                           PointerFinderSpvStorageClassCpp11 finder,
+                           uint32_t pointee_type_id,
+                           spv::StorageClass sc) {
+    return (type_mgr->*finder)(pointee_type_id, sc);
+}
+}  // namespace three_sided_patch
+
 namespace {
 
 // Input SPIR-V needs only to conform to Vulkan 1.1 requirements.
@@ -42,6 +101,17 @@
 // will satisfy SPV_ENV_WEBGPU_0 validation.
 const spv_target_env kInputEnv = SPV_ENV_VULKAN_1_1;
 
+/// @param inst a SPIR-V instruction
+/// @returns Returns the opcode for an instruciton
+inline spv::Op opcode(const spvtools::opt::Instruction& inst) {
+    return static_cast<spv::Op>(inst.opcode());
+}
+/// @param inst a SPIR-V instruction pointer
+/// @returns Returns the opcode for an instruciton
+inline spv::Op opcode(const spvtools::opt::Instruction* inst) {
+    return static_cast<spv::Op>(inst->opcode());
+}
+
 // A FunctionTraverser is used to compute an ordering of functions in the
 // module such that callees precede callers.
 class FunctionTraverser {
@@ -70,7 +140,7 @@
         visited_.insert(&f);
         for (const auto& bb : f) {
             for (const auto& inst : bb) {
-                if (inst.opcode() != SpvOpFunctionCall) {
+                if (opcode(inst) != spv::Op::OpFunctionCall) {
                     continue;
                 }
                 const auto* callee = id_to_func_[inst.GetSingleWordInOperand(0)];
@@ -89,17 +159,17 @@
 };
 
 // Returns true if the opcode operates as if its operands are signed integral.
-bool AssumesSignedOperands(SpvOp opcode) {
+bool AssumesSignedOperands(spv::Op opcode) {
     switch (opcode) {
-        case SpvOpSNegate:
-        case SpvOpSDiv:
-        case SpvOpSRem:
-        case SpvOpSMod:
-        case SpvOpSLessThan:
-        case SpvOpSLessThanEqual:
-        case SpvOpSGreaterThan:
-        case SpvOpSGreaterThanEqual:
-        case SpvOpConvertSToF:
+        case spv::Op::OpSNegate:
+        case spv::Op::OpSDiv:
+        case spv::Op::OpSRem:
+        case spv::Op::OpSMod:
+        case spv::Op::OpSLessThan:
+        case spv::Op::OpSLessThanEqual:
+        case spv::Op::OpSGreaterThan:
+        case spv::Op::OpSGreaterThanEqual:
+        case spv::Op::OpConvertSToF:
             return true;
         default:
             break;
@@ -126,15 +196,15 @@
 }
 
 // Returns true if the opcode operates as if its operands are unsigned integral.
-bool AssumesUnsignedOperands(SpvOp opcode) {
+bool AssumesUnsignedOperands(spv::Op opcode) {
     switch (opcode) {
-        case SpvOpUDiv:
-        case SpvOpUMod:
-        case SpvOpULessThan:
-        case SpvOpULessThanEqual:
-        case SpvOpUGreaterThan:
-        case SpvOpUGreaterThanEqual:
-        case SpvOpConvertUToF:
+        case spv::Op::OpUDiv:
+        case spv::Op::OpUMod:
+        case spv::Op::OpULessThan:
+        case spv::Op::OpULessThanEqual:
+        case spv::Op::OpUGreaterThan:
+        case spv::Op::OpUGreaterThanEqual:
+        case spv::Op::OpConvertUToF:
             return true;
         default:
             break;
@@ -163,18 +233,18 @@
 // the signedness of the second operand to match the signedness of the
 // first operand, and it's not one of the OpU* or OpS* instructions.
 // (Those are handled via MakeOperand.)
-bool AssumesSecondOperandSignednessMatchesFirstOperand(SpvOp opcode) {
+bool AssumesSecondOperandSignednessMatchesFirstOperand(spv::Op opcode) {
     switch (opcode) {
         // All the OpI* integer binary operations.
-        case SpvOpIAdd:
-        case SpvOpISub:
-        case SpvOpIMul:
-        case SpvOpIEqual:
-        case SpvOpINotEqual:
+        case spv::Op::OpIAdd:
+        case spv::Op::OpISub:
+        case spv::Op::OpIMul:
+        case spv::Op::OpIEqual:
+        case spv::Op::OpINotEqual:
         // All the bitwise integer binary operations.
-        case SpvOpBitwiseAnd:
-        case SpvOpBitwiseOr:
-        case SpvOpBitwiseXor:
+        case spv::Op::OpBitwiseAnd:
+        case spv::Op::OpBitwiseOr:
+        case spv::Op::OpBitwiseXor:
             return true;
         default:
             break;
@@ -184,24 +254,24 @@
 
 // Returns true if the corresponding WGSL operation requires
 // the signedness of the result to match the signedness of the first operand.
-bool AssumesResultSignednessMatchesFirstOperand(SpvOp opcode) {
+bool AssumesResultSignednessMatchesFirstOperand(spv::Op opcode) {
     switch (opcode) {
-        case SpvOpNot:
-        case SpvOpSNegate:
-        case SpvOpBitCount:
-        case SpvOpBitReverse:
-        case SpvOpSDiv:
-        case SpvOpSMod:
-        case SpvOpSRem:
-        case SpvOpIAdd:
-        case SpvOpISub:
-        case SpvOpIMul:
-        case SpvOpBitwiseAnd:
-        case SpvOpBitwiseOr:
-        case SpvOpBitwiseXor:
-        case SpvOpShiftLeftLogical:
-        case SpvOpShiftRightLogical:
-        case SpvOpShiftRightArithmetic:
+        case spv::Op::OpNot:
+        case spv::Op::OpSNegate:
+        case spv::Op::OpBitCount:
+        case spv::Op::OpBitReverse:
+        case spv::Op::OpSDiv:
+        case spv::Op::OpSMod:
+        case spv::Op::OpSRem:
+        case spv::Op::OpIAdd:
+        case spv::Op::OpISub:
+        case spv::Op::OpIMul:
+        case spv::Op::OpBitwiseAnd:
+        case spv::Op::OpBitwiseOr:
+        case spv::Op::OpBitwiseXor:
+        case spv::Op::OpShiftLeftLogical:
+        case spv::Op::OpShiftRightLogical:
+        case spv::Op::OpShiftRightArithmetic:
             return true;
         default:
             break;
@@ -240,12 +310,12 @@
     if (deco.size() < 1) {
         return false;
     }
-    switch (deco[0]) {
-        case SpvDecorationLocation:
-        case SpvDecorationFlat:
-        case SpvDecorationNoPerspective:
-        case SpvDecorationCentroid:
-        case SpvDecorationSample:
+    switch (static_cast<spv::Decoration>(deco[0])) {
+        case spv::Decoration::Location:
+        case spv::Decoration::Flat:
+        case spv::Decoration::NoPerspective:
+        case spv::Decoration::Centroid:
+        case spv::Decoration::Sample:
             return true;
         default:
             break;
@@ -388,16 +458,16 @@
     const auto& decorations = deco_mgr_->GetDecorationsFor(id, true);
     std::unordered_set<uint32_t> visited;
     for (const auto* inst : decorations) {
-        if (inst->opcode() != SpvOpDecorate) {
+        if (opcode(inst) != spv::Op::OpDecorate) {
             continue;
         }
         // Example: OpDecorate %struct_id Block
         // Example: OpDecorate %array_ty ArrayStride 16
         auto decoration_kind = inst->GetSingleWordInOperand(1);
-        switch (decoration_kind) {
+        switch (static_cast<spv::Decoration>(decoration_kind)) {
             // Restrict and RestrictPointer have no effect in graphics APIs.
-            case SpvDecorationRestrict:
-            case SpvDecorationRestrictPointer:
+            case spv::Decoration::Restrict:
+            case spv::Decoration::RestrictPointer:
                 break;
             default:
                 if (visited.emplace(decoration_kind).second) {
@@ -418,15 +488,15 @@
     std::unordered_set<uint32_t> visited;
     for (const auto* inst : decorations) {
         // Example: OpMemberDecorate %struct_id 1 Offset 16
-        if ((inst->opcode() != SpvOpMemberDecorate) ||
+        if ((opcode(inst) != spv::Op::OpMemberDecorate) ||
             (inst->GetSingleWordInOperand(1) != member_index)) {
             continue;
         }
         auto decoration_kind = inst->GetSingleWordInOperand(2);
-        switch (decoration_kind) {
+        switch (static_cast<spv::Decoration>(decoration_kind)) {
             // Restrict and RestrictPointer have no effect in graphics APIs.
-            case SpvDecorationRestrict:
-            case SpvDecorationRestrictPointer:
+            case spv::Decoration::Restrict:
+            case spv::Decoration::RestrictPointer:
                 break;
             default:
                 if (visited.emplace(decoration_kind).second) {
@@ -458,8 +528,8 @@
         Fail() << "malformed SPIR-V decoration: it's empty";
         return {};
     }
-    switch (decoration[0]) {
-        case SpvDecorationOffset:
+    switch (static_cast<spv::Decoration>(decoration[0])) {
+        case spv::Decoration::Offset:
             if (decoration.size() != 2) {
                 Fail() << "malformed Offset decoration: expected 1 literal operand, has "
                        << decoration.size() - 1 << ": member " << member_index << " of "
@@ -469,24 +539,24 @@
             return {
                 builder_.MemberOffset(Source{}, AInt(decoration[1])),
             };
-        case SpvDecorationNonReadable:
+        case spv::Decoration::NonReadable:
             // WGSL doesn't have a member decoration for this.  Silently drop it.
             return {};
-        case SpvDecorationNonWritable:
+        case spv::Decoration::NonWritable:
             // WGSL doesn't have a member decoration for this.
             return {};
-        case SpvDecorationColMajor:
+        case spv::Decoration::ColMajor:
             // WGSL only supports column major matrices.
             return {};
-        case SpvDecorationRelaxedPrecision:
+        case spv::Decoration::RelaxedPrecision:
             // WGSL doesn't support relaxed precision.
             return {};
-        case SpvDecorationRowMajor:
+        case spv::Decoration::RowMajor:
             Fail() << "WGSL does not support row-major matrices: can't "
                       "translate member "
                    << member_index << " of " << ShowType(struct_type_id);
             return {};
-        case SpvDecorationMatrixStride: {
+        case spv::Decoration::MatrixStride: {
             if (decoration.size() != 2) {
                 Fail() << "malformed MatrixStride decoration: expected 1 literal "
                           "operand, has "
@@ -589,15 +659,15 @@
         [this, &in_op_line_scope, &op_line_source,
          &instruction_number](const spvtools::opt::Instruction* inst) {
             ++instruction_number.line;
-            switch (inst->opcode()) {
-                case SpvOpLine:
+            switch (opcode(inst)) {
+                case spv::Op::OpLine:
                     in_op_line_scope = true;
                     // TODO(dneto): This ignores the File ID (operand 0), since the Tint
                     // Source concept doesn't represent that.
                     op_line_source.line = inst->GetSingleWordInOperand(1);
                     op_line_source.column = inst->GetSingleWordInOperand(2);
                     break;
-                case SpvOpNoLine:
+                case spv::Op::OpNoLine:
                     in_op_line_scope = false;
                     break;
                 default:
@@ -670,12 +740,12 @@
 }
 
 bool ParserImpl::IsGlslExtendedInstruction(const spvtools::opt::Instruction& inst) const {
-    return (inst.opcode() == SpvOpExtInst) &&
+    return (opcode(inst) == spv::Op::OpExtInst) &&
            (glsl_std_450_imports_.count(inst.GetSingleWordInOperand(0)) > 0);
 }
 
 bool ParserImpl::IsIgnoredExtendedInstruction(const spvtools::opt::Instruction& inst) const {
-    return (inst.opcode() == SpvOpExtInst) &&
+    return (opcode(inst) == spv::Op::OpExtInst) &&
            (ignored_imports_.count(inst.GetSingleWordInOperand(0)) > 0);
 }
 
@@ -722,15 +792,15 @@
 
     // Register names from OpName and OpMemberName
     for (const auto& inst : module_->debugs2()) {
-        switch (inst.opcode()) {
-            case SpvOpName: {
+        switch (opcode(inst)) {
+            case spv::Op::OpName: {
                 const auto name = inst.GetInOperand(1).AsString();
                 if (!name.empty()) {
                     namer_.SuggestSanitizedName(inst.GetSingleWordInOperand(0), name);
                 }
                 break;
             }
-            case SpvOpMemberName: {
+            case spv::Op::OpMemberName: {
                 const auto name = inst.GetInOperand(2).AsString();
                 if (!name.empty()) {
                     namer_.SuggestSanitizedMemberName(inst.GetSingleWordInOperand(0),
@@ -745,7 +815,7 @@
 
     // Fill in struct member names, and disambiguate them.
     for (const auto* type_inst : module_->GetTypes()) {
-        if (type_inst->opcode() == SpvOpTypeStruct) {
+        if (opcode(type_inst) == spv::Op::OpTypeStruct) {
             namer_.ResolveMemberNamesForStruct(type_inst->result_id(), type_inst->NumInOperands());
         }
     }
@@ -779,13 +849,13 @@
 bool ParserImpl::RegisterWorkgroupSizeBuiltin() {
     WorkgroupSizeInfo& info = workgroup_size_builtin_;
     for (const spvtools::opt::Instruction& inst : module_->annotations()) {
-        if (inst.opcode() != SpvOpDecorate) {
+        if (opcode(inst) != spv::Op::OpDecorate) {
             continue;
         }
-        if (inst.GetSingleWordInOperand(1) != SpvDecorationBuiltIn) {
+        if (inst.GetSingleWordInOperand(1) != uint32_t(spv::Decoration::BuiltIn)) {
             continue;
         }
-        if (inst.GetSingleWordInOperand(2) != SpvBuiltInWorkgroupSize) {
+        if (inst.GetSingleWordInOperand(2) != uint32_t(spv::BuiltIn::WorkgroupSize)) {
             continue;
         }
         info.id = inst.GetSingleWordInOperand(0);
@@ -806,8 +876,8 @@
     // const-expr yet, so avoid supporting OpSpecConstantOp here.
     // TODO(dneto): See https://github.com/gpuweb/gpuweb/issues/1272 for WGSL
     // const_expr proposals.
-    if ((composite_def->opcode() != SpvOpSpecConstantComposite &&
-         composite_def->opcode() != SpvOpConstantComposite)) {
+    if ((opcode(composite_def) != spv::Op::OpSpecConstantComposite &&
+         opcode(composite_def) != spv::Op::OpConstantComposite)) {
         return Fail() << "Invalid WorkgroupSize builtin.  Expected 3-element "
                          "OpSpecConstantComposite or OpConstantComposite:  "
                       << composite_def->PrettyPrint();
@@ -822,7 +892,8 @@
                                            int index) -> bool {
         const auto id = composite_def->GetSingleWordInOperand(static_cast<uint32_t>(index));
         const auto* def = def_use_mgr_->GetDef(id);
-        if (!def || (def->opcode() != SpvOpSpecConstant && def->opcode() != SpvOpConstant) ||
+        if (!def ||
+            (opcode(def) != spv::Op::OpSpecConstant && opcode(def) != spv::Op::OpConstant) ||
             (def->NumInOperands() != 1)) {
             return Fail() << "invalid component " << index << " of workgroupsize "
                           << (def ? def->PrettyPrint() : std::string("no definition"));
@@ -842,8 +913,8 @@
     // decorations.
     std::unordered_map<uint32_t, GridSize> local_size;
     for (const spvtools::opt::Instruction& inst : module_->execution_modes()) {
-        auto mode = static_cast<SpvExecutionMode>(inst.GetSingleWordInOperand(1));
-        if (mode == SpvExecutionModeLocalSize) {
+        auto mode = static_cast<spv::ExecutionMode>(inst.GetSingleWordInOperand(1));
+        if (mode == spv::ExecutionMode::LocalSize) {
             if (inst.NumInOperands() != 5) {
                 // This won't even get past SPIR-V binary parsing.
                 return Fail() << "invalid LocalSize execution mode: " << inst.PrettyPrint();
@@ -856,7 +927,7 @@
     }
 
     for (const spvtools::opt::Instruction& entry_point : module_->entry_points()) {
-        const auto stage = SpvExecutionModel(entry_point.GetSingleWordInOperand(0));
+        const auto stage = spv::ExecutionModel(entry_point.GetSingleWordInOperand(0));
         const uint32_t function_id = entry_point.GetSingleWordInOperand(1);
 
         const std::string ep_name = entry_point.GetOperand(2).AsString();
@@ -886,11 +957,11 @@
         for (unsigned iarg = 3; iarg < entry_point.NumInOperands(); iarg++) {
             const uint32_t var_id = entry_point.GetSingleWordInOperand(iarg);
             if (const auto* var_inst = def_use_mgr_->GetDef(var_id)) {
-                switch (SpvStorageClass(var_inst->GetSingleWordInOperand(0))) {
-                    case SpvStorageClassInput:
+                switch (spv::StorageClass(var_inst->GetSingleWordInOperand(0))) {
+                    case spv::StorageClass::Input:
                         inputs.Add(var_id);
                         break;
-                    case SpvStorageClassOutput:
+                    case spv::StorageClass::Output:
                         outputs.Add(var_id);
                         break;
                     default:
@@ -1036,7 +1107,7 @@
     *array_stride = 0;  // Implicit stride case.
     const auto type_id = type_mgr_->GetId(spv_type);
     for (auto& decoration : this->GetDecorationsFor(type_id)) {
-        if (decoration.size() == 2 && decoration[0] == SpvDecorationArrayStride) {
+        if (decoration.size() == 2 && decoration[0] == uint32_t(spv::Decoration::ArrayStride)) {
             const auto stride = decoration[1];
             if (stride == 0) {
                 return Fail() << "invalid array type ID " << type_id << ": ArrayStride can't be 0";
@@ -1057,9 +1128,9 @@
     auto struct_decorations = this->GetDecorationsFor(type_id);
     if (struct_decorations.size() == 1) {
         const auto decoration = struct_decorations[0][0];
-        if (decoration == SpvDecorationBufferBlock) {
+        if (decoration == uint32_t(spv::Decoration::BufferBlock)) {
             remap_buffer_block_type_.insert(type_id);
-        } else if (decoration != SpvDecorationBlock) {
+        } else if (decoration != uint32_t(spv::Decoration::Block)) {
             Fail() << "struct with ID " << type_id
                    << " has unrecognized decoration: " << int(decoration);
         }
@@ -1097,21 +1168,21 @@
                 Fail() << "malformed SPIR-V decoration: it's empty";
                 return nullptr;
             }
-            if ((decoration[0] == SpvDecorationBuiltIn) && (decoration.size() > 1)) {
-                switch (decoration[1]) {
-                    case SpvBuiltInPosition:
+            if ((decoration[0] == uint32_t(spv::Decoration::BuiltIn)) && (decoration.size() > 1)) {
+                switch (static_cast<spv::BuiltIn>(decoration[1])) {
+                    case spv::BuiltIn::Position:
                         // Record this built-in variable specially.
                         builtin_position_.struct_type_id = type_id;
                         builtin_position_.position_member_index = member_index;
                         builtin_position_.position_member_type_id = member_type_id;
                         create_ast_member = false;  // Not part of the WGSL structure.
                         break;
-                    case SpvBuiltInPointSize:  // not supported in WGSL, but ignore
+                    case spv::BuiltIn::PointSize:  // not supported in WGSL, but ignore
                         builtin_position_.pointsize_member_index = member_index;
                         create_ast_member = false;  // Not part of the WGSL structure.
                         break;
-                    case SpvBuiltInClipDistance:    // not supported in WGSL
-                    case SpvBuiltInCullDistance:    // not supported in WGSL
+                    case spv::BuiltIn::ClipDistance:  // not supported in WGSL
+                    case spv::BuiltIn::CullDistance:  // not supported in WGSL
                         create_ast_member = false;  // Not part of the WGSL structure.
                         break;
                     default:
@@ -1131,7 +1202,7 @@
             if (IsPipelineDecoration(decoration)) {
                 // IO decorations are handled when emitting the entry point.
                 continue;
-            } else if (decoration[0] == SpvDecorationNonWritable) {
+            } else if (decoration[0] == uint32_t(spv::Decoration::NonWritable)) {
                 // WGSL doesn't represent individual members as non-writable. Instead,
                 // apply the ReadOnly access control to the containing struct if all
                 // the members are non-writable.
@@ -1193,12 +1264,12 @@
                                     const spvtools::opt::analysis::Pointer*) {
     const auto* inst = def_use_mgr_->GetDef(type_id);
     const auto pointee_type_id = inst->GetSingleWordInOperand(1);
-    const auto storage_class = SpvStorageClass(inst->GetSingleWordInOperand(0));
+    const auto storage_class = spv::StorageClass(inst->GetSingleWordInOperand(0));
 
     if (pointee_type_id == builtin_position_.struct_type_id) {
         builtin_position_.pointer_type_id = type_id;
         // Pipeline IO builtins map to private variables.
-        builtin_position_.storage_class = SpvStorageClassPrivate;
+        builtin_position_.storage_class = spv::StorageClass::Private;
         return nullptr;
     }
     auto* ast_elem_ty = ConvertType(pointee_type_id, PtrAs::Ptr);
@@ -1244,21 +1315,21 @@
     // in WGSL. In particular, exclude user-defined pipeline IO in a
     // block-decorated struct.
     for (const auto& type_or_value : module_->types_values()) {
-        if (type_or_value.opcode() != SpvOpVariable) {
+        if (opcode(type_or_value) != spv::Op::OpVariable) {
             continue;
         }
         const auto& var = type_or_value;
-        const auto spirv_storage_class = SpvStorageClass(var.GetSingleWordInOperand(0));
-        if ((spirv_storage_class != SpvStorageClassStorageBuffer) &&
-            (spirv_storage_class != SpvStorageClassUniform)) {
+        const auto spirv_storage_class = spv::StorageClass(var.GetSingleWordInOperand(0));
+        if ((spirv_storage_class != spv::StorageClass::StorageBuffer) &&
+            (spirv_storage_class != spv::StorageClass::Uniform)) {
             continue;
         }
         const auto* ptr_type = def_use_mgr_->GetDef(var.type_id());
-        if (ptr_type->opcode() != SpvOpTypePointer) {
+        if (opcode(ptr_type) != spv::Op::OpTypePointer) {
             return Fail() << "OpVariable type expected to be a pointer: " << var.PrettyPrint();
         }
         const auto* store_type = def_use_mgr_->GetDef(ptr_type->GetSingleWordInOperand(1));
-        if (store_type->opcode() == SpvOpTypeStruct) {
+        if (opcode(store_type) == spv::Op::OpTypeStruct) {
             struct_types_for_buffers_.insert(store_type->result_id());
         } else {
             Fail() << "WGSL does not support arrays of buffers: " << var.PrettyPrint();
@@ -1276,7 +1347,8 @@
     // Manufacture a type for the gl_Position variable if we have to.
     if ((builtin_position_.struct_type_id != 0) &&
         (builtin_position_.position_member_pointer_type_id == 0)) {
-        builtin_position_.position_member_pointer_type_id = type_mgr_->FindPointerToType(
+        builtin_position_.position_member_pointer_type_id = three_sided_patch::FindPointerToType(
+            type_mgr_, &spvtools::opt::analysis::TypeManager::FindPointerToType,
             builtin_position_.position_member_type_id, builtin_position_.storage_class);
         ConvertType(builtin_position_.position_member_pointer_type_id);
     }
@@ -1290,13 +1362,13 @@
     for (auto& inst : module_->types_values()) {
         if (const auto* result_type = type_mgr_->GetType(inst.type_id())) {
             if (result_type->AsPointer()) {
-                switch (inst.opcode()) {
-                    case SpvOpVariable:
+                switch (opcode(inst)) {
+                    case spv::Op::OpVariable:
                         // This is the only valid case.
                         break;
-                    case SpvOpUndef:
+                    case spv::Op::OpUndef:
                         return Fail() << "undef pointer is not valid: " << inst.PrettyPrint();
-                    case SpvOpConstantNull:
+                    case spv::Op::OpConstantNull:
                         return Fail() << "null pointer is not valid: " << inst.PrettyPrint();
                     default:
                         return Fail()
@@ -1319,15 +1391,15 @@
         const Type* ast_type = nullptr;
         ast::LiteralExpression* ast_expr = nullptr;
 
-        switch (inst.opcode()) {
-            case SpvOpSpecConstantTrue:
-            case SpvOpSpecConstantFalse: {
+        switch (opcode(inst)) {
+            case spv::Op::OpSpecConstantTrue:
+            case spv::Op::OpSpecConstantFalse: {
                 ast_type = ConvertType(inst.type_id());
                 ast_expr = create<ast::BoolLiteralExpression>(
-                    Source{}, inst.opcode() == SpvOpSpecConstantTrue);
+                    Source{}, opcode(inst) == spv::Op::OpSpecConstantTrue);
                 break;
             }
-            case SpvOpSpecConstant: {
+            case spv::Op::OpSpecConstant: {
                 ast_type = ConvertType(inst.type_id());
                 const uint32_t literal_value = inst.GetSingleWordInOperand(0);
                 ast_expr = Switch(
@@ -1362,7 +1434,7 @@
         if (ast_type && ast_expr) {
             AttributeList spec_id_decos;
             for (const auto& deco : GetDecorationsFor(inst.result_id())) {
-                if ((deco.size() == 2) && (deco[0] == SpvDecorationSpecId)) {
+                if ((deco.size() == 2) && (deco[0] == uint32_t(spv::Decoration::SpecId))) {
                     const uint32_t id = deco[1];
                     if (id > 65535) {
                         return Fail() << "SpecId too large. WGSL override IDs must be "
@@ -1430,16 +1502,16 @@
         return false;
     }
     for (const auto& type_or_value : module_->types_values()) {
-        if (type_or_value.opcode() != SpvOpVariable) {
+        if (opcode(type_or_value) != spv::Op::OpVariable) {
             continue;
         }
         const auto& var = type_or_value;
-        const auto spirv_storage_class = SpvStorageClass(var.GetSingleWordInOperand(0));
+        const auto spirv_storage_class = spv::StorageClass(var.GetSingleWordInOperand(0));
 
         uint32_t type_id = var.type_id();
         if ((type_id == builtin_position_.pointer_type_id) &&
-            ((spirv_storage_class == SpvStorageClassInput) ||
-             (spirv_storage_class == SpvStorageClassOutput))) {
+            ((spirv_storage_class == spv::StorageClass::Input) ||
+             (spirv_storage_class == spv::StorageClass::Output))) {
             // Skip emitting gl_PerVertex.
             builtin_position_.per_vertex_var_id = var.result_id();
             builtin_position_.per_vertex_var_init_id =
@@ -1464,7 +1536,7 @@
             return false;
         }
         const Type* ast_type = nullptr;
-        if (spirv_storage_class == SpvStorageClassUniformConstant) {
+        if (spirv_storage_class == spv::StorageClass::UniformConstant) {
             // These are opaque handles: samplers or textures
             ast_type = GetTypeForHandleVar(var);
             if (!ast_type) {
@@ -1509,9 +1581,9 @@
         if (builtin_position_.per_vertex_var_init_id) {
             // The initializer is complex.
             const auto* init = def_use_mgr_->GetDef(builtin_position_.per_vertex_var_init_id);
-            switch (init->opcode()) {
-                case SpvOpConstantComposite:
-                case SpvOpSpecConstantComposite:
+            switch (opcode(init)) {
+                case spv::Op::OpConstantComposite:
+                case spv::Op::OpSpecConstantComposite:
                     ast_initializer =
                         MakeConstantExpression(
                             init->GetSingleWordInOperand(builtin_position_.position_member_index))
@@ -1541,15 +1613,15 @@
 // @returns the IntConstant for the size of the array, or nullptr
 const spvtools::opt::analysis::IntConstant* ParserImpl::GetArraySize(uint32_t var_id) {
     auto* var = def_use_mgr_->GetDef(var_id);
-    if (!var || var->opcode() != SpvOpVariable) {
+    if (!var || opcode(var) != spv::Op::OpVariable) {
         return nullptr;
     }
     auto* ptr_type = def_use_mgr_->GetDef(var->type_id());
-    if (!ptr_type || ptr_type->opcode() != SpvOpTypePointer) {
+    if (!ptr_type || opcode(ptr_type) != spv::Op::OpTypePointer) {
         return nullptr;
     }
     auto* array_type = def_use_mgr_->GetDef(ptr_type->GetSingleWordInOperand(1));
-    if (!array_type || array_type->opcode() != SpvOpTypeArray) {
+    if (!array_type || opcode(array_type) != spv::Op::OpTypeArray) {
         return nullptr;
     }
     auto* size = constant_mgr_->FindDeclaredConstant(array_type->GetSingleWordInOperand(1));
@@ -1632,23 +1704,23 @@
         if (deco.empty()) {
             return Fail() << "malformed decoration on ID " << id << ": it is empty";
         }
-        if (deco[0] == SpvDecorationBuiltIn) {
+        if (deco[0] == uint32_t(spv::Decoration::BuiltIn)) {
             if (deco.size() == 1) {
                 return Fail() << "malformed BuiltIn decoration on ID " << id << ": has no operand";
             }
-            const auto spv_builtin = static_cast<SpvBuiltIn>(deco[1]);
+            const auto spv_builtin = static_cast<spv::BuiltIn>(deco[1]);
             switch (spv_builtin) {
-                case SpvBuiltInPointSize:
+                case spv::BuiltIn::PointSize:
                     special_builtins_[id] = spv_builtin;
                     return false;  // This is not an error
-                case SpvBuiltInSampleId:
-                case SpvBuiltInVertexIndex:
-                case SpvBuiltInInstanceIndex:
-                case SpvBuiltInLocalInvocationId:
-                case SpvBuiltInLocalInvocationIndex:
-                case SpvBuiltInGlobalInvocationId:
-                case SpvBuiltInWorkgroupId:
-                case SpvBuiltInNumWorkgroups:
+                case spv::BuiltIn::SampleId:
+                case spv::BuiltIn::VertexIndex:
+                case spv::BuiltIn::InstanceIndex:
+                case spv::BuiltIn::LocalInvocationId:
+                case spv::BuiltIn::LocalInvocationIndex:
+                case spv::BuiltIn::GlobalInvocationId:
+                case spv::BuiltIn::WorkgroupId:
+                case spv::BuiltIn::NumWorkgroups:
                     // The SPIR-V variable may signed (because GLSL requires signed for
                     // some of these), but WGSL requires unsigned.  Handle specially
                     // so we always perform the conversion at load and store.
@@ -1660,7 +1732,7 @@
                         }
                     }
                     break;
-                case SpvBuiltInSampleMask: {
+                case spv::BuiltIn::SampleMask: {
                     // In SPIR-V this is used for both input and output variable.
                     // The SPIR-V variable has store type of array of integer scalar,
                     // either signed or unsigned.
@@ -1691,14 +1763,14 @@
         if (transfer_pipeline_io && IsPipelineDecoration(deco)) {
             non_builtin_pipeline_decorations.push_back(deco);
         }
-        if (deco[0] == SpvDecorationDescriptorSet) {
+        if (deco[0] == uint32_t(spv::Decoration::DescriptorSet)) {
             if (deco.size() == 1) {
                 return Fail() << "malformed DescriptorSet decoration on ID " << id
                               << ": has no operand";
             }
             decorations->Push(builder_.Group(Source{}, AInt(deco[1])));
         }
-        if (deco[0] == SpvDecorationBinding) {
+        if (deco[0] == uint32_t(spv::Decoration::Binding)) {
             if (deco.size() == 1) {
                 return Fail() << "malformed Binding decoration on ID " << id << ": has no operand";
             }
@@ -1756,8 +1828,8 @@
 
     for (const auto& deco : decorations) {
         TINT_ASSERT(Reader, deco.size() > 0);
-        switch (deco[0]) {
-            case SpvDecorationLocation:
+        switch (static_cast<spv::Decoration>(deco[0])) {
+            case spv::Decoration::Location:
                 if (deco.size() != 2) {
                     return Fail() << "malformed Location decoration on ID requires one "
                                      "literal operand";
@@ -1768,24 +1840,24 @@
                     type = ast::InterpolationType::kFlat;
                 }
                 break;
-            case SpvDecorationFlat:
+            case spv::Decoration::Flat:
                 type = ast::InterpolationType::kFlat;
                 break;
-            case SpvDecorationNoPerspective:
+            case spv::Decoration::NoPerspective:
                 if (store_type->IsIntegerScalarOrVector()) {
                     // This doesn't capture the array or struct case.
                     return Fail() << "NoPerspective is invalid on integral IO";
                 }
                 type = ast::InterpolationType::kLinear;
                 break;
-            case SpvDecorationCentroid:
+            case spv::Decoration::Centroid:
                 if (store_type->IsIntegerScalarOrVector()) {
                     // This doesn't capture the array or struct case.
                     return Fail() << "Centroid interpolation sampling is invalid on integral IO";
                 }
                 sampling = ast::InterpolationSampling::kCentroid;
                 break;
-            case SpvDecorationSample:
+            case spv::Decoration::Sample:
                 if (store_type->IsIntegerScalarOrVector()) {
                     // This doesn't capture the array or struct case.
                     return Fail() << "Sample interpolation sampling is invalid on integral IO";
@@ -1801,7 +1873,7 @@
         !ast::HasAttribute<ast::LocationAttribute>(*attributes)) {
         // WGSL requires that '@interpolate(flat)' needs to be paired with '@location', however
         // SPIR-V requires all fragment shader integer Inputs are 'flat'. If the decorations do not
-        // contain a SpvDecorationLocation, then make this perspective.
+        // contain a spv::Decoration::Location, then make this perspective.
         type = ast::InterpolationType::kPerspective;
     }
 
@@ -1825,7 +1897,7 @@
     if (!inst) {
         return false;
     }
-    if (inst->opcode() == SpvOpUndef) {
+    if (opcode(inst) == spv::Op::OpUndef) {
         return true;
     }
     return nullptr != constant_mgr_->FindDeclaredConstant(id);
@@ -1880,13 +1952,13 @@
         return {};
     }
 
-    switch (inst->opcode()) {
-        case SpvOpUndef:  // Remap undef to null.
-        case SpvOpConstantNull:
+    switch (opcode(inst)) {
+        case spv::Op::OpUndef:  // Remap undef to null.
+        case spv::Op::OpConstantNull:
             return {original_ast_type, MakeNullValue(original_ast_type)};
-        case SpvOpConstantTrue:
-        case SpvOpConstantFalse:
-        case SpvOpConstant: {
+        case spv::Op::OpConstantTrue:
+        case spv::Op::OpConstantFalse:
+        case spv::Op::OpConstant: {
             const auto* spirv_const = constant_mgr_->FindDeclaredConstant(id);
             if (spirv_const == nullptr) {
                 Fail() << "ID " << id << " is not a constant";
@@ -1895,7 +1967,7 @@
             return MakeConstantExpressionForScalarSpirvConstant(source, original_ast_type,
                                                                 spirv_const);
         }
-        case SpvOpConstantComposite: {
+        case spv::Op::OpConstantComposite: {
             // Handle vector, matrix, array, and struct
 
             // Generate a composite from explicit components.
@@ -2047,9 +2119,9 @@
         requires_signed = AssumesSignedOperands(extended_opcode);
         requires_unsigned = AssumesUnsignedOperands(extended_opcode);
     } else {
-        const auto opcode = inst.opcode();
-        requires_signed = AssumesSignedOperands(opcode);
-        requires_unsigned = AssumesUnsignedOperands(opcode);
+        const auto op = opcode(inst);
+        requires_signed = AssumesSignedOperands(op);
+        requires_unsigned = AssumesUnsignedOperands(op);
     }
     if (!requires_signed && !requires_unsigned) {
         // No conversion is required, assuming our tables are complete.
@@ -2087,7 +2159,7 @@
                                                            TypedExpression&& second_operand_expr) {
     const Type* target_type = first_operand_type->UnwrapRef();
     if ((target_type != second_operand_expr.type->UnwrapRef()) &&
-        AssumesSecondOperandSignednessMatchesFirstOperand(inst.opcode())) {
+        AssumesSecondOperandSignednessMatchesFirstOperand(opcode(inst))) {
         // Conversion is required.
         return {target_type, create<ast::BitcastExpression>(Source{}, target_type->Build(builder_),
                                                             second_operand_expr.expr)};
@@ -2099,8 +2171,8 @@
 const Type* ParserImpl::ForcedResultType(const spvtools::opt::Instruction& inst,
                                          const Type* first_operand_type) {
     first_operand_type = first_operand_type->UnwrapRef();
-    const auto opcode = inst.opcode();
-    if (AssumesResultSignednessMatchesFirstOperand(opcode)) {
+    const auto op = opcode(inst);
+    if (AssumesResultSignednessMatchesFirstOperand(op)) {
         return first_operand_type;
     }
     if (IsGlslExtendedInstruction(inst)) {
@@ -2239,36 +2311,36 @@
         if (inst == nullptr) {
             return local_fail();
         }
-        switch (inst->opcode()) {
-            case SpvOpFunctionParameter:
-            case SpvOpVariable:
+        switch (opcode(inst)) {
+            case spv::Op::OpFunctionParameter:
+            case spv::Op::OpVariable:
                 // We found the memory object declaration.
                 // Remember it as the answer for the whole path.
                 for (auto iter : visited) {
                     memo_table[iter] = inst;
                 }
                 return inst;
-            case SpvOpLoad:
+            case spv::Op::OpLoad:
                 // Follow the pointer being loaded
                 id = inst->GetSingleWordInOperand(0);
                 break;
-            case SpvOpCopyObject:
+            case spv::Op::OpCopyObject:
                 // Follow the object being copied.
                 id = inst->GetSingleWordInOperand(0);
                 break;
-            case SpvOpAccessChain:
-            case SpvOpInBoundsAccessChain:
-            case SpvOpPtrAccessChain:
-            case SpvOpInBoundsPtrAccessChain:
+            case spv::Op::OpAccessChain:
+            case spv::Op::OpInBoundsAccessChain:
+            case spv::Op::OpPtrAccessChain:
+            case spv::Op::OpInBoundsPtrAccessChain:
                 // Follow the base pointer.
                 id = inst->GetSingleWordInOperand(0);
                 break;
-            case SpvOpSampledImage:
+            case spv::Op::OpSampledImage:
                 // Follow the image or the sampler, depending on the follow_image
                 // parameter.
                 id = inst->GetSingleWordInOperand(follow_image ? 0 : 1);
                 break;
-            case SpvOpImage:
+            case spv::Op::OpImage:
                 // Follow the sampled image
                 id = inst->GetSingleWordInOperand(0);
                 break;
@@ -2300,7 +2372,7 @@
 
     // Get the SPIR-V handle type.
     const auto* ptr_type = def_use_mgr_->GetDef(var.type_id());
-    if (!ptr_type || (ptr_type->opcode() != SpvOpTypePointer)) {
+    if (!ptr_type || (opcode(ptr_type) != spv::Op::OpTypePointer)) {
         Fail() << "Invalid type for variable or function parameter " << var.PrettyPrint();
         return nullptr;
     }
@@ -2309,18 +2381,18 @@
         Fail() << "Invalid pointer type for variable or function parameter " << var.PrettyPrint();
         return nullptr;
     }
-    switch (raw_handle_type->opcode()) {
-        case SpvOpTypeSampler:
-        case SpvOpTypeImage:
+    switch (opcode(raw_handle_type)) {
+        case spv::Op::OpTypeSampler:
+        case spv::Op::OpTypeImage:
             // The expected cases.
             break;
-        case SpvOpTypeArray:
-        case SpvOpTypeRuntimeArray:
+        case spv::Op::OpTypeArray:
+        case spv::Op::OpTypeRuntimeArray:
             Fail() << "arrays of textures or samplers are not supported in WGSL; can't "
                       "translate variable or function parameter: "
                    << var.PrettyPrint();
             return nullptr;
-        case SpvOpTypeSampledImage:
+        case spv::Op::OpTypeSampledImage:
             Fail() << "WGSL does not support combined image-samplers: " << var.PrettyPrint();
             return nullptr;
         default:
@@ -2358,7 +2430,7 @@
         // In SPIR-V you could statically reference a texture or sampler without
         // using it in a way that gives us a clue on how to declare it.  Look inside
         // the store type to infer a usage.
-        if (raw_handle_type->opcode() == SpvOpTypeSampler) {
+        if (opcode(raw_handle_type) == spv::Op::OpTypeSampler) {
             usage.AddSampler();
         } else {
             // It's a texture.
@@ -2370,7 +2442,7 @@
             const auto sampled_param = raw_handle_type->GetSingleWordInOperand(5);
             const auto format_param = raw_handle_type->GetSingleWordInOperand(6);
             // Only storage images have a format.
-            if ((format_param != SpvImageFormatUnknown) ||
+            if ((format_param != uint32_t(spv::ImageFormat::Unknown)) ||
                 sampled_param == 2 /* without sampler */) {
                 // Get NonWritable and NonReadable attributes of the variable.
                 bool is_nonwritable = false;
@@ -2379,10 +2451,10 @@
                     if (deco.size() != 1) {
                         continue;
                     }
-                    if (deco[0] == SpvDecorationNonWritable) {
+                    if (deco[0] == uint32_t(spv::Decoration::NonWritable)) {
                         is_nonwritable = true;
                     }
-                    if (deco[0] == SpvDecorationNonReadable) {
+                    if (deco[0] == uint32_t(spv::Decoration::NonReadable)) {
                         is_nonreadable = true;
                     }
                 }
@@ -2429,9 +2501,9 @@
         if (image_type->is_arrayed()) {
             // Give a nicer error message here, where we have the offending variable
             // in hand, rather than inside the enum converter.
-            switch (image_type->dim()) {
-                case SpvDim2D:
-                case SpvDimCube:
+            switch (static_cast<spv::Dim>(image_type->dim())) {
+                case spv::Dim::Dim2D:
+                case spv::Dim::Cube:
                     break;
                 default:
                     Fail() << "WGSL arrayed textures must be 2d_array or cube_array: "
@@ -2450,7 +2522,7 @@
         // WGSL textures are always formatted.  Unformatted textures are always
         // sampled.
         if (usage.IsSampledTexture() || usage.IsStorageReadTexture() ||
-            (image_type->format() == SpvImageFormatUnknown)) {
+            (uint32_t(image_type->format()) == uint32_t(spv::ImageFormat::Unknown))) {
             // Make a sampled texture type.
             auto* ast_sampled_component_type =
                 ConvertType(raw_handle_type->GetSingleWordInOperand(0));
@@ -2607,34 +2679,34 @@
     for (const auto* f : topologically_ordered_functions_) {
         for (const auto& bb : *f) {
             for (const auto& inst : bb) {
-                switch (inst.opcode()) {
+                switch (opcode(inst)) {
                         // Single texel reads and writes
 
-                    case SpvOpImageRead:
+                    case spv::Op::OpImageRead:
                         handle_usage_[get_image(inst)].AddStorageReadTexture();
                         break;
-                    case SpvOpImageWrite:
+                    case spv::Op::OpImageWrite:
                         handle_usage_[get_image(inst)].AddStorageWriteTexture();
                         break;
-                    case SpvOpImageFetch:
+                    case spv::Op::OpImageFetch:
                         handle_usage_[get_image(inst)].AddSampledTexture();
                         break;
 
                         // Sampling and gathering from a sampled image.
 
-                    case SpvOpImageSampleImplicitLod:
-                    case SpvOpImageSampleExplicitLod:
-                    case SpvOpImageSampleProjImplicitLod:
-                    case SpvOpImageSampleProjExplicitLod:
-                    case SpvOpImageGather:
+                    case spv::Op::OpImageSampleImplicitLod:
+                    case spv::Op::OpImageSampleExplicitLod:
+                    case spv::Op::OpImageSampleProjImplicitLod:
+                    case spv::Op::OpImageSampleProjExplicitLod:
+                    case spv::Op::OpImageGather:
                         handle_usage_[get_image(inst)].AddSampledTexture();
                         handle_usage_[get_sampler(inst)].AddSampler();
                         break;
-                    case SpvOpImageSampleDrefImplicitLod:
-                    case SpvOpImageSampleDrefExplicitLod:
-                    case SpvOpImageSampleProjDrefImplicitLod:
-                    case SpvOpImageSampleProjDrefExplicitLod:
-                    case SpvOpImageDrefGather:
+                    case spv::Op::OpImageSampleDrefImplicitLod:
+                    case spv::Op::OpImageSampleDrefExplicitLod:
+                    case spv::Op::OpImageSampleProjDrefImplicitLod:
+                    case spv::Op::OpImageSampleProjDrefExplicitLod:
+                    case spv::Op::OpImageDrefGather:
                         // Depth reference access implies usage as a depth texture, which
                         // in turn is a sampled texture.
                         handle_usage_[get_image(inst)].AddDepthTexture();
@@ -2643,29 +2715,29 @@
 
                         // Image queries
 
-                    case SpvOpImageQuerySizeLod:
+                    case spv::Op::OpImageQuerySizeLod:
                         // Vulkan requires Sampled=1 for this. SPIR-V already requires MS=0.
                         handle_usage_[get_image(inst)].AddSampledTexture();
                         break;
-                    case SpvOpImageQuerySize:
+                    case spv::Op::OpImageQuerySize:
                         // Applies to either MS=1 or Sampled=0 or 2.
                         // So we can't force it to be multisampled, or storage image.
                         break;
-                    case SpvOpImageQueryLod:
+                    case spv::Op::OpImageQueryLod:
                         handle_usage_[get_image(inst)].AddSampledTexture();
                         handle_usage_[get_sampler(inst)].AddSampler();
                         break;
-                    case SpvOpImageQueryLevels:
+                    case spv::Op::OpImageQueryLevels:
                         // We can't tell anything more than that it's an image.
                         handle_usage_[get_image(inst)].AddTexture();
                         break;
-                    case SpvOpImageQuerySamples:
+                    case spv::Op::OpImageQuerySamples:
                         handle_usage_[get_image(inst)].AddMultisampledTexture();
                         break;
 
                         // Function calls
 
-                    case SpvOpFunctionCall: {
+                    case spv::Op::OpFunctionCall: {
                         // Propagate handle usages from callee function formal parameters to
                         // the matching caller parameters.  This is where we rely on the
                         // fact that callees have been processed earlier in the flow.
diff --git a/src/tint/reader/spirv/parser_impl.h b/src/tint/reader/spirv/parser_impl.h
index 9965aa4..6e79ecc 100644
--- a/src/tint/reader/spirv/parser_impl.h
+++ b/src/tint/reader/spirv/parser_impl.h
@@ -66,8 +66,8 @@
 
 /// The binary representation of a SPIR-V decoration enum followed by its
 /// operands, if any.
-/// Example:   { SpvDecorationBlock }
-/// Example:   { SpvDecorationArrayStride, 16 }
+/// Example:   { spv::Decoration::Block }
+/// Example:   { spv::Decoration::ArrayStride, 16 }
 using Decoration = std::vector<uint32_t>;
 
 /// DecorationList is a list of decorations
@@ -338,13 +338,13 @@
     /// Returns true when the given instruction is an extended instruction
     /// for GLSL.std.450.
     /// @param inst a SPIR-V instruction
-    /// @returns true if its an SpvOpExtInst for GLSL.std.450
+    /// @returns true if its an spv::Op::ExtInst for GLSL.std.450
     bool IsGlslExtendedInstruction(const spvtools::opt::Instruction& inst) const;
 
     /// Returns true when the given instruction is an extended instruction
     /// from an ignored extended instruction set.
     /// @param inst a SPIR-V instruction
-    /// @returns true if its an SpvOpExtInst for an ignored extended instruction
+    /// @returns true if its an spv::Op::ExtInst for an ignored extended instruction
     bool IsIgnoredExtendedInstruction(const spvtools::opt::Instruction& inst) const;
 
     /// Registers user names for SPIR-V objects, from OpName, and OpMemberName.
@@ -585,7 +585,7 @@
         /// class class.
         uint32_t pointer_type_id = 0;
         /// The SPIR-V address space.
-        SpvStorageClass storage_class = SpvStorageClassOutput;
+        spv::StorageClass storage_class = spv::StorageClass::Output;
         /// The ID of the type of a pointer to the Position member.
         uint32_t position_member_pointer_type_id = 0;
         /// The ID of the gl_PerVertex variable, if it was declared.
@@ -690,7 +690,7 @@
     const spvtools::opt::Instruction* GetInstructionForTest(uint32_t id) const;
 
     /// A map of SPIR-V identifiers to builtins
-    using BuiltInsMap = std::unordered_map<uint32_t, SpvBuiltIn>;
+    using BuiltInsMap = std::unordered_map<uint32_t, spv::BuiltIn>;
 
     /// @returns a map of builtins that should be handled specially by code
     /// generation. Either the builtin does not exist in WGSL, or a type
@@ -699,7 +699,7 @@
 
     /// @param builtin the SPIR-V builtin variable kind
     /// @returns the SPIR-V ID for the variable defining the given builtin, or 0
-    uint32_t IdForSpecialBuiltIn(SpvBuiltIn builtin) const {
+    uint32_t IdForSpecialBuiltIn(spv::BuiltIn builtin) const {
         // Do a linear search.
         for (const auto& entry : special_builtins_) {
             if (entry.second == builtin) {
diff --git a/src/tint/reader/spirv/parser_impl_convert_member_decoration_test.cc b/src/tint/reader/spirv/parser_impl_convert_member_decoration_test.cc
index 767b333..402cc41 100644
--- a/src/tint/reader/spirv/parser_impl_convert_member_decoration_test.cc
+++ b/src/tint/reader/spirv/parser_impl_convert_member_decoration_test.cc
@@ -31,7 +31,7 @@
 TEST_F(SpvParserTest, ConvertMemberDecoration_OffsetWithoutOperand) {
     auto p = parser(std::vector<uint32_t>{});
 
-    auto result = p->ConvertMemberDecoration(12, 13, nullptr, {SpvDecorationOffset});
+    auto result = p->ConvertMemberDecoration(12, 13, nullptr, {uint32_t(spv::Decoration::Offset)});
     EXPECT_TRUE(result.IsEmpty());
     EXPECT_THAT(p->error(), Eq("malformed Offset decoration: expected 1 literal "
                                "operand, has 0: member 13 of SPIR-V type 12"));
@@ -40,7 +40,8 @@
 TEST_F(SpvParserTest, ConvertMemberDecoration_OffsetWithTooManyOperands) {
     auto p = parser(std::vector<uint32_t>{});
 
-    auto result = p->ConvertMemberDecoration(12, 13, nullptr, {SpvDecorationOffset, 3, 4});
+    auto result =
+        p->ConvertMemberDecoration(12, 13, nullptr, {uint32_t(spv::Decoration::Offset), 3, 4});
     EXPECT_TRUE(result.IsEmpty());
     EXPECT_THAT(p->error(), Eq("malformed Offset decoration: expected 1 literal "
                                "operand, has 2: member 13 of SPIR-V type 12"));
@@ -49,7 +50,7 @@
 TEST_F(SpvParserTest, ConvertMemberDecoration_Offset) {
     auto p = parser(std::vector<uint32_t>{});
 
-    auto result = p->ConvertMemberDecoration(1, 1, nullptr, {SpvDecorationOffset, 8});
+    auto result = p->ConvertMemberDecoration(1, 1, nullptr, {uint32_t(spv::Decoration::Offset), 8});
     ASSERT_FALSE(result.IsEmpty());
     EXPECT_TRUE(result[0]->Is<ast::StructMemberOffsetAttribute>());
     auto* offset_deco = result[0]->As<ast::StructMemberOffsetAttribute>();
@@ -64,7 +65,8 @@
 
     spirv::F32 f32;
     spirv::Matrix matrix(&f32, 2, 2);
-    auto result = p->ConvertMemberDecoration(1, 1, &matrix, {SpvDecorationMatrixStride, 8});
+    auto result =
+        p->ConvertMemberDecoration(1, 1, &matrix, {uint32_t(spv::Decoration::MatrixStride), 8});
     EXPECT_TRUE(result.IsEmpty());
     EXPECT_TRUE(p->error().empty());
 }
@@ -74,7 +76,8 @@
 
     spirv::F32 f32;
     spirv::Matrix matrix(&f32, 2, 2);
-    auto result = p->ConvertMemberDecoration(1, 1, &matrix, {SpvDecorationMatrixStride, 16});
+    auto result =
+        p->ConvertMemberDecoration(1, 1, &matrix, {uint32_t(spv::Decoration::MatrixStride), 16});
     ASSERT_FALSE(result.IsEmpty());
     EXPECT_TRUE(result[0]->Is<ast::StrideAttribute>());
     auto* stride_deco = result[0]->As<ast::StrideAttribute>();
@@ -88,7 +91,8 @@
 
     spirv::F32 f32;
     spirv::Matrix matrix(&f32, 2, 4);
-    auto result = p->ConvertMemberDecoration(1, 1, &matrix, {SpvDecorationMatrixStride, 16});
+    auto result =
+        p->ConvertMemberDecoration(1, 1, &matrix, {uint32_t(spv::Decoration::MatrixStride), 16});
     EXPECT_TRUE(result.IsEmpty());
     EXPECT_TRUE(p->error().empty());
 }
@@ -98,7 +102,8 @@
 
     spirv::F32 f32;
     spirv::Matrix matrix(&f32, 2, 4);
-    auto result = p->ConvertMemberDecoration(1, 1, &matrix, {SpvDecorationMatrixStride, 64});
+    auto result =
+        p->ConvertMemberDecoration(1, 1, &matrix, {uint32_t(spv::Decoration::MatrixStride), 64});
     ASSERT_FALSE(result.IsEmpty());
     EXPECT_TRUE(result[0]->Is<ast::StrideAttribute>());
     auto* stride_deco = result[0]->As<ast::StrideAttribute>();
@@ -112,7 +117,8 @@
 
     spirv::F32 f32;
     spirv::Matrix matrix(&f32, 2, 3);
-    auto result = p->ConvertMemberDecoration(1, 1, &matrix, {SpvDecorationMatrixStride, 32});
+    auto result =
+        p->ConvertMemberDecoration(1, 1, &matrix, {uint32_t(spv::Decoration::MatrixStride), 32});
     ASSERT_FALSE(result.IsEmpty());
     EXPECT_TRUE(result[0]->Is<ast::StrideAttribute>());
     auto* stride_deco = result[0]->As<ast::StrideAttribute>();
@@ -127,7 +133,8 @@
     // relaxed precision f32.
     auto p = parser(std::vector<uint32_t>{});
 
-    auto result = p->ConvertMemberDecoration(1, 1, nullptr, {SpvDecorationRelaxedPrecision});
+    auto result =
+        p->ConvertMemberDecoration(1, 1, nullptr, {uint32_t(spv::Decoration::RelaxedPrecision)});
     EXPECT_TRUE(result.IsEmpty());
     EXPECT_TRUE(p->error().empty());
 }
diff --git a/src/tint/reader/spirv/parser_impl_get_decorations_test.cc b/src/tint/reader/spirv/parser_impl_get_decorations_test.cc
index 929ab1f..cbc384f 100644
--- a/src/tint/reader/spirv/parser_impl_get_decorations_test.cc
+++ b/src/tint/reader/spirv/parser_impl_get_decorations_test.cc
@@ -52,7 +52,7 @@
   )"));
     EXPECT_TRUE(p->BuildAndParseInternalModule());
     auto decorations = p->GetDecorationsFor(10);
-    EXPECT_THAT(decorations, UnorderedElementsAre(Decoration{SpvDecorationBlock}));
+    EXPECT_THAT(decorations, UnorderedElementsAre(Decoration{uint32_t(spv::Decoration::Block)}));
     EXPECT_TRUE(p->error().empty());
     p->SkipDumpingPending(kSkipReason);
 }
@@ -66,7 +66,7 @@
   )"));
     EXPECT_TRUE(p->BuildAndParseInternalModule());
     auto decorations = p->GetDecorationsFor(10);
-    EXPECT_THAT(decorations, UnorderedElementsAre(Decoration{SpvDecorationBlock}));
+    EXPECT_THAT(decorations, UnorderedElementsAre(Decoration{uint32_t(spv::Decoration::Block)}));
     EXPECT_TRUE(p->error().empty());
     p->SkipDumpingPending(kSkipReason);
 }
@@ -80,8 +80,9 @@
   )"));
     EXPECT_TRUE(p->BuildAndParseInternalModule());
     auto decorations = p->GetDecorationsFor(5);
-    EXPECT_THAT(decorations, UnorderedElementsAre(Decoration{SpvDecorationRelaxedPrecision},
-                                                  Decoration{SpvDecorationLocation, 7}));
+    EXPECT_THAT(decorations,
+                UnorderedElementsAre(Decoration{uint32_t(spv::Decoration::RelaxedPrecision)},
+                                     Decoration{uint32_t(spv::Decoration::Location), 7}));
     EXPECT_TRUE(p->error().empty());
     p->SkipDumpingPending(kSkipReason);
 }
@@ -124,7 +125,8 @@
   )"));
     EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
     auto decorations = p->GetDecorationsForMember(10, 0);
-    EXPECT_THAT(decorations, UnorderedElementsAre(Decoration{SpvDecorationRelaxedPrecision}));
+    EXPECT_THAT(decorations,
+                UnorderedElementsAre(Decoration{uint32_t(spv::Decoration::RelaxedPrecision)}));
     EXPECT_TRUE(p->error().empty());
     p->SkipDumpingPending(kSkipReason);
 }
@@ -138,7 +140,8 @@
   )"));
     EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
     auto decorations = p->GetDecorationsForMember(10, 0);
-    EXPECT_THAT(decorations, UnorderedElementsAre(Decoration{SpvDecorationRelaxedPrecision}));
+    EXPECT_THAT(decorations,
+                UnorderedElementsAre(Decoration{uint32_t(spv::Decoration::RelaxedPrecision)}));
     EXPECT_TRUE(p->error().empty());
     p->SkipDumpingPending(kSkipReason);
 }
@@ -154,7 +157,8 @@
   )"));
     EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
     auto decorations = p->GetDecorationsForMember(10, 1);
-    EXPECT_THAT(decorations, UnorderedElementsAre(Decoration{SpvDecorationArrayStride, 12}));
+    EXPECT_THAT(decorations,
+                UnorderedElementsAre(Decoration{uint32_t(spv::Decoration::ArrayStride), 12}));
     EXPECT_TRUE(p->error().empty());
 }
 
@@ -179,11 +183,11 @@
 
     EXPECT_TRUE(p->GetDecorationsForMember(50, 0).empty());
     EXPECT_THAT(p->GetDecorationsForMember(50, 1),
-                UnorderedElementsAre(Decoration{SpvDecorationRelaxedPrecision}));
+                UnorderedElementsAre(Decoration{uint32_t(spv::Decoration::RelaxedPrecision)}));
     EXPECT_THAT(p->GetDecorationsForMember(50, 2),
-                UnorderedElementsAre(Decoration{SpvDecorationColMajor},
-                                     Decoration{SpvDecorationMatrixStride, 8},
-                                     Decoration{SpvDecorationArrayStride, 16}));
+                UnorderedElementsAre(Decoration{uint32_t(spv::Decoration::ColMajor)},
+                                     Decoration{uint32_t(spv::Decoration::MatrixStride), 8},
+                                     Decoration{uint32_t(spv::Decoration::ArrayStride), 16}));
     EXPECT_TRUE(p->error().empty());
 }