Merge commit 'f2803c4a7f58237aa0dd9d39ccc6dea362527b96' into vulkan-cts

Change-Id: Icc934dee248beb972ba7e8084a6ad0c3fa2897ce
diff --git a/.appveyor.yml b/.appveyor.yml
index a50c7c2..669de03 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -42,7 +42,7 @@
   - set NINJA_URL="https://github.com/ninja-build/ninja/releases/download/v1.8.2/ninja-win.zip"
   - appveyor DownloadFile %NINJA_URL% -FileName ninja.zip
   - 7z x ninja.zip -oC:\ninja > nul
-  - set PATH=C:\ninja;%PATH%
+  - set PATH=C:\ninja;C:\Python36;%PATH%
 
 before_build:
   - git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers.git external/spirv-headers
diff --git a/.gitignore b/.gitignore
index 059b18e..e097bab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,5 +21,5 @@
 *~
 
 # C-Lion
-.idea
-cmake-build-debug
\ No newline at end of file
+/.idea/
+/cmake-build-*/
diff --git a/Android.mk b/Android.mk
index 8597c50..7c6a076 100644
--- a/Android.mk
+++ b/Android.mk
@@ -76,10 +76,12 @@
 		source/opt/aggressive_dead_code_elim_pass.cpp \
 		source/opt/basic_block.cpp \
 		source/opt/block_merge_pass.cpp \
+		source/opt/block_merge_util.cpp \
 		source/opt/build_module.cpp \
 		source/opt/cfg.cpp \
 		source/opt/cfg_cleanup_pass.cpp \
 		source/opt/ccp_pass.cpp \
+		source/opt/code_sink.cpp \
 		source/opt/combine_access_chains.cpp \
 		source/opt/common_uniform_elim_pass.cpp \
 		source/opt/compact_ids_pass.cpp \
@@ -90,19 +92,24 @@
 		source/opt/dead_branch_elim_pass.cpp \
 		source/opt/dead_insert_elim_pass.cpp \
 		source/opt/dead_variable_elimination.cpp \
+		source/opt/decompose_initialized_variables_pass.cpp \
 		source/opt/decoration_manager.cpp \
 		source/opt/def_use_manager.cpp \
 		source/opt/dominator_analysis.cpp \
 		source/opt/dominator_tree.cpp \
 		source/opt/eliminate_dead_constant_pass.cpp \
 		source/opt/eliminate_dead_functions_pass.cpp \
+		source/opt/eliminate_dead_functions_util.cpp \
+		source/opt/eliminate_dead_members_pass.cpp \
 		source/opt/feature_manager.cpp \
+		source/opt/fix_storage_class.cpp \
 		source/opt/flatten_decoration_pass.cpp \
 		source/opt/fold.cpp \
 		source/opt/folding_rules.cpp \
 		source/opt/fold_spec_constant_op_and_composite_pass.cpp \
 		source/opt/freeze_spec_constant_value_pass.cpp \
 		source/opt/function.cpp \
+		source/opt/generate_webgpu_initializers_pass.cpp \
 		source/opt/if_conversion.cpp \
 		source/opt/inline_pass.cpp \
 		source/opt/inline_exhaustive_pass.cpp \
@@ -113,6 +120,7 @@
 		source/opt/instrument_pass.cpp \
 		source/opt/ir_context.cpp \
 		source/opt/ir_loader.cpp \
+                source/opt/legalize_vector_shuffle_pass.cpp \
 		source/opt/licm_pass.cpp \
 		source/opt/local_access_chain_convert_pass.cpp \
 		source/opt/local_redundancy_elimination.cpp \
@@ -150,6 +158,7 @@
 		source/opt/simplification_pass.cpp \
 		source/opt/ssa_rewrite_pass.cpp \
 		source/opt/strength_reduction_pass.cpp \
+		source/opt/strip_atomic_counter_memory_pass.cpp \
 		source/opt/strip_debug_info_pass.cpp \
 		source/opt/strip_reflect_info_pass.cpp \
 		source/opt/struct_cfg_analysis.cpp \
diff --git a/BUILD.gn b/BUILD.gn
index 5f0eba9..6c28118 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -305,7 +305,6 @@
 
 static_library("spvtools") {
   deps = [
-    ":spvtools_core_enums_unified1",
     ":spvtools_core_tables_unified1",
     ":spvtools_generators_inc",
     ":spvtools_glsl_tables_glsl1-0",
@@ -376,6 +375,7 @@
   ]
 
   public_deps = [
+    ":spvtools_core_enums_unified1",
     ":spvtools_headers",
   ]
 
@@ -452,6 +452,8 @@
     "source/opt/basic_block.h",
     "source/opt/block_merge_pass.cpp",
     "source/opt/block_merge_pass.h",
+    "source/opt/block_merge_util.cpp",
+    "source/opt/block_merge_util.h",
     "source/opt/build_module.cpp",
     "source/opt/build_module.h",
     "source/opt/ccp_pass.cpp",
@@ -460,6 +462,8 @@
     "source/opt/cfg.h",
     "source/opt/cfg_cleanup_pass.cpp",
     "source/opt/cfg_cleanup_pass.h",
+    "source/opt/code_sink.cpp",
+    "source/opt/code_sink.h",
     "source/opt/combine_access_chains.cpp",
     "source/opt/combine_access_chains.h",
     "source/opt/common_uniform_elim_pass.cpp",
@@ -480,6 +484,8 @@
     "source/opt/dead_insert_elim_pass.h",
     "source/opt/dead_variable_elimination.cpp",
     "source/opt/dead_variable_elimination.h",
+    "source/opt/decompose_initialized_variables_pass.cpp",
+    "source/opt/decompose_initialized_variables_pass.h",
     "source/opt/decoration_manager.cpp",
     "source/opt/decoration_manager.h",
     "source/opt/def_use_manager.cpp",
@@ -492,8 +498,14 @@
     "source/opt/eliminate_dead_constant_pass.h",
     "source/opt/eliminate_dead_functions_pass.cpp",
     "source/opt/eliminate_dead_functions_pass.h",
+    "source/opt/eliminate_dead_functions_util.cpp",
+    "source/opt/eliminate_dead_functions_util.h",
+    "source/opt/eliminate_dead_members_pass.cpp",
+    "source/opt/eliminate_dead_members_pass.h",
     "source/opt/feature_manager.cpp",
     "source/opt/feature_manager.h",
+    "source/opt/fix_storage_class.cpp",
+    "source/opt/fix_storage_class.h",
     "source/opt/flatten_decoration_pass.cpp",
     "source/opt/flatten_decoration_pass.h",
     "source/opt/fold.cpp",
@@ -506,6 +518,8 @@
     "source/opt/freeze_spec_constant_value_pass.h",
     "source/opt/function.cpp",
     "source/opt/function.h",
+    "source/opt/generate_webgpu_initializers_pass.cpp",
+    "source/opt/generate_webgpu_initializers_pass.h",
     "source/opt/if_conversion.cpp",
     "source/opt/if_conversion.h",
     "source/opt/inline_exhaustive_pass.cpp",
@@ -528,6 +542,8 @@
     "source/opt/ir_loader.cpp",
     "source/opt/ir_loader.h",
     "source/opt/iterator.h",
+    "source/opt/legalize_vector_shuffle_pass.cpp",
+    "source/opt/legalize_vector_shuffle_pass.h",
     "source/opt/licm_pass.cpp",
     "source/opt/licm_pass.h",
     "source/opt/local_access_chain_convert_pass.cpp",
@@ -604,6 +620,8 @@
     "source/opt/ssa_rewrite_pass.h",
     "source/opt/strength_reduction_pass.cpp",
     "source/opt/strength_reduction_pass.h",
+    "source/opt/strip_atomic_counter_memory_pass.cpp",
+    "source/opt/strip_atomic_counter_memory_pass.h",
     "source/opt/strip_debug_info_pass.cpp",
     "source/opt/strip_debug_info_pass.h",
     "source/opt/strip_reflect_info_pass.cpp",
@@ -649,132 +667,85 @@
   ]
 }
 
-if (!build_with_chromium) {
-  googletest_dir = spirv_tools_googletest_dir
-
-  config("gtest_config") {
-    include_dirs = [
-      "${googletest_dir}/googletest",
-      "${googletest_dir}/googletest/include",
-    ]
-  }
-
-  static_library("gtest") {
-    testonly = true
+# The tests are scoped to Chromium to avoid needing to write gtest integration.
+# See Chromium's third_party/googletest/BUILD.gn for a complete integration.
+if (build_with_chromium) {
+  test("spvtools_test") {
     sources = [
-      "${googletest_dir}/googletest/src/gtest-all.cc",
+      "test/assembly_context_test.cpp",
+      "test/assembly_format_test.cpp",
+      "test/binary_destroy_test.cpp",
+      "test/binary_endianness_test.cpp",
+      "test/binary_header_get_test.cpp",
+      "test/binary_parse_test.cpp",
+      "test/binary_strnlen_s_test.cpp",
+      "test/binary_to_text.literal_test.cpp",
+      "test/binary_to_text_test.cpp",
+      "test/comment_test.cpp",
+      "test/enum_set_test.cpp",
+      "test/enum_string_mapping_test.cpp",
+      "test/ext_inst.debuginfo_test.cpp",
+      "test/ext_inst.glsl_test.cpp",
+      "test/ext_inst.opencl_test.cpp",
+      "test/fix_word_test.cpp",
+      "test/generator_magic_number_test.cpp",
+      "test/hex_float_test.cpp",
+      "test/immediate_int_test.cpp",
+      "test/libspirv_macros_test.cpp",
+      "test/name_mapper_test.cpp",
+      "test/named_id_test.cpp",
+      "test/opcode_make_test.cpp",
+      "test/opcode_require_capabilities_test.cpp",
+      "test/opcode_split_test.cpp",
+      "test/opcode_table_get_test.cpp",
+      "test/operand_capabilities_test.cpp",
+      "test/operand_pattern_test.cpp",
+      "test/operand_test.cpp",
+      "test/target_env_test.cpp",
+      "test/test_fixture.h",
+      "test/text_advance_test.cpp",
+      "test/text_destroy_test.cpp",
+      "test/text_literal_test.cpp",
+      "test/text_start_new_inst_test.cpp",
+      "test/text_to_binary.annotation_test.cpp",
+      "test/text_to_binary.barrier_test.cpp",
+      "test/text_to_binary.constant_test.cpp",
+      "test/text_to_binary.control_flow_test.cpp",
+      "test/text_to_binary.debug_test.cpp",
+      "test/text_to_binary.device_side_enqueue_test.cpp",
+      "test/text_to_binary.extension_test.cpp",
+      "test/text_to_binary.function_test.cpp",
+      "test/text_to_binary.group_test.cpp",
+      "test/text_to_binary.image_test.cpp",
+      "test/text_to_binary.literal_test.cpp",
+      "test/text_to_binary.memory_test.cpp",
+      "test/text_to_binary.misc_test.cpp",
+      "test/text_to_binary.mode_setting_test.cpp",
+      "test/text_to_binary.pipe_storage_test.cpp",
+      "test/text_to_binary.reserved_sampling_test.cpp",
+      "test/text_to_binary.subgroup_dispatch_test.cpp",
+      "test/text_to_binary.type_declaration_test.cpp",
+      "test/text_to_binary_test.cpp",
+      "test/text_word_get_test.cpp",
+      "test/unit_spirv.cpp",
+      "test/unit_spirv.h",
     ]
-    public_configs = [ ":gtest_config" ]
-  }
 
-  config("gmock_config") {
-    include_dirs = [
-      "${googletest_dir}/googlemock",
-      "${googletest_dir}/googlemock/include",
-      "${googletest_dir}/googletest/include",
-    ]
-    if (is_clang) {
-      # TODO: Can remove this if/when the issue is fixed.
-      # https://github.com/google/googletest/issues/533
-      cflags = [ "-Wno-inconsistent-missing-override" ]
-    }
-  }
-
-  static_library("gmock") {
-    testonly = true
-    sources = [
-      "${googletest_dir}/googlemock/src/gmock-all.cc",
-    ]
-    public_configs = [ ":gmock_config" ]
-  }
-}
-
-test("spvtools_test") {
-  sources = [
-    "test/assembly_context_test.cpp",
-    "test/assembly_format_test.cpp",
-    "test/binary_destroy_test.cpp",
-    "test/binary_endianness_test.cpp",
-    "test/binary_header_get_test.cpp",
-    "test/binary_parse_test.cpp",
-    "test/binary_strnlen_s_test.cpp",
-    "test/binary_to_text.literal_test.cpp",
-    "test/binary_to_text_test.cpp",
-    "test/comment_test.cpp",
-    "test/enum_set_test.cpp",
-    "test/enum_string_mapping_test.cpp",
-    "test/ext_inst.debuginfo_test.cpp",
-    "test/ext_inst.glsl_test.cpp",
-    "test/ext_inst.opencl_test.cpp",
-    "test/fix_word_test.cpp",
-    "test/generator_magic_number_test.cpp",
-    "test/hex_float_test.cpp",
-    "test/immediate_int_test.cpp",
-    "test/libspirv_macros_test.cpp",
-    "test/name_mapper_test.cpp",
-    "test/named_id_test.cpp",
-    "test/opcode_make_test.cpp",
-    "test/opcode_require_capabilities_test.cpp",
-    "test/opcode_split_test.cpp",
-    "test/opcode_table_get_test.cpp",
-    "test/operand_capabilities_test.cpp",
-    "test/operand_pattern_test.cpp",
-    "test/operand_test.cpp",
-    "test/target_env_test.cpp",
-    "test/test_fixture.h",
-    "test/text_advance_test.cpp",
-    "test/text_destroy_test.cpp",
-    "test/text_literal_test.cpp",
-    "test/text_start_new_inst_test.cpp",
-    "test/text_to_binary.annotation_test.cpp",
-    "test/text_to_binary.barrier_test.cpp",
-    "test/text_to_binary.constant_test.cpp",
-    "test/text_to_binary.control_flow_test.cpp",
-    "test/text_to_binary.debug_test.cpp",
-    "test/text_to_binary.device_side_enqueue_test.cpp",
-    "test/text_to_binary.extension_test.cpp",
-    "test/text_to_binary.function_test.cpp",
-    "test/text_to_binary.group_test.cpp",
-    "test/text_to_binary.image_test.cpp",
-    "test/text_to_binary.literal_test.cpp",
-    "test/text_to_binary.memory_test.cpp",
-    "test/text_to_binary.misc_test.cpp",
-    "test/text_to_binary.mode_setting_test.cpp",
-    "test/text_to_binary.pipe_storage_test.cpp",
-    "test/text_to_binary.reserved_sampling_test.cpp",
-    "test/text_to_binary.subgroup_dispatch_test.cpp",
-    "test/text_to_binary.type_declaration_test.cpp",
-    "test/text_to_binary_test.cpp",
-    "test/text_word_get_test.cpp",
-    "test/unit_spirv.cpp",
-    "test/unit_spirv.h",
-  ]
-
-  deps = [
-    ":spvtools",
-    ":spvtools_language_header_unified1",
-    ":spvtools_val",
-  ]
-
-  if (build_with_chromium) {
-    deps += [
+    deps = [
       "//testing/gmock",
       "//testing/gtest",
       "//testing/gtest:gtest_main",
+      ":spvtools",
+      ":spvtools_language_header_unified1",
+      ":spvtools_val",
     ]
-  } else {
-    deps += [
-      ":gmock",
-      ":gtest",
-    ]
-    sources += [ "${googletest_dir}/googletest/src/gtest_main.cc" ]
-  }
 
-  if (is_clang) {
-    cflags_cc = [ "-Wno-self-assign" ]
-  }
+    if (is_clang) {
+      cflags_cc = [ "-Wno-self-assign" ]
+    }
 
-  configs += [ ":spvtools_internal_config" ]
+    configs += [ ":spvtools_internal_config" ]
+  }
 }
 
 if (spirv_tools_standalone) {
diff --git a/CHANGES b/CHANGES
index 7f9008f..bfc70e5 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,20 +1,116 @@
 Revision history for SPIRV-Tools
 
-v2018.7-dev 2018-12-10
+v2019.3-dev 2019-04-03
+ - General:
+   - Updated Python scripts to work for both Python 2 and Python 3.
+   - Add a continuous test that does memory checks using the address sanitizer.
+ - Optimizer
+   - Remove duplicates from list of interface IDs in OpEntryPoint instruction (#2449)
+   - Bindless Validation: Descriptor Initialization Check (#2419)
+   - Add option to validate after each pass (#2462)
+   Fixes:
+   - #2412: Dead memeber elimination should not change input and output variables.
+   - #2405: Fix OpDot folding of half float vectors.
+   - #2391: Dead branch elim should not fold away back edges.
+   - #2441: Removing decorations when doing constant propagation.
+   - #2455: Maintain inst to block mapping in merge return.
+   - #2453: Fix merge return in the face of breaks.
+   - #2456: Handle dead infinite loops in DCE.
+   - #2458: Handle variable pointer in some optimizations.
+   - #2452: Fix dead branch elimination to handle unreachable blocks better.
+ - Validator
+   - Add validation of storage classes for WebGPU (#2446)
+   - Add validation for ExecutionMode in WebGPU (#2443)
+   - Implement WebGPU specific CFG validation (#2386)
+   - Allow NonWritable to target struct members. (#2420)
+   - Allow storage type mismatch for parameter in relaxed addressing mode.
+   - Allow non memory objects as parameter in relaxed addressing mode.
+   - Disallow nested Blocks and buffer blocks (#2410).
+   - Add validation for SPV_NV_cooperative_matrix (#2404)
+   - Add --strip-atomic-counter-memory (#2413)
+   - Check OpSampledImage is only passed into valid instructions (#2467)
+   - Handle function decls in Structured CFG analysis (#2474)
+   - Validate that OpUnreacahble is not statically reachable (#2473)
+   - Add pass to generate needed initializers for WebGPU (#2481)
+   Fixes:
+   - #2439: Add missing DepthGreater case to Fragment only check.
+   - #2168: Disallow BufferBlock on StorageBuffer variables for Vulkan.
+   - #2408: Restrict and Aliased decorations cannot be applied to the same id.
+   - #2447: Improve function call parameter check.
+ - Reduce
+   - Add Pass to remove unreferenced blocks. (#2398)
+   - Allows passing options to the validator. (#2401)
+   - Improve reducer algorithm and other changes (#2472)
+   - Add Pass to remove selections (#2485)
+   Fixes:
+   - #2478: fix loop to selection pass for loops with combined header/continue block
+
+v2019.2 2019-02-20
+ - General:
+   - Support SPV_EXT_physical_storage_buffer
+   - A number of memory leak have been fixed.
+   - Removed use of deprecated Google test macro:
+   - Changed the BUILD.gn to only build tests in Chromium.
+ - Optimizer
+   - Upgrade memory model improvments for modf and frexp.
+   - Add a new pass to move loads closer to their uses: code sinking.
+   - Invalidating the type manager now invalidates the constnat manager.
+   - Expand instrumentation pass for bindless bounds checking to runtime-sized descriptor arrays.
+   - Add a new pass that removes members from structs that are not used: dead member elimination.
+   Fixes:
+   - #2292: Remove undefined behaviour when folding bit shifts.
+   - #2294: Fixes for instrumentation code.
+   - #2293: Fix overflow when folding -INT_MIN.
+   - #2374: Don't merge unreachable blocks when merging blocks.
+ - Validator
+   - Support SPV_KHR_no_integer_wrap and related decorations.
+   - Validate Vulkan rules for OpTypeRuntimeArray.
+   - Validate NonWritable decoration.
+   - Many WebGPU specific validation rules were added.
+   - Validate variable pointer related function call rules.
+   - Better error messages.
+   Fixes:
+   - #2307: Check forwards references in OpTypeArray.
+   - #2315, #2303: Fixed the layout check for relaxed layout.
+   - #1628: Emit an error when an OpSwitch target is not an OpLabel.
+ - Reduce
+   - Added more documentation for spirv-reduce.
+   - Add ability to remove OpPhi instructions.
+   - Add ability to merge two basic blocks.
+   - Add ability to remove unused functions and unused basic blocks.
+   Fixes:
+
+v2019.1 2019-01-07
  - General:
    - Created a new tool called spirv-reduce.
    - Add cmake option to turn off SPIRV_TIMER_ENABLED (#2103)
    - New optimization pass to update the memory model from GLSL450 to VulkanKHR.
+   - Recognize OpTypeAccelerationStructureNV as a type instruction and ray tracing storage classes.
+   - Fix GCC8 build.
+   - Add --target-env flag to spirv-opt.
+   - Add --webgpu-mode flag to run optimizations for webgpu.
+   - The output disassembled line number stead of byte offset in validation errors. (#2091)
  - Optimizer
    - Added the instrumentation passes for bindless validation.
    - Added passes to help preserve OpLine information (#2027)
    - Add basic support for EXT_fragment_invocation_density (#2100)
    - Fix invalid OpPhi generated by merge-return. (#2172)
+   - Constant and type manager have been turned into analysies. (#2251)
    Fixes:
    - #2018: Don't inline functions with a return in a structured CFG contstruct.
    - #2047: Fix bug in folding when volatile stores are present.
    - #2053: Fix check for when folding floating pointer values is allowed.
    - #2130: Don't inline recursive functions.
+   - #2202: Handle multiple edges between two basic blocks in SSA-rewriter.
+   - #2205: Don't unswitch a latch condition during loop unswitch.
+   - #2245: Don't fold branch in loop unswitch.  Run dead branch elimination to fold them.
+   - #2204: Fix eliminate common uniform to place OpPhi instructions correctly.
+   - #2247: Fix type mismatches caused by scalar replacement.
+   - #2248: Fix missing OpPhi after merge return.
+   - #2211: After merge return, fix invalid continue target.
+   - #2210: Fix loop invariant code motion to not place code between merge instruction and branch.
+   - #2258: Handle CompositeInsert with no indices in VDCE.
+   - #2261: Have replace load size handle extact with no index.
  - Validator
    - Changed the naming convention of outputing ids with names in diagnostic messages.
    - Added validation rules for UniformConstant variables in Vulkan.
@@ -32,12 +128,14 @@
    - Allow Float16/Int8 for Vulkan 1.0 (#2153)
    - Check binding annotations in resource variables (#2151, #2167)
    - Validate OpForwardPointer (#2156)
+   - Validate operation for OpSpecConstantOp (#2260)
    Fixes:
    - #2049: Allow InstanceId for NV ray tracing
  - Reduce
    - Initial commit wit a few passes to reduce test cases.
+   - Validation is run after each reduction step.
    Fixes:
- 
+
 
 v2018.6 2018-11-07
  - General:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index df0dd8d..f23f154 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -142,6 +142,8 @@
       if(NOT "${SPIRV_USE_SANITIZER}" STREQUAL "")
         target_compile_options(${TARGET} PRIVATE
           -fsanitize=${SPIRV_USE_SANITIZER})
+        set_target_properties(${TARGET} PROPERTIES
+          LINK_FLAGS -fsanitize=${SPIRV_USE_SANITIZER})
       endif()
       target_compile_options(${TARGET} PRIVATE
          -ftemplate-depth=1024)
@@ -178,7 +180,8 @@
   endmacro()
 endif()
 
-find_host_package(PythonInterp)
+# Tests require Python3
+find_host_package(PythonInterp 3 REQUIRED)
 
 # Check for symbol exports on Linux.
 # At the moment, this check will fail on the OSX build machines for the Android NDK.
diff --git a/DEPS b/DEPS
index f7bac6d..3c5361f 100644
--- a/DEPS
+++ b/DEPS
@@ -11,7 +11,7 @@
   'googletest_revision': '98a0d007d7092b72eea0e501bb9ad17908a1a036',
   'testing_revision': '340252637e2e7c72c0901dcbeeacfff419e19b59',
   're2_revision': '6cf8ccd82dbaab2668e9b13596c68183c9ecd13f',
-  'spirv_headers_revision': 'd5b2e1255f706ce1f88812217e9a554f299848af',
+  'spirv_headers_revision': 'e74c389f81915d0a48d6df1af83c3862c5ad85ab',
 }
 
 deps = {
diff --git a/README.md b/README.md
index 534664f..edc5eca 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,5 @@
 # SPIR-V Tools
 
-[![Build status](https://ci.appveyor.com/api/projects/status/gpue87cesrx3pi0d/branch/master?svg=true)](https://ci.appveyor.com/project/Khronoswebmaster/spirv-tools/branch/master)
-<img alt="Linux" src="kokoro/img/linux.png" width="20px" height="20px" hspace="2px"/>![Linux Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_linux_release.svg)
-<img alt="MacOS" src="kokoro/img/macos.png" width="20px" height="20px" hspace="2px"/>![MacOS Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_macos_release.svg)
-<img alt="Windows" src="kokoro/img/windows.png" width="20px" height="20px" hspace="2px"/>![Windows Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_windows_release.svg)
-
 ## Overview
 
 The SPIR-V Tools project provides an API and commands for processing SPIR-V
@@ -24,6 +19,15 @@
 See the [SPIR-V Registry][spirv-registry] for the SPIR-V specification,
 headers, and XML registry.
 
+## Downloads
+
+[![Build status](https://ci.appveyor.com/api/projects/status/gpue87cesrx3pi0d/branch/master?svg=true)](https://ci.appveyor.com/project/Khronoswebmaster/spirv-tools/branch/master)
+<img alt="Linux" src="kokoro/img/linux.png" width="20px" height="20px" hspace="2px"/>[![Linux Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_linux_clang_release.svg)](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_clang_release.html)
+<img alt="MacOS" src="kokoro/img/macos.png" width="20px" height="20px" hspace="2px"/>[![MacOS Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_macos_clang_release.svg)](https://storage.googleapis.com/spirv-tools/badges/build_link_macos_clang_release.html)
+<img alt="Windows" src="kokoro/img/windows.png" width="20px" height="20px" hspace="2px"/>[![Windows Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_windows_release.svg)](https://storage.googleapis.com/spirv-tools/badges/build_link_windows_vs2017_release.html)
+
+[More downloads](downloads.md)
+
 ## Versioning SPIRV-Tools
 
 See [`CHANGES`](CHANGES) for a high level summary of recent changes, by version.
@@ -47,7 +51,7 @@
 
 * Support for SPIR-V 1.0, 1.1, 1.2, and 1.3
   * Based on SPIR-V syntax described by JSON grammar files in the
-    [SPIRV-Headers](spirv-headers) repository.
+    [SPIRV-Headers](https://github.com/KhronosGroup/SPIRV-Headers) repository.
 * Support for extended instruction sets:
   * GLSL std450 version 1.0 Rev 3
   * OpenCL version 1.0 Rev 2
@@ -128,6 +132,23 @@
 sub-project](https://github.com/KhronosGroup/SPIRV-Tools/projects/2) for
 planned and in-progress work.
 
+
+### Reducer
+
+*Note:* The reducer is still under development.
+
+The reducer simplifies and shrinks a SPIR-V module with respect to a
+user-supplied *interestingness function*.  For example, given a large
+SPIR-V module that cause some SPIR-V compiler to fail with a given
+fatal error message, the reducer could be used to look for a smaller
+version of the module that causes the compiler to fail with the same
+fatal error message.
+
+To suggest an additional capability for the reducer, [file an
+issue](https://github.com/KhronosGroup/SPIRV-Tools/issues]) with
+"Reducer:" as the start of its title.
+
+
 ### Extras
 
 * [Utility filters](#utility-filters)
@@ -252,6 +273,31 @@
 Once the build files have been generated, build using your preferred
 development environment.
 
+### Tools you'll need
+
+For building and testing SPIRV-Tools, the following tools should be
+installed regardless of your OS:
+
+- [CMake](http://www.cmake.org/): for generating compilation targets.  Version
+  2.8.12 or later.
+- [Python 3](http://www.python.org/): for utility scripts and running the test
+suite.
+
+SPIRV-Tools is regularly tested with the the following compilers:
+
+On Linux
+- GCC version 4.8.5
+- Clang version 3.8
+
+On MacOS
+- AppleClang 10.0
+
+On Windows
+- Visual Studio 2015
+- Visual Studio 2017
+
+Other compilers or later versions may work, but they are not tested.
+
 ### CMake options
 
 The following CMake options are supported:
@@ -415,6 +461,18 @@
 * `spirv-val` - the standalone validator
   * `<spirv-dir>/tools/val`
 
+### Reducer tool
+
+The reducer shrinks a SPIR-V binary module, guided by a user-supplied
+*interestingness test*.
+
+This is a work in progress, with initially only shrinks a module in a few ways.
+
+* `spirv-reduce` - the standalone reducer
+  * `<spirv-dir>/tools/reduce`
+
+Run `spirv-reduce --help` to see how to specify interestingness.
+
 ### Control flow dumper tool
 
 The control flow dumper prints the control flow graph for a SPIR-V module as a
diff --git a/downloads.md b/downloads.md
new file mode 100644
index 0000000..9c7d856
--- /dev/null
+++ b/downloads.md
@@ -0,0 +1,14 @@
+# Downloads
+Download the latest builds.
+
+## Release
+| Windows | Linux | MacOS |
+| --- | --- | --- |
+| [MSVC 2017](https://storage.googleapis.com/spirv-tools/badges/build_link_windows_vs2017_release.html) | [clang](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_clang_release.html) | [clang](https://storage.googleapis.com/spirv-tools/badges/build_link_macos_clang_release.html) |
+| | [gcc](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_gcc_release.html) | |
+
+## Debug
+| Windows | Linux | MacOS |
+| --- | --- | --- |
+| [MSVC 2017](https://storage.googleapis.com/spirv-tools/badges/build_link_windows_vs2017_debug.html) | [clang](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_clang_debug.html) | [clang](https://storage.googleapis.com/spirv-tools/badges/build_link_macos_clang_debug.html) |
+| | [gcc](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_gcc_debug.html) | |
diff --git a/examples/cpp-interface/main.cpp b/examples/cpp-interface/main.cpp
index c5354b8..a1e22c7 100644
--- a/examples/cpp-interface/main.cpp
+++ b/examples/cpp-interface/main.cpp
@@ -28,6 +28,7 @@
 
 int main() {
   const std::string source =
+      "         OpCapability Linkage "
       "         OpCapability Shader "
       "         OpMemoryModel Logical GLSL450 "
       "         OpSource GLSL 450 "
@@ -36,8 +37,8 @@
       " %spec = OpSpecConstant %int 0 "
       "%const = OpConstant %int 42";
 
-  spvtools::SpirvTools core(SPV_ENV_VULKAN_1_0);
-  spvtools::Optimizer opt(SPV_ENV_VULKAN_1_0);
+  spvtools::SpirvTools core(SPV_ENV_UNIVERSAL_1_3);
+  spvtools::Optimizer opt(SPV_ENV_UNIVERSAL_1_3);
 
   auto print_msg_to_stderr = [](spv_message_level_t, const char*,
                                 const spv_position_t&, const char* m) {
diff --git a/include/spirv-tools/instrument.hpp b/include/spirv-tools/instrument.hpp
index 69d1ad2..9711b92 100644
--- a/include/spirv-tools/instrument.hpp
+++ b/include/spirv-tools/instrument.hpp
@@ -30,17 +30,17 @@
 
 // Stream Output Buffer Offsets
 //
-// The following values provide 32-bit word offsets into the output buffer
+// The following values provide offsets into the output buffer struct
 // generated by InstrumentPass::GenDebugStreamWrite. This method is utilized
 // by InstBindlessCheckPass.
 //
-// The first word of the debug output buffer contains the next available word
+// The first member of the debug output buffer contains the next available word
 // in the data stream to be written. Shaders will atomically read and update
 // this value so as not to overwrite each others records. This value must be
 // initialized to zero
 static const int kDebugOutputSizeOffset = 0;
 
-// The second word of the output buffer is the start of the stream of records
+// The second member of the output buffer is the start of the stream of records
 // written by the instrumented shaders. Each record represents a validation
 // error. The format of the records is documented below.
 static const int kDebugOutputDataOffset = 1;
@@ -75,8 +75,8 @@
 // error.
 //
 // Vertex Shader Output Record Offsets
-static const int kInstVertOutVertexId = kInstCommonOutCnt;
-static const int kInstVertOutInstanceId = kInstCommonOutCnt + 1;
+static const int kInstVertOutVertexIndex = kInstCommonOutCnt;
+static const int kInstVertOutInstanceIndex = kInstCommonOutCnt + 1;
 
 // Frag Shader Output Record Offsets
 static const int kInstFragOutFragCoordX = kInstCommonOutCnt;
@@ -110,6 +110,16 @@
 // about the validation error.
 //
 // A bindless bounds error will output the index and the bound.
+static const int kInstBindlessBoundsOutDescIndex = kInstStageOutCnt + 1;
+static const int kInstBindlessBoundsOutDescBound = kInstStageOutCnt + 2;
+static const int kInstBindlessBoundsOutCnt = kInstStageOutCnt + 3;
+
+// A bindless uninitialized error will output the index.
+static const int kInstBindlessUninitOutDescIndex = kInstStageOutCnt + 1;
+static const int kInstBindlessUninitOutUnused = kInstStageOutCnt + 2;
+static const int kInstBindlessUninitOutCnt = kInstStageOutCnt + 3;
+
+// DEPRECATED
 static const int kInstBindlessOutDescIndex = kInstStageOutCnt + 1;
 static const int kInstBindlessOutDescBound = kInstStageOutCnt + 2;
 static const int kInstBindlessOutCnt = kInstStageOutCnt + 3;
@@ -121,15 +131,52 @@
 //
 // These are the possible validation error codes.
 static const int kInstErrorBindlessBounds = 0;
+static const int kInstErrorBindlessUninit = 1;
+
+// Direct Input Buffer Offsets
+//
+// The following values provide member offsets into the input buffers
+// consumed by InstrumentPass::GenDebugDirectRead(). This method is utilized
+// by InstBindlessCheckPass.
+//
+// The only object in an input buffer is a runtime array of unsigned
+// integers. Each validation will have its own formatting of this array.
+static const int kDebugInputDataOffset = 0;
 
 // Debug Buffer Bindings
 //
 // These are the bindings for the different buffers which are
 // read or written by the instrumentation passes.
 //
-// This is the output buffer written by InstBindlessCheckPass.
+// This is the output buffer written by InstBindlessCheckPass
+// and possibly other future validations.
 static const int kDebugOutputBindingStream = 0;
 
+// The binding for the input buffer read by InstBindlessCheckPass and
+// possibly other future validations.
+static const int kDebugInputBindingBindless = 1;
+
+// Bindless Validation Input Buffer Format
+//
+// An input buffer for bindless validation consists of a single array of
+// unsigned integers we will call Data[]. This array is formatted as follows.
+//
+// At offset kDebugInputBindlessInitOffset in Data[] is a single uint which
+// gives an offset to the start of the bindless initialization data. More
+// specifically, if the following value is zero, we know that the descriptor at
+// (set = s, binding = b, index = i) is not initialized:
+// Data[ i + Data[ b + Data[ s + Data[ kDebugInputBindlessInitOffset ] ] ] ]
+static const int kDebugInputBindlessInitOffset = 0;
+
+// DEPRECATED
+static const int kDebugInputBindlessOffsetReserved = 0;
+
+// At offset kDebugInputBindlessOffsetLengths is some number of uints which
+// provide the bindless length data. More specifically, the number of
+// descriptors at (set=s, binding=b) is:
+// Data[ Data[ s + kDebugInputBindlessOffsetLengths ] + b ]
+static const int kDebugInputBindlessOffsetLengths = 1;
+
 }  // namespace spvtools
 
 #endif  // INCLUDE_SPIRV_TOOLS_INSTRUMENT_HPP_
diff --git a/include/spirv-tools/libspirv.h b/include/spirv-tools/libspirv.h
index ff7eb6b..f794faf 100644
--- a/include/spirv-tools/libspirv.h
+++ b/include/spirv-tools/libspirv.h
@@ -427,6 +427,8 @@
   SPV_ENV_UNIVERSAL_1_3,  // SPIR-V 1.3 latest revision, no other restrictions.
   SPV_ENV_VULKAN_1_1,     // Vulkan 1.1 latest revision.
   SPV_ENV_WEBGPU_0,       // Work in progress WebGPU 1.0.
+  SPV_ENV_UNIVERSAL_1_4,  // SPIR-V 1.4 latest revision, no other restrictions.
+  SPV_ENV_VULKAN_1_1_SPIRV_1_4,  // Vulkan 1.1 with SPIR-V 1.4 binary.
 } spv_target_env;
 
 // SPIR-V Validator can be parameterized with the following Universal Limits.
@@ -445,6 +447,10 @@
 // Returns a string describing the given SPIR-V target environment.
 SPIRV_TOOLS_EXPORT const char* spvTargetEnvDescription(spv_target_env env);
 
+// Parses s into *env and returns true if successful.  If unparsable, returns
+// false and sets *env to SPV_ENV_UNIVERSAL_1_0.
+SPIRV_TOOLS_EXPORT bool spvParseTargetEnv(const char* s, spv_target_env* env);
+
 // Creates a context object.  Returns null if env is invalid.
 SPIRV_TOOLS_EXPORT spv_context spvContextCreate(spv_target_env env);
 
@@ -498,6 +504,11 @@
 SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetRelaxBlockLayout(
     spv_validator_options options, bool val);
 
+// Records whether the validator should use standard block layout rules for
+// uniform blocks.
+SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetUniformBufferStandardLayout(
+    spv_validator_options options, bool val);
+
 // Records whether the validator should use "scalar" block layout rules.
 // Scalar layout rules are more permissive than relaxed block layout.
 //
@@ -553,14 +564,17 @@
 // Destroys the given reducer options object.
 SPIRV_TOOLS_EXPORT void spvReducerOptionsDestroy(spv_reducer_options options);
 
-// Records the maximum number of reduction steps that should run before the
-// reducer gives up.
+// Sets the maximum number of reduction steps that should run before the reducer
+// gives up.
 SPIRV_TOOLS_EXPORT void spvReducerOptionsSetStepLimit(
     spv_reducer_options options, uint32_t step_limit);
 
-// Sets seed for random number generation.
-SPIRV_TOOLS_EXPORT void spvReducerOptionsSetSeed(spv_reducer_options options,
-                                                 uint32_t seed);
+// Sets the fail-on-validation-error option; if true, the reducer will return
+// kStateInvalid if a reduction step yields a state that fails SPIR-V
+// validation. Otherwise, an invalid state is treated as uninteresting and the
+// reduction backtracks and continues.
+SPIRV_TOOLS_EXPORT void spvReducerOptionsSetFailOnValidationError(
+    spv_reducer_options options, bool fail_on_validation_error);
 
 // Encodes the given SPIR-V assembly text to its binary representation. The
 // length parameter specifies the number of bytes for text. Encoded binary will
diff --git a/include/spirv-tools/libspirv.hpp b/include/spirv-tools/libspirv.hpp
index 9cb5afe..27b87e7 100644
--- a/include/spirv-tools/libspirv.hpp
+++ b/include/spirv-tools/libspirv.hpp
@@ -88,6 +88,12 @@
     spvValidatorOptionsSetRelaxBlockLayout(options_, val);
   }
 
+  // Enables VK_KHR_uniform_buffer_standard_layout when validating standard
+  // uniform layout.  If true, disables scalar block layout rules.
+  void SetUniformBufferStandardLayout(bool val) {
+    spvValidatorOptionsSetUniformBufferStandardLayout(options_, val);
+  }
+
   // Enables VK_EXT_scalar_block_layout when validating standard
   // uniform/storage buffer/push-constant layout.  If true, disables
   // relaxed block layout rules.
@@ -151,16 +157,20 @@
   ~ReducerOptions() { spvReducerOptionsDestroy(options_); }
 
   // Allow implicit conversion to the underlying object.
-  operator spv_reducer_options() const { return options_; }
+  operator spv_reducer_options() const {  // NOLINT(google-explicit-constructor)
+    return options_;
+  }
 
-  // Records the maximum number of reduction steps that should
-  // run before the reducer gives up.
+  // See spvReducerOptionsSetStepLimit.
   void set_step_limit(uint32_t step_limit) {
     spvReducerOptionsSetStepLimit(options_, step_limit);
   }
 
-  // Sets a seed to be used for random number generation.
-  void set_seed(uint32_t seed) { spvReducerOptionsSetSeed(options_, seed); }
+  // See spvReducerOptionsSetFailOnValidationError.
+  void set_fail_on_validation_error(bool fail_on_validation_error) {
+    spvReducerOptionsSetFailOnValidationError(options_,
+                                              fail_on_validation_error);
+  }
 
  private:
   spv_reducer_options options_;
@@ -235,6 +245,9 @@
   bool Validate(const uint32_t* binary, size_t binary_size,
                 spv_validator_options options) const;
 
+  // Was this object successfully constructed.
+  bool IsValid() const;
+
  private:
   struct Impl;  // Opaque struct for holding the data fields used by this class.
   std::unique_ptr<Impl> impl_;  // Unique pointer to implementation data.
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index f8769c9..af9f3e5 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -101,6 +101,16 @@
   // from time to time.
   Optimizer& RegisterSizePasses();
 
+  // Registers passes that have been prescribed for converting from Vulkan to
+  // WebGPU. This sequence of passes is subject to constant review and will
+  // change from time to time.
+  Optimizer& RegisterVulkanToWebGPUPasses();
+
+  // Registers passes that have been prescribed for converting from WebGPU to
+  // Vulkan. This sequence of passes is subject to constant review and will
+  // change from time to time.
+  Optimizer& RegisterWebGPUToVulkanPasses();
+
   // Registers passes that attempt to legalize the generated code.
   //
   // Note: this recipe is specially designed for legalizing SPIR-V. It should be
@@ -194,6 +204,9 @@
   // |out| output stream.
   Optimizer& SetTimeReport(std::ostream* out);
 
+  // Sets the option to validate the module after each pass.
+  Optimizer& SetValidateAfterAll(bool validate);
+
  private:
   struct Impl;                  // Opaque struct for holding internal data.
   std::unique_ptr<Impl> impl_;  // Unique pointer to internal data.
@@ -203,6 +216,13 @@
 // A null pass does nothing to the SPIR-V module to be optimized.
 Optimizer::PassToken CreateNullPass();
 
+// Creates a strip-atomic-counter-memory pass.
+// A strip-atomic-counter-memory pass removes all usages of the
+// AtomicCounterMemory bit in Memory Semantics bitmasks. This bit is a no-op in
+// Vulkan, so isn't needed in that env. And the related capability is not
+// allowed in WebGPU, so it is not allowed in that env.
+Optimizer::PassToken CreateStripAtomicCounterMemoryPass();
+
 // Creates a strip-debug-info pass.
 // A strip-debug-info pass removes all debug instructions (as documented in
 // Section 3.32.2 of the SPIR-V spec) of the SPIR-V module to be optimized.
@@ -221,6 +241,11 @@
 // functions are not needed because they will never be called.
 Optimizer::PassToken CreateEliminateDeadFunctionsPass();
 
+// Creates an eliminate-dead-members pass.
+// An eliminate-dead-members pass will remove all unused members of structures.
+// This will not affect the data layout of the remaining members.
+Optimizer::PassToken CreateEliminateDeadMembersPass();
+
 // Creates a set-spec-constant-default-value pass from a mapping from spec-ids
 // to the default values in the form of string.
 // A set-spec-constant-default-value pass sets the default values for the
@@ -687,10 +712,14 @@
 
 // Create a pass to instrument bindless descriptor checking
 // This pass instruments all bindless references to check that descriptor
-// array indices are inbounds. If the reference is invalid, a record is
-// written to the debug output buffer (if space allows) and a null value is
-// returned. This pass is designed to support bindless validation in the Vulkan
-// validation layers.
+// array indices are inbounds, and if the descriptor indexing extension is
+// enabled, that the descriptor has been initialized. If the reference is
+// invalid, a record is written to the debug output buffer (if space allows)
+// and a null value is returned. This pass is designed to support bindless
+// validation in the Vulkan validation layers.
+//
+// TODO(greg-lunarg): Add support for buffer references. Currently only does
+// checking for image references.
 //
 // Dead code elimination should be run after this pass as the original,
 // potentially invalid code is not removed and could cause undefined behavior,
@@ -706,10 +735,12 @@
 // The instrumentation will read and write buffers in debug
 // descriptor set |desc_set|. It will write |shader_id| in each output record
 // to identify the shader module which generated the record.
-//
-// TODO(greg-lunarg): Add support for vk_ext_descriptor_indexing.
-Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
-                                                 uint32_t shader_id);
+// |input_length_enable| controls instrumentation of runtime descriptor array
+// references, and |input_init_enable| controls instrumentation of descriptor
+// initialization checking, both of which require input buffer support.
+Optimizer::PassToken CreateInstBindlessCheckPass(
+    uint32_t desc_set, uint32_t shader_id, bool input_length_enable = false,
+    bool input_init_enable = false);
 
 // Create a pass to upgrade to the VulkanKHR memory model.
 // This pass upgrades the Logical GLSL450 memory model to Logical VulkanKHR.
@@ -717,6 +748,30 @@
 // conform to that model's requirements.
 Optimizer::PassToken CreateUpgradeMemoryModelPass();
 
+// Create a pass to do code sinking.  Code sinking is a transformation
+// where an instruction is moved into a more deeply nested construct.
+Optimizer::PassToken CreateCodeSinkingPass();
+
+// Create a pass to adds initializers for OpVariable calls that require them
+// in WebGPU. Currently this pass naively initializes variables that are
+// missing an initializer with a null value. In the future it may initialize
+// variables to the first value stored in them, if that is a constant.
+Optimizer::PassToken CreateGenerateWebGPUInitializersPass();
+
+// Create a pass to fix incorrect storage classes.  In order to make code
+// generation simpler, DXC may generate code where the storage classes do not
+// match up correctly.  This pass will fix the errors that it can.
+Optimizer::PassToken CreateFixStorageClassPass();
+
+// Create a pass to legalize OpVectorShuffle operands going into WebGPU. WebGPU
+// forbids using 0xFFFFFFFF, which indicates an undefined result, so this pass
+// converts those literals to 0.
+Optimizer::PassToken CreateLegalizeVectorShufflePass();
+
+// Create a pass to decompose initialized variables into a seperate variable
+// declaration and an initial store.
+Optimizer::PassToken CreateDecomposeInitializedVariablesPass();
+
 }  // namespace spvtools
 
 #endif  // INCLUDE_SPIRV_TOOLS_OPTIMIZER_HPP_
diff --git a/kokoro/linux-clang-asan/build.sh b/kokoro/linux-clang-asan/build.sh
new file mode 100644
index 0000000..8f86e6e
--- /dev/null
+++ b/kokoro/linux-clang-asan/build.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+# Copyright (c) 2019 Google LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Linux Build Script.
+
+# Fail on any error.
+set -e
+# Display commands being run.
+set -x
+
+SCRIPT_DIR=`dirname "$BASH_SOURCE"`
+source $SCRIPT_DIR/../scripts/linux/build.sh ASAN clang
diff --git a/kokoro/linux-clang-asan/continuous.cfg b/kokoro/linux-clang-asan/continuous.cfg
new file mode 100644
index 0000000..3a98fc7
--- /dev/null
+++ b/kokoro/linux-clang-asan/continuous.cfg
@@ -0,0 +1,16 @@
+# Copyright (c) 2019 Google LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Continuous build configuration.
+build_file: "SPIRV-Tools/kokoro/linux-clang-asan/build.sh"
diff --git a/kokoro/linux-clang-asan/presubmit.cfg b/kokoro/linux-clang-asan/presubmit.cfg
new file mode 100644
index 0000000..ceac44b
--- /dev/null
+++ b/kokoro/linux-clang-asan/presubmit.cfg
@@ -0,0 +1,16 @@
+# Copyright (c) 2019 Google LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Presubmit build configuration.
+build_file: "SPIRV-Tools/kokoro/linux-clang-asan/build.sh"
diff --git a/kokoro/linux-clang-debug/continuous.cfg b/kokoro/linux-clang-debug/continuous.cfg
index e92f059..3350f3b 100644
--- a/kokoro/linux-clang-debug/continuous.cfg
+++ b/kokoro/linux-clang-debug/continuous.cfg
@@ -14,3 +14,9 @@
 
 # Continuous build configuration.
 build_file: "SPIRV-Tools/kokoro/linux-clang-debug/build.sh"
+
+action {
+  define_artifacts {
+    regex: "install.tgz"
+  }
+}
diff --git a/kokoro/linux-clang-release/continuous.cfg b/kokoro/linux-clang-release/continuous.cfg
index 687434a..8b075c6 100644
--- a/kokoro/linux-clang-release/continuous.cfg
+++ b/kokoro/linux-clang-release/continuous.cfg
@@ -14,3 +14,9 @@
 
 # Continuous build configuration.
 build_file: "SPIRV-Tools/kokoro/linux-clang-release/build.sh"
+
+action {
+  define_artifacts {
+    regex: "install.tgz"
+  }
+}
diff --git a/kokoro/linux-gcc-debug/continuous.cfg b/kokoro/linux-gcc-debug/continuous.cfg
index 4f8418d..d9579d5 100644
--- a/kokoro/linux-gcc-debug/continuous.cfg
+++ b/kokoro/linux-gcc-debug/continuous.cfg
@@ -14,3 +14,9 @@
 
 # Continuous build configuration.
 build_file: "SPIRV-Tools/kokoro/linux-gcc-debug/build.sh"
+
+action {
+  define_artifacts {
+    regex: "install.tgz"
+  }
+}
diff --git a/kokoro/linux-gcc-release/continuous.cfg b/kokoro/linux-gcc-release/continuous.cfg
index 41a0024..ead07bf 100644
--- a/kokoro/linux-gcc-release/continuous.cfg
+++ b/kokoro/linux-gcc-release/continuous.cfg
@@ -14,3 +14,9 @@
 
 # Continuous build configuration.
 build_file: "SPIRV-Tools/kokoro/linux-gcc-release/build.sh"
+
+action {
+  define_artifacts {
+    regex: "install.tgz"
+  }
+}
diff --git a/kokoro/macos-clang-debug/continuous.cfg b/kokoro/macos-clang-debug/continuous.cfg
index 84aaa5c..f5f274a 100644
--- a/kokoro/macos-clang-debug/continuous.cfg
+++ b/kokoro/macos-clang-debug/continuous.cfg
@@ -14,3 +14,9 @@
 
 # Continuous build configuration.
 build_file: "SPIRV-Tools/kokoro/macos-clang-debug/build.sh"
+
+action {
+  define_artifacts {
+    regex: "install.tgz"
+  }
+}
diff --git a/kokoro/macos-clang-release/continuous.cfg b/kokoro/macos-clang-release/continuous.cfg
index a8e23a7..710185e 100644
--- a/kokoro/macos-clang-release/continuous.cfg
+++ b/kokoro/macos-clang-release/continuous.cfg
@@ -14,3 +14,9 @@
 
 # Continuous build configuration.
 build_file: "SPIRV-Tools/kokoro/macos-clang-release/build.sh"
+
+action {
+  define_artifacts {
+    regex: "install.tgz"
+  }
+}
diff --git a/kokoro/scripts/linux/build.sh b/kokoro/scripts/linux/build.sh
index d457539..e89d2ea 100644
--- a/kokoro/scripts/linux/build.sh
+++ b/kokoro/scripts/linux/build.sh
@@ -31,8 +31,7 @@
 CMAKE_C_CXX_COMPILER=""
 if [ $COMPILER = "clang" ]
 then
-  sudo ln -s /usr/bin/clang-3.8 /usr/bin/clang
-  sudo ln -s /usr/bin/clang++-3.8 /usr/bin/clang++
+  PATH=/usr/lib/llvm-3.8/bin:$PATH
   CMAKE_C_CXX_COMPILER="-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++"
 fi
 
@@ -47,8 +46,8 @@
 ADDITIONAL_CMAKE_FLAGS=""
 if [ $CONFIG = "ASAN" ]
 then
-  ADDITIONAL_CMAKE_FLAGS="-DCMAKE_CXX_FLAGS=-fsanitize=address -DCMAKE_C_FLAGS=-fsanitize=address"
-  export ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer-3.4
+  ADDITIONAL_CMAKE_FLAGS="SPIRV_USE_SANITIZER=address"
+  [ $COMPILER = "clang" ] || { echo "$CONFIG requires clang"; exit 1; }
 elif [ $CONFIG = "COVERAGE" ]
 then
   ADDITIONAL_CMAKE_FLAGS="-DENABLE_CODE_COVERAGE=ON"
@@ -78,7 +77,7 @@
 # Invoke the build.
 BUILD_SHA=${KOKORO_GITHUB_COMMIT:-$KOKORO_GITHUB_PULL_REQUEST_COMMIT}
 echo $(date): Starting build...
-cmake -GNinja -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_INSTALL_PREFIX=install -DRE2_BUILD_TESTING=OFF $ADDITIONAL_CMAKE_FLAGS $CMAKE_C_CXX_COMPILER ..
+cmake -DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 -GNinja -DCMAKE_INSTALL_PREFIX=$KOKORO_ARTIFACTS_DIR/install -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DRE2_BUILD_TESTING=OFF $ADDITIONAL_CMAKE_FLAGS $CMAKE_C_CXX_COMPILER ..
 
 echo $(date): Build everything...
 ninja
@@ -98,3 +97,7 @@
 fi
 echo $(date): ctest completed.
 
+# Package the build.
+ninja install
+cd $KOKORO_ARTIFACTS_DIR
+tar czf install.tgz install
diff --git a/kokoro/scripts/macos/build.sh b/kokoro/scripts/macos/build.sh
index a7f0453..98e6366 100644
--- a/kokoro/scripts/macos/build.sh
+++ b/kokoro/scripts/macos/build.sh
@@ -41,7 +41,15 @@
 # Invoke the build.
 BUILD_SHA=${KOKORO_GITHUB_COMMIT:-$KOKORO_GITHUB_PULL_REQUEST_COMMIT}
 echo $(date): Starting build...
-cmake -GNinja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=$BUILD_TYPE ..
+# We need Python 3.  At the moment python3.7 is the newest Python on Kokoro.
+cmake \
+  -GNinja \
+  -DCMAKE_INSTALL_PREFIX=$KOKORO_ARTIFACTS_DIR/install \
+  -DPYTHON_EXECUTABLE:FILEPATH=/usr/local/bin/python3.7 \
+  -DCMAKE_C_COMPILER=clang \
+  -DCMAKE_CXX_COMPILER=clang++ \
+  -DCMAKE_BUILD_TYPE=$BUILD_TYPE \
+  ..
 
 echo $(date): Build everything...
 ninja
@@ -51,3 +59,8 @@
 ctest -j4 --output-on-failure --timeout 300
 echo $(date): ctest completed.
 
+# Package the build.
+ninja install
+cd $KOKORO_ARTIFACTS_DIR
+tar czf install.tgz install
+
diff --git a/kokoro/scripts/windows/build.bat b/kokoro/scripts/windows/build.bat
index a2472fb..0532118 100644
--- a/kokoro/scripts/windows/build.bat
+++ b/kokoro/scripts/windows/build.bat
@@ -21,8 +21,8 @@
 set BUILD_TYPE=%1
 set VS_VERSION=%2
 
-:: Force usage of python 2.7 rather than 3.6
-set PATH=C:\python27;%PATH%
+:: Force usage of python 3.6
+set PATH=C:\python36;%PATH%
 
 cd %SRC%
 git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers external/spirv-headers
@@ -58,33 +58,45 @@
   set BUILD_SHA=%KOKORO_GITHUB_COMMIT%
 )
 
+set CMAKE_FLAGS=-DCMAKE_INSTALL_PREFIX=%KOKORO_ARTIFACTS_DIR%\install -GNinja -DSPIRV_BUILD_COMPRESSION=ON -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DRE2_BUILD_TESTING=OFF -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe
+
 :: Skip building tests for VS2013
 if %VS_VERSION% == 2013 (
-  cmake -GNinja -DSPIRV_SKIP_TESTS=ON -DSPIRV_BUILD_COMPRESSION=ON -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DCMAKE_INSTALL_PREFIX=install -DRE2_BUILD_TESTING=OFF -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe ..
-) else (
-  cmake -GNinja -DSPIRV_BUILD_COMPRESSION=ON -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DCMAKE_INSTALL_PREFIX=install -DRE2_BUILD_TESTING=OFF -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe ..
+  set CMAKE_FLAGS=%CMAKE_FLAGS% -DSPIRV_SKIP_TESTS=ON
 )
 
-if %ERRORLEVEL% GEQ 1 exit /b %ERRORLEVEL%
+cmake %CMAKE_FLAGS% ..
+
+if %ERRORLEVEL% NEQ 0 exit /b %ERRORLEVEL%
 
 echo "Build everything... %DATE% %TIME%"
 ninja
-if %ERRORLEVEL% GEQ 1 exit /b %ERRORLEVEL%
+if %ERRORLEVEL% NEQ 0 exit /b %ERRORLEVEL%
 echo "Build Completed %DATE% %TIME%"
 
+:: This lets us use !ERRORLEVEL! inside an IF ... () and get the actual error at that point.
+setlocal ENABLEDELAYEDEXPANSION
+
 :: ################################################
 :: Run the tests (We no longer run tests on VS2013)
 :: ################################################
-if NOT %VS_VERSION% == 2013 (
-  echo "Running Tests... %DATE% %TIME%"
+echo "Running Tests... %DATE% %TIME%"
+if %VS_VERSION% NEQ 2013 (
   ctest -C %BUILD_TYPE% --output-on-failure --timeout 300
-  if %ERRORLEVEL% GEQ 1 exit /b %ERRORLEVEL%
-  echo "Tests Completed %DATE% %TIME%"
+  if !ERRORLEVEL! NEQ 0 exit /b !ERRORLEVEL!
 )
+echo "Tests Completed %DATE% %TIME%"
+
+:: ################################################
+:: Install and package.
+:: ################################################
+ninja install
+cd %KOKORO_ARTIFACTS_DIR%
+zip -r install.zip install
 
 :: Clean up some directories.
 rm -rf %SRC%\build
 rm -rf %SRC%\external
 
-exit /b %ERRORLEVEL%
+exit /b 0
 
diff --git a/kokoro/shaderc-smoketest/build.sh b/kokoro/shaderc-smoketest/build.sh
index 638ca8c..0856c9b 100644
--- a/kokoro/shaderc-smoketest/build.sh
+++ b/kokoro/shaderc-smoketest/build.sh
@@ -37,7 +37,7 @@
 
 # Get shaderc dependencies. Link the appropriate SPIRV-Tools.
 git clone https://github.com/google/googletest.git
-git clone https://github.com/google/glslang.git
+git clone https://github.com/KhronosGroup/glslang.git
 ln -s $GITHUB_DIR/SPIRV-Tools spirv-tools
 git clone https://github.com/KhronosGroup/SPIRV-Headers.git spirv-headers
 git clone https://github.com/google/re2
diff --git a/kokoro/windows-msvc-2017-debug/continuous.cfg b/kokoro/windows-msvc-2017-debug/continuous.cfg
index b842c30..25c5e11 100644
--- a/kokoro/windows-msvc-2017-debug/continuous.cfg
+++ b/kokoro/windows-msvc-2017-debug/continuous.cfg
@@ -14,3 +14,9 @@
 
 # Continuous build configuration.
 build_file: "SPIRV-Tools/kokoro/windows-msvc-2017-debug/build.bat"
+
+action {
+  define_artifacts {
+    regex: "install.zip"
+  }
+}
diff --git a/kokoro/windows-msvc-2017-release/continuous.cfg b/kokoro/windows-msvc-2017-release/continuous.cfg
index 7b8c2ff..a9ac6ec 100644
--- a/kokoro/windows-msvc-2017-release/continuous.cfg
+++ b/kokoro/windows-msvc-2017-release/continuous.cfg
@@ -14,3 +14,9 @@
 
 # Continuous build configuration.
 build_file: "SPIRV-Tools/kokoro/windows-msvc-2017-release/build.bat"
+
+action {
+  define_artifacts {
+    regex: "install.zip"
+  }
+}
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index efd6330..1f96018 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -101,7 +101,7 @@
   list(APPEND EXTINST_CPP_DEPENDS ${GRAMMAR_INC_FILE})
 endmacro(spvtools_opencl_tables)
 
-macro(spvtools_vendor_tables VENDOR_TABLE)
+macro(spvtools_vendor_tables VENDOR_TABLE SHORT_NAME)
   set(INSTS_FILE "${spirv-tools_BINARY_DIR}/${VENDOR_TABLE}.insts.inc")
   set(GRAMMAR_FILE "${spirv-tools_SOURCE_DIR}/source/extinst.${VENDOR_TABLE}.grammar.json")
   add_custom_command(OUTPUT ${INSTS_FILE}
@@ -110,9 +110,9 @@
       --vendor-insts-output=${INSTS_FILE}
     DEPENDS ${GRAMMAR_PROCESSING_SCRIPT} ${GRAMMAR_FILE}
     COMMENT "Generate extended instruction tables for ${VENDOR_TABLE}.")
-  list(APPEND EXTINST_CPP_DEPENDS ${INSTS_FILE})
-  add_custom_target(spirv-tools-${VENDOR_TABLE} DEPENDS ${INSTS_FILE})
-  set_property(TARGET spirv-tools-${VENDOR_TABLE} PROPERTY FOLDER "SPIRV-Tools build")
+  add_custom_target(spv-tools-${SHORT_NAME} DEPENDS ${INSTS_FILE})
+  set_property(TARGET spv-tools-${SHORT_NAME} PROPERTY FOLDER "SPIRV-Tools build")
+  list(APPEND EXTINST_CPP_DEPENDS spv-tools-${SHORT_NAME})
 endmacro(spvtools_vendor_tables)
 
 macro(spvtools_extinst_lang_headers NAME GRAMMAR_FILE)
@@ -125,20 +125,20 @@
       --extinst-output-base=${OUTBASE}
     DEPENDS ${LANG_HEADER_PROCESSING_SCRIPT} ${GRAMMAR_FILE}
     COMMENT "Generate language specific header for ${NAME}.")
-  list(APPEND EXTINST_CPP_DEPENDS ${OUT_H})
   add_custom_target(spirv-tools-header-${NAME} DEPENDS ${OUT_H})
   set_property(TARGET spirv-tools-header-${NAME} PROPERTY FOLDER "SPIRV-Tools build")
+  list(APPEND EXTINST_CPP_DEPENDS spirv-tools-header-${NAME})
 endmacro(spvtools_extinst_lang_headers)
 
 spvtools_core_tables("unified1")
 spvtools_enum_string_mapping("unified1")
 spvtools_opencl_tables("unified1")
 spvtools_glsl_tables("unified1")
-spvtools_vendor_tables("spv-amd-shader-explicit-vertex-parameter")
-spvtools_vendor_tables("spv-amd-shader-trinary-minmax")
-spvtools_vendor_tables("spv-amd-gcn-shader")
-spvtools_vendor_tables("spv-amd-shader-ballot")
-spvtools_vendor_tables("debuginfo")
+spvtools_vendor_tables("spv-amd-shader-explicit-vertex-parameter" "spv-amd-sevp")
+spvtools_vendor_tables("spv-amd-shader-trinary-minmax" "spv-amd-stm")
+spvtools_vendor_tables("spv-amd-gcn-shader" "spv-amd-gs")
+spvtools_vendor_tables("spv-amd-shader-ballot" "spv-amd-sb")
+spvtools_vendor_tables("debuginfo" "debuginfo")
 spvtools_extinst_lang_headers("DebugInfo" ${DEBUGINFO_GRAMMAR_JSON_FILE})
 
 spvtools_vimsyntax("unified1" "1.0")
@@ -159,36 +159,18 @@
 # The following .cpp files include the above generated .inc files.
 # Add those .inc files as their dependencies.
 #
-# Why using such an awkward way?
-# * If we use add_custom_target() to define a target to generate all .inc files
-#   and let ${SPIRV_TOOLS} depend on it, then we need to run ninja twice every
-#   time the grammar is updated: the first time is for generating those .inc
-#   files, and the second time is for rebuilding .cpp files, when ninja finds
-#   out that .inc files are updated.
-# * If we use add_custom_command() with PRE_BUILD, then the grammar processing
-#   script will always run no matter whether the grammar is updated.
-# * add_dependencies() is used to add *target* dependencies to a target.
-# * The following solution only generates .inc files when the script or the
-#   grammar files is updated, and in a single ninja run.
-set_source_files_properties(
-  ${CMAKE_CURRENT_SOURCE_DIR}/opcode.cpp
-  PROPERTIES OBJECT_DEPENDS "${OPCODE_CPP_DEPENDS}")
-set_source_files_properties(
-  ${CMAKE_CURRENT_SOURCE_DIR}/operand.cpp
-  PROPERTIES OBJECT_DEPENDS "${OPERAND_CPP_DEPENDS}")
-set_source_files_properties(
-  ${CMAKE_CURRENT_SOURCE_DIR}/ext_inst.cpp
-  PROPERTIES OBJECT_DEPENDS "${EXTINST_CPP_DEPENDS}")
-set_source_files_properties(
-  ${CMAKE_CURRENT_SOURCE_DIR}/enum_string_mapping.cpp
-  PROPERTIES OBJECT_DEPENDS "${ENUM_STRING_MAPPING_CPP_DEPENDS}")
+# We need to wrap the .inc files with a custom target to avoid problems when
+# multiple targets depend on the same custom command.
+add_custom_target(core_tables
+  DEPENDS ${OPCODE_CPP_DEPENDS} ${OPERAND_CPP_DEPENDS})
+add_custom_target(enum_string_mapping
+  DEPENDS ${EXTENSION_H_DEPENDS} ${ENUM_STRING_MAPPING_CPP_DEPENDS})
+add_custom_target(extinst_tables
+  DEPENDS ${EXTINST_CPP_DEPENDS})
 
 set_source_files_properties(
   ${CMAKE_CURRENT_SOURCE_DIR}/extensions.h
   PROPERTIES HEADER_FILE_ONLY TRUE)
-set_source_files_properties(
-  ${CMAKE_CURRENT_SOURCE_DIR}/extensions.h
-  PROPERTIES OBJECT_DEPENDS "${EXTENSION_H_DEPENDS}")
 
 set(SPIRV_TOOLS_BUILD_VERSION_INC
   ${spirv-tools_BINARY_DIR}/build-version.inc)
@@ -361,6 +343,7 @@
   )
 set_property(TARGET ${SPIRV_TOOLS} PROPERTY FOLDER "SPIRV-Tools libraries")
 spvtools_check_symbol_exports(${SPIRV_TOOLS})
+add_dependencies( ${SPIRV_TOOLS} core_tables enum_string_mapping extinst_tables )
 
 add_library(${SPIRV_TOOLS}-shared SHARED ${SPIRV_SOURCES})
 spvtools_default_compile_options(${SPIRV_TOOLS}-shared)
@@ -376,6 +359,15 @@
   PRIVATE SPIRV_TOOLS_IMPLEMENTATION
   PUBLIC SPIRV_TOOLS_SHAREDLIB
 )
+add_dependencies( ${SPIRV_TOOLS}-shared core_tables enum_string_mapping extinst_tables )
+
+if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux")
+  find_library(LIBRT rt)
+  if(LIBRT)
+    target_link_libraries(${SPIRV_TOOLS} ${LIBRT})
+    target_link_libraries(${SPIRV_TOOLS}-shared ${LIBRT})
+  endif()
+endif()
 
 if(ENABLE_SPIRV_TOOLS_INSTALL)
   install(TARGETS ${SPIRV_TOOLS} ${SPIRV_TOOLS}-shared
diff --git a/source/assembly_grammar.cpp b/source/assembly_grammar.cpp
index 4d98e3d..79f18ee 100644
--- a/source/assembly_grammar.cpp
+++ b/source/assembly_grammar.cpp
@@ -154,10 +154,11 @@
     CASE(InBoundsAccessChain),
     CASE(PtrAccessChain),
     CASE(InBoundsPtrAccessChain),
+    CASE(CooperativeMatrixLengthNV)
 };
 
-// The 59 is determined by counting the opcodes listed in the spec.
-static_assert(59 == sizeof(kOpSpecConstantOpcodes)/sizeof(kOpSpecConstantOpcodes[0]),
+// The 60 is determined by counting the opcodes listed in the spec.
+static_assert(60 == sizeof(kOpSpecConstantOpcodes)/sizeof(kOpSpecConstantOpcodes[0]),
               "OpSpecConstantOp opcode table is incomplete");
 #undef CASE
 // clang-format on
diff --git a/source/binary.cpp b/source/binary.cpp
index 6604d80..636dac8 100644
--- a/source/binary.cpp
+++ b/source/binary.cpp
@@ -123,8 +123,8 @@
   // returned object will be propagated to the current parse's diagnostic
   // object.
   spvtools::DiagnosticStream diagnostic(spv_result_t error) {
-    return spvtools::DiagnosticStream({0, 0, _.word_index}, consumer_, "",
-                                      error);
+    return spvtools::DiagnosticStream({0, 0, _.instruction_count}, consumer_,
+                                      "", error);
   }
 
   // Returns a diagnostic stream object with the default parse error code.
@@ -179,6 +179,7 @@
           num_words(num_words_arg),
           diagnostic(diagnostic_arg),
           word_index(0),
+          instruction_count(0),
           endian(),
           requires_endian_conversion(false) {
       // Temporary storage for parser state within a single instruction.
@@ -192,6 +193,7 @@
     size_t num_words;            // Number of words in the module.
     spv_diagnostic* diagnostic;  // Where diagnostics go.
     size_t word_index;           // The current position in words.
+    size_t instruction_count;    // The count of processed instructions
     spv_endianness_t endian;     // The endianness of the binary.
     // Is the SPIR-V binary in a different endiannes from the host native
     // endianness?
@@ -269,6 +271,8 @@
 }
 
 spv_result_t Parser::parseInstruction() {
+  _.instruction_count++;
+
   // The zero values for all members except for opcode are the
   // correct initial values.
   spv_parsed_instruction_t inst = {};
diff --git a/source/ext_inst.cpp b/source/ext_inst.cpp
index 08c775e..1198f76 100644
--- a/source/ext_inst.cpp
+++ b/source/ext_inst.cpp
@@ -30,6 +30,7 @@
 #include "glsl.std.450.insts.inc"
 #include "opencl.std.insts.inc"
 
+#include "spirv-tools/libspirv.h"
 #include "spv-amd-gcn-shader.insts.inc"
 #include "spv-amd-shader-ballot.insts.inc"
 #include "spv-amd-shader-explicit-vertex-parameter.insts.inc"
@@ -80,7 +81,9 @@
     case SPV_ENV_OPENGL_4_5:
     case SPV_ENV_UNIVERSAL_1_3:
     case SPV_ENV_VULKAN_1_1:
+    case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
     case SPV_ENV_WEBGPU_0:
+    case SPV_ENV_UNIVERSAL_1_4:
       *pExtInstTable = &kTable_1_0;
       return SPV_SUCCESS;
     default:
diff --git a/source/libspirv.cpp b/source/libspirv.cpp
index b5fe897..a1ed11d 100644
--- a/source/libspirv.cpp
+++ b/source/libspirv.cpp
@@ -128,4 +128,6 @@
   return valid;
 }
 
+bool SpirvTools::IsValid() const { return impl_->context != nullptr; }
+
 }  // namespace spvtools
diff --git a/source/opcode.cpp b/source/opcode.cpp
index 78c2386..ddf2deb 100644
--- a/source/opcode.cpp
+++ b/source/opcode.cpp
@@ -260,6 +260,7 @@
     case SpvOpTypeMatrix:
     case SpvOpTypeArray:
     case SpvOpTypeStruct:
+    case SpvOpTypeCooperativeMatrixNV:
       return true;
     default:
       return false;
@@ -325,6 +326,7 @@
     case SpvOpTypePipeStorage:
     case SpvOpTypeNamedBarrier:
     case SpvOpTypeAccelerationStructureNV:
+    case SpvOpTypeCooperativeMatrixNV:
       return true;
     default:
       // In particular, OpTypeForwardPointer does not generate a type,
@@ -603,3 +605,35 @@
       return false;
   }
 }
+
+std::vector<uint32_t> spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpMemoryBarrier:
+      return {1};
+    case SpvOpAtomicStore:
+    case SpvOpControlBarrier:
+    case SpvOpAtomicFlagClear:
+    case SpvOpMemoryNamedBarrier:
+      return {2};
+    case SpvOpAtomicLoad:
+    case SpvOpAtomicExchange:
+    case SpvOpAtomicIIncrement:
+    case SpvOpAtomicIDecrement:
+    case SpvOpAtomicIAdd:
+    case SpvOpAtomicISub:
+    case SpvOpAtomicSMin:
+    case SpvOpAtomicUMin:
+    case SpvOpAtomicSMax:
+    case SpvOpAtomicUMax:
+    case SpvOpAtomicAnd:
+    case SpvOpAtomicOr:
+    case SpvOpAtomicXor:
+    case SpvOpAtomicFlagTestAndSet:
+      return {4};
+    case SpvOpAtomicCompareExchange:
+    case SpvOpAtomicCompareExchangeWeak:
+      return {4, 5};
+    default:
+      return {};
+  }
+}
diff --git a/source/opcode.h b/source/opcode.h
index 76f9a0e..ed64f1b 100644
--- a/source/opcode.h
+++ b/source/opcode.h
@@ -133,4 +133,8 @@
 // Returns true if the given opcode is a debug instruction.
 bool spvOpcodeIsDebug(SpvOp opcode);
 
+// Returns a vector containing the indices of the memory semantics <id>
+// operands for |opcode|.
+std::vector<uint32_t> spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode);
+
 #endif  // SOURCE_OPCODE_H_
diff --git a/source/operand.cpp b/source/operand.cpp
index 923074e..00dc53d 100644
--- a/source/operand.cpp
+++ b/source/operand.cpp
@@ -481,6 +481,9 @@
     case SpvOpTypeForwardPointer:
       out = [](unsigned index) { return index == 0; };
       break;
+    case SpvOpTypeArray:
+      out = [](unsigned index) { return index == 1; };
+      break;
     default:
       out = [](unsigned) { return false; };
       break;
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt
index 96ee8b3..b02485a 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -15,10 +15,12 @@
   aggressive_dead_code_elim_pass.h
   basic_block.h
   block_merge_pass.h
+  block_merge_util.h
   build_module.h
   ccp_pass.h
   cfg_cleanup_pass.h
   cfg.h
+  code_sink.h
   combine_access_chains.h
   common_uniform_elim_pass.h
   compact_ids_pass.h
@@ -29,19 +31,24 @@
   dead_branch_elim_pass.h
   dead_insert_elim_pass.h
   dead_variable_elimination.h
+  decompose_initialized_variables_pass.h
   decoration_manager.h
   def_use_manager.h
   dominator_analysis.h
   dominator_tree.h
   eliminate_dead_constant_pass.h
   eliminate_dead_functions_pass.h
+  eliminate_dead_functions_util.h
+  eliminate_dead_members_pass.h
   feature_manager.h
+  fix_storage_class.h
   flatten_decoration_pass.h
   fold.h
   folding_rules.h
   fold_spec_constant_op_and_composite_pass.h
   freeze_spec_constant_value_pass.h
   function.h
+  generate_webgpu_initializers_pass.h
   if_conversion.h
   inline_exhaustive_pass.h
   inline_opaque_pass.h
@@ -92,6 +99,7 @@
   simplification_pass.h
   ssa_rewrite_pass.h
   strength_reduction_pass.h
+  strip_atomic_counter_memory_pass.h
   strip_debug_info_pass.h
   strip_reflect_info_pass.h
   struct_cfg_analysis.h
@@ -107,10 +115,12 @@
   aggressive_dead_code_elim_pass.cpp
   basic_block.cpp
   block_merge_pass.cpp
+  block_merge_util.cpp
   build_module.cpp
   ccp_pass.cpp
   cfg_cleanup_pass.cpp
   cfg.cpp
+  code_sink.cpp
   combine_access_chains.cpp
   common_uniform_elim_pass.cpp
   compact_ids_pass.cpp
@@ -121,19 +131,24 @@
   dead_branch_elim_pass.cpp
   dead_insert_elim_pass.cpp
   dead_variable_elimination.cpp
+  decompose_initialized_variables_pass.cpp
   decoration_manager.cpp
   def_use_manager.cpp
   dominator_analysis.cpp
   dominator_tree.cpp
   eliminate_dead_constant_pass.cpp
   eliminate_dead_functions_pass.cpp
+  eliminate_dead_functions_util.cpp
+  eliminate_dead_members_pass.cpp
   feature_manager.cpp
+  fix_storage_class.cpp
   flatten_decoration_pass.cpp
   fold.cpp
   folding_rules.cpp
   fold_spec_constant_op_and_composite_pass.cpp
   freeze_spec_constant_value_pass.cpp
   function.cpp
+  generate_webgpu_initializers_pass.cpp
   if_conversion.cpp
   inline_exhaustive_pass.cpp
   inline_opaque_pass.cpp
@@ -144,6 +159,7 @@
   instrument_pass.cpp
   ir_context.cpp
   ir_loader.cpp
+  legalize_vector_shuffle_pass.cpp
   licm_pass.cpp
   local_access_chain_convert_pass.cpp
   local_redundancy_elimination.cpp
@@ -181,6 +197,7 @@
   simplification_pass.cpp
   ssa_rewrite_pass.cpp
   strength_reduction_pass.cpp
+  strip_atomic_counter_memory_pass.cpp
   strip_debug_info_pass.cpp
   strip_reflect_info_pass.cpp
   struct_cfg_analysis.cpp
diff --git a/source/opt/aggressive_dead_code_elim_pass.cpp b/source/opt/aggressive_dead_code_elim_pass.cpp
index 82d7499..4063eb5 100644
--- a/source/opt/aggressive_dead_code_elim_pass.cpp
+++ b/source/opt/aggressive_dead_code_elim_pass.cpp
@@ -24,6 +24,7 @@
 #include "source/latest_version_glsl_std_450_header.h"
 #include "source/opt/iterator.h"
 #include "source/opt/reflect.h"
+#include "source/spirv_constant.h"
 
 namespace spvtools {
 namespace opt {
@@ -513,6 +514,26 @@
       AddBranch(mergeBlockId, *bi);
       for (++bi; (*bi)->id() != mergeBlockId; ++bi) {
       }
+
+      auto merge_terminator = (*bi)->terminator();
+      if (merge_terminator->opcode() == SpvOpUnreachable) {
+        // The merge was unreachable. This is undefined behaviour so just
+        // return (or return an undef). Then mark the new return as live.
+        auto func_ret_type_inst = get_def_use_mgr()->GetDef(func->type_id());
+        if (func_ret_type_inst->opcode() == SpvOpTypeVoid) {
+          merge_terminator->SetOpcode(SpvOpReturn);
+        } else {
+          // Find an undef for the return value and make sure it gets kept by
+          // the pass.
+          auto undef_id = Type2Undef(func->type_id());
+          auto undef = get_def_use_mgr()->GetDef(undef_id);
+          live_insts_.Set(undef->unique_id());
+          merge_terminator->SetOpcode(SpvOpReturnValue);
+          merge_terminator->SetInOperands({{SPV_OPERAND_TYPE_ID, {undef_id}}});
+          get_def_use_mgr()->AnalyzeInstUse(merge_terminator);
+        }
+        live_insts_.Set(merge_terminator->unique_id());
+      }
     } else {
       ++bi;
     }
@@ -528,7 +549,26 @@
   }
   // Keep all entry points.
   for (auto& entry : get_module()->entry_points()) {
-    AddToWorklist(&entry);
+    if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
+      // In SPIR-V 1.4 and later, entry points must list all global variables
+      // used. DCE can still remove non-input/output variables and update the
+      // interface list. Mark the entry point as live and inputs and outputs as
+      // live, but defer decisions all other interfaces.
+      live_insts_.Set(entry.unique_id());
+      // The actual function is live always.
+      AddToWorklist(
+          get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(1u)));
+      for (uint32_t i = 3; i < entry.NumInOperands(); ++i) {
+        auto* var = get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(i));
+        auto storage_class = var->GetSingleWordInOperand(0u);
+        if (storage_class == SpvStorageClassInput ||
+            storage_class == SpvStorageClassOutput) {
+          AddToWorklist(var);
+        }
+      }
+    } else {
+      AddToWorklist(&entry);
+    }
   }
   // Keep workgroup size.
   for (auto& anno : get_module()->annotations()) {
@@ -546,11 +586,19 @@
   // TODO(greg-lunarg): Handle additional capabilities
   if (!context()->get_feature_mgr()->HasCapability(SpvCapabilityShader))
     return Status::SuccessWithoutChange;
+
   // Current functionality assumes relaxed logical addressing (see
   // instruction.h)
   // TODO(greg-lunarg): Handle non-logical addressing
   if (context()->get_feature_mgr()->HasCapability(SpvCapabilityAddresses))
     return Status::SuccessWithoutChange;
+
+  // The variable pointer extension is no longer needed to use the capability,
+  // so we have to look for the capability.
+  if (context()->get_feature_mgr()->HasCapability(
+          SpvCapabilityVariablePointersStorageBuffer))
+    return Status::SuccessWithoutChange;
+
   // If any extensions in the module are not explicitly supported,
   // return unmodified.
   if (!AllExtensionsSupported()) return Status::SuccessWithoutChange;
@@ -752,6 +800,29 @@
     }
   }
 
+  if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
+    // Remove the dead interface variables from the entry point interface list.
+    for (auto& entry : get_module()->entry_points()) {
+      std::vector<Operand> new_operands;
+      for (uint32_t i = 0; i < entry.NumInOperands(); ++i) {
+        if (i < 3) {
+          // Execution model, function id and name are always valid.
+          new_operands.push_back(entry.GetInOperand(i));
+        } else {
+          auto* var =
+              get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(i));
+          if (!IsDead(var)) {
+            new_operands.push_back(entry.GetInOperand(i));
+          }
+        }
+      }
+      if (new_operands.size() != entry.NumInOperands()) {
+        entry.SetInOperands(std::move(new_operands));
+        get_def_use_mgr()->UpdateDefUse(&entry);
+      }
+    }
+  }
+
   return modified;
 }
 
diff --git a/source/opt/aggressive_dead_code_elim_pass.h b/source/opt/aggressive_dead_code_elim_pass.h
index b4b34b0..c043a96 100644
--- a/source/opt/aggressive_dead_code_elim_pass.h
+++ b/source/opt/aggressive_dead_code_elim_pass.h
@@ -49,7 +49,9 @@
   Status Process() override;
 
   IRContext::Analysis GetPreservedAnalyses() override {
-    return IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping;
+    return IRContext::kAnalysisDefUse |
+           IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/basic_block.cpp b/source/opt/basic_block.cpp
index aafee51..3608448 100644
--- a/source/opt/basic_block.cpp
+++ b/source/opt/basic_block.cpp
@@ -108,21 +108,29 @@
 
 void BasicBlock::ForEachSuccessorLabel(
     const std::function<void(const uint32_t)>& f) const {
+  WhileEachSuccessorLabel([f](const uint32_t l) {
+    f(l);
+    return true;
+  });
+}
+
+bool BasicBlock::WhileEachSuccessorLabel(
+    const std::function<bool(const uint32_t)>& f) const {
   const auto br = &insts_.back();
   switch (br->opcode()) {
-    case SpvOpBranch: {
-      f(br->GetOperand(0).words[0]);
-    } break;
+    case SpvOpBranch:
+      return f(br->GetOperand(0).words[0]);
     case SpvOpBranchConditional:
     case SpvOpSwitch: {
       bool is_first = true;
-      br->ForEachInId([&is_first, &f](const uint32_t* idp) {
-        if (!is_first) f(*idp);
+      return br->WhileEachInId([&is_first, &f](const uint32_t* idp) {
+        if (!is_first) return f(*idp);
         is_first = false;
+        return true;
       });
-    } break;
+    }
     default:
-      break;
+      return true;
   }
 }
 
@@ -184,6 +192,12 @@
   return mbid;
 }
 
+uint32_t BasicBlock::MergeBlockId() const {
+  uint32_t mbid = MergeBlockIdIfAny();
+  assert(mbid && "Expected block to have a corresponding merge block");
+  return mbid;
+}
+
 uint32_t BasicBlock::ContinueBlockIdIfAny() const {
   auto merge_ii = cend();
   --merge_ii;
@@ -197,6 +211,12 @@
   return cbid;
 }
 
+uint32_t BasicBlock::ContinueBlockId() const {
+  uint32_t cbid = ContinueBlockIdIfAny();
+  assert(cbid && "Expected block to have a corresponding continue target");
+  return cbid;
+}
+
 std::ostream& operator<<(std::ostream& str, const BasicBlock& block) {
   str << block.PrettyPrint();
   return str;
diff --git a/source/opt/basic_block.h b/source/opt/basic_block.h
index ff3a412..0bab337 100644
--- a/source/opt/basic_block.h
+++ b/source/opt/basic_block.h
@@ -160,6 +160,11 @@
   void ForEachSuccessorLabel(
       const std::function<void(const uint32_t)>& f) const;
 
+  // Runs the given function |f| on each label id of each successor block.  If
+  // |f| returns false, iteration is terminated and this function returns false.
+  bool WhileEachSuccessorLabel(
+      const std::function<bool(const uint32_t)>& f) const;
+
   // Runs the given function |f| on each label id of each successor block.
   // Modifying the pointed value will change the branch taken by the basic
   // block. It is the caller responsibility to update or invalidate the CFG.
@@ -183,10 +188,16 @@
   // block, if any.  If none, returns zero.
   uint32_t MergeBlockIdIfAny() const;
 
+  // Returns MergeBlockIdIfAny() and asserts that it is non-zero.
+  uint32_t MergeBlockId() const;
+
   // Returns the ID of the continue block declared by a merge instruction in
   // this block, if any.  If none, returns zero.
   uint32_t ContinueBlockIdIfAny() const;
 
+  // Returns ContinueBlockIdIfAny() and asserts that it is non-zero.
+  uint32_t ContinueBlockId() const;
+
   // Returns the terminator instruction.  Assumes the terminator exists.
   Instruction* terminator() { return &*tail(); }
   const Instruction* terminator() const { return &*ctail(); }
diff --git a/source/opt/block_merge_pass.cpp b/source/opt/block_merge_pass.cpp
index 09deb21..c7315ba 100644
--- a/source/opt/block_merge_pass.cpp
+++ b/source/opt/block_merge_pass.cpp
@@ -18,6 +18,7 @@
 
 #include <vector>
 
+#include "source/opt/block_merge_util.h"
 #include "source/opt/ir_context.h"
 #include "source/opt/iterator.h"
 
@@ -27,112 +28,17 @@
 bool BlockMergePass::MergeBlocks(Function* func) {
   bool modified = false;
   for (auto bi = func->begin(); bi != func->end();) {
-    // Find block with single successor which has no other predecessors.
-    auto ii = bi->end();
-    --ii;
-    Instruction* br = &*ii;
-    if (br->opcode() != SpvOpBranch) {
+    if (blockmergeutil::CanMergeWithSuccessor(context(), &*bi)) {
+      blockmergeutil::MergeWithSuccessor(context(), func, bi);
+      // Reprocess block.
+      modified = true;
+    } else {
       ++bi;
-      continue;
     }
-
-    const uint32_t lab_id = br->GetSingleWordInOperand(0);
-    if (cfg()->preds(lab_id).size() != 1) {
-      ++bi;
-      continue;
-    }
-
-    bool pred_is_merge = IsMerge(&*bi);
-    bool succ_is_merge = IsMerge(lab_id);
-    if (pred_is_merge && succ_is_merge) {
-      // Cannot merge two merges together.
-      ++bi;
-      continue;
-    }
-
-    Instruction* merge_inst = bi->GetMergeInst();
-    bool pred_is_header = IsHeader(&*bi);
-    if (pred_is_header && lab_id != merge_inst->GetSingleWordInOperand(0u)) {
-      bool succ_is_header = IsHeader(lab_id);
-      if (pred_is_header && succ_is_header) {
-        // Cannot merge two headers together when the successor is not the merge
-        // block of the predecessor.
-        ++bi;
-        continue;
-      }
-
-      // If this is a header block and the successor is not its merge, we must
-      // be careful about which blocks we are willing to merge together.
-      // OpLoopMerge must be followed by a conditional or unconditional branch.
-      // The merge must be a loop merge because a selection merge cannot be
-      // followed by an unconditional branch.
-      BasicBlock* succ_block = context()->get_instr_block(lab_id);
-      SpvOp succ_term_op = succ_block->terminator()->opcode();
-      assert(merge_inst->opcode() == SpvOpLoopMerge);
-      if (succ_term_op != SpvOpBranch &&
-          succ_term_op != SpvOpBranchConditional) {
-        ++bi;
-        continue;
-      }
-    }
-
-    // Merge blocks.
-    context()->KillInst(br);
-    auto sbi = bi;
-    for (; sbi != func->end(); ++sbi)
-      if (sbi->id() == lab_id) break;
-    // If bi is sbi's only predecessor, it dominates sbi and thus
-    // sbi must follow bi in func's ordering.
-    assert(sbi != func->end());
-
-    // Update the inst-to-block mapping for the instructions in sbi.
-    for (auto& inst : *sbi) {
-      context()->set_instr_block(&inst, &*bi);
-    }
-
-    // Now actually move the instructions.
-    bi->AddInstructions(&*sbi);
-
-    if (merge_inst) {
-      if (pred_is_header && lab_id == merge_inst->GetSingleWordInOperand(0u)) {
-        // Merging the header and merge blocks, so remove the structured control
-        // flow declaration.
-        context()->KillInst(merge_inst);
-      } else {
-        // Move the merge instruction to just before the terminator.
-        merge_inst->InsertBefore(bi->terminator());
-      }
-    }
-    context()->ReplaceAllUsesWith(lab_id, bi->id());
-    context()->KillInst(sbi->GetLabelInst());
-    (void)sbi.Erase();
-    // Reprocess block.
-    modified = true;
   }
   return modified;
 }
 
-bool BlockMergePass::IsHeader(BasicBlock* block) {
-  return block->GetMergeInst() != nullptr;
-}
-
-bool BlockMergePass::IsHeader(uint32_t id) {
-  return IsHeader(context()->get_instr_block(get_def_use_mgr()->GetDef(id)));
-}
-
-bool BlockMergePass::IsMerge(uint32_t id) {
-  return !get_def_use_mgr()->WhileEachUse(id, [](Instruction* user,
-                                                 uint32_t index) {
-    SpvOp op = user->opcode();
-    if ((op == SpvOpLoopMerge || op == SpvOpSelectionMerge) && index == 0u) {
-      return false;
-    }
-    return true;
-  });
-}
-
-bool BlockMergePass::IsMerge(BasicBlock* block) { return IsMerge(block->id()); }
-
 Pass::Status BlockMergePass::Process() {
   // Process all entry point functions.
   ProcessFunction pfn = [this](Function* fp) { return MergeBlocks(fp); };
diff --git a/source/opt/block_merge_pass.h b/source/opt/block_merge_pass.h
index be14b02..aabf789 100644
--- a/source/opt/block_merge_pass.h
+++ b/source/opt/block_merge_pass.h
@@ -44,7 +44,8 @@
     return IRContext::kAnalysisDefUse |
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
-           IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 
  private:
@@ -53,14 +54,6 @@
   // with no other predecessors. Merge these blocks into a single block.
   bool MergeBlocks(Function* func);
 
-  // Returns true if |block| (or |id|) contains a merge instruction.
-  bool IsHeader(BasicBlock* block);
-  bool IsHeader(uint32_t id);
-
-  // Returns true if |block| (or |id|) is the merge target of a merge
-  // instruction.
-  bool IsMerge(BasicBlock* block);
-  bool IsMerge(uint32_t id);
 };
 
 }  // namespace opt
diff --git a/source/opt/block_merge_util.cpp b/source/opt/block_merge_util.cpp
new file mode 100644
index 0000000..107723d
--- /dev/null
+++ b/source/opt/block_merge_util.cpp
@@ -0,0 +1,168 @@
+// Copyright (c) 2017 The Khronos Group Inc.
+// Copyright (c) 2017 Valve Corporation
+// Copyright (c) 2017 LunarG Inc.
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "block_merge_util.h"
+
+namespace spvtools {
+namespace opt {
+namespace blockmergeutil {
+
+namespace {
+
+// Returns true if |block| contains a merge instruction.
+bool IsHeader(BasicBlock* block) { return block->GetMergeInst() != nullptr; }
+
+// Returns true if |id| contains a merge instruction.
+bool IsHeader(IRContext* context, uint32_t id) {
+  return IsHeader(
+      context->get_instr_block(context->get_def_use_mgr()->GetDef(id)));
+}
+
+// Returns true if |id| is the merge target of a merge instruction.
+bool IsMerge(IRContext* context, uint32_t id) {
+  return !context->get_def_use_mgr()->WhileEachUse(id, [](Instruction* user,
+                                                          uint32_t index) {
+    SpvOp op = user->opcode();
+    if ((op == SpvOpLoopMerge || op == SpvOpSelectionMerge) && index == 0u) {
+      return false;
+    }
+    return true;
+  });
+}
+
+// Returns true if |block| is the merge target of a merge instruction.
+bool IsMerge(IRContext* context, BasicBlock* block) {
+  return IsMerge(context, block->id());
+}
+
+// Removes any OpPhi instructions in |block|, which should have exactly one
+// predecessor, replacing uses of OpPhi ids with the ids associated with the
+// predecessor.
+void EliminateOpPhiInstructions(IRContext* context, BasicBlock* block) {
+  block->ForEachPhiInst([context](Instruction* phi) {
+    assert(2 == phi->NumInOperands() &&
+           "A block can only have one predecessor for block merging to make "
+           "sense.");
+    context->ReplaceAllUsesWith(phi->result_id(),
+                                phi->GetSingleWordInOperand(0));
+    context->KillInst(phi);
+  });
+}
+
+}  // Anonymous namespace
+
+bool CanMergeWithSuccessor(IRContext* context, BasicBlock* block) {
+  // Find block with single successor which has no other predecessors.
+  auto ii = block->end();
+  --ii;
+  Instruction* br = &*ii;
+  if (br->opcode() != SpvOpBranch) {
+    return false;
+  }
+
+  const uint32_t lab_id = br->GetSingleWordInOperand(0);
+  if (context->cfg()->preds(lab_id).size() != 1) {
+    return false;
+  }
+
+  bool pred_is_merge = IsMerge(context, block);
+  bool succ_is_merge = IsMerge(context, lab_id);
+  if (pred_is_merge && succ_is_merge) {
+    // Cannot merge two merges together.
+    return false;
+  }
+
+  // Don't bother trying to merge unreachable blocks.
+  if (auto dominators = context->GetDominatorAnalysis(block->GetParent())) {
+    if (!dominators->IsReachable(block)) return false;
+  }
+
+  Instruction* merge_inst = block->GetMergeInst();
+  const bool pred_is_header = IsHeader(block);
+  if (pred_is_header && lab_id != merge_inst->GetSingleWordInOperand(0u)) {
+    bool succ_is_header = IsHeader(context, lab_id);
+    if (pred_is_header && succ_is_header) {
+      // Cannot merge two headers together when the successor is not the merge
+      // block of the predecessor.
+      return false;
+    }
+
+    // If this is a header block and the successor is not its merge, we must
+    // be careful about which blocks we are willing to merge together.
+    // OpLoopMerge must be followed by a conditional or unconditional branch.
+    // The merge must be a loop merge because a selection merge cannot be
+    // followed by an unconditional branch.
+    BasicBlock* succ_block = context->get_instr_block(lab_id);
+    SpvOp succ_term_op = succ_block->terminator()->opcode();
+    assert(merge_inst->opcode() == SpvOpLoopMerge);
+    if (succ_term_op != SpvOpBranch && succ_term_op != SpvOpBranchConditional) {
+      return false;
+    }
+  }
+  return true;
+}
+
+void MergeWithSuccessor(IRContext* context, Function* func,
+                        Function::iterator bi) {
+  assert(CanMergeWithSuccessor(context, &*bi) &&
+         "Precondition failure for MergeWithSuccessor: it must be legal to "
+         "merge the block and its successor.");
+
+  auto ii = bi->end();
+  --ii;
+  Instruction* br = &*ii;
+  const uint32_t lab_id = br->GetSingleWordInOperand(0);
+  Instruction* merge_inst = bi->GetMergeInst();
+  bool pred_is_header = IsHeader(&*bi);
+
+  // Merge blocks.
+  context->KillInst(br);
+  auto sbi = bi;
+  for (; sbi != func->end(); ++sbi)
+    if (sbi->id() == lab_id) break;
+  // If bi is sbi's only predecessor, it dominates sbi and thus
+  // sbi must follow bi in func's ordering.
+  assert(sbi != func->end());
+
+  // Update the inst-to-block mapping for the instructions in sbi.
+  for (auto& inst : *sbi) {
+    context->set_instr_block(&inst, &*bi);
+  }
+
+  EliminateOpPhiInstructions(context, &*sbi);
+
+  // Now actually move the instructions.
+  bi->AddInstructions(&*sbi);
+
+  if (merge_inst) {
+    if (pred_is_header && lab_id == merge_inst->GetSingleWordInOperand(0u)) {
+      // Merging the header and merge blocks, so remove the structured control
+      // flow declaration.
+      context->KillInst(merge_inst);
+    } else {
+      // Move the merge instruction to just before the terminator.
+      merge_inst->InsertBefore(bi->terminator());
+    }
+  }
+  context->ReplaceAllUsesWith(lab_id, bi->id());
+  context->KillInst(sbi->GetLabelInst());
+  (void)sbi.Erase();
+}
+
+}  // namespace blockmergeutil
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/block_merge_util.h b/source/opt/block_merge_util.h
new file mode 100644
index 0000000..e71e3d6
--- /dev/null
+++ b/source/opt/block_merge_util.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2017 The Khronos Group Inc.
+// Copyright (c) 2017 Valve Corporation
+// Copyright (c) 2017 LunarG Inc.
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_BLOCK_MERGE_UTIL_H_
+#define SOURCE_OPT_BLOCK_MERGE_UTIL_H_
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+// Provides functions for determining when it is safe to merge blocks, and for
+// actually merging blocks, for use by various analyses and passes.
+namespace blockmergeutil {
+
+// Returns true if and only if |block| has exactly one successor and merging
+// this successor into |block| has no impact on the semantics or validity of the
+// SPIR-V module.
+bool CanMergeWithSuccessor(IRContext* context, BasicBlock* block);
+
+// Requires that |bi| has a successor that can be safely merged into |bi|, and
+// performs the merge.
+void MergeWithSuccessor(IRContext* context, Function* func,
+                        Function::iterator bi);
+
+}  // namespace blockmergeutil
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  //  SOURCE_OPT_BLOCK_MERGE_UTIL_H_
diff --git a/source/opt/ccp_pass.cpp b/source/opt/ccp_pass.cpp
index 8356195..2de9250 100644
--- a/source/opt/ccp_pass.cpp
+++ b/source/opt/ccp_pass.cpp
@@ -271,6 +271,7 @@
     uint32_t id = it.first;
     uint32_t cst_id = it.second;
     if (!IsVaryingValue(cst_id) && id != cst_id) {
+      context()->KillNamesAndDecorates(id);
       retval |= context()->ReplaceAllUsesWith(id, cst_id);
     }
   }
diff --git a/source/opt/ccp_pass.h b/source/opt/ccp_pass.h
index 178fd12..527f459 100644
--- a/source/opt/ccp_pass.h
+++ b/source/opt/ccp_pass.h
@@ -40,7 +40,8 @@
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
            IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
-           IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/cfg.cpp b/source/opt/cfg.cpp
index 7e1097e..72693cb 100644
--- a/source/opt/cfg.cpp
+++ b/source/opt/cfg.cpp
@@ -150,15 +150,25 @@
 void CFG::ComputePostOrderTraversal(BasicBlock* bb,
                                     std::vector<BasicBlock*>* order,
                                     std::unordered_set<BasicBlock*>* seen) {
-  seen->insert(bb);
-  static_cast<const BasicBlock*>(bb)->ForEachSuccessorLabel(
-      [&order, &seen, this](const uint32_t sbid) {
-        BasicBlock* succ_bb = id2block_[sbid];
-        if (!seen->count(succ_bb)) {
-          ComputePostOrderTraversal(succ_bb, order, seen);
-        }
-      });
-  order->push_back(bb);
+  std::vector<BasicBlock*> stack;
+  stack.push_back(bb);
+  while (!stack.empty()) {
+    bb = stack.back();
+    seen->insert(bb);
+    static_cast<const BasicBlock*>(bb)->WhileEachSuccessorLabel(
+        [&seen, &stack, this](const uint32_t sbid) {
+          BasicBlock* succ_bb = id2block_[sbid];
+          if (!seen->count(succ_bb)) {
+            stack.push_back(succ_bb);
+            return false;
+          }
+          return true;
+        });
+    if (stack.back() == bb) {
+      order->push_back(bb);
+      stack.pop_back();
+    }
+  }
 }
 
 BasicBlock* CFG::SplitLoopHeader(BasicBlock* bb) {
diff --git a/source/opt/cfg_cleanup_pass.h b/source/opt/cfg_cleanup_pass.h
index afbc67c..5095428 100644
--- a/source/opt/cfg_cleanup_pass.h
+++ b/source/opt/cfg_cleanup_pass.h
@@ -30,7 +30,8 @@
   Status Process() override;
 
   IRContext::Analysis GetPreservedAnalyses() override {
-    return IRContext::kAnalysisDefUse;
+    return IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 };
 
diff --git a/source/opt/code_sink.cpp b/source/opt/code_sink.cpp
new file mode 100644
index 0000000..9d54ee5
--- /dev/null
+++ b/source/opt/code_sink.cpp
@@ -0,0 +1,319 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "code_sink.h"
+
+#include <set>
+#include <vector>
+
+#include "source/opt/instruction.h"
+#include "source/opt/ir_builder.h"
+#include "source/opt/ir_context.h"
+#include "source/util/bit_vector.h"
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status CodeSinkingPass::Process() {
+  bool modified = false;
+  for (Function& function : *get_module()) {
+    cfg()->ForEachBlockInPostOrder(function.entry().get(),
+                                   [&modified, this](BasicBlock* bb) {
+                                     if (SinkInstructionsInBB(bb)) {
+                                       modified = true;
+                                     }
+                                   });
+  }
+  return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+bool CodeSinkingPass::SinkInstructionsInBB(BasicBlock* bb) {
+  bool modified = false;
+  for (auto inst = bb->rbegin(); inst != bb->rend(); ++inst) {
+    if (SinkInstruction(&*inst)) {
+      inst = bb->rbegin();
+      modified = true;
+    }
+  }
+  return modified;
+}
+
+bool CodeSinkingPass::SinkInstruction(Instruction* inst) {
+  if (inst->opcode() != SpvOpLoad && inst->opcode() != SpvOpAccessChain) {
+    return false;
+  }
+
+  if (ReferencesMutableMemory(inst)) {
+    return false;
+  }
+
+  if (BasicBlock* target_bb = FindNewBasicBlockFor(inst)) {
+    Instruction* pos = &*target_bb->begin();
+    while (pos->opcode() == SpvOpPhi) {
+      pos = pos->NextNode();
+    }
+
+    inst->InsertBefore(pos);
+    context()->set_instr_block(inst, target_bb);
+    return true;
+  }
+  return false;
+}
+
+BasicBlock* CodeSinkingPass::FindNewBasicBlockFor(Instruction* inst) {
+  assert(inst->result_id() != 0 && "Instruction should have a result.");
+  BasicBlock* original_bb = context()->get_instr_block(inst);
+  BasicBlock* bb = original_bb;
+
+  std::unordered_set<uint32_t> bbs_with_uses;
+  get_def_use_mgr()->ForEachUse(
+      inst, [&bbs_with_uses, this](Instruction* use, uint32_t idx) {
+        if (use->opcode() != SpvOpPhi) {
+          BasicBlock* use_bb = context()->get_instr_block(use);
+          if (use_bb) {
+            bbs_with_uses.insert(use_bb->id());
+          }
+        } else {
+          bbs_with_uses.insert(use->GetSingleWordOperand(idx + 1));
+        }
+      });
+
+  while (true) {
+    // If |inst| is used in |bb|, then |inst| cannot be moved any further.
+    if (bbs_with_uses.count(bb->id())) {
+      break;
+    }
+
+    // If |bb| has one successor (succ_bb), and |bb| is the only predecessor
+    // of succ_bb, then |inst| can be moved to succ_bb.  If succ_bb, has move
+    // then one predecessor, then moving |inst| into succ_bb could cause it to
+    // be executed more often, so the search has to stop.
+    if (bb->terminator()->opcode() == SpvOpBranch) {
+      uint32_t succ_bb_id = bb->terminator()->GetSingleWordInOperand(0);
+      if (cfg()->preds(succ_bb_id).size() == 1) {
+        bb = context()->get_instr_block(succ_bb_id);
+        continue;
+      } else {
+        break;
+      }
+    }
+
+    // The remaining checks need to know the merge node.  If there is no merge
+    // instruction or an OpLoopMerge, then it is a break or continue.  We could
+    // figure it out, but not worth doing it now.
+    Instruction* merge_inst = bb->GetMergeInst();
+    if (merge_inst == nullptr || merge_inst->opcode() != SpvOpSelectionMerge) {
+      break;
+    }
+
+    // Check all of the successors of |bb| it see which lead to a use of |inst|
+    // before reaching the merge node.
+    bool used_in_multiple_blocks = false;
+    uint32_t bb_used_in = 0;
+    bb->ForEachSuccessorLabel([this, bb, &bb_used_in, &used_in_multiple_blocks,
+                               &bbs_with_uses](uint32_t* succ_bb_id) {
+      if (IntersectsPath(*succ_bb_id, bb->MergeBlockIdIfAny(), bbs_with_uses)) {
+        if (bb_used_in == 0) {
+          bb_used_in = *succ_bb_id;
+        } else {
+          used_in_multiple_blocks = true;
+        }
+      }
+    });
+
+    // If more than one successor, which is not the merge block, uses |inst|
+    // then we have to leave |inst| in bb because there is none of the
+    // successors dominate all uses of |inst|.
+    if (used_in_multiple_blocks) {
+      break;
+    }
+
+    if (bb_used_in == 0) {
+      // If |inst| is not used before reaching the merge node, then we can move
+      // |inst| to the merge node.
+      bb = context()->get_instr_block(bb->MergeBlockIdIfAny());
+    } else {
+      // If the only successor that leads to a used of |inst| has more than 1
+      // predecessor, then moving |inst| could cause it to be executed more
+      // often, so we cannot move it.
+      if (cfg()->preds(bb_used_in).size() != 1) {
+        break;
+      }
+
+      // If |inst| is used after the merge block, then |bb_used_in| does not
+      // dominate all of the uses.  So we cannot move |inst| any further.
+      if (IntersectsPath(bb->MergeBlockIdIfAny(), original_bb->id(),
+                         bbs_with_uses)) {
+        break;
+      }
+
+      // Otherwise, |bb_used_in| dominates all uses, so move |inst| into that
+      // block.
+      bb = context()->get_instr_block(bb_used_in);
+    }
+    continue;
+  }
+  return (bb != original_bb ? bb : nullptr);
+}
+
+bool CodeSinkingPass::ReferencesMutableMemory(Instruction* inst) {
+  if (!inst->IsLoad()) {
+    return false;
+  }
+
+  Instruction* base_ptr = inst->GetBaseAddress();
+  if (base_ptr->opcode() != SpvOpVariable) {
+    return true;
+  }
+
+  if (base_ptr->IsReadOnlyVariable()) {
+    return false;
+  }
+
+  if (HasUniformMemorySync()) {
+    return true;
+  }
+
+  if (base_ptr->GetSingleWordInOperand(0) != SpvStorageClassUniform) {
+    return true;
+  }
+
+  return HasPossibleStore(base_ptr);
+}
+
+bool CodeSinkingPass::HasUniformMemorySync() {
+  if (checked_for_uniform_sync_) {
+    return has_uniform_sync_;
+  }
+
+  bool has_sync = false;
+  get_module()->ForEachInst([this, &has_sync](Instruction* inst) {
+    switch (inst->opcode()) {
+      case SpvOpMemoryBarrier: {
+        uint32_t mem_semantics_id = inst->GetSingleWordInOperand(1);
+        if (IsSyncOnUniform(mem_semantics_id)) {
+          has_sync = true;
+        }
+        break;
+      }
+      case SpvOpControlBarrier:
+      case SpvOpAtomicLoad:
+      case SpvOpAtomicStore:
+      case SpvOpAtomicExchange:
+      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: {
+        uint32_t mem_semantics_id = inst->GetSingleWordInOperand(2);
+        if (IsSyncOnUniform(mem_semantics_id)) {
+          has_sync = true;
+        }
+        break;
+      }
+      case SpvOpAtomicCompareExchange:
+      case SpvOpAtomicCompareExchangeWeak:
+        if (IsSyncOnUniform(inst->GetSingleWordInOperand(2)) ||
+            IsSyncOnUniform(inst->GetSingleWordInOperand(3))) {
+          has_sync = true;
+        }
+        break;
+      default:
+        break;
+    }
+  });
+  has_uniform_sync_ = has_sync;
+  return has_sync;
+}
+
+bool CodeSinkingPass::IsSyncOnUniform(uint32_t mem_semantics_id) const {
+  const analysis::Constant* mem_semantics_const =
+      context()->get_constant_mgr()->FindDeclaredConstant(mem_semantics_id);
+  assert(mem_semantics_const != nullptr &&
+         "Expecting memory semantics id to be a constant.");
+  assert(mem_semantics_const->AsIntConstant() &&
+         "Memory semantics should be an integer.");
+  uint32_t mem_semantics_int = mem_semantics_const->GetU32();
+
+  // If it does not affect uniform memory, then it is does not apply to uniform
+  // memory.
+  if ((mem_semantics_int & SpvMemorySemanticsUniformMemoryMask) == 0) {
+    return false;
+  }
+
+  // Check if there is an acquire or release.  If so not, this it does not add
+  // any memory constraints.
+  return (mem_semantics_int & (SpvMemorySemanticsAcquireMask |
+                               SpvMemorySemanticsAcquireReleaseMask |
+                               SpvMemorySemanticsReleaseMask)) != 0;
+}
+
+bool CodeSinkingPass::HasPossibleStore(Instruction* var_inst) {
+  assert(var_inst->opcode() == SpvOpVariable ||
+         var_inst->opcode() == SpvOpAccessChain ||
+         var_inst->opcode() == SpvOpPtrAccessChain);
+
+  return get_def_use_mgr()->WhileEachUser(var_inst, [this](Instruction* use) {
+    switch (use->opcode()) {
+      case SpvOpStore:
+        return true;
+      case SpvOpAccessChain:
+      case SpvOpPtrAccessChain:
+        return HasPossibleStore(use);
+      default:
+        return false;
+    }
+  });
+}
+
+bool CodeSinkingPass::IntersectsPath(uint32_t start, uint32_t end,
+                                     const std::unordered_set<uint32_t>& set) {
+  std::vector<uint32_t> worklist;
+  worklist.push_back(start);
+  std::unordered_set<uint32_t> already_done;
+  already_done.insert(start);
+
+  while (!worklist.empty()) {
+    BasicBlock* bb = context()->get_instr_block(worklist.back());
+    worklist.pop_back();
+
+    if (bb->id() == end) {
+      continue;
+    }
+
+    if (set.count(bb->id())) {
+      return true;
+    }
+
+    bb->ForEachSuccessorLabel([&already_done, &worklist](uint32_t* succ_bb_id) {
+      if (already_done.insert(*succ_bb_id).second) {
+        worklist.push_back(*succ_bb_id);
+      }
+    });
+  }
+  return false;
+}
+
+// namespace opt
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/code_sink.h b/source/opt/code_sink.h
new file mode 100644
index 0000000..d24df03
--- /dev/null
+++ b/source/opt/code_sink.h
@@ -0,0 +1,107 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_CODE_SINK_H_
+#define SOURCE_OPT_CODE_SINK_H_
+
+#include <unordered_map>
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// This pass does code sinking for OpAccessChain and OpLoad on variables in
+// uniform storage or in read only memory.  Code sinking is a transformation
+// where an instruction is moved into a more deeply nested construct.
+//
+// The goal is to move these instructions as close as possible to their uses
+// without having to execute them more often or to replicate the instruction.
+// Moving the instruction in this way can lead to shorter live ranges, which can
+// lead to less register pressure.  It can also cause instructions to be
+// executed less often because they could be moved into one path of a selection
+// construct.
+//
+// This optimization can cause register pressure to rise if the operands of the
+// instructions go dead after the instructions being moved. That is why we only
+// move certain OpLoad and OpAccessChain instructions.  They generally have
+// constants, loop induction variables, and global pointers as operands.  The
+// operands are live for a longer time in most cases.
+class CodeSinkingPass : public Pass {
+ public:
+  const char* name() const override { return "code-sink"; }
+  Status Process() override;
+
+  // Return the mask of preserved Analyses.
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisDefUse |
+           IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG |
+           IRContext::kAnalysisDominatorAnalysis |
+           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
+  }
+
+ private:
+  // Sinks the instructions in |bb| as much as possible.  Returns true if
+  // something changes.
+  bool SinkInstructionsInBB(BasicBlock* bb);
+
+  // Tries the sink |inst| as much as possible.  Returns true if the instruction
+  // is moved.
+  bool SinkInstruction(Instruction* inst);
+
+  // Returns the basic block in which to move |inst| to move is as close as
+  // possible to the uses of |inst| without increasing the number of times
+  // |inst| will be executed.  Return |nullptr| if there is no need to move
+  // |inst|.
+  BasicBlock* FindNewBasicBlockFor(Instruction* inst);
+
+  // Return true if |inst| reference memory and it is possible that the data in
+  // the memory changes at some point.
+  bool ReferencesMutableMemory(Instruction* inst);
+
+  // Returns true if the module contains an instruction that has a memory
+  // semantics id as an operand, and the memory semantics enforces a
+  // synchronization of uniform memory.  See section 3.25 of the SPIR-V
+  // specification.
+  bool HasUniformMemorySync();
+
+  // Returns true if there may be a store to the variable |var_inst|.
+  bool HasPossibleStore(Instruction* var_inst);
+
+  // Returns true if one of the basic blocks in |set| exists on a path from the
+  // basic block |start| to |end|.
+  bool IntersectsPath(uint32_t start, uint32_t end,
+                      const std::unordered_set<uint32_t>& set);
+
+  // Returns true if |mem_semantics_id| is the id of a constant that, when
+  // interpreted as a memory semantics mask enforces synchronization of uniform
+  // memory.  See section 3.25 of the SPIR-V specification.
+  bool IsSyncOnUniform(uint32_t mem_semantics_id) const;
+
+  // True if a check has for uniform storage has taken place.
+  bool checked_for_uniform_sync_;
+
+  // Cache of whether or not the module has a memory sync on uniform storage.
+  // only valid if |check_for_uniform_sync_| is true.
+  bool has_uniform_sync_;
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_CODE_SINK_H_
diff --git a/source/opt/combine_access_chains.h b/source/opt/combine_access_chains.h
index 75885da..531209e 100644
--- a/source/opt/combine_access_chains.h
+++ b/source/opt/combine_access_chains.h
@@ -33,7 +33,8 @@
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
            IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
-           IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/common_uniform_elim_pass.cpp b/source/opt/common_uniform_elim_pass.cpp
index efa40aa..7762fba 100644
--- a/source/opt/common_uniform_elim_pass.cpp
+++ b/source/opt/common_uniform_elim_pass.cpp
@@ -282,6 +282,7 @@
       uint32_t replId;
       GenACLoadRepl(ptrInst, &newInsts, &replId);
       inst = ReplaceAndDeleteLoad(inst, replId, ptrInst);
+      assert(inst->opcode() != SpvOpPhi);
       inst = inst->InsertBefore(std::move(newInsts));
       modified = true;
     }
@@ -355,6 +356,10 @@
     if (mergeBlockId == bp->id()) {
       mergeBlockId = 0;
       insertItr = bp->begin();
+      while (insertItr->opcode() == SpvOpPhi) {
+        ++insertItr;
+      }
+
       // Update insertItr until it will not be removed. Without this code,
       // ReplaceAndDeleteLoad() can set |insertItr| as a dangling pointer.
       while (IsUniformLoadToBeRemoved(&*insertItr)) ++insertItr;
@@ -509,6 +514,9 @@
   // TODO(greg-lunarg): Add support for physical addressing
   if (context()->get_feature_mgr()->HasCapability(SpvCapabilityAddresses))
     return Status::SuccessWithoutChange;
+  if (context()->get_feature_mgr()->HasCapability(
+          SpvCapabilityVariablePointersStorageBuffer))
+    return Status::SuccessWithoutChange;
   // Do not process if any disallowed extensions are enabled
   if (!AllExtensionsSupported()) return Status::SuccessWithoutChange;
   // Do not process if module contains OpGroupDecorate. Additional
diff --git a/source/opt/const_folding_rules.cpp b/source/opt/const_folding_rules.cpp
index f6013a3..3df5a83 100644
--- a/source/opt/const_folding_rules.cpp
+++ b/source/opt/const_folding_rules.cpp
@@ -582,13 +582,17 @@
     std::vector<uint32_t> words = result.GetWords();
     const analysis::Constant* result_const =
         const_mgr->GetConstant(float_type, words);
-    for (uint32_t i = 0; i < a_components.size(); ++i) {
+    for (uint32_t i = 0; i < a_components.size() && result_const != nullptr;
+         ++i) {
       if (a_components[i] == nullptr || b_components[i] == nullptr) {
         return nullptr;
       }
 
       const analysis::Constant* component = FOLD_FPARITH_OP(*)(
           new_type, a_components[i], b_components[i], const_mgr);
+      if (component == nullptr) {
+        return nullptr;
+      }
       result_const =
           FOLD_FPARITH_OP(+)(new_type, result_const, component, const_mgr);
     }
diff --git a/source/opt/copy_prop_arrays.cpp b/source/opt/copy_prop_arrays.cpp
index e508c05..751786c 100644
--- a/source/opt/copy_prop_arrays.cpp
+++ b/source/opt/copy_prop_arrays.cpp
@@ -563,11 +563,6 @@
 }
 void CopyPropagateArrays::UpdateUses(Instruction* original_ptr_inst,
                                      Instruction* new_ptr_inst) {
-  // TODO (s-perron): Keep the def-use manager up to date.  Not done now because
-  // it can cause problems for the |ForEachUse| traversals.  Can be use by
-  // keeping a list of instructions that need updating, and then updating them
-  // in |PropagateObject|.
-
   analysis::TypeManager* type_mgr = context()->get_type_mgr();
   analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
   analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
@@ -703,74 +698,6 @@
   }
 }
 
-uint32_t CopyPropagateArrays::GenerateCopy(Instruction* object_inst,
-                                           uint32_t new_type_id,
-                                           Instruction* insertion_position) {
-  analysis::TypeManager* type_mgr = context()->get_type_mgr();
-  analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
-
-  uint32_t original_type_id = object_inst->type_id();
-  if (original_type_id == new_type_id) {
-    return object_inst->result_id();
-  }
-
-  InstructionBuilder ir_builder(
-      context(), insertion_position,
-      IRContext::kAnalysisInstrToBlockMapping | IRContext::kAnalysisDefUse);
-
-  analysis::Type* original_type = type_mgr->GetType(original_type_id);
-  analysis::Type* new_type = type_mgr->GetType(new_type_id);
-
-  if (const analysis::Array* original_array_type = original_type->AsArray()) {
-    uint32_t original_element_type_id =
-        type_mgr->GetId(original_array_type->element_type());
-
-    analysis::Array* new_array_type = new_type->AsArray();
-    assert(new_array_type != nullptr && "Can't copy an array to a non-array.");
-    uint32_t new_element_type_id =
-        type_mgr->GetId(new_array_type->element_type());
-
-    std::vector<uint32_t> element_ids;
-    const analysis::Constant* length_const =
-        const_mgr->FindDeclaredConstant(original_array_type->LengthId());
-    assert(length_const->AsIntConstant());
-    uint32_t array_length = length_const->AsIntConstant()->GetU32();
-    for (uint32_t i = 0; i < array_length; i++) {
-      Instruction* extract = ir_builder.AddCompositeExtract(
-          original_element_type_id, object_inst->result_id(), {i});
-      element_ids.push_back(
-          GenerateCopy(extract, new_element_type_id, insertion_position));
-    }
-
-    return ir_builder.AddCompositeConstruct(new_type_id, element_ids)
-        ->result_id();
-  } else if (const analysis::Struct* original_struct_type =
-                 original_type->AsStruct()) {
-    analysis::Struct* new_struct_type = new_type->AsStruct();
-
-    const std::vector<const analysis::Type*>& original_types =
-        original_struct_type->element_types();
-    const std::vector<const analysis::Type*>& new_types =
-        new_struct_type->element_types();
-    std::vector<uint32_t> element_ids;
-    for (uint32_t i = 0; i < original_types.size(); i++) {
-      Instruction* extract = ir_builder.AddCompositeExtract(
-          type_mgr->GetId(original_types[i]), object_inst->result_id(), {i});
-      element_ids.push_back(GenerateCopy(extract, type_mgr->GetId(new_types[i]),
-                                         insertion_position));
-    }
-    return ir_builder.AddCompositeConstruct(new_type_id, element_ids)
-        ->result_id();
-  } else {
-    // If we do not have an aggregate type, then we have a problem.  Either we
-    // found multiple instances of the same type, or we are copying to an
-    // incompatible type.  Either way the code is illegal.
-    assert(false &&
-           "Don't know how to copy this type.  Code is likely illegal.");
-  }
-  return 0;
-}
-
 uint32_t CopyPropagateArrays::GetMemberTypeId(
     uint32_t id, const std::vector<uint32_t>& access_chain) const {
   for (uint32_t element_index : access_chain) {
diff --git a/source/opt/copy_prop_arrays.h b/source/opt/copy_prop_arrays.h
index ed9da0e..f4314a7 100644
--- a/source/opt/copy_prop_arrays.h
+++ b/source/opt/copy_prop_arrays.h
@@ -47,7 +47,8 @@
     return IRContext::kAnalysisDefUse | IRContext::kAnalysisCFG |
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisDecorations |
-           IRContext::kAnalysisDominatorAnalysis | IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisDominatorAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
   }
 
  private:
@@ -216,12 +217,6 @@
   // |original_ptr_inst| to |type_id| and still have valid code.
   bool CanUpdateUses(Instruction* original_ptr_inst, uint32_t type_id);
 
-  // Returns the id whose value is the same as |object_to_copy| except its type
-  // is |new_type_id|.  Any instructions need to generate this value will be
-  // inserted before |insertion_position|.
-  uint32_t GenerateCopy(Instruction* object_to_copy, uint32_t new_type_id,
-                        Instruction* insertion_position);
-
   // Returns a store to |var_inst| that writes to the entire variable, and is
   // the only store that does so.  Note it does not look through OpAccessChain
   // instruction, so partial stores are not considered.
diff --git a/source/opt/dead_branch_elim_pass.cpp b/source/opt/dead_branch_elim_pass.cpp
index 9893536..087c2af 100644
--- a/source/opt/dead_branch_elim_pass.cpp
+++ b/source/opt/dead_branch_elim_pass.cpp
@@ -95,7 +95,7 @@
     Function* func, std::unordered_set<BasicBlock*>* live_blocks) {
   StructuredCFGAnalysis* cfgAnalysis = context()->GetStructuredCFGAnalysis();
 
-  std::unordered_set<BasicBlock*> continues;
+  std::unordered_set<BasicBlock*> blocks_with_backedge;
   std::vector<BasicBlock*> stack;
   stack.push_back(&*func->begin());
   bool modified = false;
@@ -107,7 +107,10 @@
     if (!live_blocks->insert(block).second) continue;
 
     uint32_t cont_id = block->ContinueBlockIdIfAny();
-    if (cont_id != 0) continues.insert(GetParentBlock(cont_id));
+    if (cont_id != 0) {
+      AddBlocksWithBackEdge(cont_id, block->id(), block->MergeBlockIdIfAny(),
+                            &blocks_with_backedge);
+    }
 
     Instruction* terminator = block->terminator();
     uint32_t live_lab_id = 0;
@@ -146,13 +149,23 @@
       }
     }
 
-    // Don't simplify branches of continue blocks. A path from the continue to
-    // the header is required.
-    // TODO(alan-baker): They can be simplified iff there remains a path to the
-    // backedge. Structured control flow should guarantee one path hits the
-    // backedge, but I've removed the requirement for structured control flow
-    // from this pass.
-    bool simplify = live_lab_id != 0 && !continues.count(block);
+    // Don't simplify back edges unless it becomes a branch to the header. Every
+    // loop must have exactly one back edge to the loop header, so we cannot
+    // remove it.
+    bool simplify = false;
+    if (live_lab_id != 0) {
+      if (!blocks_with_backedge.count(block)) {
+        // This is not a back edge.
+        simplify = true;
+      } else {
+        const auto& struct_cfg_analysis = context()->GetStructuredCFGAnalysis();
+        uint32_t header_id = struct_cfg_analysis->ContainingLoop(block->id());
+        if (live_lab_id == header_id) {
+          // The new branch will be a branch to the header.
+          simplify = true;
+        }
+      }
+    }
 
     if (simplify) {
       modified = true;
@@ -323,21 +336,7 @@
     const std::unordered_map<BasicBlock*, BasicBlock*>& unreachable_continues) {
   bool modified = false;
   for (auto ebi = func->begin(); ebi != func->end();) {
-    if (unreachable_merges.count(&*ebi)) {
-      if (ebi->begin() != ebi->tail() ||
-          ebi->terminator()->opcode() != SpvOpUnreachable) {
-        // Make unreachable, but leave the label.
-        KillAllInsts(&*ebi, false);
-        // Add unreachable terminator.
-        ebi->AddInstruction(
-            MakeUnique<Instruction>(context(), SpvOpUnreachable, 0, 0,
-                                    std::initializer_list<Operand>{}));
-        context()->AnalyzeUses(ebi->terminator());
-        context()->set_instr_block(ebi->terminator(), &*ebi);
-        modified = true;
-      }
-      ++ebi;
-    } else if (unreachable_continues.count(&*ebi)) {
+    if (unreachable_continues.count(&*ebi)) {
       uint32_t cont_id = unreachable_continues.find(&*ebi)->second->id();
       if (ebi->begin() != ebi->tail() ||
           ebi->terminator()->opcode() != SpvOpBranch ||
@@ -354,6 +353,20 @@
         modified = true;
       }
       ++ebi;
+    } else if (unreachable_merges.count(&*ebi)) {
+      if (ebi->begin() != ebi->tail() ||
+          ebi->terminator()->opcode() != SpvOpUnreachable) {
+        // Make unreachable, but leave the label.
+        KillAllInsts(&*ebi, false);
+        // Add unreachable terminator.
+        ebi->AddInstruction(
+            MakeUnique<Instruction>(context(), SpvOpUnreachable, 0, 0,
+                                    std::initializer_list<Operand>{}));
+        context()->AnalyzeUses(ebi->terminator());
+        context()->set_instr_block(ebi->terminator(), &*ebi);
+        modified = true;
+      }
+      ++ebi;
     } else if (!live_blocks.count(&*ebi)) {
       // Kill this block.
       KillAllInsts(&*ebi);
@@ -538,5 +551,39 @@
   return nullptr;
 }
 
+void DeadBranchElimPass::AddBlocksWithBackEdge(
+    uint32_t cont_id, uint32_t header_id, uint32_t merge_id,
+    std::unordered_set<BasicBlock*>* blocks_with_back_edges) {
+  std::unordered_set<uint32_t> visited;
+  visited.insert(cont_id);
+  visited.insert(header_id);
+  visited.insert(merge_id);
+
+  std::vector<uint32_t> work_list;
+  work_list.push_back(cont_id);
+
+  while (!work_list.empty()) {
+    uint32_t bb_id = work_list.back();
+    work_list.pop_back();
+
+    BasicBlock* bb = context()->get_instr_block(bb_id);
+
+    bool has_back_edge = false;
+    bb->ForEachSuccessorLabel([header_id, &visited, &work_list,
+                               &has_back_edge](uint32_t* succ_label_id) {
+      if (visited.insert(*succ_label_id).second) {
+        work_list.push_back(*succ_label_id);
+      }
+      if (*succ_label_id == header_id) {
+        has_back_edge = true;
+      }
+    });
+
+    if (has_back_edge) {
+      blocks_with_back_edges->insert(bb);
+    }
+  }
+}
+
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/dead_branch_elim_pass.h b/source/opt/dead_branch_elim_pass.h
index a3a4f5a..2ced589 100644
--- a/source/opt/dead_branch_elim_pass.h
+++ b/source/opt/dead_branch_elim_pass.h
@@ -44,7 +44,9 @@
   Status Process() override;
 
   IRContext::Analysis GetPreservedAnalyses() override {
-    return IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping;
+    return IRContext::kAnalysisDefUse |
+           IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
   }
 
  private:
@@ -146,6 +148,14 @@
                                                uint32_t merge_block_id,
                                                uint32_t loop_merge_id,
                                                uint32_t loop_continue_id);
+
+  // Adds to |blocks_with_back_edges| all of the blocks on the path from the
+  // basic block |cont_id| to |header_id| and |merge_id|.  The intention is that
+  // |cond_id| is a the continue target of a loop, |header_id| is the header of
+  // the loop, and |merge_id| is the merge block of the loop.
+  void AddBlocksWithBackEdge(
+      uint32_t cont_id, uint32_t header_id, uint32_t merge_id,
+      std::unordered_set<BasicBlock*>* blocks_with_back_edges);
 };
 
 }  // namespace opt
diff --git a/source/opt/dead_insert_elim_pass.h b/source/opt/dead_insert_elim_pass.h
index 0b111d0..01f12bb 100644
--- a/source/opt/dead_insert_elim_pass.h
+++ b/source/opt/dead_insert_elim_pass.h
@@ -45,7 +45,8 @@
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
            IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
-           IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/dead_variable_elimination.h b/source/opt/dead_variable_elimination.h
index 40a7bc0..5dde71b 100644
--- a/source/opt/dead_variable_elimination.h
+++ b/source/opt/dead_variable_elimination.h
@@ -30,7 +30,8 @@
   Status Process() override;
 
   IRContext::Analysis GetPreservedAnalyses() override {
-    return IRContext::kAnalysisDefUse;
+    return IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/decompose_initialized_variables_pass.cpp b/source/opt/decompose_initialized_variables_pass.cpp
new file mode 100644
index 0000000..875bf7e
--- /dev/null
+++ b/source/opt/decompose_initialized_variables_pass.cpp
@@ -0,0 +1,112 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/decompose_initialized_variables_pass.h"
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+using inst_iterator = InstructionList::iterator;
+
+namespace {
+
+bool HasInitializer(Instruction* inst) {
+  if (inst->opcode() != SpvOpVariable) return false;
+  if (inst->NumOperands() < 4) return false;
+
+  return true;
+}
+
+}  // namespace
+
+Pass::Status DecomposeInitializedVariablesPass::Process() {
+  auto* module = context()->module();
+  std::unordered_set<Instruction*> changed;
+
+  std::vector<std::tuple<uint32_t, uint32_t>> global_stores;
+  for (auto iter = module->types_values_begin();
+       iter != module->types_values_end(); ++iter) {
+    Instruction* inst = &(*iter);
+    if (!HasInitializer(inst)) continue;
+
+    auto var_id = inst->result_id();
+    auto val_id = inst->GetOperand(3).words[0];
+    global_stores.push_back(std::make_tuple(var_id, val_id));
+    iter->RemoveOperand(3);
+    changed.insert(&*iter);
+  }
+
+  std::unordered_set<uint32_t> entry_ids;
+  for (auto entry = module->entry_points().begin();
+       entry != module->entry_points().end(); ++entry) {
+    entry_ids.insert(entry->GetSingleWordInOperand(1));
+  }
+
+  for (auto func = module->begin(); func != module->end(); ++func) {
+    std::vector<Instruction*> function_stores;
+    auto first_block = func->entry().get();
+    inst_iterator insert_point = first_block->begin();
+    for (auto iter = first_block->begin();
+         iter != first_block->end() && iter->opcode() == SpvOpVariable;
+         ++iter) {
+      // For valid SPIRV-V, there is guaranteed to be at least one instruction
+      // after the OpVariable instructions.
+      insert_point = (*iter).NextNode();
+      Instruction* inst = &(*iter);
+      if (!HasInitializer(inst)) continue;
+
+      auto var_id = inst->result_id();
+      auto val_id = inst->GetOperand(3).words[0];
+      Instruction* store_inst = new Instruction(
+          context(), SpvOpStore, 0, 0,
+          {{SPV_OPERAND_TYPE_ID, {var_id}}, {SPV_OPERAND_TYPE_ID, {val_id}}});
+      function_stores.push_back(store_inst);
+      iter->RemoveOperand(3);
+      changed.insert(&*iter);
+    }
+
+    if (entry_ids.find(func->result_id()) != entry_ids.end()) {
+      for (auto store_ids : global_stores) {
+        uint32_t var_id;
+        uint32_t val_id;
+        std::tie(var_id, val_id) = store_ids;
+        auto* store_inst = new Instruction(
+            context(), SpvOpStore, 0, 0,
+            {{SPV_OPERAND_TYPE_ID, {var_id}}, {SPV_OPERAND_TYPE_ID, {val_id}}});
+        context()->set_instr_block(store_inst, &*first_block);
+        first_block->AddInstruction(std::unique_ptr<Instruction>(store_inst));
+        store_inst->InsertBefore(&*insert_point);
+        changed.insert(store_inst);
+      }
+    }
+
+    for (auto store = function_stores.begin(); store != function_stores.end();
+         ++store) {
+      context()->set_instr_block(*store, first_block);
+      (*store)->InsertBefore(&*insert_point);
+      changed.insert(*store);
+    }
+  }
+
+  auto* def_use_mgr = get_def_use_mgr();
+  for (auto* inst : changed) def_use_mgr->UpdateDefUse(inst);
+
+  return !changed.empty() ? Pass::Status::SuccessWithChange
+                          : Pass::Status::SuccessWithoutChange;
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/decompose_initialized_variables_pass.h b/source/opt/decompose_initialized_variables_pass.h
new file mode 100644
index 0000000..c0bd35e
--- /dev/null
+++ b/source/opt/decompose_initialized_variables_pass.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_DECOMPOSE_INITALIZED_VAIRABLES_PASS_H_
+#define SOURCE_OPT_DECOMPOSE_INITALIZED_VAIRABLES_PASS_H_
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// Converts variable declartions with initializers into seperate declaration and
+// assignment statements. This is done due to known issues with some Vulkan
+// implementations' handling of initialized variables.
+//
+// Only decomposes variables with storage classes that are valid in Vulkan
+// execution environments; Output, Private, and Function.
+// Currently only Function is implemented.
+class DecomposeInitializedVariablesPass : public Pass {
+ public:
+  const char* name() const override {
+    return "decompose-initialized-variables";
+  }
+  Status Process() override;
+
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
+           IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
+           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisScalarEvolution |
+           IRContext::kAnalysisRegisterPressure |
+           IRContext::kAnalysisValueNumberTable |
+           IRContext::kAnalysisStructuredCFG |
+           IRContext::kAnalysisBuiltinVarId |
+           IRContext::kAnalysisIdToFuncMapping | IRContext::kAnalysisTypes |
+           IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants;
+  }
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_DECOMPOSE_INITALIZED_VAIRABLES_PASS_H_
diff --git a/source/opt/eliminate_dead_functions_pass.cpp b/source/opt/eliminate_dead_functions_pass.cpp
index f067be5..a465521 100644
--- a/source/opt/eliminate_dead_functions_pass.cpp
+++ b/source/opt/eliminate_dead_functions_pass.cpp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "source/opt/eliminate_dead_functions_pass.h"
+#include "source/opt/eliminate_dead_functions_util.h"
 
 #include <unordered_set>
 
@@ -36,8 +37,8 @@
        funcIter != get_module()->end();) {
     if (live_function_set.count(&*funcIter) == 0) {
       modified = true;
-      EliminateFunction(&*funcIter);
-      funcIter = funcIter.Erase();
+      funcIter =
+          eliminatedeadfunctionsutil::EliminateFunction(context(), &funcIter);
     } else {
       ++funcIter;
     }
@@ -47,10 +48,5 @@
                   : Pass::Status::SuccessWithoutChange;
 }
 
-void EliminateDeadFunctionsPass::EliminateFunction(Function* func) {
-  // Remove all of the instruction in the function body
-  func->ForEachInst([this](Instruction* inst) { context()->KillInst(inst); },
-                    true);
-}
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/eliminate_dead_functions_pass.h b/source/opt/eliminate_dead_functions_pass.h
index 165e9a6..6ed5c42 100644
--- a/source/opt/eliminate_dead_functions_pass.h
+++ b/source/opt/eliminate_dead_functions_pass.h
@@ -30,7 +30,8 @@
   Status Process() override;
 
   IRContext::Analysis GetPreservedAnalyses() override {
-    return IRContext::kAnalysisDefUse;
+    return IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/eliminate_dead_functions_util.cpp b/source/opt/eliminate_dead_functions_util.cpp
new file mode 100644
index 0000000..8a38959
--- /dev/null
+++ b/source/opt/eliminate_dead_functions_util.cpp
@@ -0,0 +1,32 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "eliminate_dead_functions_util.h"
+
+namespace spvtools {
+namespace opt {
+
+namespace eliminatedeadfunctionsutil {
+
+Module::iterator EliminateFunction(IRContext* context,
+                                   Module::iterator* func_iter) {
+  (*func_iter)
+      ->ForEachInst([context](Instruction* inst) { context->KillInst(inst); },
+                    true);
+  return func_iter->Erase();
+}
+
+}  // namespace eliminatedeadfunctionsutil
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/eliminate_dead_functions_util.h b/source/opt/eliminate_dead_functions_util.h
new file mode 100644
index 0000000..9fcce95
--- /dev/null
+++ b/source/opt/eliminate_dead_functions_util.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_ELIMINATE_DEAD_FUNCTIONS_UTIL_H_
+#define SOURCE_OPT_ELIMINATE_DEAD_FUNCTIONS_UTIL_H_
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+// Provides functionality for eliminating functions that are not needed, for use
+// by various analyses and passes.
+namespace eliminatedeadfunctionsutil {
+
+// Removes all of the function's instructions, removes the function from the
+// module, and returns the next iterator.
+Module::iterator EliminateFunction(IRContext* context,
+                                   Module::iterator* func_iter);
+
+}  // namespace eliminatedeadfunctionsutil
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  //  SOURCE_OPT_ELIMINATE_DEAD_FUNCTIONS_UTIL_H_
diff --git a/source/opt/eliminate_dead_members_pass.cpp b/source/opt/eliminate_dead_members_pass.cpp
new file mode 100644
index 0000000..0b73b2d
--- /dev/null
+++ b/source/opt/eliminate_dead_members_pass.cpp
@@ -0,0 +1,637 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/eliminate_dead_members_pass.h"
+
+#include "ir_builder.h"
+#include "source/opt/ir_context.h"
+
+namespace {
+const uint32_t kRemovedMember = 0xFFFFFFFF;
+}
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status EliminateDeadMembersPass::Process() {
+  if (!context()->get_feature_mgr()->HasCapability(SpvCapabilityShader))
+    return Status::SuccessWithoutChange;
+
+  FindLiveMembers();
+  if (RemoveDeadMembers()) {
+    return Status::SuccessWithChange;
+  }
+  return Status::SuccessWithoutChange;
+}
+
+void EliminateDeadMembersPass::FindLiveMembers() {
+  // Until we have implemented the rewritting of OpSpecConsantOp instructions,
+  // we have to mark them as fully used just to be safe.
+  for (auto& inst : get_module()->types_values()) {
+    if (inst.opcode() == SpvOpSpecConstantOp) {
+      MarkTypeAsFullyUsed(inst.type_id());
+    } else if (inst.opcode() == SpvOpVariable) {
+      switch (inst.GetSingleWordInOperand(0)) {
+        case SpvStorageClassInput:
+        case SpvStorageClassOutput:
+          MarkPointeeTypeAsFullUsed(inst.type_id());
+          break;
+        default:
+          break;
+      }
+    }
+  }
+
+  for (const Function& func : *get_module()) {
+    FindLiveMembers(func);
+  }
+}
+
+void EliminateDeadMembersPass::FindLiveMembers(const Function& function) {
+  function.ForEachInst(
+      [this](const Instruction* inst) { FindLiveMembers(inst); });
+}
+
+void EliminateDeadMembersPass::FindLiveMembers(const Instruction* inst) {
+  switch (inst->opcode()) {
+    case SpvOpStore:
+      MarkMembersAsLiveForStore(inst);
+      break;
+    case SpvOpCopyMemory:
+    case SpvOpCopyMemorySized:
+      MarkMembersAsLiveForCopyMemory(inst);
+      break;
+    case SpvOpCompositeExtract:
+      MarkMembersAsLiveForExtract(inst);
+      break;
+    case SpvOpAccessChain:
+    case SpvOpInBoundsAccessChain:
+    case SpvOpPtrAccessChain:
+    case SpvOpInBoundsPtrAccessChain:
+      MarkMembersAsLiveForAccessChain(inst);
+      break;
+    case SpvOpReturnValue:
+      // This should be an issue only if we are returning from the entry point.
+      // However, for now I will keep it more conservative because functions are
+      // often inlined leaving only the entry points.
+      MarkOperandTypeAsFullyUsed(inst, 0);
+      break;
+    case SpvOpArrayLength:
+      MarkMembersAsLiveForArrayLength(inst);
+      break;
+    case SpvOpLoad:
+    case SpvOpCompositeInsert:
+    case SpvOpCompositeConstruct:
+      break;
+    default:
+      // This path is here for safety.  All instructions that can reference
+      // structs in a function body should be handled above.  However, this will
+      // keep the pass valid, but not optimal, as new instructions get added
+      // or if something was missed.
+      MarkStructOperandsAsFullyUsed(inst);
+      break;
+  }
+}
+
+void EliminateDeadMembersPass::MarkMembersAsLiveForStore(
+    const Instruction* inst) {
+  // We should only have to mark the members as live if the store is to
+  // memory that is read outside of the shader.  Other passes can remove all
+  // store to memory that is not visible outside of the shader, so we do not
+  // complicate the code for now.
+  assert(inst->opcode() == SpvOpStore);
+  uint32_t object_id = inst->GetSingleWordInOperand(1);
+  Instruction* object_inst = context()->get_def_use_mgr()->GetDef(object_id);
+  uint32_t object_type_id = object_inst->type_id();
+  MarkTypeAsFullyUsed(object_type_id);
+}
+
+void EliminateDeadMembersPass::MarkTypeAsFullyUsed(uint32_t type_id) {
+  Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+  assert(type_inst != nullptr);
+  if (type_inst->opcode() != SpvOpTypeStruct) {
+    return;
+  }
+
+  // Mark every member of the current struct as used.
+  for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) {
+    used_members_[type_id].insert(i);
+  }
+
+  // Mark any sub struct as fully used.
+  for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) {
+    MarkTypeAsFullyUsed(type_inst->GetSingleWordInOperand(i));
+  }
+}
+
+void EliminateDeadMembersPass::MarkPointeeTypeAsFullUsed(uint32_t ptr_type_id) {
+  Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id);
+  assert(ptr_type_inst->opcode() == SpvOpTypePointer);
+  MarkTypeAsFullyUsed(ptr_type_inst->GetSingleWordInOperand(1));
+}
+
+void EliminateDeadMembersPass::MarkMembersAsLiveForCopyMemory(
+    const Instruction* inst) {
+  uint32_t target_id = inst->GetSingleWordInOperand(0);
+  Instruction* target_inst = get_def_use_mgr()->GetDef(target_id);
+  uint32_t pointer_type_id = target_inst->type_id();
+  Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
+  uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
+  MarkTypeAsFullyUsed(type_id);
+}
+
+void EliminateDeadMembersPass::MarkMembersAsLiveForExtract(
+    const Instruction* inst) {
+  assert(inst->opcode() == SpvOpCompositeExtract);
+
+  uint32_t composite_id = inst->GetSingleWordInOperand(0);
+  Instruction* composite_inst = get_def_use_mgr()->GetDef(composite_id);
+  uint32_t type_id = composite_inst->type_id();
+
+  for (uint32_t i = 1; i < inst->NumInOperands(); ++i) {
+    Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+    uint32_t member_idx = inst->GetSingleWordInOperand(i);
+    switch (type_inst->opcode()) {
+      case SpvOpTypeStruct:
+        used_members_[type_id].insert(member_idx);
+        type_id = type_inst->GetSingleWordInOperand(member_idx);
+        break;
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+      case SpvOpTypeVector:
+      case SpvOpTypeMatrix:
+        type_id = type_inst->GetSingleWordInOperand(0);
+        break;
+      default:
+        assert(false);
+    }
+  }
+}
+
+void EliminateDeadMembersPass::MarkMembersAsLiveForAccessChain(
+    const Instruction* inst) {
+  assert(inst->opcode() == SpvOpAccessChain ||
+         inst->opcode() == SpvOpInBoundsAccessChain ||
+         inst->opcode() == SpvOpPtrAccessChain ||
+         inst->opcode() == SpvOpInBoundsPtrAccessChain);
+
+  uint32_t pointer_id = inst->GetSingleWordInOperand(0);
+  Instruction* pointer_inst = get_def_use_mgr()->GetDef(pointer_id);
+  uint32_t pointer_type_id = pointer_inst->type_id();
+  Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
+  uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
+
+  analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
+
+  // For a pointer access chain, we need to skip the |element| index.  It is not
+  // a reference to the member of a struct, and it does not change the type.
+  uint32_t i = (inst->opcode() == SpvOpAccessChain ||
+                        inst->opcode() == SpvOpInBoundsAccessChain
+                    ? 1
+                    : 2);
+  for (; i < inst->NumInOperands(); ++i) {
+    Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+    switch (type_inst->opcode()) {
+      case SpvOpTypeStruct: {
+        const analysis::IntConstant* member_idx =
+            const_mgr->FindDeclaredConstant(inst->GetSingleWordInOperand(i))
+                ->AsIntConstant();
+        assert(member_idx);
+        if (member_idx->type()->AsInteger()->width() == 32) {
+          used_members_[type_id].insert(member_idx->GetU32());
+          type_id = type_inst->GetSingleWordInOperand(member_idx->GetU32());
+        } else {
+          used_members_[type_id].insert(
+              static_cast<uint32_t>(member_idx->GetU64()));
+          type_id = type_inst->GetSingleWordInOperand(
+              static_cast<uint32_t>(member_idx->GetU64()));
+        }
+      } break;
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+      case SpvOpTypeVector:
+      case SpvOpTypeMatrix:
+        type_id = type_inst->GetSingleWordInOperand(0);
+        break;
+      default:
+        assert(false);
+    }
+  }
+}
+
+void EliminateDeadMembersPass::MarkOperandTypeAsFullyUsed(
+    const Instruction* inst, uint32_t in_idx) {
+  uint32_t op_id = inst->GetSingleWordInOperand(in_idx);
+  Instruction* op_inst = get_def_use_mgr()->GetDef(op_id);
+  MarkTypeAsFullyUsed(op_inst->type_id());
+}
+
+void EliminateDeadMembersPass::MarkMembersAsLiveForArrayLength(
+    const Instruction* inst) {
+  assert(inst->opcode() == SpvOpArrayLength);
+  uint32_t object_id = inst->GetSingleWordInOperand(0);
+  Instruction* object_inst = get_def_use_mgr()->GetDef(object_id);
+  uint32_t pointer_type_id = object_inst->type_id();
+  Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
+  uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
+  used_members_[type_id].insert(inst->GetSingleWordInOperand(1));
+}
+
+bool EliminateDeadMembersPass::RemoveDeadMembers() {
+  bool modified = false;
+
+  // First update all of the OpTypeStruct instructions.
+  get_module()->ForEachInst([&modified, this](Instruction* inst) {
+    switch (inst->opcode()) {
+      case SpvOpTypeStruct:
+        modified |= UpdateOpTypeStruct(inst);
+        break;
+      default:
+        break;
+    }
+  });
+
+  // Now update all of the instructions that reference the OpTypeStructs.
+  get_module()->ForEachInst([&modified, this](Instruction* inst) {
+    switch (inst->opcode()) {
+      case SpvOpMemberName:
+        modified |= UpdateOpMemberNameOrDecorate(inst);
+        break;
+      case SpvOpMemberDecorate:
+        modified |= UpdateOpMemberNameOrDecorate(inst);
+        break;
+      case SpvOpGroupMemberDecorate:
+        modified |= UpdateOpGroupMemberDecorate(inst);
+        break;
+      case SpvOpSpecConstantComposite:
+      case SpvOpConstantComposite:
+      case SpvOpCompositeConstruct:
+        modified |= UpdateConstantComposite(inst);
+        break;
+      case SpvOpAccessChain:
+      case SpvOpInBoundsAccessChain:
+      case SpvOpPtrAccessChain:
+      case SpvOpInBoundsPtrAccessChain:
+        modified |= UpdateAccessChain(inst);
+        break;
+      case SpvOpCompositeExtract:
+        modified |= UpdateCompsiteExtract(inst);
+        break;
+      case SpvOpCompositeInsert:
+        modified |= UpdateCompositeInsert(inst);
+        break;
+      case SpvOpArrayLength:
+        modified |= UpdateOpArrayLength(inst);
+        break;
+      case SpvOpSpecConstantOp:
+        assert(false && "Not yet implemented.");
+        // with OpCompositeExtract, OpCompositeInsert
+        // For kernels: OpAccessChain, OpInBoundsAccessChain, OpPtrAccessChain,
+        // OpInBoundsPtrAccessChain
+        break;
+      default:
+        break;
+    }
+  });
+  return modified;
+}
+
+bool EliminateDeadMembersPass::UpdateOpTypeStruct(Instruction* inst) {
+  assert(inst->opcode() == SpvOpTypeStruct);
+
+  const auto& live_members = used_members_[inst->result_id()];
+  if (live_members.size() == inst->NumInOperands()) {
+    return false;
+  }
+
+  Instruction::OperandList new_operands;
+  for (uint32_t idx : live_members) {
+    new_operands.emplace_back(inst->GetInOperand(idx));
+  }
+
+  inst->SetInOperands(std::move(new_operands));
+  context()->UpdateDefUse(inst);
+  return true;
+}
+
+bool EliminateDeadMembersPass::UpdateOpMemberNameOrDecorate(Instruction* inst) {
+  assert(inst->opcode() == SpvOpMemberName ||
+         inst->opcode() == SpvOpMemberDecorate);
+
+  uint32_t type_id = inst->GetSingleWordInOperand(0);
+  auto live_members = used_members_.find(type_id);
+  if (live_members == used_members_.end()) {
+    return false;
+  }
+
+  uint32_t orig_member_idx = inst->GetSingleWordInOperand(1);
+  uint32_t new_member_idx = GetNewMemberIndex(type_id, orig_member_idx);
+
+  if (new_member_idx == kRemovedMember) {
+    context()->KillInst(inst);
+    return true;
+  }
+
+  if (new_member_idx == orig_member_idx) {
+    return false;
+  }
+
+  inst->SetInOperand(1, {new_member_idx});
+  return true;
+}
+
+bool EliminateDeadMembersPass::UpdateOpGroupMemberDecorate(Instruction* inst) {
+  assert(inst->opcode() == SpvOpGroupMemberDecorate);
+
+  bool modified = false;
+
+  Instruction::OperandList new_operands;
+  new_operands.emplace_back(inst->GetInOperand(0));
+  for (uint32_t i = 1; i < inst->NumInOperands(); i += 2) {
+    uint32_t type_id = inst->GetSingleWordInOperand(i);
+    uint32_t member_idx = inst->GetSingleWordInOperand(i + 1);
+    uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx);
+
+    if (new_member_idx == kRemovedMember) {
+      modified = true;
+      continue;
+    }
+
+    new_operands.emplace_back(inst->GetOperand(i));
+    if (new_member_idx != member_idx) {
+      new_operands.emplace_back(
+          Operand({SPV_OPERAND_TYPE_LITERAL_INTEGER, {new_member_idx}}));
+      modified = true;
+    } else {
+      new_operands.emplace_back(inst->GetOperand(i + 1));
+    }
+  }
+
+  if (!modified) {
+    return false;
+  }
+
+  if (new_operands.size() == 1) {
+    context()->KillInst(inst);
+    return true;
+  }
+
+  inst->SetInOperands(std::move(new_operands));
+  context()->UpdateDefUse(inst);
+  return true;
+}
+
+bool EliminateDeadMembersPass::UpdateConstantComposite(Instruction* inst) {
+  assert(inst->opcode() == SpvOpConstantComposite ||
+         inst->opcode() == SpvOpCompositeConstruct);
+  uint32_t type_id = inst->type_id();
+
+  bool modified = false;
+  Instruction::OperandList new_operands;
+  for (uint32_t i = 0; i < inst->NumInOperands(); ++i) {
+    uint32_t new_idx = GetNewMemberIndex(type_id, i);
+    if (new_idx == kRemovedMember) {
+      modified = true;
+    } else {
+      new_operands.emplace_back(inst->GetInOperand(i));
+    }
+  }
+  inst->SetInOperands(std::move(new_operands));
+  context()->UpdateDefUse(inst);
+  return modified;
+}
+
+bool EliminateDeadMembersPass::UpdateAccessChain(Instruction* inst) {
+  assert(inst->opcode() == SpvOpAccessChain ||
+         inst->opcode() == SpvOpInBoundsAccessChain ||
+         inst->opcode() == SpvOpPtrAccessChain ||
+         inst->opcode() == SpvOpInBoundsPtrAccessChain);
+
+  uint32_t pointer_id = inst->GetSingleWordInOperand(0);
+  Instruction* pointer_inst = get_def_use_mgr()->GetDef(pointer_id);
+  uint32_t pointer_type_id = pointer_inst->type_id();
+  Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
+  uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
+
+  analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
+  Instruction::OperandList new_operands;
+  bool modified = false;
+  new_operands.emplace_back(inst->GetInOperand(0));
+
+  // For pointer access chains we want to copy the element operand.
+  if (inst->opcode() == SpvOpPtrAccessChain ||
+      inst->opcode() == SpvOpInBoundsPtrAccessChain) {
+    new_operands.emplace_back(inst->GetInOperand(1));
+  }
+
+  for (uint32_t i = static_cast<uint32_t>(new_operands.size());
+       i < inst->NumInOperands(); ++i) {
+    Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+    switch (type_inst->opcode()) {
+      case SpvOpTypeStruct: {
+        const analysis::IntConstant* member_idx =
+            const_mgr->FindDeclaredConstant(inst->GetSingleWordInOperand(i))
+                ->AsIntConstant();
+        assert(member_idx);
+        uint32_t orig_member_idx;
+        if (member_idx->type()->AsInteger()->width() == 32) {
+          orig_member_idx = member_idx->GetU32();
+        } else {
+          orig_member_idx = static_cast<uint32_t>(member_idx->GetU64());
+        }
+        uint32_t new_member_idx = GetNewMemberIndex(type_id, orig_member_idx);
+        assert(new_member_idx != kRemovedMember);
+        if (orig_member_idx != new_member_idx) {
+          InstructionBuilder ir_builder(
+              context(), inst,
+              IRContext::kAnalysisDefUse |
+                  IRContext::kAnalysisInstrToBlockMapping);
+          uint32_t const_id =
+              ir_builder.GetUintConstant(new_member_idx)->result_id();
+          new_operands.emplace_back(Operand({SPV_OPERAND_TYPE_ID, {const_id}}));
+          modified = true;
+        } else {
+          new_operands.emplace_back(inst->GetInOperand(i));
+        }
+        // The type will have already been rewritten, so use the new member
+        // index.
+        type_id = type_inst->GetSingleWordInOperand(new_member_idx);
+      } break;
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+      case SpvOpTypeVector:
+      case SpvOpTypeMatrix:
+        new_operands.emplace_back(inst->GetInOperand(i));
+        type_id = type_inst->GetSingleWordInOperand(0);
+        break;
+      default:
+        assert(false);
+        break;
+    }
+  }
+
+  if (!modified) {
+    return false;
+  }
+  inst->SetInOperands(std::move(new_operands));
+  context()->UpdateDefUse(inst);
+  return true;
+}
+
+uint32_t EliminateDeadMembersPass::GetNewMemberIndex(uint32_t type_id,
+                                                     uint32_t member_idx) {
+  auto live_members = used_members_.find(type_id);
+  if (live_members == used_members_.end()) {
+    return member_idx;
+  }
+
+  auto current_member = live_members->second.find(member_idx);
+  if (current_member == live_members->second.end()) {
+    return kRemovedMember;
+  }
+
+  return static_cast<uint32_t>(
+      std::distance(live_members->second.begin(), current_member));
+}
+
+bool EliminateDeadMembersPass::UpdateCompsiteExtract(Instruction* inst) {
+  uint32_t object_id = inst->GetSingleWordInOperand(0);
+  Instruction* object_inst = get_def_use_mgr()->GetDef(object_id);
+  uint32_t type_id = object_inst->type_id();
+
+  Instruction::OperandList new_operands;
+  bool modified = false;
+  new_operands.emplace_back(inst->GetInOperand(0));
+  for (uint32_t i = 1; i < inst->NumInOperands(); ++i) {
+    uint32_t member_idx = inst->GetSingleWordInOperand(i);
+    uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx);
+    assert(new_member_idx != kRemovedMember);
+    if (member_idx != new_member_idx) {
+      modified = true;
+    }
+    new_operands.emplace_back(
+        Operand({SPV_OPERAND_TYPE_LITERAL_INTEGER, {new_member_idx}}));
+
+    Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+    switch (type_inst->opcode()) {
+      case SpvOpTypeStruct:
+        assert(i != 1 || (inst->opcode() != SpvOpPtrAccessChain &&
+                          inst->opcode() != SpvOpInBoundsPtrAccessChain));
+        // The type will have already been rewriten, so use the new member
+        // index.
+        type_id = type_inst->GetSingleWordInOperand(new_member_idx);
+        break;
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+      case SpvOpTypeVector:
+      case SpvOpTypeMatrix:
+        type_id = type_inst->GetSingleWordInOperand(0);
+        break;
+      default:
+        assert(false);
+    }
+  }
+
+  if (!modified) {
+    return false;
+  }
+  inst->SetInOperands(std::move(new_operands));
+  context()->UpdateDefUse(inst);
+  return true;
+}
+
+bool EliminateDeadMembersPass::UpdateCompositeInsert(Instruction* inst) {
+  uint32_t composite_id = inst->GetSingleWordInOperand(1);
+  Instruction* composite_inst = get_def_use_mgr()->GetDef(composite_id);
+  uint32_t type_id = composite_inst->type_id();
+
+  Instruction::OperandList new_operands;
+  bool modified = false;
+  new_operands.emplace_back(inst->GetInOperand(0));
+  new_operands.emplace_back(inst->GetInOperand(1));
+  for (uint32_t i = 2; i < inst->NumInOperands(); ++i) {
+    uint32_t member_idx = inst->GetSingleWordInOperand(i);
+    uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx);
+    if (new_member_idx == kRemovedMember) {
+      context()->KillInst(inst);
+      return true;
+    }
+
+    if (member_idx != new_member_idx) {
+      modified = true;
+    }
+    new_operands.emplace_back(
+        Operand({SPV_OPERAND_TYPE_LITERAL_INTEGER, {new_member_idx}}));
+
+    Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+    switch (type_inst->opcode()) {
+      case SpvOpTypeStruct:
+        // The type will have already been rewritten, so use the new member
+        // index.
+        type_id = type_inst->GetSingleWordInOperand(new_member_idx);
+        break;
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+      case SpvOpTypeVector:
+      case SpvOpTypeMatrix:
+        type_id = type_inst->GetSingleWordInOperand(0);
+        break;
+      default:
+        assert(false);
+    }
+  }
+
+  if (!modified) {
+    return false;
+  }
+  inst->SetInOperands(std::move(new_operands));
+  context()->UpdateDefUse(inst);
+  return true;
+}
+
+bool EliminateDeadMembersPass::UpdateOpArrayLength(Instruction* inst) {
+  uint32_t struct_id = inst->GetSingleWordInOperand(0);
+  Instruction* struct_inst = get_def_use_mgr()->GetDef(struct_id);
+  uint32_t pointer_type_id = struct_inst->type_id();
+  Instruction* pointer_type_inst = get_def_use_mgr()->GetDef(pointer_type_id);
+  uint32_t type_id = pointer_type_inst->GetSingleWordInOperand(1);
+
+  uint32_t member_idx = inst->GetSingleWordInOperand(1);
+  uint32_t new_member_idx = GetNewMemberIndex(type_id, member_idx);
+  assert(new_member_idx != kRemovedMember);
+
+  if (member_idx == new_member_idx) {
+    return false;
+  }
+
+  inst->SetInOperand(1, {new_member_idx});
+  context()->UpdateDefUse(inst);
+  return true;
+}
+
+void EliminateDeadMembersPass::MarkStructOperandsAsFullyUsed(
+    const Instruction* inst) {
+  if (inst->type_id() != 0) {
+    MarkTypeAsFullyUsed(inst->type_id());
+  }
+
+  inst->ForEachInId([this](const uint32_t* id) {
+    Instruction* instruction = get_def_use_mgr()->GetDef(*id);
+    if (instruction->type_id() != 0) {
+      MarkTypeAsFullyUsed(instruction->type_id());
+    }
+  });
+}
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/eliminate_dead_members_pass.h b/source/opt/eliminate_dead_members_pass.h
new file mode 100644
index 0000000..4feaa55
--- /dev/null
+++ b/source/opt/eliminate_dead_members_pass.h
@@ -0,0 +1,146 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_ELIMINATE_DEAD_MEMBERS_PASS_H_
+#define SOURCE_OPT_ELIMINATE_DEAD_MEMBERS_PASS_H_
+
+#include "source/opt/def_use_manager.h"
+#include "source/opt/function.h"
+#include "source/opt/mem_pass.h"
+#include "source/opt/module.h"
+
+namespace spvtools {
+namespace opt {
+
+// Remove unused members from structures.  The remaining members will remain at
+// the same offset.
+class EliminateDeadMembersPass : public MemPass {
+ public:
+  const char* name() const override { return "eliminate-dead-members"; }
+  Status Process() override;
+
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisDefUse |
+           IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG |
+           IRContext::kAnalysisDominatorAnalysis |
+           IRContext::kAnalysisLoopAnalysis |
+           IRContext::kAnalysisScalarEvolution |
+           IRContext::kAnalysisRegisterPressure |
+           IRContext::kAnalysisValueNumberTable |
+           IRContext::kAnalysisStructuredCFG |
+           IRContext::kAnalysisBuiltinVarId |
+           IRContext::kAnalysisIdToFuncMapping;
+  }
+
+ private:
+  // Populate |used_members_| with the member of structures that are live in the
+  // current context.
+  void FindLiveMembers();
+
+  // Add to |used_members_| the member of structures that are live in
+  // |function|.
+  void FindLiveMembers(const Function& function);
+  // Add to |used_members_| the member of structures that are live in |inst|.
+  void FindLiveMembers(const Instruction* inst);
+
+  // Add to |used_members_| the members that are live in the |OpStore|
+  // instruction |inst|.
+  void MarkMembersAsLiveForStore(const Instruction* inst);
+
+  // Add to |used_members_| the members that are live in the |OpCopyMemory*|
+  // instruction |inst|.
+  void MarkMembersAsLiveForCopyMemory(const Instruction* inst);
+
+  // Add to |used_members_| the members that are live in the
+  // |OpCompositeExtract| instruction |inst|.
+  void MarkMembersAsLiveForExtract(const Instruction* inst);
+
+  // Add to |used_members_| the members that are live in the |Op*AccessChain|
+  // instruction |inst|.
+  void MarkMembersAsLiveForAccessChain(const Instruction* inst);
+
+  // Add the member referenced by the OpArrayLength instruction |inst| to
+  // |uses_members_|.
+  void MarkMembersAsLiveForArrayLength(const Instruction* inst);
+
+  // Remove dead members from structs and updates any instructions that need to
+  // be updated as a consequence.  Return true if something changed.
+  bool RemoveDeadMembers();
+
+  // Update |inst|, which must be an |OpMemberName| or |OpMemberDecorate|
+  // instruction, so it references the correct member after the struct is
+  // updated.  Return true if something changed.
+  bool UpdateOpMemberNameOrDecorate(Instruction* inst);
+
+  // Update |inst|, which must be an |OpGroupMemberDecorate| instruction, so it
+  // references the correct member after the struct is updated.  Return true if
+  // something changed.
+  bool UpdateOpGroupMemberDecorate(Instruction* inst);
+
+  // Update the |OpTypeStruct| instruction |inst| my removing the members that
+  // are not live.  Return true if something changed.
+  bool UpdateOpTypeStruct(Instruction* inst);
+
+  // Update the |OpConstantComposite| instruction |inst| to match the change
+  // made to the type that was being generated.  Return true if something
+  // changed.
+  bool UpdateConstantComposite(Instruction* inst);
+
+  // Update the |Op*AccessChain| instruction |inst| to reference the correct
+  // members. All members referenced in the access chain must be live.  This
+  // function must be called after the |OpTypeStruct| instruction for the type
+  // has been updated.  Return true if something changed.
+  bool UpdateAccessChain(Instruction* inst);
+
+  // Update the |OpCompositeExtract| instruction |inst| to reference the correct
+  // members. All members referenced in the instruction must be live.  This
+  // function must be called after the |OpTypeStruct| instruction for the type
+  // has been updated.  Return true if something changed.
+  bool UpdateCompsiteExtract(Instruction* inst);
+
+  // Update the |OpCompositeInsert| instruction |inst| to reference the correct
+  // members. If the member being inserted is not live, then |inst| is killed.
+  // This function must be called after the |OpTypeStruct| instruction for the
+  // type has been updated.  Return true if something changed.
+  bool UpdateCompositeInsert(Instruction* inst);
+
+  // Update the |OpArrayLength| instruction |inst| to reference the correct
+  // member. The member referenced in the instruction must be live.  Return true
+  // if something changed.
+  bool UpdateOpArrayLength(Instruction* inst);
+
+  // Add all of the members of type |type_id| and members of any subtypes to
+  // |used_members_|.
+  void MarkTypeAsFullyUsed(uint32_t type_id);
+
+  // Add all of the members of the type of the operand |in_idx| in |inst| and
+  // members of any subtypes to |uses_members_|.
+  void MarkOperandTypeAsFullyUsed(const Instruction* inst, uint32_t in_idx);
+
+  // Return the index of the member that use to be the |member_idx|th member of
+  // |type_id|.  If the member has been removed, |kRemovedMember| is returned.
+  uint32_t GetNewMemberIndex(uint32_t type_id, uint32_t member_idx);
+
+  // A map from a type id to a set of indices representing the members of the
+  // type that are used, and must be kept.
+  std::unordered_map<uint32_t, std::set<uint32_t>> used_members_;
+  void MarkStructOperandsAsFullyUsed(const Instruction* inst);
+  void MarkPointeeTypeAsFullUsed(uint32_t ptr_type_id);
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_ELIMINATE_DEAD_MEMBERS_PASS_H_
diff --git a/source/opt/fix_storage_class.cpp b/source/opt/fix_storage_class.cpp
new file mode 100644
index 0000000..03da0d0
--- /dev/null
+++ b/source/opt/fix_storage_class.cpp
@@ -0,0 +1,330 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "fix_storage_class.h"
+
+#include <set>
+
+#include "source/opt/instruction.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status FixStorageClass::Process() {
+  bool modified = false;
+
+  get_module()->ForEachInst([this, &modified](Instruction* inst) {
+    if (inst->opcode() == SpvOpVariable) {
+      std::set<uint32_t> seen;
+      std::vector<std::pair<Instruction*, uint32_t>> uses;
+      get_def_use_mgr()->ForEachUse(inst,
+                                    [&uses](Instruction* use, uint32_t op_idx) {
+                                      uses.push_back({use, op_idx});
+                                    });
+
+      for (auto& use : uses) {
+        modified |= PropagateStorageClass(
+            use.first,
+            static_cast<SpvStorageClass>(inst->GetSingleWordInOperand(0)),
+            &seen);
+        assert(seen.empty() && "Seen was not properly reset.");
+        modified |=
+            PropagateType(use.first, inst->type_id(), use.second, &seen);
+        assert(seen.empty() && "Seen was not properly reset.");
+      }
+    }
+  });
+  return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+bool FixStorageClass::PropagateStorageClass(Instruction* inst,
+                                            SpvStorageClass storage_class,
+                                            std::set<uint32_t>* seen) {
+  if (!IsPointerResultType(inst)) {
+    return false;
+  }
+
+  if (IsPointerToStorageClass(inst, storage_class)) {
+    if (inst->opcode() == SpvOpPhi) {
+      if (!seen->insert(inst->result_id()).second) {
+        return false;
+      }
+    }
+
+    bool modified = false;
+    std::vector<Instruction*> uses;
+    get_def_use_mgr()->ForEachUser(
+        inst, [&uses](Instruction* use) { uses.push_back(use); });
+    for (Instruction* use : uses) {
+      modified |= PropagateStorageClass(use, storage_class, seen);
+    }
+
+    if (inst->opcode() == SpvOpPhi) {
+      seen->erase(inst->result_id());
+    }
+    return modified;
+  }
+
+  switch (inst->opcode()) {
+    case SpvOpAccessChain:
+    case SpvOpPtrAccessChain:
+    case SpvOpInBoundsAccessChain:
+    case SpvOpCopyObject:
+    case SpvOpPhi:
+    case SpvOpSelect:
+      FixInstructionStorageClass(inst, storage_class, seen);
+      return true;
+    case SpvOpFunctionCall:
+      // We cannot be sure of the actual connection between the storage class
+      // of the parameter and the storage class of the result, so we should not
+      // do anything.  If the result type needs to be fixed, the function call
+      // should be inlined.
+      return false;
+    case SpvOpImageTexelPointer:
+    case SpvOpLoad:
+    case SpvOpStore:
+    case SpvOpCopyMemory:
+    case SpvOpCopyMemorySized:
+    case SpvOpVariable:
+    case SpvOpBitcast:
+      // Nothing to change for these opcode.  The result type is the same
+      // regardless of the storage class of the operand.
+      return false;
+    default:
+      assert(false &&
+             "Not expecting instruction to have a pointer result type.");
+      return false;
+  }
+}
+
+void FixStorageClass::FixInstructionStorageClass(Instruction* inst,
+                                                 SpvStorageClass storage_class,
+                                                 std::set<uint32_t>* seen) {
+  assert(IsPointerResultType(inst) &&
+         "The result type of the instruction must be a pointer.");
+
+  ChangeResultStorageClass(inst, storage_class);
+
+  std::vector<Instruction*> uses;
+  get_def_use_mgr()->ForEachUser(
+      inst, [&uses](Instruction* use) { uses.push_back(use); });
+  for (Instruction* use : uses) {
+    PropagateStorageClass(use, storage_class, seen);
+  }
+}
+
+void FixStorageClass::ChangeResultStorageClass(
+    Instruction* inst, SpvStorageClass storage_class) const {
+  analysis::TypeManager* type_mgr = context()->get_type_mgr();
+  Instruction* result_type_inst = get_def_use_mgr()->GetDef(inst->type_id());
+  assert(result_type_inst->opcode() == SpvOpTypePointer);
+  uint32_t pointee_type_id = result_type_inst->GetSingleWordInOperand(1);
+  uint32_t new_result_type_id =
+      type_mgr->FindPointerToType(pointee_type_id, storage_class);
+  inst->SetResultType(new_result_type_id);
+  context()->UpdateDefUse(inst);
+}
+
+bool FixStorageClass::IsPointerResultType(Instruction* inst) {
+  if (inst->type_id() == 0) {
+    return false;
+  }
+  const analysis::Type* ret_type =
+      context()->get_type_mgr()->GetType(inst->type_id());
+  return ret_type->AsPointer() != nullptr;
+}
+
+bool FixStorageClass::IsPointerToStorageClass(Instruction* inst,
+                                              SpvStorageClass storage_class) {
+  analysis::TypeManager* type_mgr = context()->get_type_mgr();
+  analysis::Type* pType = type_mgr->GetType(inst->type_id());
+  const analysis::Pointer* result_type = pType->AsPointer();
+
+  if (result_type == nullptr) {
+    return false;
+  }
+
+  return (result_type->storage_class() == storage_class);
+}
+
+bool FixStorageClass::ChangeResultType(Instruction* inst,
+                                       uint32_t new_type_id) {
+  if (inst->type_id() == new_type_id) {
+    return false;
+  }
+
+  context()->ForgetUses(inst);
+  inst->SetResultType(new_type_id);
+  context()->AnalyzeUses(inst);
+  return true;
+}
+
+bool FixStorageClass::PropagateType(Instruction* inst, uint32_t type_id,
+                                    uint32_t op_idx, std::set<uint32_t>* seen) {
+  assert(type_id != 0 && "Not given a valid type in PropagateType");
+  bool modified = false;
+
+  // If the type of operand |op_idx| forces the result type of |inst| to a
+  // particular type, then we want find that type.
+  uint32_t new_type_id = 0;
+  switch (inst->opcode()) {
+    case SpvOpAccessChain:
+    case SpvOpPtrAccessChain:
+    case SpvOpInBoundsAccessChain:
+    case SpvOpInBoundsPtrAccessChain:
+      if (op_idx == 2) {
+        new_type_id = WalkAccessChainType(inst, type_id);
+      }
+      break;
+    case SpvOpCopyObject:
+      new_type_id = type_id;
+      break;
+    case SpvOpPhi:
+      if (seen->insert(inst->result_id()).second) {
+        new_type_id = type_id;
+      }
+      break;
+    case SpvOpSelect:
+      if (op_idx > 2) {
+        new_type_id = type_id;
+      }
+      break;
+    case SpvOpFunctionCall:
+      // We cannot be sure of the actual connection between the type
+      // of the parameter and the type of the result, so we should not
+      // do anything.  If the result type needs to be fixed, the function call
+      // should be inlined.
+      return false;
+    case SpvOpLoad: {
+      Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+      new_type_id = type_inst->GetSingleWordInOperand(1);
+      break;
+    }
+    case SpvOpStore: {
+      uint32_t obj_id = inst->GetSingleWordInOperand(1);
+      Instruction* obj_inst = get_def_use_mgr()->GetDef(obj_id);
+      uint32_t obj_type_id = obj_inst->type_id();
+
+      uint32_t ptr_id = inst->GetSingleWordInOperand(0);
+      Instruction* ptr_inst = get_def_use_mgr()->GetDef(ptr_id);
+      uint32_t pointee_type_id = GetPointeeTypeId(ptr_inst);
+
+      if (obj_type_id != pointee_type_id) {
+        uint32_t copy_id = GenerateCopy(obj_inst, pointee_type_id, inst);
+        inst->SetInOperand(1, {copy_id});
+        context()->UpdateDefUse(inst);
+      }
+    } break;
+    case SpvOpCopyMemory:
+    case SpvOpCopyMemorySized:
+      // TODO: May need to expand the copy as we do with the stores.
+      break;
+    case SpvOpCompositeConstruct:
+    case SpvOpCompositeExtract:
+    case SpvOpCompositeInsert:
+      // TODO: DXC does not seem to generate code that will require changes to
+      // these opcode.  The can be implemented when they come up.
+      break;
+    case SpvOpImageTexelPointer:
+    case SpvOpBitcast:
+      // Nothing to change for these opcode.  The result type is the same
+      // regardless of the type of the operand.
+      return false;
+    default:
+      // I expect the remaining instructions to act on types that are guaranteed
+      // to be unique, so no change will be necessary.
+      break;
+  }
+
+  // If the operand forces the result type, then make sure the result type
+  // matches, and update the uses of |inst|.  We do not have to check the uses
+  // of |inst| in the result type is not forced because we are only looking for
+  // issue that come from mismatches between function formal and actual
+  // parameters after the function has been inlined.  These parameters are
+  // pointers. Once the type no longer depends on the type of the parameter,
+  // then the types should have be correct.
+  if (new_type_id != 0) {
+    modified = ChangeResultType(inst, new_type_id);
+
+    std::vector<std::pair<Instruction*, uint32_t>> uses;
+    get_def_use_mgr()->ForEachUse(inst,
+                                  [&uses](Instruction* use, uint32_t idx) {
+                                    uses.push_back({use, idx});
+                                  });
+
+    for (auto& use : uses) {
+      PropagateType(use.first, new_type_id, use.second, seen);
+    }
+
+    if (inst->opcode() == SpvOpPhi) {
+      seen->erase(inst->result_id());
+    }
+  }
+  return modified;
+}
+
+uint32_t FixStorageClass::WalkAccessChainType(Instruction* inst, uint32_t id) {
+  uint32_t start_idx = 0;
+  switch (inst->opcode()) {
+    case SpvOpAccessChain:
+    case SpvOpInBoundsAccessChain:
+      start_idx = 1;
+      break;
+    case SpvOpPtrAccessChain:
+    case SpvOpInBoundsPtrAccessChain:
+      start_idx = 2;
+      break;
+    default:
+      assert(false);
+      break;
+  }
+
+  Instruction* orig_type_inst = get_def_use_mgr()->GetDef(id);
+  assert(orig_type_inst->opcode() == SpvOpTypePointer);
+  id = orig_type_inst->GetSingleWordInOperand(1);
+
+  for (uint32_t i = start_idx; i < inst->NumInOperands(); ++i) {
+    Instruction* type_inst = get_def_use_mgr()->GetDef(id);
+    switch (type_inst->opcode()) {
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+      case SpvOpTypeMatrix:
+      case SpvOpTypeVector:
+        id = type_inst->GetSingleWordInOperand(0);
+        break;
+      case SpvOpTypeStruct: {
+        const analysis::Constant* index_const =
+            context()->get_constant_mgr()->FindDeclaredConstant(
+                inst->GetSingleWordInOperand(i));
+        uint32_t index = index_const->GetU32();
+        id = type_inst->GetSingleWordInOperand(index);
+        break;
+      }
+      default:
+        break;
+    }
+    assert(id != 0 &&
+           "Tried to extract from an object where it cannot be done.");
+  }
+
+  return context()->get_type_mgr()->FindPointerToType(
+      id,
+      static_cast<SpvStorageClass>(orig_type_inst->GetSingleWordInOperand(0)));
+}
+
+// namespace opt
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/fix_storage_class.h b/source/opt/fix_storage_class.h
new file mode 100644
index 0000000..e72e864
--- /dev/null
+++ b/source/opt/fix_storage_class.h
@@ -0,0 +1,93 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_FIX_STORAGE_CLASS_H_
+#define SOURCE_OPT_FIX_STORAGE_CLASS_H_
+
+#include <unordered_map>
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// This pass tries to fix validation error due to a mismatch of storage classes
+// in instructions.  There is no guarantee that all such error will be fixed,
+// and it is possible that in fixing these errors, it could lead to other
+// errors.
+class FixStorageClass : public Pass {
+ public:
+  const char* name() const override { return "fix-storage-class"; }
+  Status Process() override;
+
+  // Return the mask of preserved Analyses.
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisDefUse |
+           IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG |
+           IRContext::kAnalysisDominatorAnalysis |
+           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
+  }
+
+ private:
+  // Changes the storage class of the result of |inst| to |storage_class| in
+  // appropriate, and propagates the change to the users of |inst| as well.
+  // Returns true of any changes were made.
+  // |seen| is used to track OpPhi instructions that should not be processed.
+  bool PropagateStorageClass(Instruction* inst, SpvStorageClass storage_class,
+                             std::set<uint32_t>* seen);
+
+  // Changes the storage class of the result of |inst| to |storage_class|.
+  // Is it assumed that the result type of |inst| is a pointer type.
+  // Propagates the change to the users of |inst| as well.
+  // Returns true of any changes were made.
+  // |seen| is used to track OpPhi instructions that should not be processed by
+  // |PropagateStorageClass|
+  void FixInstructionStorageClass(Instruction* inst,
+                                  SpvStorageClass storage_class,
+                                  std::set<uint32_t>* seen);
+
+  // Changes the storage class of the result of |inst| to |storage_class|.  The
+  // result type of |inst| must be a pointer.
+  void ChangeResultStorageClass(Instruction* inst,
+                                SpvStorageClass storage_class) const;
+
+  // Returns true if the result type of |inst| is a pointer.
+  bool IsPointerResultType(Instruction* inst);
+
+  // Returns true if the result of |inst| is a pointer to storage class
+  // |storage_class|.
+  bool IsPointerToStorageClass(Instruction* inst,
+                               SpvStorageClass storage_class);
+
+  // Change |inst| to match that operand |op_idx| now has type |type_id|, and
+  // adjust any uses of |inst| accordingly. Returns true if the code changed.
+  bool PropagateType(Instruction* inst, uint32_t type_id, uint32_t op_idx,
+                     std::set<uint32_t>* seen);
+
+  // Changes the result type of |inst| to |new_type_id|.
+  bool ChangeResultType(Instruction* inst, uint32_t new_type_id);
+
+  // Returns the type id of the member of the type |id| that would be returned
+  // by following the indices of the access chain instruction |inst|.
+  uint32_t WalkAccessChainType(Instruction* inst, uint32_t id);
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_FIX_STORAGE_CLASS_H_
diff --git a/source/opt/fold.cpp b/source/opt/fold.cpp
index d6b583f..944f438 100644
--- a/source/opt/fold.cpp
+++ b/source/opt/fold.cpp
@@ -45,8 +45,13 @@
 uint32_t InstructionFolder::UnaryOperate(SpvOp opcode, uint32_t operand) const {
   switch (opcode) {
     // Arthimetics
-    case SpvOp::SpvOpSNegate:
-      return -static_cast<int32_t>(operand);
+    case SpvOp::SpvOpSNegate: {
+      int32_t s_operand = static_cast<int32_t>(operand);
+      if (s_operand == std::numeric_limits<int32_t>::min()) {
+        return s_operand;
+      }
+      return -s_operand;
+    }
     case SpvOp::SpvOpNot:
       return ~operand;
     case SpvOp::SpvOpLogicalNot:
@@ -115,8 +120,10 @@
 
     // Shifting
     case SpvOp::SpvOpShiftRightLogical:
-      if (b > 32) {
-        // This is undefined behaviour.  Choose 0 for consistency.
+      if (b >= 32) {
+        // This is undefined behaviour when |b| > 32.  Choose 0 for consistency.
+        // When |b| == 32, doing the shift in C++ in undefined, but the result
+        // will be 0, so just return that value.
         return 0;
       }
       return a >> b;
@@ -125,10 +132,21 @@
         // This is undefined behaviour.  Choose 0 for consistency.
         return 0;
       }
+      if (b == 32) {
+        // Doing the shift in C++ is undefined, but the result is defined in the
+        // spir-v spec.  Find that value another way.
+        if (static_cast<int32_t>(a) >= 0) {
+          return 0;
+        } else {
+          return static_cast<uint32_t>(-1);
+        }
+      }
       return (static_cast<int32_t>(a)) >> b;
     case SpvOp::SpvOpShiftLeftLogical:
-      if (b > 32) {
-        // This is undefined behaviour.  Choose 0 for consistency.
+      if (b >= 32) {
+        // This is undefined behaviour when |b| > 32.  Choose 0 for consistency.
+        // When |b| == 32, doing the shift in C++ in undefined, but the result
+        // will be 0, so just return that value.
         return 0;
       }
       return a << b;
@@ -307,7 +325,8 @@
       if (constants[1] != nullptr) {
         // When shifting by a value larger than the size of the result, the
         // result is undefined.  We are setting the undefined behaviour to a
-        // result of 0.
+        // result of 0.  If the shift amount is the same as the size of the
+        // result, then the result is defined, and it 0.
         uint32_t shift_amount = constants[1]->GetU32BitValue();
         if (shift_amount >= 32) {
           *result = 0;
diff --git a/source/opt/folding_rules.cpp b/source/opt/folding_rules.cpp
index 3274319..18d5149 100644
--- a/source/opt/folding_rules.cpp
+++ b/source/opt/folding_rules.cpp
@@ -2167,6 +2167,37 @@
   };
 }
 
+// Removes duplicate ids from the interface list of an OpEntryPoint
+// instruction.
+FoldingRule RemoveRedundantOperands() {
+  return [](IRContext*, Instruction* inst,
+            const std::vector<const analysis::Constant*>&) {
+    assert(inst->opcode() == SpvOpEntryPoint &&
+           "Wrong opcode.  Should be OpEntryPoint.");
+    bool has_redundant_operand = false;
+    std::unordered_set<uint32_t> seen_operands;
+    std::vector<Operand> new_operands;
+
+    new_operands.emplace_back(inst->GetOperand(0));
+    new_operands.emplace_back(inst->GetOperand(1));
+    new_operands.emplace_back(inst->GetOperand(2));
+    for (uint32_t i = 3; i < inst->NumOperands(); ++i) {
+      if (seen_operands.insert(inst->GetSingleWordOperand(i)).second) {
+        new_operands.emplace_back(inst->GetOperand(i));
+      } else {
+        has_redundant_operand = true;
+      }
+    }
+
+    if (!has_redundant_operand) {
+      return false;
+    }
+
+    inst->SetInOperands(std::move(new_operands));
+    return true;
+  };
+}
+
 }  // namespace
 
 FoldingRules::FoldingRules() {
@@ -2183,6 +2214,8 @@
 
   rules_[SpvOpDot].push_back(DotProductDoingExtract());
 
+  rules_[SpvOpEntryPoint].push_back(RemoveRedundantOperands());
+
   rules_[SpvOpExtInst].push_back(RedundantFMix());
 
   rules_[SpvOpFAdd].push_back(RedundantFAdd());
diff --git a/source/opt/function.cpp b/source/opt/function.cpp
index d4457ad..9bd46e2 100644
--- a/source/opt/function.cpp
+++ b/source/opt/function.cpp
@@ -46,29 +46,77 @@
 
 void Function::ForEachInst(const std::function<void(Instruction*)>& f,
                            bool run_on_debug_line_insts) {
-  if (def_inst_) def_inst_->ForEachInst(f, run_on_debug_line_insts);
-  for (auto& param : params_) param->ForEachInst(f, run_on_debug_line_insts);
-  for (auto& bb : blocks_) bb->ForEachInst(f, run_on_debug_line_insts);
-  if (end_inst_) end_inst_->ForEachInst(f, run_on_debug_line_insts);
+  WhileEachInst(
+      [&f](Instruction* inst) {
+        f(inst);
+        return true;
+      },
+      run_on_debug_line_insts);
 }
 
 void Function::ForEachInst(const std::function<void(const Instruction*)>& f,
                            bool run_on_debug_line_insts) const {
-  if (def_inst_)
-    static_cast<const Instruction*>(def_inst_.get())
-        ->ForEachInst(f, run_on_debug_line_insts);
+  WhileEachInst(
+      [&f](const Instruction* inst) {
+        f(inst);
+        return true;
+      },
+      run_on_debug_line_insts);
+}
 
-  for (const auto& param : params_)
-    static_cast<const Instruction*>(param.get())
-        ->ForEachInst(f, run_on_debug_line_insts);
+bool Function::WhileEachInst(const std::function<bool(Instruction*)>& f,
+                             bool run_on_debug_line_insts) {
+  if (def_inst_) {
+    if (!def_inst_->WhileEachInst(f, run_on_debug_line_insts)) {
+      return false;
+    }
+  }
 
-  for (const auto& bb : blocks_)
-    static_cast<const BasicBlock*>(bb.get())->ForEachInst(
-        f, run_on_debug_line_insts);
+  for (auto& param : params_) {
+    if (!param->WhileEachInst(f, run_on_debug_line_insts)) {
+      return false;
+    }
+  }
+
+  for (auto& bb : blocks_) {
+    if (!bb->WhileEachInst(f, run_on_debug_line_insts)) {
+      return false;
+    }
+  }
+
+  if (end_inst_) return end_inst_->WhileEachInst(f, run_on_debug_line_insts);
+
+  return true;
+}
+
+bool Function::WhileEachInst(const std::function<bool(const Instruction*)>& f,
+                             bool run_on_debug_line_insts) const {
+  if (def_inst_) {
+    if (!static_cast<const Instruction*>(def_inst_.get())
+             ->WhileEachInst(f, run_on_debug_line_insts)) {
+      return false;
+    }
+  }
+
+  for (const auto& param : params_) {
+    if (!static_cast<const Instruction*>(param.get())
+             ->WhileEachInst(f, run_on_debug_line_insts)) {
+      return false;
+    }
+  }
+
+  for (const auto& bb : blocks_) {
+    if (!static_cast<const BasicBlock*>(bb.get())->WhileEachInst(
+            f, run_on_debug_line_insts)) {
+      return false;
+    }
+  }
 
   if (end_inst_)
-    static_cast<const Instruction*>(end_inst_.get())
-        ->ForEachInst(f, run_on_debug_line_insts);
+    return static_cast<const Instruction*>(end_inst_.get())
+        ->WhileEachInst(f, run_on_debug_line_insts);
+
+  return true;
 }
 
 void Function::ForEachParam(const std::function<void(Instruction*)>& f,
diff --git a/source/opt/function.h b/source/opt/function.h
index 7c0166f..c80b078 100644
--- a/source/opt/function.h
+++ b/source/opt/function.h
@@ -110,6 +110,10 @@
                    bool run_on_debug_line_insts = false);
   void ForEachInst(const std::function<void(const Instruction*)>& f,
                    bool run_on_debug_line_insts = false) const;
+  bool WhileEachInst(const std::function<bool(Instruction*)>& f,
+                     bool run_on_debug_line_insts = false);
+  bool WhileEachInst(const std::function<bool(const Instruction*)>& f,
+                     bool run_on_debug_line_insts = false) const;
 
   // Runs the given function |f| on each parameter instruction in this function,
   // and optionally on debug line instructions that might precede them.
diff --git a/source/opt/generate_webgpu_initializers_pass.cpp b/source/opt/generate_webgpu_initializers_pass.cpp
new file mode 100644
index 0000000..9334b43
--- /dev/null
+++ b/source/opt/generate_webgpu_initializers_pass.cpp
@@ -0,0 +1,112 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/generate_webgpu_initializers_pass.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+using inst_iterator = InstructionList::iterator;
+
+namespace {
+
+bool NeedsWebGPUInitializer(Instruction* inst) {
+  if (inst->opcode() != SpvOpVariable) return false;
+
+  auto storage_class = inst->GetSingleWordOperand(2);
+  if (storage_class != SpvStorageClassOutput &&
+      storage_class != SpvStorageClassPrivate &&
+      storage_class != SpvStorageClassFunction) {
+    return false;
+  }
+
+  if (inst->NumOperands() > 3) return false;
+
+  return true;
+}
+
+}  // namespace
+
+Pass::Status GenerateWebGPUInitializersPass::Process() {
+  auto* module = context()->module();
+  bool changed = false;
+
+  // Handle global/module scoped variables
+  for (auto iter = module->types_values_begin();
+       iter != module->types_values_end(); ++iter) {
+    Instruction* inst = &(*iter);
+
+    if (inst->opcode() == SpvOpConstantNull) {
+      null_constant_type_map_[inst->type_id()] = inst;
+      seen_null_constants_.insert(inst);
+      continue;
+    }
+
+    if (!NeedsWebGPUInitializer(inst)) continue;
+
+    changed = true;
+
+    auto* constant_inst = GetNullConstantForVariable(inst);
+    if (seen_null_constants_.find(constant_inst) ==
+        seen_null_constants_.end()) {
+      constant_inst->InsertBefore(inst);
+      null_constant_type_map_[inst->type_id()] = inst;
+      seen_null_constants_.insert(inst);
+    }
+    AddNullInitializerToVariable(constant_inst, inst);
+  }
+
+  // Handle local/function scoped variables
+  for (auto func = module->begin(); func != module->end(); ++func) {
+    auto block = func->entry().get();
+    for (auto iter = block->begin();
+         iter != block->end() && iter->opcode() == SpvOpVariable; ++iter) {
+      Instruction* inst = &(*iter);
+      if (!NeedsWebGPUInitializer(inst)) continue;
+
+      changed = true;
+      auto* constant_inst = GetNullConstantForVariable(inst);
+      AddNullInitializerToVariable(constant_inst, inst);
+    }
+  }
+
+  return changed ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+Instruction* GenerateWebGPUInitializersPass::GetNullConstantForVariable(
+    Instruction* variable_inst) {
+  auto constant_mgr = context()->get_constant_mgr();
+  auto* def_use_mgr = get_def_use_mgr();
+
+  auto* ptr_inst = def_use_mgr->GetDef(variable_inst->type_id());
+  auto type_id = ptr_inst->GetInOperand(1).words[0];
+  if (null_constant_type_map_.find(type_id) == null_constant_type_map_.end()) {
+    auto* constant_type = context()->get_type_mgr()->GetType(type_id);
+    auto* constant = constant_mgr->GetConstant(constant_type, {});
+    return constant_mgr->GetDefiningInstruction(constant, type_id);
+  } else {
+    return null_constant_type_map_[type_id];
+  }
+}
+
+void GenerateWebGPUInitializersPass::AddNullInitializerToVariable(
+    Instruction* constant_inst, Instruction* variable_inst) {
+  auto constant_id = constant_inst->result_id();
+  variable_inst->AddOperand(Operand(SPV_OPERAND_TYPE_ID, {constant_id}));
+  get_def_use_mgr()->AnalyzeInstUse(variable_inst);
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/generate_webgpu_initializers_pass.h b/source/opt/generate_webgpu_initializers_pass.h
new file mode 100644
index 0000000..f95e84c
--- /dev/null
+++ b/source/opt/generate_webgpu_initializers_pass.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_GENERATE_WEBGPU_INITIALIZERS_PASS_H_
+#define SOURCE_OPT_GENERATE_WEBGPU_INITIALIZERS_PASS_H_
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// Adds initializers to variables with storage classes Output, Private, and
+// Function if they are missing. In the WebGPU environment these storage classes
+// require that the variables are initialized. Currently they are initialized to
+// NULL, though in the future some of them may be initialized to the first value
+// that is stored in them, if that was a constant.
+class GenerateWebGPUInitializersPass : public Pass {
+ public:
+  const char* name() const override { return "generate-webgpu-initializers"; }
+  Status Process() override;
+
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
+           IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
+           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisScalarEvolution |
+           IRContext::kAnalysisRegisterPressure |
+           IRContext::kAnalysisValueNumberTable |
+           IRContext::kAnalysisStructuredCFG |
+           IRContext::kAnalysisBuiltinVarId |
+           IRContext::kAnalysisIdToFuncMapping | IRContext::kAnalysisTypes |
+           IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants;
+  }
+
+ private:
+  using NullConstantTypeMap = std::unordered_map<uint32_t, Instruction*>;
+  NullConstantTypeMap null_constant_type_map_;
+  std::unordered_set<Instruction*> seen_null_constants_;
+
+  Instruction* GetNullConstantForVariable(Instruction* variable_inst);
+  void AddNullInitializerToVariable(Instruction* constant_inst,
+                                    Instruction* variable_inst);
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_GENERATE_WEBGPU_INITIALIZERS_PASS_H_
diff --git a/source/opt/if_conversion.h b/source/opt/if_conversion.h
index 609bdf3..db84e70 100644
--- a/source/opt/if_conversion.h
+++ b/source/opt/if_conversion.h
@@ -32,7 +32,8 @@
   IRContext::Analysis GetPreservedAnalyses() override {
     return IRContext::kAnalysisDefUse | IRContext::kAnalysisDominatorAnalysis |
            IRContext::kAnalysisInstrToBlockMapping | IRContext::kAnalysisCFG |
-           IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/inline_exhaustive_pass.cpp b/source/opt/inline_exhaustive_pass.cpp
index 10b5e98..24f4e73 100644
--- a/source/opt/inline_exhaustive_pass.cpp
+++ b/source/opt/inline_exhaustive_pass.cpp
@@ -21,7 +21,7 @@
 namespace spvtools {
 namespace opt {
 
-bool InlineExhaustivePass::InlineExhaustive(Function* func) {
+Pass::Status InlineExhaustivePass::InlineExhaustive(Function* func) {
   bool modified = false;
   // Using block iterators here because of block erasures and insertions.
   for (auto bi = func->begin(); bi != func->end(); ++bi) {
@@ -30,7 +30,9 @@
         // Inline call.
         std::vector<std::unique_ptr<BasicBlock>> newBlocks;
         std::vector<std::unique_ptr<Instruction>> newVars;
-        GenInlineCode(&newBlocks, &newVars, ii, bi);
+        if (!GenInlineCode(&newBlocks, &newVars, ii, bi)) {
+          return Status::Failure;
+        }
         // If call block is replaced with more than one block, point
         // succeeding phis at new last block.
         if (newBlocks.size() > 1) UpdateSucceedingPhis(newBlocks);
@@ -58,14 +60,18 @@
       }
     }
   }
-  return modified;
+  return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
 }
 
 Pass::Status InlineExhaustivePass::ProcessImpl() {
+  Status status = Status::SuccessWithoutChange;
   // Attempt exhaustive inlining on each entry point function in module
-  ProcessFunction pfn = [this](Function* fp) { return InlineExhaustive(fp); };
-  bool modified = context()->ProcessEntryPointCallTree(pfn);
-  return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+  ProcessFunction pfn = [&status, this](Function* fp) {
+    status = CombineStatus(status, InlineExhaustive(fp));
+    return false;
+  };
+  context()->ProcessEntryPointCallTree(pfn);
+  return status;
 }
 
 InlineExhaustivePass::InlineExhaustivePass() = default;
diff --git a/source/opt/inline_exhaustive_pass.h b/source/opt/inline_exhaustive_pass.h
index 103e091..c2e8547 100644
--- a/source/opt/inline_exhaustive_pass.h
+++ b/source/opt/inline_exhaustive_pass.h
@@ -40,8 +40,8 @@
 
  private:
   // Exhaustively inline all function calls in func as well as in
-  // all code that is inlined into func. Return true if func is modified.
-  bool InlineExhaustive(Function* func);
+  // all code that is inlined into func. Returns the status.
+  Status InlineExhaustive(Function* func);
 
   void Initialize();
   Pass::Status ProcessImpl();
diff --git a/source/opt/inline_opaque_pass.cpp b/source/opt/inline_opaque_pass.cpp
index e94f26d..6ccaf90 100644
--- a/source/opt/inline_opaque_pass.cpp
+++ b/source/opt/inline_opaque_pass.cpp
@@ -63,7 +63,7 @@
   });
 }
 
-bool InlineOpaquePass::InlineOpaque(Function* func) {
+Pass::Status InlineOpaquePass::InlineOpaque(Function* func) {
   bool modified = false;
   // Using block iterators here because of block erasures and insertions.
   for (auto bi = func->begin(); bi != func->end(); ++bi) {
@@ -72,7 +72,10 @@
         // Inline call.
         std::vector<std::unique_ptr<BasicBlock>> newBlocks;
         std::vector<std::unique_ptr<Instruction>> newVars;
-        GenInlineCode(&newBlocks, &newVars, ii, bi);
+        if (!GenInlineCode(&newBlocks, &newVars, ii, bi)) {
+          return Status::Failure;
+        }
+
         // If call block is replaced with more than one block, point
         // succeeding phis at new last block.
         if (newBlocks.size() > 1) UpdateSucceedingPhis(newBlocks);
@@ -90,16 +93,20 @@
       }
     }
   }
-  return modified;
+  return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
 }
 
 void InlineOpaquePass::Initialize() { InitializeInline(); }
 
 Pass::Status InlineOpaquePass::ProcessImpl() {
+  Status status = Status::SuccessWithoutChange;
   // Do opaque inlining on each function in entry point call tree
-  ProcessFunction pfn = [this](Function* fp) { return InlineOpaque(fp); };
-  bool modified = context()->ProcessEntryPointCallTree(pfn);
-  return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+  ProcessFunction pfn = [&status, this](Function* fp) {
+    status = CombineStatus(status, InlineOpaque(fp));
+    return false;
+  };
+  context()->ProcessEntryPointCallTree(pfn);
+  return status;
 }
 
 InlineOpaquePass::InlineOpaquePass() = default;
diff --git a/source/opt/inline_opaque_pass.h b/source/opt/inline_opaque_pass.h
index aad43fd..1e3081d 100644
--- a/source/opt/inline_opaque_pass.h
+++ b/source/opt/inline_opaque_pass.h
@@ -48,7 +48,7 @@
   // Inline all function calls in |func| that have opaque params or return
   // type. Inline similarly all code that is inlined into func. Return true
   // if func is modified.
-  bool InlineOpaque(Function* func);
+  Status InlineOpaque(Function* func);
 
   void Initialize();
   Pass::Status ProcessImpl();
diff --git a/source/opt/inline_pass.cpp b/source/opt/inline_pass.cpp
index cdd5659..f348bbe 100644
--- a/source/opt/inline_pass.cpp
+++ b/source/opt/inline_pass.cpp
@@ -34,7 +34,11 @@
 
 uint32_t InlinePass::AddPointerToType(uint32_t type_id,
                                       SpvStorageClass storage_class) {
-  uint32_t resultId = TakeNextId();
+  uint32_t resultId = context()->TakeNextId();
+  if (resultId == 0) {
+    return resultId;
+  }
+
   std::unique_ptr<Instruction> type_inst(
       new Instruction(context(), SpvOpTypePointer, 0, resultId,
                       {{spv_operand_type_t::SPV_OPERAND_TYPE_STORAGE_CLASS,
@@ -108,10 +112,16 @@
   if (false_id_ != 0) return false_id_;
   uint32_t boolId = get_module()->GetGlobalValue(SpvOpTypeBool);
   if (boolId == 0) {
-    boolId = TakeNextId();
+    boolId = context()->TakeNextId();
+    if (boolId == 0) {
+      return 0;
+    }
     get_module()->AddGlobalValue(SpvOpTypeBool, boolId, 0);
   }
-  false_id_ = TakeNextId();
+  false_id_ = context()->TakeNextId();
+  if (false_id_ == 0) {
+    return 0;
+  }
   get_module()->AddGlobalValue(SpvOpConstantFalse, false_id_, boolId);
   return false_id_;
 }
@@ -120,50 +130,64 @@
     Function* calleeFn, BasicBlock::iterator call_inst_itr,
     std::unordered_map<uint32_t, uint32_t>* callee2caller) {
   int param_idx = 0;
-  calleeFn->ForEachParam([&call_inst_itr, &param_idx,
-                          &callee2caller](const Instruction* cpi) {
-    const uint32_t pid = cpi->result_id();
-    (*callee2caller)[pid] = call_inst_itr->GetSingleWordOperand(
-        kSpvFunctionCallArgumentId + param_idx);
-    ++param_idx;
-  });
+  calleeFn->ForEachParam(
+      [&call_inst_itr, &param_idx, &callee2caller](const Instruction* cpi) {
+        const uint32_t pid = cpi->result_id();
+        (*callee2caller)[pid] = call_inst_itr->GetSingleWordOperand(
+            kSpvFunctionCallArgumentId + param_idx);
+        ++param_idx;
+      });
 }
 
-void InlinePass::CloneAndMapLocals(
+bool InlinePass::CloneAndMapLocals(
     Function* calleeFn, std::vector<std::unique_ptr<Instruction>>* new_vars,
     std::unordered_map<uint32_t, uint32_t>* callee2caller) {
   auto callee_block_itr = calleeFn->begin();
   auto callee_var_itr = callee_block_itr->begin();
   while (callee_var_itr->opcode() == SpvOp::SpvOpVariable) {
     std::unique_ptr<Instruction> var_inst(callee_var_itr->Clone(context()));
-    uint32_t newId = TakeNextId();
+    uint32_t newId = context()->TakeNextId();
+    if (newId == 0) {
+      return false;
+    }
     get_decoration_mgr()->CloneDecorations(callee_var_itr->result_id(), newId);
     var_inst->SetResultId(newId);
     (*callee2caller)[callee_var_itr->result_id()] = newId;
     new_vars->push_back(std::move(var_inst));
     ++callee_var_itr;
   }
+  return true;
 }
 
 uint32_t InlinePass::CreateReturnVar(
     Function* calleeFn, std::vector<std::unique_ptr<Instruction>>* new_vars) {
   uint32_t returnVarId = 0;
   const uint32_t calleeTypeId = calleeFn->type_id();
-  analysis::Type* calleeType = context()->get_type_mgr()->GetType(calleeTypeId);
-  if (calleeType->AsVoid() == nullptr) {
-    // Find or create ptr to callee return type.
-    uint32_t returnVarTypeId = context()->get_type_mgr()->FindPointerToType(
-        calleeTypeId, SpvStorageClassFunction);
-    if (returnVarTypeId == 0)
-      returnVarTypeId = AddPointerToType(calleeTypeId, SpvStorageClassFunction);
-    // Add return var to new function scope variables.
-    returnVarId = TakeNextId();
-    std::unique_ptr<Instruction> var_inst(
-        new Instruction(context(), SpvOpVariable, returnVarTypeId, returnVarId,
-                        {{spv_operand_type_t::SPV_OPERAND_TYPE_STORAGE_CLASS,
-                          {SpvStorageClassFunction}}}));
-    new_vars->push_back(std::move(var_inst));
+  analysis::TypeManager* type_mgr = context()->get_type_mgr();
+  assert(type_mgr->GetType(calleeTypeId)->AsVoid() == nullptr &&
+         "Cannot create a return variable of type void.");
+  // Find or create ptr to callee return type.
+  uint32_t returnVarTypeId =
+      type_mgr->FindPointerToType(calleeTypeId, SpvStorageClassFunction);
+
+  if (returnVarTypeId == 0) {
+    returnVarTypeId = AddPointerToType(calleeTypeId, SpvStorageClassFunction);
+    if (returnVarTypeId == 0) {
+      return 0;
+    }
   }
+
+  // Add return var to new function scope variables.
+  returnVarId = context()->TakeNextId();
+  if (returnVarId == 0) {
+    return 0;
+  }
+
+  std::unique_ptr<Instruction> var_inst(
+      new Instruction(context(), SpvOpVariable, returnVarTypeId, returnVarId,
+                      {{spv_operand_type_t::SPV_OPERAND_TYPE_STORAGE_CLASS,
+                        {SpvStorageClassFunction}}}));
+  new_vars->push_back(std::move(var_inst));
   get_decoration_mgr()->CloneDecorations(calleeFn->result_id(), returnVarId);
   return returnVarId;
 }
@@ -172,37 +196,44 @@
   return inst->opcode() == SpvOpSampledImage || inst->opcode() == SpvOpImage;
 }
 
-void InlinePass::CloneSameBlockOps(
+bool InlinePass::CloneSameBlockOps(
     std::unique_ptr<Instruction>* inst,
     std::unordered_map<uint32_t, uint32_t>* postCallSB,
     std::unordered_map<uint32_t, Instruction*>* preCallSB,
     std::unique_ptr<BasicBlock>* block_ptr) {
-  (*inst)->ForEachInId(
-      [&postCallSB, &preCallSB, &block_ptr, this](uint32_t* iid) {
-        const auto mapItr = (*postCallSB).find(*iid);
-        if (mapItr == (*postCallSB).end()) {
-          const auto mapItr2 = (*preCallSB).find(*iid);
-          if (mapItr2 != (*preCallSB).end()) {
-            // Clone pre-call same-block ops, map result id.
-            const Instruction* inInst = mapItr2->second;
-            std::unique_ptr<Instruction> sb_inst(inInst->Clone(context()));
-            CloneSameBlockOps(&sb_inst, postCallSB, preCallSB, block_ptr);
-            const uint32_t rid = sb_inst->result_id();
-            const uint32_t nid = this->TakeNextId();
-            get_decoration_mgr()->CloneDecorations(rid, nid);
-            sb_inst->SetResultId(nid);
-            (*postCallSB)[rid] = nid;
-            *iid = nid;
-            (*block_ptr)->AddInstruction(std::move(sb_inst));
-          }
-        } else {
-          // Reset same-block op operand.
-          *iid = mapItr->second;
+  return (*inst)->WhileEachInId([&postCallSB, &preCallSB, &block_ptr,
+                                 this](uint32_t* iid) {
+    const auto mapItr = (*postCallSB).find(*iid);
+    if (mapItr == (*postCallSB).end()) {
+      const auto mapItr2 = (*preCallSB).find(*iid);
+      if (mapItr2 != (*preCallSB).end()) {
+        // Clone pre-call same-block ops, map result id.
+        const Instruction* inInst = mapItr2->second;
+        std::unique_ptr<Instruction> sb_inst(inInst->Clone(context()));
+        if (!CloneSameBlockOps(&sb_inst, postCallSB, preCallSB, block_ptr)) {
+          return false;
         }
-      });
+
+        const uint32_t rid = sb_inst->result_id();
+        const uint32_t nid = context()->TakeNextId();
+        if (nid == 0) {
+          return false;
+        }
+        get_decoration_mgr()->CloneDecorations(rid, nid);
+        sb_inst->SetResultId(nid);
+        (*postCallSB)[rid] = nid;
+        *iid = nid;
+        (*block_ptr)->AddInstruction(std::move(sb_inst));
+      }
+    } else {
+      // Reset same-block op operand.
+      *iid = mapItr->second;
+    }
+    return true;
+  });
 }
 
-void InlinePass::GenInlineCode(
+bool InlinePass::GenInlineCode(
     std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
     std::vector<std::unique_ptr<Instruction>>* new_vars,
     BasicBlock::iterator call_inst_itr,
@@ -232,10 +263,20 @@
 
   // Define caller local variables for all callee variables and create map to
   // them.
-  CloneAndMapLocals(calleeFn, new_vars, &callee2caller);
+  if (!CloneAndMapLocals(calleeFn, new_vars, &callee2caller)) {
+    return false;
+  }
 
   // Create return var if needed.
-  uint32_t returnVarId = CreateReturnVar(calleeFn, new_vars);
+  const uint32_t calleeTypeId = calleeFn->type_id();
+  uint32_t returnVarId = 0;
+  analysis::Type* calleeType = context()->get_type_mgr()->GetType(calleeTypeId);
+  if (calleeType->AsVoid() == nullptr) {
+    returnVarId = CreateReturnVar(calleeFn, new_vars);
+    if (returnVarId == 0) {
+      return false;
+    }
+  }
 
   // Create set of callee result ids. Used to detect forward references
   std::unordered_set<uint32_t> callee_result_ids;
@@ -269,241 +310,294 @@
   uint32_t singleTripLoopContinueId = 0;
   uint32_t returnLabelId = 0;
   bool multiBlocks = false;
-  const uint32_t calleeTypeId = calleeFn->type_id();
   // new_blk_ptr is a new basic block in the caller.  New instructions are
   // written to it.  It is created when we encounter the OpLabel
   // of the first callee block.  It is appended to new_blocks only when
   // it is complete.
   std::unique_ptr<BasicBlock> new_blk_ptr;
-  calleeFn->ForEachInst([&new_blocks, &callee2caller, &call_block_itr,
-                         &call_inst_itr, &new_blk_ptr, &prevInstWasReturn,
-                         &returnLabelId, &returnVarId, caller_is_loop_header,
-                         callee_begins_with_structured_header, &calleeTypeId,
-                         &multiBlocks, &postCallSB, &preCallSB, earlyReturn,
-                         &singleTripLoopHeaderId, &singleTripLoopContinueId,
-                         &callee_result_ids, this](const Instruction* cpi) {
-    switch (cpi->opcode()) {
-      case SpvOpFunction:
-      case SpvOpFunctionParameter:
-        // Already processed
-        break;
-      case SpvOpVariable:
-        if (cpi->NumInOperands() == 2) {
-          assert(callee2caller.count(cpi->result_id()) &&
-                 "Expected the variable to have already been mapped.");
-          uint32_t new_var_id = callee2caller.at(cpi->result_id());
+  bool successful = calleeFn->WhileEachInst(
+      [&new_blocks, &callee2caller, &call_block_itr, &call_inst_itr,
+       &new_blk_ptr, &prevInstWasReturn, &returnLabelId, &returnVarId,
+       caller_is_loop_header, callee_begins_with_structured_header,
+       &calleeTypeId, &multiBlocks, &postCallSB, &preCallSB, earlyReturn,
+       &singleTripLoopHeaderId, &singleTripLoopContinueId, &callee_result_ids,
+       this](const Instruction* cpi) {
+        switch (cpi->opcode()) {
+          case SpvOpFunction:
+          case SpvOpFunctionParameter:
+            // Already processed
+            break;
+          case SpvOpVariable:
+            if (cpi->NumInOperands() == 2) {
+              assert(callee2caller.count(cpi->result_id()) &&
+                     "Expected the variable to have already been mapped.");
+              uint32_t new_var_id = callee2caller.at(cpi->result_id());
 
-          // The initializer must be a constant or global value.  No mapped
-          // should be used.
-          uint32_t val_id = cpi->GetSingleWordInOperand(1);
-          AddStore(new_var_id, val_id, &new_blk_ptr);
-        }
-        break;
-      case SpvOpUnreachable:
-      case SpvOpKill: {
-        // Generate a return label so that we split the block with the function
-        // call. Copy the terminator into the new block.
-        if (returnLabelId == 0) returnLabelId = this->TakeNextId();
-        std::unique_ptr<Instruction> terminator(
-            new Instruction(context(), cpi->opcode(), 0, 0, {}));
-        new_blk_ptr->AddInstruction(std::move(terminator));
-        break;
-      }
-      case SpvOpLabel: {
-        // If previous instruction was early return, insert branch
-        // instruction to return block.
-        if (prevInstWasReturn) {
-          if (returnLabelId == 0) returnLabelId = this->TakeNextId();
-          AddBranch(returnLabelId, &new_blk_ptr);
-          prevInstWasReturn = false;
-        }
-        // Finish current block (if it exists) and get label for next block.
-        uint32_t labelId;
-        bool firstBlock = false;
-        if (new_blk_ptr != nullptr) {
-          new_blocks->push_back(std::move(new_blk_ptr));
-          // If result id is already mapped, use it, otherwise get a new
-          // one.
-          const uint32_t rid = cpi->result_id();
-          const auto mapItr = callee2caller.find(rid);
-          labelId = (mapItr != callee2caller.end()) ? mapItr->second
-                                                    : this->TakeNextId();
-        } else {
-          // First block needs to use label of original block
-          // but map callee label in case of phi reference.
-          labelId = call_block_itr->id();
-          callee2caller[cpi->result_id()] = labelId;
-          firstBlock = true;
-        }
-        // Create first/next block.
-        new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(labelId));
-        if (firstBlock) {
-          // Copy contents of original caller block up to call instruction.
-          for (auto cii = call_block_itr->begin(); cii != call_inst_itr;
-               cii = call_block_itr->begin()) {
-            Instruction* inst = &*cii;
-            inst->RemoveFromList();
-            std::unique_ptr<Instruction> cp_inst(inst);
-            // Remember same-block ops for possible regeneration.
-            if (IsSameBlockOp(&*cp_inst)) {
-              auto* sb_inst_ptr = cp_inst.get();
-              preCallSB[cp_inst->result_id()] = sb_inst_ptr;
+              // The initializer must be a constant or global value.  No mapped
+              // should be used.
+              uint32_t val_id = cpi->GetSingleWordInOperand(1);
+              AddStore(new_var_id, val_id, &new_blk_ptr);
+            }
+            break;
+          case SpvOpUnreachable:
+          case SpvOpKill: {
+            // Generate a return label so that we split the block with the
+            // function call. Copy the terminator into the new block.
+            if (returnLabelId == 0) {
+              returnLabelId = context()->TakeNextId();
+              if (returnLabelId == 0) {
+                return false;
+              }
+            }
+            std::unique_ptr<Instruction> terminator(
+                new Instruction(context(), cpi->opcode(), 0, 0, {}));
+            new_blk_ptr->AddInstruction(std::move(terminator));
+            break;
+          }
+          case SpvOpLabel: {
+            // If previous instruction was early return, insert branch
+            // instruction to return block.
+            if (prevInstWasReturn) {
+              if (returnLabelId == 0) {
+                returnLabelId = context()->TakeNextId();
+                if (returnLabelId == 0) {
+                  return false;
+                }
+              }
+              AddBranch(returnLabelId, &new_blk_ptr);
+              prevInstWasReturn = false;
+            }
+            // Finish current block (if it exists) and get label for next block.
+            uint32_t labelId;
+            bool firstBlock = false;
+            if (new_blk_ptr != nullptr) {
+              new_blocks->push_back(std::move(new_blk_ptr));
+              // If result id is already mapped, use it, otherwise get a new
+              // one.
+              const uint32_t rid = cpi->result_id();
+              const auto mapItr = callee2caller.find(rid);
+              labelId = (mapItr != callee2caller.end())
+                            ? mapItr->second
+                            : context()->TakeNextId();
+              if (labelId == 0) {
+                return false;
+              }
+            } else {
+              // First block needs to use label of original block
+              // but map callee label in case of phi reference.
+              labelId = call_block_itr->id();
+              callee2caller[cpi->result_id()] = labelId;
+              firstBlock = true;
+            }
+            // Create first/next block.
+            new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(labelId));
+            if (firstBlock) {
+              // Copy contents of original caller block up to call instruction.
+              for (auto cii = call_block_itr->begin(); cii != call_inst_itr;
+                   cii = call_block_itr->begin()) {
+                Instruction* inst = &*cii;
+                inst->RemoveFromList();
+                std::unique_ptr<Instruction> cp_inst(inst);
+                // Remember same-block ops for possible regeneration.
+                if (IsSameBlockOp(&*cp_inst)) {
+                  auto* sb_inst_ptr = cp_inst.get();
+                  preCallSB[cp_inst->result_id()] = sb_inst_ptr;
+                }
+                new_blk_ptr->AddInstruction(std::move(cp_inst));
+              }
+              if (caller_is_loop_header &&
+                  callee_begins_with_structured_header) {
+                // We can't place both the caller's merge instruction and
+                // another merge instruction in the same block.  So split the
+                // calling block. Insert an unconditional branch to a new guard
+                // block.  Later, once we know the ID of the last block,  we
+                // will move the caller's OpLoopMerge from the last generated
+                // block into the first block. We also wait to avoid
+                // invalidating various iterators.
+                const auto guard_block_id = context()->TakeNextId();
+                if (guard_block_id == 0) {
+                  return false;
+                }
+                AddBranch(guard_block_id, &new_blk_ptr);
+                new_blocks->push_back(std::move(new_blk_ptr));
+                // Start the next block.
+                new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(guard_block_id));
+                // Reset the mapping of the callee's entry block to point to
+                // the guard block.  Do this so we can fix up phis later on to
+                // satisfy dominance.
+                callee2caller[cpi->result_id()] = guard_block_id;
+              }
+              // If callee has early return, insert a header block for
+              // single-trip loop that will encompass callee code.  Start
+              // postheader block.
+              //
+              // Note: Consider the following combination:
+              //  - the caller is a single block loop
+              //  - the callee does not begin with a structure header
+              //  - the callee has multiple returns.
+              // We still need to split the caller block and insert a guard
+              // block. But we only need to do it once. We haven't done it yet,
+              // but the single-trip loop header will serve the same purpose.
+              if (earlyReturn) {
+                singleTripLoopHeaderId = context()->TakeNextId();
+                if (singleTripLoopHeaderId == 0) {
+                  return false;
+                }
+                AddBranch(singleTripLoopHeaderId, &new_blk_ptr);
+                new_blocks->push_back(std::move(new_blk_ptr));
+                new_blk_ptr =
+                    MakeUnique<BasicBlock>(NewLabel(singleTripLoopHeaderId));
+                returnLabelId = context()->TakeNextId();
+                singleTripLoopContinueId = context()->TakeNextId();
+                if (returnLabelId == 0 || singleTripLoopContinueId == 0) {
+                  return false;
+                }
+                AddLoopMerge(returnLabelId, singleTripLoopContinueId,
+                             &new_blk_ptr);
+                uint32_t postHeaderId = context()->TakeNextId();
+                if (postHeaderId == 0) {
+                  return false;
+                }
+                AddBranch(postHeaderId, &new_blk_ptr);
+                new_blocks->push_back(std::move(new_blk_ptr));
+                new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(postHeaderId));
+                multiBlocks = true;
+                // Reset the mapping of the callee's entry block to point to
+                // the post-header block.  Do this so we can fix up phis later
+                // on to satisfy dominance.
+                callee2caller[cpi->result_id()] = postHeaderId;
+              }
+            } else {
+              multiBlocks = true;
+            }
+          } break;
+          case SpvOpReturnValue: {
+            // Store return value to return variable.
+            assert(returnVarId != 0);
+            uint32_t valId = cpi->GetInOperand(kSpvReturnValueId).words[0];
+            const auto mapItr = callee2caller.find(valId);
+            if (mapItr != callee2caller.end()) {
+              valId = mapItr->second;
+            }
+            AddStore(returnVarId, valId, &new_blk_ptr);
+
+            // Remember we saw a return; if followed by a label, will need to
+            // insert branch.
+            prevInstWasReturn = true;
+          } break;
+          case SpvOpReturn: {
+            // Remember we saw a return; if followed by a label, will need to
+            // insert branch.
+            prevInstWasReturn = true;
+          } break;
+          case SpvOpFunctionEnd: {
+            // If there was an early return, we generated a return label id
+            // for it.  Now we have to generate the return block with that Id.
+            if (returnLabelId != 0) {
+              // If previous instruction was return, insert branch instruction
+              // to return block.
+              if (prevInstWasReturn) AddBranch(returnLabelId, &new_blk_ptr);
+              if (earlyReturn) {
+                // If we generated a loop header for the single-trip loop
+                // to accommodate early returns, insert the continue
+                // target block now, with a false branch back to the loop
+                // header.
+                new_blocks->push_back(std::move(new_blk_ptr));
+                new_blk_ptr =
+                    MakeUnique<BasicBlock>(NewLabel(singleTripLoopContinueId));
+                uint32_t false_id = GetFalseId();
+                if (false_id == 0) {
+                  return false;
+                }
+                AddBranchCond(false_id, singleTripLoopHeaderId, returnLabelId,
+                              &new_blk_ptr);
+              }
+              // Generate the return block.
+              new_blocks->push_back(std::move(new_blk_ptr));
+              new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(returnLabelId));
+              multiBlocks = true;
+            }
+            // Load return value into result id of call, if it exists.
+            if (returnVarId != 0) {
+              const uint32_t resId = call_inst_itr->result_id();
+              assert(resId != 0);
+              AddLoad(calleeTypeId, resId, returnVarId, &new_blk_ptr);
+            }
+            // Copy remaining instructions from caller block.
+            for (Instruction* inst = call_inst_itr->NextNode(); inst;
+                 inst = call_inst_itr->NextNode()) {
+              inst->RemoveFromList();
+              std::unique_ptr<Instruction> cp_inst(inst);
+              // If multiple blocks generated, regenerate any same-block
+              // instruction that has not been seen in this last block.
+              if (multiBlocks) {
+                if (!CloneSameBlockOps(&cp_inst, &postCallSB, &preCallSB,
+                                       &new_blk_ptr)) {
+                  return false;
+                }
+
+                // Remember same-block ops in this block.
+                if (IsSameBlockOp(&*cp_inst)) {
+                  const uint32_t rid = cp_inst->result_id();
+                  postCallSB[rid] = rid;
+                }
+              }
+              new_blk_ptr->AddInstruction(std::move(cp_inst));
+            }
+            // Finalize inline code.
+            new_blocks->push_back(std::move(new_blk_ptr));
+          } break;
+          default: {
+            // Copy callee instruction and remap all input Ids.
+            std::unique_ptr<Instruction> cp_inst(cpi->Clone(context()));
+            bool succeeded = cp_inst->WhileEachInId(
+                [&callee2caller, &callee_result_ids, this](uint32_t* iid) {
+                  const auto mapItr = callee2caller.find(*iid);
+                  if (mapItr != callee2caller.end()) {
+                    *iid = mapItr->second;
+                  } else if (callee_result_ids.find(*iid) !=
+                             callee_result_ids.end()) {
+                    // Forward reference. Allocate a new id, map it,
+                    // use it and check for it when remapping result ids
+                    const uint32_t nid = context()->TakeNextId();
+                    if (nid == 0) {
+                      return false;
+                    }
+                    callee2caller[*iid] = nid;
+                    *iid = nid;
+                  }
+                  return true;
+                });
+            if (!succeeded) {
+              return false;
+            }
+            // If result id is non-zero, remap it. If already mapped, use mapped
+            // value, else use next id.
+            const uint32_t rid = cp_inst->result_id();
+            if (rid != 0) {
+              const auto mapItr = callee2caller.find(rid);
+              uint32_t nid;
+              if (mapItr != callee2caller.end()) {
+                nid = mapItr->second;
+              } else {
+                nid = context()->TakeNextId();
+                if (nid == 0) {
+                  return false;
+                }
+                callee2caller[rid] = nid;
+              }
+              cp_inst->SetResultId(nid);
+              get_decoration_mgr()->CloneDecorations(rid, nid);
             }
             new_blk_ptr->AddInstruction(std::move(cp_inst));
-          }
-          if (caller_is_loop_header && callee_begins_with_structured_header) {
-            // We can't place both the caller's merge instruction and another
-            // merge instruction in the same block.  So split the calling block.
-            // Insert an unconditional branch to a new guard block.  Later,
-            // once we know the ID of the last block,  we will move the caller's
-            // OpLoopMerge from the last generated block into the first block.
-            // We also wait to avoid invalidating various iterators.
-            const auto guard_block_id = this->TakeNextId();
-            AddBranch(guard_block_id, &new_blk_ptr);
-            new_blocks->push_back(std::move(new_blk_ptr));
-            // Start the next block.
-            new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(guard_block_id));
-            // Reset the mapping of the callee's entry block to point to
-            // the guard block.  Do this so we can fix up phis later on to
-            // satisfy dominance.
-            callee2caller[cpi->result_id()] = guard_block_id;
-          }
-          // If callee has early return, insert a header block for
-          // single-trip loop that will encompass callee code.  Start postheader
-          // block.
-          //
-          // Note: Consider the following combination:
-          //  - the caller is a single block loop
-          //  - the callee does not begin with a structure header
-          //  - the callee has multiple returns.
-          // We still need to split the caller block and insert a guard block.
-          // But we only need to do it once. We haven't done it yet, but the
-          // single-trip loop header will serve the same purpose.
-          if (earlyReturn) {
-            singleTripLoopHeaderId = this->TakeNextId();
-            AddBranch(singleTripLoopHeaderId, &new_blk_ptr);
-            new_blocks->push_back(std::move(new_blk_ptr));
-            new_blk_ptr =
-                MakeUnique<BasicBlock>(NewLabel(singleTripLoopHeaderId));
-            returnLabelId = this->TakeNextId();
-            singleTripLoopContinueId = this->TakeNextId();
-            AddLoopMerge(returnLabelId, singleTripLoopContinueId, &new_blk_ptr);
-            uint32_t postHeaderId = this->TakeNextId();
-            AddBranch(postHeaderId, &new_blk_ptr);
-            new_blocks->push_back(std::move(new_blk_ptr));
-            new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(postHeaderId));
-            multiBlocks = true;
-            // Reset the mapping of the callee's entry block to point to
-            // the post-header block.  Do this so we can fix up phis later
-            // on to satisfy dominance.
-            callee2caller[cpi->result_id()] = postHeaderId;
-          }
-        } else {
-          multiBlocks = true;
+          } break;
         }
-      } break;
-      case SpvOpReturnValue: {
-        // Store return value to return variable.
-        assert(returnVarId != 0);
-        uint32_t valId = cpi->GetInOperand(kSpvReturnValueId).words[0];
-        const auto mapItr = callee2caller.find(valId);
-        if (mapItr != callee2caller.end()) {
-          valId = mapItr->second;
-        }
-        AddStore(returnVarId, valId, &new_blk_ptr);
+        return true;
+      });
 
-        // Remember we saw a return; if followed by a label, will need to
-        // insert branch.
-        prevInstWasReturn = true;
-      } break;
-      case SpvOpReturn: {
-        // Remember we saw a return; if followed by a label, will need to
-        // insert branch.
-        prevInstWasReturn = true;
-      } break;
-      case SpvOpFunctionEnd: {
-        // If there was an early return, we generated a return label id
-        // for it.  Now we have to generate the return block with that Id.
-        if (returnLabelId != 0) {
-          // If previous instruction was return, insert branch instruction
-          // to return block.
-          if (prevInstWasReturn) AddBranch(returnLabelId, &new_blk_ptr);
-          if (earlyReturn) {
-            // If we generated a loop header for the single-trip loop
-            // to accommodate early returns, insert the continue
-            // target block now, with a false branch back to the loop header.
-            new_blocks->push_back(std::move(new_blk_ptr));
-            new_blk_ptr =
-                MakeUnique<BasicBlock>(NewLabel(singleTripLoopContinueId));
-            AddBranchCond(GetFalseId(), singleTripLoopHeaderId, returnLabelId,
-                          &new_blk_ptr);
-          }
-          // Generate the return block.
-          new_blocks->push_back(std::move(new_blk_ptr));
-          new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(returnLabelId));
-          multiBlocks = true;
-        }
-        // Load return value into result id of call, if it exists.
-        if (returnVarId != 0) {
-          const uint32_t resId = call_inst_itr->result_id();
-          assert(resId != 0);
-          AddLoad(calleeTypeId, resId, returnVarId, &new_blk_ptr);
-        }
-        // Copy remaining instructions from caller block.
-        for (Instruction* inst = call_inst_itr->NextNode(); inst;
-             inst = call_inst_itr->NextNode()) {
-          inst->RemoveFromList();
-          std::unique_ptr<Instruction> cp_inst(inst);
-          // If multiple blocks generated, regenerate any same-block
-          // instruction that has not been seen in this last block.
-          if (multiBlocks) {
-            CloneSameBlockOps(&cp_inst, &postCallSB, &preCallSB, &new_blk_ptr);
-            // Remember same-block ops in this block.
-            if (IsSameBlockOp(&*cp_inst)) {
-              const uint32_t rid = cp_inst->result_id();
-              postCallSB[rid] = rid;
-            }
-          }
-          new_blk_ptr->AddInstruction(std::move(cp_inst));
-        }
-        // Finalize inline code.
-        new_blocks->push_back(std::move(new_blk_ptr));
-      } break;
-      default: {
-        // Copy callee instruction and remap all input Ids.
-        std::unique_ptr<Instruction> cp_inst(cpi->Clone(context()));
-        cp_inst->ForEachInId([&callee2caller, &callee_result_ids,
-                              this](uint32_t* iid) {
-          const auto mapItr = callee2caller.find(*iid);
-          if (mapItr != callee2caller.end()) {
-            *iid = mapItr->second;
-          } else if (callee_result_ids.find(*iid) != callee_result_ids.end()) {
-            // Forward reference. Allocate a new id, map it,
-            // use it and check for it when remapping result ids
-            const uint32_t nid = this->TakeNextId();
-            callee2caller[*iid] = nid;
-            *iid = nid;
-          }
-        });
-        // If result id is non-zero, remap it. If already mapped, use mapped
-        // value, else use next id.
-        const uint32_t rid = cp_inst->result_id();
-        if (rid != 0) {
-          const auto mapItr = callee2caller.find(rid);
-          uint32_t nid;
-          if (mapItr != callee2caller.end()) {
-            nid = mapItr->second;
-          } else {
-            nid = this->TakeNextId();
-            callee2caller[rid] = nid;
-          }
-          cp_inst->SetResultId(nid);
-          get_decoration_mgr()->CloneDecorations(rid, nid);
-        }
-        new_blk_ptr->AddInstruction(std::move(cp_inst));
-      } break;
-    }
-  });
+  if (!successful) {
+    return false;
+  }
 
   if (caller_is_loop_header && (new_blocks->size() > 1)) {
     // Move the OpLoopMerge from the last block back to the first, where
@@ -532,6 +626,7 @@
   for (auto& blk : *new_blocks) {
     id2block_[blk->id()] = &*blk;
   }
+  return true;
 }
 
 bool InlinePass::IsInlinableFunctionCall(const Instruction* inst) {
diff --git a/source/opt/inline_pass.h b/source/opt/inline_pass.h
index e23e7f0..ecfe964 100644
--- a/source/opt/inline_pass.h
+++ b/source/opt/inline_pass.h
@@ -41,7 +41,8 @@
  protected:
   InlinePass();
 
-  // Add pointer to type to module and return resultId.
+  // Add pointer to type to module and return resultId.  Returns 0 if the type
+  // could not be created.
   uint32_t AddPointerToType(uint32_t type_id, SpvStorageClass storage_class);
 
   // Add unconditional branch to labelId to end of block block_ptr.
@@ -67,20 +68,22 @@
   std::unique_ptr<Instruction> NewLabel(uint32_t label_id);
 
   // Returns the id for the boolean false value. Looks in the module first
-  // and creates it if not found. Remembers it for future calls.
+  // and creates it if not found. Remembers it for future calls.  Returns 0 if
+  // the value could not be created.
   uint32_t GetFalseId();
 
   // Map callee params to caller args
   void MapParams(Function* calleeFn, BasicBlock::iterator call_inst_itr,
                  std::unordered_map<uint32_t, uint32_t>* callee2caller);
 
-  // Clone and map callee locals
-  void CloneAndMapLocals(Function* calleeFn,
+  // Clone and map callee locals.  Return true if successful.
+  bool CloneAndMapLocals(Function* calleeFn,
                          std::vector<std::unique_ptr<Instruction>>* new_vars,
                          std::unordered_map<uint32_t, uint32_t>* callee2caller);
 
-  // Create return variable for callee clone code if needed. Return id
-  // if created, otherwise 0.
+  // Create return variable for callee clone code.  The return type of
+  // |calleeFn| must not be void.  Returns  the id of the return variable if
+  // created.  Returns 0 if the return variable could not be created.
   uint32_t CreateReturnVar(Function* calleeFn,
                            std::vector<std::unique_ptr<Instruction>>* new_vars);
 
@@ -92,7 +95,7 @@
   // Look in preCallSB for instructions that need cloning. Look in
   // postCallSB for instructions already cloned. Add cloned instruction
   // to postCallSB.
-  void CloneSameBlockOps(std::unique_ptr<Instruction>* inst,
+  bool CloneSameBlockOps(std::unique_ptr<Instruction>* inst,
                          std::unordered_map<uint32_t, uint32_t>* postCallSB,
                          std::unordered_map<uint32_t, Instruction*>* preCallSB,
                          std::unique_ptr<BasicBlock>* block_ptr);
@@ -111,7 +114,9 @@
   // Also return in new_vars additional OpVariable instructions required by
   // and to be inserted into the caller function after the block at
   // call_block_itr is replaced with new_blocks.
-  void GenInlineCode(std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
+  //
+  // Returns true if successful.
+  bool GenInlineCode(std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
                      std::vector<std::unique_ptr<Instruction>>* new_vars,
                      BasicBlock::iterator call_inst_itr,
                      UptrVectorIterator<BasicBlock> call_block_itr);
diff --git a/source/opt/inst_bindless_check_pass.cpp b/source/opt/inst_bindless_check_pass.cpp
index 1901f76..47c8134 100644
--- a/source/opt/inst_bindless_check_pass.cpp
+++ b/source/opt/inst_bindless_check_pass.cpp
@@ -35,14 +35,79 @@
 namespace spvtools {
 namespace opt {
 
-void InstBindlessCheckPass::GenBindlessCheckCode(
-    BasicBlock::iterator ref_inst_itr,
-    UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t instruction_idx,
-    uint32_t stage_idx, std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
-  // Look for reference through bindless descriptor. If not, return.
-  std::unique_ptr<BasicBlock> new_blk_ptr;
-  uint32_t image_id;
-  switch (ref_inst_itr->opcode()) {
+uint32_t InstBindlessCheckPass::GenDebugReadLength(
+    uint32_t var_id, InstructionBuilder* builder) {
+  uint32_t desc_set_idx =
+      var2desc_set_[var_id] + kDebugInputBindlessOffsetLengths;
+  uint32_t desc_set_idx_id = builder->GetUintConstantId(desc_set_idx);
+  uint32_t binding_idx_id = builder->GetUintConstantId(var2binding_[var_id]);
+  return GenDebugDirectRead({desc_set_idx_id, binding_idx_id}, builder);
+}
+
+uint32_t InstBindlessCheckPass::GenDebugReadInit(uint32_t var_id,
+                                                 uint32_t desc_idx_id,
+                                                 InstructionBuilder* builder) {
+  uint32_t desc_set_base_id =
+      builder->GetUintConstantId(kDebugInputBindlessInitOffset);
+  uint32_t desc_set_idx_id = builder->GetUintConstantId(var2desc_set_[var_id]);
+  uint32_t binding_idx_id = builder->GetUintConstantId(var2binding_[var_id]);
+  uint32_t u_desc_idx_id = GenUintCastCode(desc_idx_id, builder);
+  return GenDebugDirectRead(
+      {desc_set_base_id, desc_set_idx_id, binding_idx_id, u_desc_idx_id},
+      builder);
+}
+
+uint32_t InstBindlessCheckPass::CloneOriginalReference(
+    ref_analysis* ref, InstructionBuilder* builder) {
+  // Clone descriptor load
+  Instruction* load_inst = get_def_use_mgr()->GetDef(ref->load_id);
+  Instruction* new_load_inst =
+      builder->AddLoad(load_inst->type_id(),
+                       load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx));
+  uid2offset_[new_load_inst->unique_id()] = uid2offset_[load_inst->unique_id()];
+  uint32_t new_load_id = new_load_inst->result_id();
+  get_decoration_mgr()->CloneDecorations(load_inst->result_id(), new_load_id);
+  uint32_t new_image_id = new_load_id;
+  // Clone Image/SampledImage with new load, if needed
+  if (ref->image_id != 0) {
+    Instruction* image_inst = get_def_use_mgr()->GetDef(ref->image_id);
+    if (image_inst->opcode() == SpvOp::SpvOpSampledImage) {
+      Instruction* new_image_inst = builder->AddBinaryOp(
+          image_inst->type_id(), SpvOpSampledImage, new_load_id,
+          image_inst->GetSingleWordInOperand(kSpvSampledImageSamplerIdInIdx));
+      uid2offset_[new_image_inst->unique_id()] =
+          uid2offset_[image_inst->unique_id()];
+      new_image_id = new_image_inst->result_id();
+    } else {
+      assert(image_inst->opcode() == SpvOp::SpvOpImage && "expecting OpImage");
+      Instruction* new_image_inst =
+          builder->AddUnaryOp(image_inst->type_id(), SpvOpImage, new_load_id);
+      uid2offset_[new_image_inst->unique_id()] =
+          uid2offset_[image_inst->unique_id()];
+      new_image_id = new_image_inst->result_id();
+    }
+    get_decoration_mgr()->CloneDecorations(ref->image_id, new_image_id);
+  }
+  // Clone original reference using new image code
+  std::unique_ptr<Instruction> new_ref_inst(ref->ref_inst->Clone(context()));
+  uint32_t ref_result_id = ref->ref_inst->result_id();
+  uint32_t new_ref_id = 0;
+  if (ref_result_id != 0) {
+    new_ref_id = TakeNextId();
+    new_ref_inst->SetResultId(new_ref_id);
+  }
+  new_ref_inst->SetInOperand(kSpvImageSampleImageIdInIdx, {new_image_id});
+  // Register new reference and add to new block
+  Instruction* added_inst = builder->AddInstruction(std::move(new_ref_inst));
+  uid2offset_[added_inst->unique_id()] =
+      uid2offset_[ref->ref_inst->unique_id()];
+  if (new_ref_id != 0)
+    get_decoration_mgr()->CloneDecorations(ref_result_id, new_ref_id);
+  return new_ref_id;
+}
+
+uint32_t InstBindlessCheckPass::GetDescriptorValueId(Instruction* inst) {
+  switch (inst->opcode()) {
     case SpvOp::SpvOpImageSampleImplicitLod:
     case SpvOp::SpvOpImageSampleExplicitLod:
     case SpvOp::SpvOpImageSampleDrefImplicitLod:
@@ -75,152 +140,205 @@
     case SpvOp::SpvOpImageSparseFetch:
     case SpvOp::SpvOpImageSparseRead:
     case SpvOp::SpvOpImageWrite:
-      image_id =
-          ref_inst_itr->GetSingleWordInOperand(kSpvImageSampleImageIdInIdx);
-      break;
+      return inst->GetSingleWordInOperand(kSpvImageSampleImageIdInIdx);
     default:
-      return;
+      break;
   }
-  Instruction* image_inst = get_def_use_mgr()->GetDef(image_id);
-  uint32_t load_id;
-  Instruction* load_inst;
+  return 0;
+}
+
+bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst,
+                                                       ref_analysis* ref) {
+  ref->image_id = GetDescriptorValueId(ref_inst);
+  if (ref->image_id == 0) return false;
+  Instruction* image_inst = get_def_use_mgr()->GetDef(ref->image_id);
   if (image_inst->opcode() == SpvOp::SpvOpSampledImage) {
-    load_id = image_inst->GetSingleWordInOperand(kSpvSampledImageImageIdInIdx);
-    load_inst = get_def_use_mgr()->GetDef(load_id);
+    ref->load_id =
+        image_inst->GetSingleWordInOperand(kSpvSampledImageImageIdInIdx);
   } else if (image_inst->opcode() == SpvOp::SpvOpImage) {
-    load_id = image_inst->GetSingleWordInOperand(kSpvImageSampledImageIdInIdx);
-    load_inst = get_def_use_mgr()->GetDef(load_id);
+    ref->load_id =
+        image_inst->GetSingleWordInOperand(kSpvImageSampledImageIdInIdx);
   } else {
-    load_id = image_id;
-    load_inst = image_inst;
-    image_id = 0;
+    ref->load_id = ref->image_id;
+    ref->image_id = 0;
   }
+  Instruction* load_inst = get_def_use_mgr()->GetDef(ref->load_id);
   if (load_inst->opcode() != SpvOp::SpvOpLoad) {
-    // TODO(greg-lunarg): Handle additional possibilities
-    return;
+    // TODO(greg-lunarg): Handle additional possibilities?
+    return false;
   }
-  uint32_t ptr_id = load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx);
-  Instruction* ptr_inst = get_def_use_mgr()->GetDef(ptr_id);
-  if (ptr_inst->opcode() != SpvOp::SpvOpAccessChain) return;
-  if (ptr_inst->NumInOperands() != 2) {
-    assert(false && "unexpected bindless index number");
-    return;
+  ref->ptr_id = load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx);
+  Instruction* ptr_inst = get_def_use_mgr()->GetDef(ref->ptr_id);
+  if (ptr_inst->opcode() == SpvOp::SpvOpVariable) {
+    ref->index_id = 0;
+    ref->var_id = ref->ptr_id;
+  } else if (ptr_inst->opcode() == SpvOp::SpvOpAccessChain) {
+    if (ptr_inst->NumInOperands() != 2) {
+      assert(false && "unexpected bindless index number");
+      return false;
+    }
+    ref->index_id =
+        ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx);
+    ref->var_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainBaseIdInIdx);
+    Instruction* var_inst = get_def_use_mgr()->GetDef(ref->var_id);
+    if (var_inst->opcode() != SpvOpVariable) {
+      assert(false && "unexpected bindless base");
+      return false;
+    }
+  } else {
+    // TODO(greg-lunarg): Handle additional possibilities?
+    return false;
   }
-  uint32_t index_id =
-      ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx);
-  ptr_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainBaseIdInIdx);
-  ptr_inst = get_def_use_mgr()->GetDef(ptr_id);
-  if (ptr_inst->opcode() != SpvOpVariable) {
-    assert(false && "unexpected bindless base");
-    return;
-  }
-  uint32_t var_type_id = ptr_inst->type_id();
-  Instruction* var_type_inst = get_def_use_mgr()->GetDef(var_type_id);
-  uint32_t ptr_type_id =
-      var_type_inst->GetSingleWordInOperand(kSpvTypePointerTypeIdInIdx);
-  Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id);
-  // TODO(greg-lunarg): Handle RuntimeArray. Will need to pull length
-  // out of debug input buffer.
-  if (ptr_type_inst->opcode() != SpvOpTypeArray) return;
-  // If index and bound both compile-time constants and index < bound,
-  // return without changing
-  uint32_t length_id =
-      ptr_type_inst->GetSingleWordInOperand(kSpvTypeArrayLengthIdInIdx);
-  Instruction* index_inst = get_def_use_mgr()->GetDef(index_id);
-  Instruction* length_inst = get_def_use_mgr()->GetDef(length_id);
-  if (index_inst->opcode() == SpvOpConstant &&
-      length_inst->opcode() == SpvOpConstant &&
-      index_inst->GetSingleWordInOperand(kSpvConstantValueInIdx) <
-          length_inst->GetSingleWordInOperand(kSpvConstantValueInIdx))
-    return;
-  // Generate full runtime bounds test code with true branch
-  // being full reference and false branch being debug output and zero
-  // for the referenced value.
-  MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
+  ref->ref_inst = ref_inst;
+  return true;
+}
+
+void InstBindlessCheckPass::GenCheckCode(
+    uint32_t check_id, uint32_t error_id, uint32_t length_id,
+    uint32_t stage_idx, ref_analysis* ref,
+    std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+  BasicBlock* back_blk_ptr = &*new_blocks->back();
   InstructionBuilder builder(
-      context(), &*new_blk_ptr,
+      context(), back_blk_ptr,
       IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
-  uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessBounds);
-  Instruction* ult_inst =
-      builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, index_id, length_id);
+  // Gen conditional branch on check_id. Valid branch generates original
+  // reference. Invalid generates debug output and zero result (if needed).
   uint32_t merge_blk_id = TakeNextId();
   uint32_t valid_blk_id = TakeNextId();
   uint32_t invalid_blk_id = TakeNextId();
   std::unique_ptr<Instruction> merge_label(NewLabel(merge_blk_id));
   std::unique_ptr<Instruction> valid_label(NewLabel(valid_blk_id));
   std::unique_ptr<Instruction> invalid_label(NewLabel(invalid_blk_id));
-  (void)builder.AddConditionalBranch(ult_inst->result_id(), valid_blk_id,
-                                     invalid_blk_id, merge_blk_id,
-                                     SpvSelectionControlMaskNone);
-  // Close selection block and gen valid reference block
-  new_blocks->push_back(std::move(new_blk_ptr));
-  new_blk_ptr.reset(new BasicBlock(std::move(valid_label)));
+  (void)builder.AddConditionalBranch(check_id, valid_blk_id, invalid_blk_id,
+                                     merge_blk_id, SpvSelectionControlMaskNone);
+  // Gen valid bounds branch
+  std::unique_ptr<BasicBlock> new_blk_ptr(
+      new BasicBlock(std::move(valid_label)));
   builder.SetInsertPoint(&*new_blk_ptr);
-  // Clone descriptor load
-  Instruction* new_load_inst =
-      builder.AddLoad(load_inst->type_id(),
-                      load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx));
-  uint32_t new_load_id = new_load_inst->result_id();
-  get_decoration_mgr()->CloneDecorations(load_inst->result_id(), new_load_id);
-  uint32_t new_image_id = new_load_id;
-  // Clone Image/SampledImage with new load, if needed
-  if (image_id != 0) {
-    if (image_inst->opcode() == SpvOp::SpvOpSampledImage) {
-      Instruction* new_image_inst = builder.AddBinaryOp(
-          image_inst->type_id(), SpvOpSampledImage, new_load_id,
-          image_inst->GetSingleWordInOperand(kSpvSampledImageSamplerIdInIdx));
-      new_image_id = new_image_inst->result_id();
-    } else {
-      assert(image_inst->opcode() == SpvOp::SpvOpImage && "expecting OpImage");
-      Instruction* new_image_inst =
-          builder.AddUnaryOp(image_inst->type_id(), SpvOpImage, new_load_id);
-      new_image_id = new_image_inst->result_id();
-    }
-    get_decoration_mgr()->CloneDecorations(image_id, new_image_id);
-  }
-  // Clone original reference using new image code
-  std::unique_ptr<Instruction> new_ref_inst(ref_inst_itr->Clone(context()));
-  uint32_t ref_result_id = ref_inst_itr->result_id();
-  uint32_t new_ref_id = 0;
-  if (ref_result_id != 0) {
-    new_ref_id = TakeNextId();
-    new_ref_inst->SetResultId(new_ref_id);
-  }
-  new_ref_inst->SetInOperand(kSpvImageSampleImageIdInIdx, {new_image_id});
-  // Register new reference and add to new block
-  builder.AddInstruction(std::move(new_ref_inst));
-  if (new_ref_id != 0)
-    get_decoration_mgr()->CloneDecorations(ref_result_id, new_ref_id);
-  // Close valid block and gen invalid block
+  uint32_t new_ref_id = CloneOriginalReference(ref, &builder);
   (void)builder.AddBranch(merge_blk_id);
   new_blocks->push_back(std::move(new_blk_ptr));
+  // Gen invalid block
   new_blk_ptr.reset(new BasicBlock(std::move(invalid_label)));
   builder.SetInsertPoint(&*new_blk_ptr);
-  uint32_t u_index_id = GenUintCastCode(index_id, &builder);
-  GenDebugStreamWrite(instruction_idx, stage_idx,
+  uint32_t u_index_id = GenUintCastCode(ref->index_id, &builder);
+  GenDebugStreamWrite(uid2offset_[ref->ref_inst->unique_id()], stage_idx,
                       {error_id, u_index_id, length_id}, &builder);
   // Remember last invalid block id
   uint32_t last_invalid_blk_id = new_blk_ptr->GetLabelInst()->result_id();
   // Gen zero for invalid  reference
-  uint32_t ref_type_id = ref_inst_itr->type_id();
-  // Close invalid block and gen merge block
+  uint32_t ref_type_id = ref->ref_inst->type_id();
   (void)builder.AddBranch(merge_blk_id);
   new_blocks->push_back(std::move(new_blk_ptr));
+  // Gen merge block
   new_blk_ptr.reset(new BasicBlock(std::move(merge_label)));
   builder.SetInsertPoint(&*new_blk_ptr);
   // Gen phi of new reference and zero, if necessary, and replace the
   // result id of the original reference with that of the Phi. Kill original
-  // reference and move in remainder of original block.
+  // reference.
   if (new_ref_id != 0) {
     Instruction* phi_inst = builder.AddPhi(
         ref_type_id, {new_ref_id, valid_blk_id, builder.GetNullId(ref_type_id),
                       last_invalid_blk_id});
-    context()->ReplaceAllUsesWith(ref_result_id, phi_inst->result_id());
+    context()->ReplaceAllUsesWith(ref->ref_inst->result_id(),
+                                  phi_inst->result_id());
   }
-  context()->KillInst(&*ref_inst_itr);
-  MovePostludeCode(ref_block_itr, &new_blk_ptr);
-  // Add remainder/merge block to new blocks
   new_blocks->push_back(std::move(new_blk_ptr));
+  context()->KillInst(ref->ref_inst);
+}
+
+void InstBindlessCheckPass::GenBoundsCheckCode(
+    BasicBlock::iterator ref_inst_itr,
+    UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
+    std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+  // Look for reference through indexed descriptor. If found, analyze and
+  // save components. If not, return.
+  ref_analysis ref;
+  if (!AnalyzeDescriptorReference(&*ref_inst_itr, &ref)) return;
+  Instruction* ptr_inst = get_def_use_mgr()->GetDef(ref.ptr_id);
+  if (ptr_inst->opcode() != SpvOp::SpvOpAccessChain) return;
+  // If index and bound both compile-time constants and index < bound,
+  // return without changing
+  Instruction* var_inst = get_def_use_mgr()->GetDef(ref.var_id);
+  uint32_t var_type_id = var_inst->type_id();
+  Instruction* var_type_inst = get_def_use_mgr()->GetDef(var_type_id);
+  uint32_t desc_type_id =
+      var_type_inst->GetSingleWordInOperand(kSpvTypePointerTypeIdInIdx);
+  Instruction* desc_type_inst = get_def_use_mgr()->GetDef(desc_type_id);
+  uint32_t length_id = 0;
+  if (desc_type_inst->opcode() == SpvOpTypeArray) {
+    length_id =
+        desc_type_inst->GetSingleWordInOperand(kSpvTypeArrayLengthIdInIdx);
+    Instruction* index_inst = get_def_use_mgr()->GetDef(ref.index_id);
+    Instruction* length_inst = get_def_use_mgr()->GetDef(length_id);
+    if (index_inst->opcode() == SpvOpConstant &&
+        length_inst->opcode() == SpvOpConstant &&
+        index_inst->GetSingleWordInOperand(kSpvConstantValueInIdx) <
+            length_inst->GetSingleWordInOperand(kSpvConstantValueInIdx))
+      return;
+  } else if (!input_length_enabled_ ||
+             desc_type_inst->opcode() != SpvOpTypeRuntimeArray) {
+    return;
+  }
+  // Move original block's preceding instructions into first new block
+  std::unique_ptr<BasicBlock> new_blk_ptr;
+  MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
+  InstructionBuilder builder(
+      context(), &*new_blk_ptr,
+      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+  new_blocks->push_back(std::move(new_blk_ptr));
+  uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessBounds);
+  // If length id not yet set, descriptor array is runtime size so
+  // generate load of length from stage's debug input buffer.
+  if (length_id == 0) {
+    assert(desc_type_inst->opcode() == SpvOpTypeRuntimeArray &&
+           "unexpected bindless type");
+    length_id = GenDebugReadLength(ref.var_id, &builder);
+  }
+  // Generate full runtime bounds test code with true branch
+  // being full reference and false branch being debug output and zero
+  // for the referenced value.
+  Instruction* ult_inst =
+      builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, ref.index_id, length_id);
+  GenCheckCode(ult_inst->result_id(), error_id, length_id, stage_idx, &ref,
+               new_blocks);
+  // Move original block's remaining code into remainder/merge block and add
+  // to new blocks
+  BasicBlock* back_blk_ptr = &*new_blocks->back();
+  MovePostludeCode(ref_block_itr, back_blk_ptr);
+}
+
+void InstBindlessCheckPass::GenInitCheckCode(
+    BasicBlock::iterator ref_inst_itr,
+    UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
+    std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+  // Look for reference through descriptor. If not, return.
+  ref_analysis ref;
+  if (!AnalyzeDescriptorReference(&*ref_inst_itr, &ref)) return;
+  // Move original block's preceding instructions into first new block
+  std::unique_ptr<BasicBlock> new_blk_ptr;
+  MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
+  InstructionBuilder builder(
+      context(), &*new_blk_ptr,
+      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+  new_blocks->push_back(std::move(new_blk_ptr));
+  // Read initialization status from debug input buffer. If index id not yet
+  // set, binding is single descriptor, so set index to constant 0.
+  uint32_t zero_id = builder.GetUintConstantId(0u);
+  if (ref.index_id == 0) ref.index_id = zero_id;
+  uint32_t init_id = GenDebugReadInit(ref.var_id, ref.index_id, &builder);
+  // Generate full runtime non-zero init test code with true branch
+  // being full reference and false branch being debug output and zero
+  // for the referenced value.
+  Instruction* uneq_inst =
+      builder.AddBinaryOp(GetBoolId(), SpvOpINotEqual, init_id, zero_id);
+  uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessUninit);
+  GenCheckCode(uneq_inst->result_id(), error_id, zero_id, stage_idx, &ref,
+               new_blocks);
+  // Move original block's remaining code into remainder/merge block and add
+  // to new blocks
+  BasicBlock* back_blk_ptr = &*new_blocks->back();
+  MovePostludeCode(ref_block_itr, back_blk_ptr);
 }
 
 void InstBindlessCheckPass::InitializeInstBindlessCheck() {
@@ -236,21 +354,43 @@
       break;
     }
   }
+  // If descriptor indexing extension and runtime array length support enabled,
+  // create variable mappings. Length support is always enabled if descriptor
+  // init check is enabled.
+  if (ext_descriptor_indexing_defined_ && input_length_enabled_)
+    for (auto& anno : get_module()->annotations())
+      if (anno.opcode() == SpvOpDecorate) {
+        if (anno.GetSingleWordInOperand(1u) == SpvDecorationDescriptorSet)
+          var2desc_set_[anno.GetSingleWordInOperand(0u)] =
+              anno.GetSingleWordInOperand(2u);
+        else if (anno.GetSingleWordInOperand(1u) == SpvDecorationBinding)
+          var2binding_[anno.GetSingleWordInOperand(0u)] =
+              anno.GetSingleWordInOperand(2u);
+      }
 }
 
 Pass::Status InstBindlessCheckPass::ProcessImpl() {
-  // Perform instrumentation on each entry point function in module
+  // Perform bindless bounds check on each entry point function in module
   InstProcessFunction pfn =
       [this](BasicBlock::iterator ref_inst_itr,
-             UptrVectorIterator<BasicBlock> ref_block_itr,
-             uint32_t instruction_idx, uint32_t stage_idx,
+             UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
              std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
-        return GenBindlessCheckCode(ref_inst_itr, ref_block_itr,
-                                    instruction_idx, stage_idx, new_blocks);
+        return GenBoundsCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
+                                  new_blocks);
       };
   bool modified = InstProcessEntryPointCallTree(pfn);
-  // This pass does not update inst->blk info
-  context()->InvalidateAnalyses(IRContext::kAnalysisInstrToBlockMapping);
+  if (ext_descriptor_indexing_defined_ && input_init_enabled_) {
+    // Perform descriptor initialization check on each entry point function in
+    // module
+    pfn = [this](BasicBlock::iterator ref_inst_itr,
+                 UptrVectorIterator<BasicBlock> ref_block_itr,
+                 uint32_t stage_idx,
+                 std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+      return GenInitCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
+                              new_blocks);
+    };
+    modified |= InstProcessEntryPointCallTree(pfn);
+  }
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
 
diff --git a/source/opt/inst_bindless_check_pass.h b/source/opt/inst_bindless_check_pass.h
index 3ab5ab7..79c34f1 100644
--- a/source/opt/inst_bindless_check_pass.h
+++ b/source/opt/inst_bindless_check_pass.h
@@ -29,10 +29,16 @@
 class InstBindlessCheckPass : public InstrumentPass {
  public:
   // For test harness only
-  InstBindlessCheckPass() : InstrumentPass(7, 23, kInstValidationIdBindless) {}
+  InstBindlessCheckPass()
+      : InstrumentPass(7, 23, kInstValidationIdBindless),
+        input_length_enabled_(true),
+        input_init_enabled_(true) {}
   // For all other interfaces
-  InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id)
-      : InstrumentPass(desc_set, shader_id, kInstValidationIdBindless) {}
+  InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id,
+                        bool input_length_enable, bool input_init_enable)
+      : InstrumentPass(desc_set, shader_id, kInstValidationIdBindless),
+        input_length_enabled_(input_length_enable),
+        input_init_enabled_(input_init_enable) {}
 
   ~InstBindlessCheckPass() override = default;
 
@@ -42,28 +48,40 @@
   const char* name() const override { return "inst-bindless-check-pass"; }
 
  private:
-  // Initialize state for instrumenting bindless checking
-  void InitializeInstBindlessCheck();
-
-  // This function does bindless checking instrumentation on a single
-  // instruction. It is designed to be passed to
+  // These functions do bindless checking instrumentation on a single
+  // instruction which references through a descriptor (ie references into an
+  // image or buffer). Refer to Vulkan API for further information on
+  // descriptors. GenBoundsCheckCode checks that an index into a descriptor
+  // array (array of images or buffers) is in-bounds. GenInitCheckCode
+  // checks that the referenced descriptor has been initialized, if the
+  // SPV_EXT_descriptor_indexing extension is enabled.
+  //
+  // TODO(greg-lunarg): Add support for buffers. Currently only does
+  // checking of references of images.
+  //
+  // The functions are designed to be passed to
   // InstrumentPass::InstProcessEntryPointCallTree(), which applies the
   // function to each instruction in a module and replaces the instruction
   // if warranted.
   //
   // If |ref_inst_itr| is a bindless reference, return in |new_blocks| the
   // result of instrumenting it with validation code within its block at
-  // |ref_block_itr|. Specifically, generate code to check that the index
-  // into the descriptor array is in-bounds. If the check passes, execute
-  // the remainder of the reference, otherwise write a record to the debug
+  // |ref_block_itr|.  The validation code first executes a check for the
+  // specific condition called for. If the check passes, it executes
+  // the remainder of the reference, otherwise writes a record to the debug
   // output buffer stream including |function_idx, instruction_idx, stage_idx|
-  // and replace the reference with the null value of the original type. The
+  // and replaces the reference with the null value of the original type. The
   // block at |ref_block_itr| can just be replaced with the blocks in
   // |new_blocks|, which will contain at least two blocks. The last block will
   // comprise all instructions following |ref_inst_itr|,
   // preceded by a phi instruction.
   //
-  // This instrumentation pass utilizes GenDebugStreamWrite() to write its
+  // These instrumentation functions utilize GenDebugDirectRead() to read data
+  // from the debug input buffer, specifically the lengths of variable length
+  // descriptor arrays, and the initialization status of each descriptor.
+  // The format of the debug input buffer is documented in instrument.hpp.
+  //
+  // These instrumentation functions utilize GenDebugStreamWrite() to write its
   // error records. The validation-specific part of the error record will
   // have the format:
   //
@@ -76,15 +94,83 @@
   //
   // The Descriptor Array Size is the size of the descriptor array which was
   // indexed.
-  void GenBindlessCheckCode(
-      BasicBlock::iterator ref_inst_itr,
-      UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t instruction_idx,
-      uint32_t stage_idx, std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+  void GenBoundsCheckCode(BasicBlock::iterator ref_inst_itr,
+                          UptrVectorIterator<BasicBlock> ref_block_itr,
+                          uint32_t stage_idx,
+                          std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
 
+  void GenInitCheckCode(BasicBlock::iterator ref_inst_itr,
+                        UptrVectorIterator<BasicBlock> ref_block_itr,
+                        uint32_t stage_idx,
+                        std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+
+  // Generate instructions into |builder| to read length of runtime descriptor
+  // array |var_id| from debug input buffer and return id of value.
+  uint32_t GenDebugReadLength(uint32_t var_id, InstructionBuilder* builder);
+
+  // Generate instructions into |builder| to read initialization status of
+  // descriptor array |image_id| at |index_id| from debug input buffer and
+  // return id of value.
+  uint32_t GenDebugReadInit(uint32_t image_id, uint32_t index_id,
+                            InstructionBuilder* builder);
+
+  // Analysis data for descriptor reference components, generated by
+  // AnalyzeDescriptorReference. It is necessary and sufficient for further
+  // analysis and regeneration of the reference.
+  typedef struct ref_analysis {
+    uint32_t image_id;
+    uint32_t load_id;
+    uint32_t ptr_id;
+    uint32_t var_id;
+    uint32_t index_id;
+    Instruction* ref_inst;
+  } ref_analysis;
+
+  // Clone original original reference encapsulated by |ref| into |builder|.
+  // This may generate more than one instruction if neccessary.
+  uint32_t CloneOriginalReference(ref_analysis* ref,
+                                  InstructionBuilder* builder);
+
+  // If |inst| references through a descriptor, (ie references into an image
+  // or buffer), return the id of the value it references. Else return 0.
+  uint32_t GetDescriptorValueId(Instruction* inst);
+
+  // Analyze descriptor reference |ref_inst| and save components into |ref|.
+  // Return true if |ref_inst| is a descriptor reference, false otherwise.
+  bool AnalyzeDescriptorReference(Instruction* ref_inst, ref_analysis* ref);
+
+  // Generate instrumentation code for generic test result |check_id|, starting
+  // with |builder| of block |new_blk_ptr|, adding new blocks to |new_blocks|.
+  // Generate conditional branch to a valid or invalid branch. Generate valid
+  // block which does original reference |ref|. Generate invalid block which
+  // writes debug error output utilizing |ref|, |error_id|, |length_id| and
+  // |stage_idx|. Generate merge block for valid and invalid branches. Kill
+  // original reference.
+  void GenCheckCode(uint32_t check_id, uint32_t error_id, uint32_t length_id,
+                    uint32_t stage_idx, ref_analysis* ref,
+                    std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+
+  // Initialize state for instrumenting bindless checking
+  void InitializeInstBindlessCheck();
+
+  // Apply GenBoundsCheckCode to every instruction in module. Then apply
+  // GenInitCheckCode to every instruction in module.
   Pass::Status ProcessImpl();
 
   // True if VK_EXT_descriptor_indexing is defined
   bool ext_descriptor_indexing_defined_;
+
+  // Enable instrumentation of runtime array length checking
+  bool input_length_enabled_;
+
+  // Enable instrumentation of descriptor initialization checking
+  bool input_init_enabled_;
+
+  // Mapping from variable to descriptor set
+  std::unordered_map<uint32_t, uint32_t> var2desc_set_;
+
+  // Mapping from variable to binding
+  std::unordered_map<uint32_t, uint32_t> var2binding_;
 };
 
 }  // namespace opt
diff --git a/source/opt/instruction.cpp b/source/opt/instruction.cpp
index 5f3c5a8..5a82e53 100644
--- a/source/opt/instruction.cpp
+++ b/source/opt/instruction.cpp
@@ -155,12 +155,6 @@
 }
 
 Instruction* Instruction::GetBaseAddress() const {
-  assert((IsLoad() || opcode() == SpvOpStore || opcode() == SpvOpAccessChain ||
-          opcode() == SpvOpPtrAccessChain ||
-          opcode() == SpvOpInBoundsAccessChain || opcode() == SpvOpCopyObject ||
-          opcode() == SpvOpImageTexelPointer) &&
-         "GetBaseAddress should only be called on instructions that take a "
-         "pointer or image.");
   uint32_t base = GetSingleWordInOperand(kLoadBaseIndex);
   Instruction* base_inst = context()->get_def_use_mgr()->GetDef(base);
   bool done = false;
@@ -182,24 +176,6 @@
         break;
     }
   }
-
-  switch (opcode()) {
-    case SpvOpLoad:
-    case SpvOpStore:
-    case SpvOpAccessChain:
-    case SpvOpInBoundsAccessChain:
-    case SpvOpPtrAccessChain:
-    case SpvOpImageTexelPointer:
-    case SpvOpCopyObject:
-      // A load or store through a pointer.
-      assert(base_inst->IsValidBasePointer() &&
-             "We cannot have a base pointer come from this load");
-      break;
-    default:
-      // A load or store of an image.
-      assert(base_inst->IsValidBaseImage() && "We are expecting an image.");
-      break;
-  }
   return base_inst;
 }
 
diff --git a/source/opt/instrument_pass.cpp b/source/opt/instrument_pass.cpp
index 7f56a1e..668c980 100644
--- a/source/opt/instrument_pass.cpp
+++ b/source/opt/instrument_pass.cpp
@@ -57,8 +57,7 @@
 }
 
 void InstrumentPass::MovePostludeCode(
-    UptrVectorIterator<BasicBlock> ref_block_itr,
-    std::unique_ptr<BasicBlock>* new_blk_ptr) {
+    UptrVectorIterator<BasicBlock> ref_block_itr, BasicBlock* new_blk_ptr) {
   // new_blk_ptr->reset(new BasicBlock(NewLabel(ref_block_itr->id())));
   // Move contents of original ref block.
   for (auto cii = ref_block_itr->begin(); cii != ref_block_itr->end();
@@ -77,7 +76,7 @@
         same_block_post_[rid] = rid;
       }
     }
-    (*new_blk_ptr)->AddInstruction(std::move(mv_inst));
+    new_blk_ptr->AddInstruction(std::move(mv_inst));
   }
 }
 
@@ -107,7 +106,7 @@
       builder->AddBinaryOp(GetUintId(), SpvOpIAdd, base_offset_id,
                            builder->GetUintConstantId(field_offset));
   uint32_t buf_id = GetOutputBufferId();
-  uint32_t buf_uint_ptr_id = GetOutputBufferUintPtrId();
+  uint32_t buf_uint_ptr_id = GetBufferUintPtrId();
   Instruction* achain_inst =
       builder->AddTernaryOp(buf_uint_ptr_id, SpvOpAccessChain, buf_id,
                             builder->GetUintConstantId(kDebugOutputDataOffset),
@@ -168,10 +167,10 @@
   switch (stage_idx) {
     case SpvExecutionModelVertex: {
       // Load and store VertexId and InstanceId
-      GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInVertexId),
-                           kInstVertOutVertexId, base_offset_id, builder);
-      GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInInstanceId),
-                           kInstVertOutInstanceId, base_offset_id, builder);
+      GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInVertexIndex),
+                           kInstVertOutVertexIndex, base_offset_id, builder);
+      GenBuiltinOutputCode(context()->GetBuiltinVarId(SpvBuiltInInstanceIndex),
+                           kInstVertOutInstanceIndex, base_offset_id, builder);
     } break;
     case SpvExecutionModelGLCompute: {
       // Load and store GlobalInvocationId. Second word is unused; store zero.
@@ -222,6 +221,16 @@
   (void)builder->AddNaryOp(GetVoidId(), SpvOpFunctionCall, args);
 }
 
+uint32_t InstrumentPass::GenDebugDirectRead(
+    const std::vector<uint32_t>& offset_ids, InstructionBuilder* builder) {
+  // Call debug input function. Pass func_idx and offset ids as args.
+  uint32_t off_id_cnt = static_cast<uint32_t>(offset_ids.size());
+  uint32_t input_func_id = GetDirectReadFunctionId(off_id_cnt);
+  std::vector<uint32_t> args = {input_func_id};
+  (void)args.insert(args.end(), offset_ids.begin(), offset_ids.end());
+  return builder->AddNaryOp(GetUintId(), SpvOpFunctionCall, args)->result_id();
+}
+
 bool InstrumentPass::IsSameBlockOp(const Instruction* inst) const {
   return inst->opcode() == SpvOpSampledImage || inst->opcode() == SpvOpImage;
 }
@@ -230,7 +239,7 @@
     std::unique_ptr<Instruction>* inst,
     std::unordered_map<uint32_t, uint32_t>* same_blk_post,
     std::unordered_map<uint32_t, Instruction*>* same_blk_pre,
-    std::unique_ptr<BasicBlock>* block_ptr) {
+    BasicBlock* block_ptr) {
   (*inst)->ForEachInId(
       [&same_blk_post, &same_blk_pre, &block_ptr, this](uint32_t* iid) {
         const auto map_itr = (*same_blk_post).find(*iid);
@@ -247,7 +256,7 @@
             sb_inst->SetResultId(nid);
             (*same_blk_post)[rid] = nid;
             *iid = nid;
-            (*block_ptr)->AddInstruction(std::move(sb_inst));
+            block_ptr->AddInstruction(std::move(sb_inst));
           }
         } else {
           // Reset same-block op operand.
@@ -280,12 +289,12 @@
 }
 
 // Return id for output buffer uint ptr type
-uint32_t InstrumentPass::GetOutputBufferUintPtrId() {
-  if (output_buffer_uint_ptr_id_ == 0) {
-    output_buffer_uint_ptr_id_ = context()->get_type_mgr()->FindPointerToType(
+uint32_t InstrumentPass::GetBufferUintPtrId() {
+  if (buffer_uint_ptr_id_ == 0) {
+    buffer_uint_ptr_id_ = context()->get_type_mgr()->FindPointerToType(
         GetUintId(), SpvStorageClassStorageBuffer);
   }
-  return output_buffer_uint_ptr_id_;
+  return buffer_uint_ptr_id_;
 }
 
 uint32_t InstrumentPass::GetOutputBufferBinding() {
@@ -298,20 +307,74 @@
   return 0;
 }
 
+uint32_t InstrumentPass::GetInputBufferBinding() {
+  switch (validation_id_) {
+    case kInstValidationIdBindless:
+      return kDebugInputBindingBindless;
+    default:
+      assert(false && "unexpected validation id");
+  }
+  return 0;
+}
+
+analysis::Type* InstrumentPass::GetUintRuntimeArrayType(
+    analysis::DecorationManager* deco_mgr, analysis::TypeManager* type_mgr) {
+  if (uint_rarr_ty_ == nullptr) {
+    analysis::Integer uint_ty(32, false);
+    analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
+    analysis::RuntimeArray uint_rarr_ty_tmp(reg_uint_ty);
+    uint_rarr_ty_ = type_mgr->GetRegisteredType(&uint_rarr_ty_tmp);
+    uint32_t uint_arr_ty_id = type_mgr->GetTypeInstruction(uint_rarr_ty_);
+    // By the Vulkan spec, a pre-existing RuntimeArray of uint must be part of
+    // a block, and will therefore be decorated with an ArrayStride. Therefore
+    // the undecorated type returned here will not be pre-existing and can
+    // safely be decorated. Since this type is now decorated, it is out of
+    // sync with the TypeManager and therefore the TypeManager must be
+    // invalidated after this pass.
+    assert(context()->get_def_use_mgr()->NumUses(uint_arr_ty_id) == 0 &&
+           "used RuntimeArray type returned");
+    deco_mgr->AddDecorationVal(uint_arr_ty_id, SpvDecorationArrayStride, 4u);
+  }
+  return uint_rarr_ty_;
+}
+
+void InstrumentPass::AddStorageBufferExt() {
+  if (storage_buffer_ext_defined_) return;
+  if (!get_feature_mgr()->HasExtension(kSPV_KHR_storage_buffer_storage_class)) {
+    const std::string ext_name("SPV_KHR_storage_buffer_storage_class");
+    const auto num_chars = ext_name.size();
+    // Compute num words, accommodate the terminating null character.
+    const auto num_words = (num_chars + 1 + 3) / 4;
+    std::vector<uint32_t> ext_words(num_words, 0u);
+    std::memcpy(ext_words.data(), ext_name.data(), num_chars);
+    context()->AddExtension(std::unique_ptr<Instruction>(
+        new Instruction(context(), SpvOpExtension, 0u, 0u,
+                        {{SPV_OPERAND_TYPE_LITERAL_STRING, ext_words}})));
+  }
+  storage_buffer_ext_defined_ = true;
+}
+
 // Return id for output buffer
 uint32_t InstrumentPass::GetOutputBufferId() {
   if (output_buffer_id_ == 0) {
     // If not created yet, create one
     analysis::DecorationManager* deco_mgr = get_decoration_mgr();
     analysis::TypeManager* type_mgr = context()->get_type_mgr();
+    analysis::Type* reg_uint_rarr_ty =
+        GetUintRuntimeArrayType(deco_mgr, type_mgr);
     analysis::Integer uint_ty(32, false);
     analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
-    analysis::RuntimeArray uint_rarr_ty(reg_uint_ty);
-    analysis::Type* reg_uint_rarr_ty =
-        type_mgr->GetRegisteredType(&uint_rarr_ty);
-    analysis::Struct obuf_ty({reg_uint_ty, reg_uint_rarr_ty});
-    analysis::Type* reg_obuf_ty = type_mgr->GetRegisteredType(&obuf_ty);
-    uint32_t obufTyId = type_mgr->GetTypeInstruction(reg_obuf_ty);
+    analysis::Struct buf_ty({reg_uint_ty, reg_uint_rarr_ty});
+    analysis::Type* reg_buf_ty = type_mgr->GetRegisteredType(&buf_ty);
+    uint32_t obufTyId = type_mgr->GetTypeInstruction(reg_buf_ty);
+    // By the Vulkan spec, a pre-existing struct containing a RuntimeArray
+    // must be a block, and will therefore be decorated with Block. Therefore
+    // the undecorated type returned here will not be pre-existing and can
+    // safely be decorated. Since this type is now decorated, it is out of
+    // sync with the TypeManager and therefore the TypeManager must be
+    // invalidated after this pass.
+    assert(context()->get_def_use_mgr()->NumUses(obufTyId) == 0 &&
+           "used struct type returned");
     deco_mgr->AddDecoration(obufTyId, SpvDecorationBlock);
     deco_mgr->AddMemberDecoration(obufTyId, kDebugOutputSizeOffset,
                                   SpvDecorationOffset, 0);
@@ -329,23 +392,48 @@
                                desc_set_);
     deco_mgr->AddDecorationVal(output_buffer_id_, SpvDecorationBinding,
                                GetOutputBufferBinding());
-    // Look for storage buffer extension. If none, create one.
-    if (!get_feature_mgr()->HasExtension(
-            kSPV_KHR_storage_buffer_storage_class)) {
-      const std::string ext_name("SPV_KHR_storage_buffer_storage_class");
-      const auto num_chars = ext_name.size();
-      // Compute num words, accommodate the terminating null character.
-      const auto num_words = (num_chars + 1 + 3) / 4;
-      std::vector<uint32_t> ext_words(num_words, 0u);
-      std::memcpy(ext_words.data(), ext_name.data(), num_chars);
-      context()->AddExtension(std::unique_ptr<Instruction>(
-          new Instruction(context(), SpvOpExtension, 0u, 0u,
-                          {{SPV_OPERAND_TYPE_LITERAL_STRING, ext_words}})));
-    }
+    AddStorageBufferExt();
   }
   return output_buffer_id_;
 }
 
+uint32_t InstrumentPass::GetInputBufferId() {
+  if (input_buffer_id_ == 0) {
+    // If not created yet, create one
+    analysis::DecorationManager* deco_mgr = get_decoration_mgr();
+    analysis::TypeManager* type_mgr = context()->get_type_mgr();
+    analysis::Type* reg_uint_rarr_ty =
+        GetUintRuntimeArrayType(deco_mgr, type_mgr);
+    analysis::Struct buf_ty({reg_uint_rarr_ty});
+    analysis::Type* reg_buf_ty = type_mgr->GetRegisteredType(&buf_ty);
+    uint32_t ibufTyId = type_mgr->GetTypeInstruction(reg_buf_ty);
+    // By the Vulkan spec, a pre-existing struct containing a RuntimeArray
+    // must be a block, and will therefore be decorated with Block. Therefore
+    // the undecorated type returned here will not be pre-existing and can
+    // safely be decorated. Since this type is now decorated, it is out of
+    // sync with the TypeManager and therefore the TypeManager must be
+    // invalidated after this pass.
+    assert(context()->get_def_use_mgr()->NumUses(ibufTyId) == 0 &&
+           "used struct type returned");
+    deco_mgr->AddDecoration(ibufTyId, SpvDecorationBlock);
+    deco_mgr->AddMemberDecoration(ibufTyId, 0, SpvDecorationOffset, 0);
+    uint32_t ibufTyPtrId_ =
+        type_mgr->FindPointerToType(ibufTyId, SpvStorageClassStorageBuffer);
+    input_buffer_id_ = TakeNextId();
+    std::unique_ptr<Instruction> newVarOp(new Instruction(
+        context(), SpvOpVariable, ibufTyPtrId_, input_buffer_id_,
+        {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
+          {SpvStorageClassStorageBuffer}}}));
+    context()->AddGlobalValue(std::move(newVarOp));
+    deco_mgr->AddDecorationVal(input_buffer_id_, SpvDecorationDescriptorSet,
+                               desc_set_);
+    deco_mgr->AddDecorationVal(input_buffer_id_, SpvDecorationBinding,
+                               GetInputBufferBinding());
+    AddStorageBufferExt();
+  }
+  return input_buffer_id_;
+}
+
 uint32_t InstrumentPass::GetVec4FloatId() {
   if (v4float_id_ == 0) {
     analysis::TypeManager* type_mgr = context()->get_type_mgr();
@@ -445,7 +533,7 @@
     // Gen test if debug output buffer size will not be exceeded.
     uint32_t obuf_record_sz = kInstStageOutCnt + val_spec_param_cnt;
     uint32_t buf_id = GetOutputBufferId();
-    uint32_t buf_uint_ptr_id = GetOutputBufferUintPtrId();
+    uint32_t buf_uint_ptr_id = GetBufferUintPtrId();
     Instruction* obuf_curr_sz_ac_inst =
         builder.AddBinaryOp(buf_uint_ptr_id, SpvOpAccessChain, buf_id,
                             builder.GetUintConstantId(kDebugOutputSizeOffset));
@@ -513,6 +601,81 @@
   return output_func_id_;
 }
 
+uint32_t InstrumentPass::GetDirectReadFunctionId(uint32_t param_cnt) {
+  uint32_t func_id = param2input_func_id_[param_cnt];
+  if (func_id != 0) return func_id;
+  // Create input function for param_cnt
+  func_id = TakeNextId();
+  analysis::TypeManager* type_mgr = context()->get_type_mgr();
+  std::vector<const analysis::Type*> param_types;
+  for (uint32_t c = 0; c < param_cnt; ++c)
+    param_types.push_back(type_mgr->GetType(GetUintId()));
+  analysis::Function func_ty(type_mgr->GetType(GetUintId()), param_types);
+  analysis::Type* reg_func_ty = type_mgr->GetRegisteredType(&func_ty);
+  std::unique_ptr<Instruction> func_inst(new Instruction(
+      get_module()->context(), SpvOpFunction, GetUintId(), func_id,
+      {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
+        {SpvFunctionControlMaskNone}},
+       {spv_operand_type_t::SPV_OPERAND_TYPE_ID,
+        {type_mgr->GetTypeInstruction(reg_func_ty)}}}));
+  get_def_use_mgr()->AnalyzeInstDefUse(&*func_inst);
+  std::unique_ptr<Function> input_func =
+      MakeUnique<Function>(std::move(func_inst));
+  // Add parameters
+  std::vector<uint32_t> param_vec;
+  for (uint32_t c = 0; c < param_cnt; ++c) {
+    uint32_t pid = TakeNextId();
+    param_vec.push_back(pid);
+    std::unique_ptr<Instruction> param_inst(new Instruction(
+        get_module()->context(), SpvOpFunctionParameter, GetUintId(), pid, {}));
+    get_def_use_mgr()->AnalyzeInstDefUse(&*param_inst);
+    input_func->AddParameter(std::move(param_inst));
+  }
+  // Create block
+  uint32_t blk_id = TakeNextId();
+  std::unique_ptr<Instruction> blk_label(NewLabel(blk_id));
+  std::unique_ptr<BasicBlock> new_blk_ptr =
+      MakeUnique<BasicBlock>(std::move(blk_label));
+  InstructionBuilder builder(
+      context(), &*new_blk_ptr,
+      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+  // For each offset parameter, generate new offset with parameter, adding last
+  // loaded value if it exists, and load value from input buffer at new offset.
+  // Return last loaded value.
+  uint32_t buf_id = GetInputBufferId();
+  uint32_t buf_uint_ptr_id = GetBufferUintPtrId();
+  uint32_t last_value_id = 0;
+  for (uint32_t p = 0; p < param_cnt; ++p) {
+    uint32_t offset_id;
+    if (p == 0) {
+      offset_id = param_vec[0];
+    } else {
+      Instruction* offset_inst = builder.AddBinaryOp(
+          GetUintId(), SpvOpIAdd, last_value_id, param_vec[p]);
+      offset_id = offset_inst->result_id();
+    }
+    Instruction* ac_inst = builder.AddTernaryOp(
+        buf_uint_ptr_id, SpvOpAccessChain, buf_id,
+        builder.GetUintConstantId(kDebugInputDataOffset), offset_id);
+    Instruction* load_inst =
+        builder.AddUnaryOp(GetUintId(), SpvOpLoad, ac_inst->result_id());
+    last_value_id = load_inst->result_id();
+  }
+  (void)builder.AddInstruction(MakeUnique<Instruction>(
+      context(), SpvOpReturnValue, 0, 0,
+      std::initializer_list<Operand>{{SPV_OPERAND_TYPE_ID, {last_value_id}}}));
+  // Close block and function and add function to module
+  new_blk_ptr->SetParent(&*input_func);
+  input_func->AddBasicBlock(std::move(new_blk_ptr));
+  std::unique_ptr<Instruction> func_end_inst(
+      new Instruction(get_module()->context(), SpvOpFunctionEnd, 0, 0, {}));
+  get_def_use_mgr()->AnalyzeInstDefUse(&*func_end_inst);
+  input_func->SetFunctionEnd(std::move(func_end_inst));
+  context()->AddFunction(std::move(input_func));
+  param2input_func_id_[param_cnt] = func_id;
+  return func_id;
+}
+
 bool InstrumentPass::InstrumentFunction(Function* func, uint32_t stage_idx,
                                         InstProcessFunction& pfn) {
   bool modified = false;
@@ -523,21 +686,17 @@
     ++function_idx;
   }
   std::vector<std::unique_ptr<BasicBlock>> new_blks;
-  // Start count after function instruction
-  uint32_t instruction_idx = funcIdx2offset_[function_idx] + 1;
   // Using block iterators here because of block erasures and insertions.
   for (auto bi = func->begin(); bi != func->end(); ++bi) {
-    // Count block's label
-    ++instruction_idx;
-    for (auto ii = bi->begin(); ii != bi->end(); ++instruction_idx) {
-      // Bump instruction count if debug instructions
-      instruction_idx += static_cast<uint32_t>(ii->dbg_line_insts().size());
+    for (auto ii = bi->begin(); ii != bi->end();) {
       // Generate instrumentation if warranted
-      pfn(ii, bi, instruction_idx, stage_idx, &new_blks);
+      pfn(ii, bi, stage_idx, &new_blks);
       if (new_blks.size() == 0) {
         ++ii;
         continue;
       }
+      // Add new blocks to label id map
+      for (auto& blk : new_blks) id2block_[blk->id()] = &*blk;
       // If there are new blocks we know there will always be two or
       // more, so update succeeding phis with label of new last block.
       size_t newBlocksSize = new_blks.size();
@@ -567,6 +726,9 @@
                                                   uint32_t stage_idx) {
   bool modified = false;
   std::unordered_set<uint32_t> done;
+  // Don't process input and output functions
+  for (auto& ifn : param2input_func_id_) done.insert(ifn.second);
+  if (output_func_id_ != 0) done.insert(output_func_id_);
   // Process all functions from roots
   while (!roots->empty()) {
     const uint32_t fi = roots->front();
@@ -618,14 +780,17 @@
 
 void InstrumentPass::InitializeInstrument() {
   output_buffer_id_ = 0;
-  output_buffer_uint_ptr_id_ = 0;
+  buffer_uint_ptr_id_ = 0;
   output_func_id_ = 0;
   output_func_param_cnt_ = 0;
+  input_buffer_id_ = 0;
   v4float_id_ = 0;
   uint_id_ = 0;
   v4uint_id_ = 0;
   bool_id_ = 0;
   void_id_ = 0;
+  storage_buffer_ext_defined_ = false;
+  uint_rarr_ty_ = nullptr;
 
   // clear collections
   id2function_.clear();
@@ -639,70 +804,68 @@
     }
   }
 
-  // Calculate instruction offset of first function
-  uint32_t pre_func_size = 0;
+  // Remember original instruction offsets
+  uint32_t module_offset = 0;
   Module* module = get_module();
   for (auto& i : context()->capabilities()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
   for (auto& i : module->extensions()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
   for (auto& i : module->ext_inst_imports()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
-  ++pre_func_size;  // memory_model
+  ++module_offset;  // memory_model
   for (auto& i : module->entry_points()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
   for (auto& i : module->execution_modes()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
   for (auto& i : module->debugs1()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
   for (auto& i : module->debugs2()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
   for (auto& i : module->debugs3()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
   for (auto& i : module->annotations()) {
     (void)i;
-    ++pre_func_size;
+    ++module_offset;
   }
   for (auto& i : module->types_values()) {
-    pre_func_size += 1;
-    pre_func_size += static_cast<uint32_t>(i.dbg_line_insts().size());
+    module_offset += 1;
+    module_offset += static_cast<uint32_t>(i.dbg_line_insts().size());
   }
-  funcIdx2offset_[0] = pre_func_size;
 
-  // Set instruction offsets for all other functions.
-  uint32_t func_idx = 1;
-  auto prev_fn = get_module()->begin();
-  auto curr_fn = prev_fn;
-  for (++curr_fn; curr_fn != get_module()->end(); ++curr_fn) {
-    // Count function and end instructions
-    uint32_t func_size = 2;
-    for (auto& blk : *prev_fn) {
+  auto curr_fn = get_module()->begin();
+  for (; curr_fn != get_module()->end(); ++curr_fn) {
+    // Count function instruction
+    module_offset += 1;
+    curr_fn->ForEachParam(
+        [&module_offset](const Instruction*) { module_offset += 1; }, true);
+    for (auto& blk : *curr_fn) {
       // Count label
-      func_size += 1;
+      module_offset += 1;
       for (auto& inst : blk) {
-        func_size += 1;
-        func_size += static_cast<uint32_t>(inst.dbg_line_insts().size());
+        module_offset += static_cast<uint32_t>(inst.dbg_line_insts().size());
+        uid2offset_[inst.unique_id()] = module_offset;
+        module_offset += 1;
       }
     }
-    funcIdx2offset_[func_idx] = func_size;
-    ++prev_fn;
-    ++func_idx;
+    // Count function end instruction
+    module_offset += 1;
   }
 }
 
diff --git a/source/opt/instrument_pass.h b/source/opt/instrument_pass.h
index baf510b..c4b97d6 100644
--- a/source/opt/instrument_pass.h
+++ b/source/opt/instrument_pass.h
@@ -65,17 +65,16 @@
   using cbb_ptr = const BasicBlock*;
 
  public:
-  using InstProcessFunction = std::function<void(
-      BasicBlock::iterator, UptrVectorIterator<BasicBlock>, uint32_t, uint32_t,
-      std::vector<std::unique_ptr<BasicBlock>>*)>;
+  using InstProcessFunction =
+      std::function<void(BasicBlock::iterator, UptrVectorIterator<BasicBlock>,
+                         uint32_t, std::vector<std::unique_ptr<BasicBlock>>*)>;
 
   ~InstrumentPass() override = default;
 
   IRContext::Analysis GetPreservedAnalyses() override {
-    return IRContext::kAnalysisDefUse |
-           IRContext::kAnalysisInstrToBlockMapping |
-           IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
-           IRContext::kAnalysisNameMap | IRContext::kAnalysisBuiltinVarId;
+    return IRContext::kAnalysisDefUse | IRContext::kAnalysisDecorations |
+           IRContext::kAnalysisCombinators | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisBuiltinVarId | IRContext::kAnalysisConstants;
   }
 
  protected:
@@ -106,7 +105,7 @@
   // Move all code in |ref_block_itr| succeeding the instruction |ref_inst_itr|
   // to be instrumented into block |new_blk_ptr|.
   void MovePostludeCode(UptrVectorIterator<BasicBlock> ref_block_itr,
-                        std::unique_ptr<BasicBlock>* new_blk_ptr);
+                        BasicBlock* new_blk_ptr);
 
   // Generate instructions in |builder| which will atomically fetch and
   // increment the size of the debug output buffer stream of the current
@@ -194,6 +193,17 @@
                            const std::vector<uint32_t>& validation_ids,
                            InstructionBuilder* builder);
 
+  // Generate in |builder| instructions to read the unsigned integer from the
+  // input buffer specified by the offsets in |offset_ids|. Given offsets
+  // o0, o1, ... oN, and input buffer ibuf, return the id for the value:
+  //
+  // ibuf[...ibuf[ibuf[o0]+o1]...+oN]
+  //
+  // The binding and the format of the input buffer is determined by each
+  // specific validation, which is specified at the creation of the pass.
+  uint32_t GenDebugDirectRead(const std::vector<uint32_t>& offset_ids,
+                              InstructionBuilder* builder);
+
   // Generate code to cast |value_id| to unsigned, if needed. Return
   // an id to the unsigned equivalent.
   uint32_t GenUintCastCode(uint32_t value_id, InstructionBuilder* builder);
@@ -210,15 +220,28 @@
   // Return id for void type
   uint32_t GetVoidId();
 
-  // Return id for output buffer uint type
-  uint32_t GetOutputBufferUintPtrId();
+  // Return pointer to type for runtime array of uint
+  analysis::Type* GetUintRuntimeArrayType(analysis::DecorationManager* deco_mgr,
+                                          analysis::TypeManager* type_mgr);
+
+  // Return id for buffer uint type
+  uint32_t GetBufferUintPtrId();
 
   // Return binding for output buffer for current validation.
   uint32_t GetOutputBufferBinding();
 
+  // Return binding for input buffer for current validation.
+  uint32_t GetInputBufferBinding();
+
+  // Add storage buffer extension if needed
+  void AddStorageBufferExt();
+
   // Return id for debug output buffer
   uint32_t GetOutputBufferId();
 
+  // Return id for debug input buffer
+  uint32_t GetInputBufferId();
+
   // Return id for v4float type
   uint32_t GetVec4FloatId();
 
@@ -226,10 +249,14 @@
   uint32_t GetVec4UintId();
 
   // Return id for output function. Define if it doesn't exist with
-  // |val_spec_arg_cnt| validation-specific uint32 arguments.
+  // |val_spec_param_cnt| validation-specific uint32 parameters.
   uint32_t GetStreamWriteFunctionId(uint32_t stage_idx,
                                     uint32_t val_spec_param_cnt);
 
+  // Return id for input function taking |param_cnt| uint32 parameters. Define
+  // if it doesn't exist.
+  uint32_t GetDirectReadFunctionId(uint32_t param_cnt);
+
   // Apply instrumentation function |pfn| to every instruction in |func|.
   // If code is generated for an instruction, replace the instruction's
   // block with the new blocks that are generated. Continue processing at the
@@ -291,7 +318,7 @@
       std::unique_ptr<Instruction>* inst,
       std::unordered_map<uint32_t, uint32_t>* same_blk_post,
       std::unordered_map<uint32_t, Instruction*>* same_blk_pre,
-      std::unique_ptr<BasicBlock>* block_ptr);
+      BasicBlock* block_ptr);
 
   // Update phis in succeeding blocks to point to new last block
   void UpdateSucceedingPhis(
@@ -310,8 +337,8 @@
   // CFG. It has functionality not present in CFG. Consolidate.
   std::unordered_map<uint32_t, BasicBlock*> id2block_;
 
-  // Map from function's position index to the offset of its first instruction
-  std::unordered_map<uint32_t, uint32_t> funcIdx2offset_;
+  // Map from instruction's unique id to offset in original file.
+  std::unordered_map<uint32_t, uint32_t> uid2offset_;
 
   // result id for OpConstantFalse
   uint32_t validation_id_;
@@ -320,14 +347,20 @@
   uint32_t output_buffer_id_;
 
   // type id for output buffer element
-  uint32_t output_buffer_uint_ptr_id_;
+  uint32_t buffer_uint_ptr_id_;
 
   // id for debug output function
   uint32_t output_func_id_;
 
+  // ids for debug input functions
+  std::unordered_map<uint32_t, uint32_t> param2input_func_id_;
+
   // param count for output function
   uint32_t output_func_param_cnt_;
 
+  // id for input buffer variable
+  uint32_t input_buffer_id_;
+
   // id for v4float type
   uint32_t v4float_id_;
 
@@ -343,6 +376,12 @@
   // id for void type
   uint32_t void_id_;
 
+  // boolean to remember storage buffer extension
+  bool storage_buffer_ext_defined_;
+
+  // runtime array of uint type
+  analysis::Type* uint_rarr_ty_;
+
   // Pre-instrumentation same-block insts
   std::unordered_map<uint32_t, Instruction*> same_block_pre_;
 
diff --git a/source/opt/ir_builder.h b/source/opt/ir_builder.h
index 2f741d8..da74055 100644
--- a/source/opt/ir_builder.h
+++ b/source/opt/ir_builder.h
@@ -455,6 +455,16 @@
     return AddInstruction(std::move(new_inst));
   }
 
+  Instruction* AddStore(uint32_t ptr_id, uint32_t obj_id) {
+    std::vector<Operand> operands;
+    operands.push_back({SPV_OPERAND_TYPE_ID, {ptr_id}});
+    operands.push_back({SPV_OPERAND_TYPE_ID, {obj_id}});
+
+    std::unique_ptr<Instruction> new_inst(
+        new Instruction(GetContext(), SpvOpStore, 0, 0, operands));
+    return AddInstruction(std::move(new_inst));
+  }
+
   // Inserts the new instruction before the insertion point.
   Instruction* AddInstruction(std::unique_ptr<Instruction>&& insn) {
     Instruction* insn_ptr = &*insert_before_.InsertBefore(std::move(insn));
diff --git a/source/opt/ir_context.cpp b/source/opt/ir_context.cpp
index af9ac1a..61c5425 100644
--- a/source/opt/ir_context.cpp
+++ b/source/opt/ir_context.cpp
@@ -74,6 +74,12 @@
   if (set & kAnalysisIdToFuncMapping) {
     BuildIdToFuncMapping();
   }
+  if (set & kAnalysisConstants) {
+    BuildConstantManager();
+  }
+  if (set & kAnalysisTypes) {
+    BuildTypeManager();
+  }
 }
 
 void IRContext::InvalidateAnalysesExceptFor(
@@ -83,6 +89,11 @@
 }
 
 void IRContext::InvalidateAnalyses(IRContext::Analysis analyses_to_invalidate) {
+  // The ConstantManager contains Type pointers. If the TypeManager goes
+  // away, the ConstantManager has to go away.
+  if (analyses_to_invalidate & kAnalysisTypes) {
+    analyses_to_invalidate |= kAnalysisConstants;
+  }
   if (analyses_to_invalidate & kAnalysisDefUse) {
     def_use_mgr_.reset(nullptr);
   }
@@ -117,6 +128,12 @@
   if (analyses_to_invalidate & kAnalysisIdToFuncMapping) {
     id_to_func_.clear();
   }
+  if (analyses_to_invalidate & kAnalysisConstants) {
+    constant_mgr_.reset(nullptr);
+  }
+  if (analyses_to_invalidate & kAnalysisTypes) {
+    type_mgr_.reset(nullptr);
+  }
 
   valid_analyses_ = Analysis(valid_analyses_ & ~analyses_to_invalidate);
 }
@@ -328,6 +345,7 @@
                                SpvOpTypeImage,
                                SpvOpTypeSampler,
                                SpvOpTypeSampledImage,
+                               SpvOpTypeAccelerationStructureNV,
                                SpvOpTypeArray,
                                SpvOpTypeRuntimeArray,
                                SpvOpTypeStruct,
@@ -656,8 +674,8 @@
         reg_type = type_mgr->GetRegisteredType(&v4float_ty);
         break;
       }
-      case SpvBuiltInVertexId:
-      case SpvBuiltInInstanceId:
+      case SpvBuiltInVertexIndex:
+      case SpvBuiltInInstanceIndex:
       case SpvBuiltInPrimitiveId:
       case SpvBuiltInInvocationId:
       case SpvBuiltInGlobalInvocationId: {
diff --git a/source/opt/ir_context.h b/source/opt/ir_context.h
index 94bfd01..185b494 100644
--- a/source/opt/ir_context.h
+++ b/source/opt/ir_context.h
@@ -76,7 +76,9 @@
     kAnalysisStructuredCFG = 1 << 11,
     kAnalysisBuiltinVarId = 1 << 12,
     kAnalysisIdToFuncMapping = 1 << 13,
-    kAnalysisEnd = 1 << 14
+    kAnalysisConstants = 1 << 14,
+    kAnalysisTypes = 1 << 15,
+    kAnalysisEnd = 1 << 16
   };
 
   using ProcessFunction = std::function<bool(Function*)>;
@@ -292,8 +294,9 @@
   // created yet, it creates one.  NOTE: Once created, the constant manager
   // remains active and it is never re-built.
   analysis::ConstantManager* get_constant_mgr() {
-    if (!constant_mgr_)
-      constant_mgr_ = MakeUnique<analysis::ConstantManager>(this);
+    if (!AreAnalysesValid(kAnalysisConstants)) {
+      BuildConstantManager();
+    }
     return constant_mgr_.get();
   }
 
@@ -301,8 +304,9 @@
   // yet, it creates one. NOTE: Once created, the type manager remains active it
   // is never re-built.
   analysis::TypeManager* get_type_mgr() {
-    if (!type_mgr_)
-      type_mgr_ = MakeUnique<analysis::TypeManager>(consumer(), this);
+    if (!AreAnalysesValid(kAnalysisTypes)) {
+      BuildTypeManager();
+    }
     return type_mgr_.get();
   }
 
@@ -584,6 +588,20 @@
     valid_analyses_ = valid_analyses_ | kAnalysisStructuredCFG;
   }
 
+  // Builds the constant manager from scratch, even if it was already
+  // valid.
+  void BuildConstantManager() {
+    constant_mgr_ = MakeUnique<analysis::ConstantManager>(this);
+    valid_analyses_ = valid_analyses_ | kAnalysisConstants;
+  }
+
+  // Builds the type manager from scratch, even if it was already
+  // valid.
+  void BuildTypeManager() {
+    type_mgr_ = MakeUnique<analysis::TypeManager>(consumer(), this);
+    valid_analyses_ = valid_analyses_ | kAnalysisTypes;
+  }
+
   // Removes all computed dominator and post-dominator trees. This will force
   // the context to rebuild the trees on demand.
   void ResetDominatorAnalysis() {
diff --git a/source/opt/ir_loader.cpp b/source/opt/ir_loader.cpp
index 46e2bee..edd4832 100644
--- a/source/opt/ir_loader.cpp
+++ b/source/opt/ir_loader.cpp
@@ -116,8 +116,10 @@
                  opcode == SpvOpUndef) {
         module_->AddGlobalValue(std::move(spv_inst));
       } else {
-        SPIRV_UNIMPLEMENTED(consumer_,
-                            "unhandled inst type outside function definition");
+        Errorf(consumer_, src, loc,
+               "Unhandled inst type (opcode: %d) found outside function definition.",
+               opcode);
+        return false;
       }
     } else {
       if (block_ == nullptr) {  // Inside function but outside blocks
diff --git a/source/opt/legalize_vector_shuffle_pass.cpp b/source/opt/legalize_vector_shuffle_pass.cpp
new file mode 100644
index 0000000..b5d5d59
--- /dev/null
+++ b/source/opt/legalize_vector_shuffle_pass.cpp
@@ -0,0 +1,39 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/legalize_vector_shuffle_pass.h"
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status LegalizeVectorShufflePass::Process() {
+  bool changed = false;
+  context()->module()->ForEachInst([&changed](Instruction* inst) {
+    if (inst->opcode() != SpvOpVectorShuffle) return;
+
+    for (uint32_t idx = 2; idx < inst->NumInOperands(); ++idx) {
+      auto literal = inst->GetSingleWordInOperand(idx);
+      if (literal != 0xFFFFFFFF) continue;
+      changed = true;
+      inst->SetInOperand(idx, {0});
+    }
+  });
+
+  return changed ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/legalize_vector_shuffle_pass.h b/source/opt/legalize_vector_shuffle_pass.h
new file mode 100644
index 0000000..ca6e1df
--- /dev/null
+++ b/source/opt/legalize_vector_shuffle_pass.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_LEGALIZE_VECTOR_SHUFFLE_PASS_H_
+#define SOURCE_OPT_LEGALIZE_VECTOR_SHUFFLE_PASS_H_
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// Converts any usages of 0xFFFFFFFF for the literals in OpVectorShuffle to a
+// literal 0. This is needed because using OxFFFFFFFF is forbidden by the WebGPU
+// spec. 0xFFFFFFFF in the main spec indicates that the result for this
+// component has no source, thus is undefined. Since this is undefined
+// behaviour we are free to use 0.
+class LegalizeVectorShufflePass : public Pass {
+ public:
+  const char* name() const override { return "legalize-vector-shuffle"; }
+  Status Process() override;
+
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
+           IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
+           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisScalarEvolution |
+           IRContext::kAnalysisRegisterPressure |
+           IRContext::kAnalysisValueNumberTable |
+           IRContext::kAnalysisStructuredCFG |
+           IRContext::kAnalysisBuiltinVarId |
+           IRContext::kAnalysisIdToFuncMapping | IRContext::kAnalysisTypes |
+           IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants;
+  }
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_LEGALIZE_VECTOR_SHUFFLE_PASS_H_
diff --git a/source/opt/licm_pass.cpp b/source/opt/licm_pass.cpp
index c553221..82851fd 100644
--- a/source/opt/licm_pass.cpp
+++ b/source/opt/licm_pass.cpp
@@ -124,7 +124,14 @@
   if (!pre_header_bb) {
     return false;
   }
-  inst->InsertBefore(std::move(&(*pre_header_bb->tail())));
+  Instruction* insertion_point = &*pre_header_bb->tail();
+  Instruction* previous_node = insertion_point->PreviousNode();
+  if (previous_node && (previous_node->opcode() == SpvOpLoopMerge ||
+                        previous_node->opcode() == SpvOpSelectionMerge)) {
+    insertion_point = previous_node;
+  }
+
+  inst->InsertBefore(insertion_point);
   context()->set_instr_block(inst, pre_header_bb);
   return true;
 }
diff --git a/source/opt/licm_pass.h b/source/opt/licm_pass.h
index a94ae11..597fe92 100644
--- a/source/opt/licm_pass.h
+++ b/source/opt/licm_pass.h
@@ -61,7 +61,7 @@
   // Returns true if |bb| is immediately contained in |loop|
   bool IsImmediatelyContainedInLoop(Loop* loop, Function* f, BasicBlock* bb);
 
-  // Move the instruction to the given BasicBlock
+  // Move the instruction to the preheader of |loop|.
   // This method will update the instruction to block mapping for the context
   bool HoistInstruction(Loop* loop, Instruction* inst);
 };
diff --git a/source/opt/local_access_chain_convert_pass.cpp b/source/opt/local_access_chain_convert_pass.cpp
index 5b976a1..2258e61 100644
--- a/source/opt/local_access_chain_convert_pass.cpp
+++ b/source/opt/local_access_chain_convert_pass.cpp
@@ -264,6 +264,12 @@
 }
 
 bool LocalAccessChainConvertPass::AllExtensionsSupported() const {
+  // This capability can now exist without the extension, so we have to check
+  // for the capability.  This pass is only looking at function scope symbols,
+  // so we do not care if there are variable pointers on storage buffers.
+  if (context()->get_feature_mgr()->HasCapability(
+          SpvCapabilityVariablePointers))
+    return false;
   // If any extension not in whitelist, return false
   for (auto& ei : get_module()->extensions()) {
     const char* extName =
diff --git a/source/opt/local_access_chain_convert_pass.h b/source/opt/local_access_chain_convert_pass.h
index 9d06890..1c7e3d5 100644
--- a/source/opt/local_access_chain_convert_pass.h
+++ b/source/opt/local_access_chain_convert_pass.h
@@ -44,7 +44,8 @@
   Status Process() override;
 
   IRContext::Analysis GetPreservedAnalyses() override {
-    return IRContext::kAnalysisDefUse;
+    return IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 
   using ProcessFunction = std::function<bool(Function*)>;
diff --git a/source/opt/local_redundancy_elimination.h b/source/opt/local_redundancy_elimination.h
index 9f55c8b..770457a 100644
--- a/source/opt/local_redundancy_elimination.h
+++ b/source/opt/local_redundancy_elimination.h
@@ -41,7 +41,8 @@
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
            IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
-           IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 
  protected:
diff --git a/source/opt/local_single_block_elim_pass.cpp b/source/opt/local_single_block_elim_pass.cpp
index 9330ab7..3e2eb7e 100644
--- a/source/opt/local_single_block_elim_pass.cpp
+++ b/source/opt/local_single_block_elim_pass.cpp
@@ -187,6 +187,7 @@
   // Assumes relaxed logical addressing only (see instruction.h).
   if (context()->get_feature_mgr()->HasCapability(SpvCapabilityAddresses))
     return Status::SuccessWithoutChange;
+
   // Do not process if module contains OpGroupDecorate. Additional
   // support required in KillNamesAndDecorates().
   // TODO(greg-lunarg): Add support for OpGroupDecorate
@@ -233,8 +234,7 @@
       "SPV_NV_geometry_shader_passthrough",
       "SPV_AMD_texture_gather_bias_lod",
       "SPV_KHR_storage_buffer_storage_class",
-      // SPV_KHR_variable_pointers
-      //   Currently do not support extended pointer expressions
+      "SPV_KHR_variable_pointers",
       "SPV_AMD_gpu_shader_int16",
       "SPV_KHR_post_depth_coverage",
       "SPV_KHR_shader_atomic_counter_ops",
diff --git a/source/opt/local_single_block_elim_pass.h b/source/opt/local_single_block_elim_pass.h
index 3dead98..0fe7732 100644
--- a/source/opt/local_single_block_elim_pass.h
+++ b/source/opt/local_single_block_elim_pass.h
@@ -42,7 +42,9 @@
   Status Process() override;
 
   IRContext::Analysis GetPreservedAnalyses() override {
-    return IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping;
+    return IRContext::kAnalysisDefUse |
+           IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/local_single_store_elim_pass.cpp b/source/opt/local_single_store_elim_pass.cpp
index 6c09dec..f47777e 100644
--- a/source/opt/local_single_store_elim_pass.cpp
+++ b/source/opt/local_single_store_elim_pass.cpp
@@ -98,8 +98,7 @@
       "SPV_NV_geometry_shader_passthrough",
       "SPV_AMD_texture_gather_bias_lod",
       "SPV_KHR_storage_buffer_storage_class",
-      // SPV_KHR_variable_pointers
-      //   Currently do not support extended pointer expressions
+      "SPV_KHR_variable_pointers",
       "SPV_AMD_gpu_shader_int16",
       "SPV_KHR_post_depth_coverage",
       "SPV_KHR_shader_atomic_counter_ops",
diff --git a/source/opt/local_single_store_elim_pass.h b/source/opt/local_single_store_elim_pass.h
index d3d64b8..4cf8bbb 100644
--- a/source/opt/local_single_store_elim_pass.h
+++ b/source/opt/local_single_store_elim_pass.h
@@ -45,7 +45,9 @@
   Status Process() override;
 
   IRContext::Analysis GetPreservedAnalyses() override {
-    return IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping;
+    return IRContext::kAnalysisDefUse |
+           IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/local_ssa_elim_pass.cpp b/source/opt/local_ssa_elim_pass.cpp
index 8209aa4..df327a6 100644
--- a/source/opt/local_ssa_elim_pass.cpp
+++ b/source/opt/local_ssa_elim_pass.cpp
@@ -83,8 +83,7 @@
       "SPV_NV_geometry_shader_passthrough",
       "SPV_AMD_texture_gather_bias_lod",
       "SPV_KHR_storage_buffer_storage_class",
-      // SPV_KHR_variable_pointers
-      //   Currently do not support extended pointer expressions
+      "SPV_KHR_variable_pointers",
       "SPV_AMD_gpu_shader_int16",
       "SPV_KHR_post_depth_coverage",
       "SPV_KHR_shader_atomic_counter_ops",
diff --git a/source/opt/local_ssa_elim_pass.h b/source/opt/local_ssa_elim_pass.h
index 63d3c33..de80d5a 100644
--- a/source/opt/local_ssa_elim_pass.h
+++ b/source/opt/local_ssa_elim_pass.h
@@ -48,7 +48,9 @@
   Status Process() override;
 
   IRContext::Analysis GetPreservedAnalyses() override {
-    return IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping;
+    return IRContext::kAnalysisDefUse |
+           IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/loop_descriptor.cpp b/source/opt/loop_descriptor.cpp
index 5aff34c..11f7e9c 100644
--- a/source/opt/loop_descriptor.cpp
+++ b/source/opt/loop_descriptor.cpp
@@ -937,22 +937,23 @@
 
   for (Loop* loop : loops_to_remove_) {
     loops_.erase(std::find(loops_.begin(), loops_.end(), loop));
+    delete loop;
   }
 
   for (auto& pair : loops_to_add_) {
     Loop* parent = pair.first;
-    Loop* loop = pair.second;
+    std::unique_ptr<Loop> loop = std::move(pair.second);
 
     if (parent) {
       loop->SetParent(nullptr);
-      parent->AddNestedLoop(loop);
+      parent->AddNestedLoop(loop.get());
 
       for (uint32_t block_id : loop->GetBlocks()) {
         parent->AddBasicBlock(block_id);
       }
     }
 
-    loops_.emplace_back(loop);
+    loops_.emplace_back(loop.release());
   }
 
   loops_to_add_.clear();
diff --git a/source/opt/loop_descriptor.h b/source/opt/loop_descriptor.h
index 38f017b..6e2b828 100644
--- a/source/opt/loop_descriptor.h
+++ b/source/opt/loop_descriptor.h
@@ -499,8 +499,8 @@
 
   // Mark the loop |loop_to_add| as needing to be added when the user calls
   // PostModificationCleanup. |parent| may be null.
-  inline void AddLoop(Loop* loop_to_add, Loop* parent) {
-    loops_to_add_.emplace_back(std::make_pair(parent, loop_to_add));
+  inline void AddLoop(std::unique_ptr<Loop>&& loop_to_add, Loop* parent) {
+    loops_to_add_.emplace_back(std::make_pair(parent, std::move(loop_to_add)));
   }
 
   // Checks all loops in |this| and will create pre-headers for all loops
@@ -537,7 +537,9 @@
   // TODO(dneto): This should be a vector of unique_ptr.  But VisualStudio 2013
   // is unable to compile it.
   using LoopContainerType = std::vector<Loop*>;
-  using LoopsToAddContainerType = std::vector<std::pair<Loop*, Loop*>>;
+
+  using LoopsToAddContainerType =
+      std::vector<std::pair<Loop*, std::unique_ptr<Loop>>>;
 
   // Creates loop descriptors for the function |f|.
   void PopulateList(IRContext* context, const Function* f);
diff --git a/source/opt/loop_unroller.cpp b/source/opt/loop_unroller.cpp
index 0d49d88..10fac04 100644
--- a/source/opt/loop_unroller.cpp
+++ b/source/opt/loop_unroller.cpp
@@ -394,12 +394,12 @@
   // This is a naked new due to the VS2013 requirement of not having unique
   // pointers in vectors, as it will be inserted into a vector with
   // loop_descriptor.AddLoop.
-  Loop* new_loop = new Loop(*loop);
+  std::unique_ptr<Loop> new_loop = MakeUnique<Loop>(*loop);
 
   // Clear the basic blocks of the new loop.
   new_loop->ClearBlocks();
 
-  DuplicateLoop(loop, new_loop);
+  DuplicateLoop(loop, new_loop.get());
 
   // Add the blocks to the function.
   AddBlocksToFunction(loop->GetMergeBlock());
@@ -414,10 +414,10 @@
   loop_induction_variable_ = state_.new_phi;
   // Unroll the new loop by the factor with the usual -1 to account for the
   // existing block iteration.
-  Unroll(new_loop, factor);
+  Unroll(new_loop.get(), factor);
 
-  LinkLastPhisToStart(new_loop);
-  AddBlocksToLoop(new_loop);
+  LinkLastPhisToStart(new_loop.get());
+  AddBlocksToLoop(new_loop.get());
 
   // Add the new merge block to the back of the list of blocks to be added. It
   // needs to be the last block added to maintain dominator order in the binary.
@@ -507,7 +507,7 @@
 
   LoopDescriptor& loop_descriptor = *context_->GetLoopDescriptor(&function_);
 
-  loop_descriptor.AddLoop(new_loop, loop->GetParent());
+  loop_descriptor.AddLoop(std::move(new_loop), loop->GetParent());
 
   RemoveDeadInstructions();
 }
diff --git a/source/opt/loop_unroller.h b/source/opt/loop_unroller.h
index 41a91a2..71e7cca 100644
--- a/source/opt/loop_unroller.h
+++ b/source/opt/loop_unroller.h
@@ -34,7 +34,8 @@
     return IRContext::kAnalysisDefUse |
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
-           IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/loop_unswitch_pass.cpp b/source/opt/loop_unswitch_pass.cpp
index 7e374d9..502fc6b 100644
--- a/source/opt/loop_unswitch_pass.cpp
+++ b/source/opt/loop_unswitch_pass.cpp
@@ -39,8 +39,6 @@
 namespace {
 
 static const uint32_t kTypePointerStorageClassInIdx = 0;
-static const uint32_t kBranchCondTrueLabIdInIdx = 1;
-static const uint32_t kBranchCondFalseLabIdInIdx = 2;
 
 }  // anonymous namespace
 
@@ -74,9 +72,13 @@
 
     for (uint32_t bb_id : loop_->GetBlocks()) {
       BasicBlock* bb = cfg.block(bb_id);
+      if (loop_->GetLatchBlock() == bb) {
+        continue;
+      }
+
       if (bb->terminator()->IsBranch() &&
           bb->terminator()->opcode() != SpvOpBranch) {
-        if (IsConditionLoopInvariant(bb->terminator())) {
+        if (IsConditionNonConstantLoopInvariant(bb->terminator())) {
           switch_block_ = bb;
           break;
         }
@@ -110,6 +112,35 @@
     return bb;
   }
 
+  Instruction* GetValueForDefaultPathForSwitch(Instruction* switch_inst) {
+    assert(switch_inst->opcode() == SpvOpSwitch &&
+           "The given instructoin must be an OpSwitch.");
+
+    // Find a value that can be used to select the default path.
+    // If none are possible, then it will just use 0.  The value does not matter
+    // because this path will never be taken becaues the new switch outside of
+    // the loop cannot select this path either.
+    std::vector<uint32_t> existing_values;
+    for (uint32_t i = 2; i < switch_inst->NumInOperands(); i += 2) {
+      existing_values.push_back(switch_inst->GetSingleWordInOperand(i));
+    }
+    std::sort(existing_values.begin(), existing_values.end());
+    uint32_t value_for_default_path = 0;
+    if (existing_values.size() < std::numeric_limits<uint32_t>::max()) {
+      for (value_for_default_path = 0;
+           value_for_default_path < existing_values.size();
+           value_for_default_path++) {
+        if (existing_values[value_for_default_path] != value_for_default_path) {
+          break;
+        }
+      }
+    }
+    InstructionBuilder builder(
+        context_, static_cast<Instruction*>(nullptr),
+        IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+    return builder.GetUintConstant(value_for_default_path);
+  }
+
   // Unswitches |loop_|.
   void PerformUnswitch() {
     assert(CanUnswitchLoop() &&
@@ -154,6 +185,7 @@
       if_merge_block->ForEachPhiInst(
           [loop_merge_block, &builder, this](Instruction* phi) {
             Instruction* cloned = phi->Clone(context_);
+            cloned->SetResultId(TakeNextId());
             builder.AddInstruction(std::unique_ptr<Instruction>(cloned));
             phi->SetInOperand(0, {cloned->result_id()});
             phi->SetInOperand(1, {loop_merge_block->id()});
@@ -177,7 +209,6 @@
         ploop->AddBasicBlock(loop_merge_block);
         loop_desc_.SetBasicBlockToLoop(loop_merge_block->id(), ploop);
       }
-
       // Update the dominator tree.
       DominatorTreeNode* loop_merge_dtn =
           dom_tree->GetOrInsertNode(loop_merge_block);
@@ -195,7 +226,7 @@
 
     ////////////////////////////////////////////////////////////////////////////
     // Step 2: Build a new preheader for |loop_|, use the old one
-    //         for the constant branch.
+    //         for the invariant branch.
     ////////////////////////////////////////////////////////////////////////////
 
     BasicBlock* if_block = loop_->GetPreHeaderBlock();
@@ -273,7 +304,6 @@
     std::vector<std::pair<Instruction*, BasicBlock*>> constant_branch;
     // Special case for the original loop
     Instruction* original_loop_constant_value;
-    BasicBlock* original_loop_target;
     if (iv_opcode == SpvOpBranchConditional) {
       constant_branch.emplace_back(
           cst_mgr->GetDefiningInstruction(cst_mgr->GetConstant(cond_type, {0})),
@@ -283,7 +313,9 @@
     } else {
       // We are looking to take the default branch, so we can't provide a
       // specific value.
-      original_loop_constant_value = nullptr;
+      original_loop_constant_value =
+          GetValueForDefaultPathForSwitch(iv_condition);
+
       for (uint32_t i = 2; i < iv_condition->NumInOperands(); i += 2) {
         constant_branch.emplace_back(
             cst_mgr->GetDefiningInstruction(cst_mgr->GetConstant(
@@ -323,24 +355,7 @@
       ////////////////////////////////////
 
       {
-        std::unordered_set<uint32_t> dead_blocks;
-        std::unordered_set<uint32_t> unreachable_merges;
-        SimplifyLoop(
-            make_range(
-                UptrVectorIterator<BasicBlock>(&clone_result.cloned_bb_,
-                                               clone_result.cloned_bb_.begin()),
-                UptrVectorIterator<BasicBlock>(&clone_result.cloned_bb_,
-                                               clone_result.cloned_bb_.end())),
-            cloned_loop, condition, specialisation_value, &dead_blocks);
-
-        // We tagged dead blocks, create the loop before we invalidate any basic
-        // block.
-        cloned_loop =
-            CleanLoopNest(cloned_loop, dead_blocks, &unreachable_merges);
-        CleanUpCFG(
-            UptrVectorIterator<BasicBlock>(&clone_result.cloned_bb_,
-                                           clone_result.cloned_bb_.begin()),
-            dead_blocks, unreachable_merges);
+        SpecializeLoop(cloned_loop, condition, specialisation_value);
 
         ///////////////////////////////////////////////////////////
         // Step 5: Connect convergent edges to the landing pads. //
@@ -349,27 +364,25 @@
         for (uint32_t merge_bb_id : if_merging_blocks) {
           BasicBlock* merge = context_->cfg()->block(merge_bb_id);
           // We are in LCSSA so we only care about phi instructions.
-          merge->ForEachPhiInst([is_from_original_loop, &dead_blocks,
-                                 &clone_result](Instruction* phi) {
-            uint32_t num_in_operands = phi->NumInOperands();
-            for (uint32_t i = 0; i < num_in_operands; i += 2) {
-              uint32_t pred = phi->GetSingleWordInOperand(i + 1);
-              if (is_from_original_loop(pred)) {
-                pred = clone_result.value_map_.at(pred);
-                if (!dead_blocks.count(pred)) {
-                  uint32_t incoming_value_id = phi->GetSingleWordInOperand(i);
-                  // Not all the incoming value are coming from the loop.
-                  ValueMapTy::iterator new_value =
-                      clone_result.value_map_.find(incoming_value_id);
-                  if (new_value != clone_result.value_map_.end()) {
-                    incoming_value_id = new_value->second;
+          merge->ForEachPhiInst(
+              [is_from_original_loop, &clone_result](Instruction* phi) {
+                uint32_t num_in_operands = phi->NumInOperands();
+                for (uint32_t i = 0; i < num_in_operands; i += 2) {
+                  uint32_t pred = phi->GetSingleWordInOperand(i + 1);
+                  if (is_from_original_loop(pred)) {
+                    pred = clone_result.value_map_.at(pred);
+                    uint32_t incoming_value_id = phi->GetSingleWordInOperand(i);
+                    // Not all the incoming values are coming from the loop.
+                    ValueMapTy::iterator new_value =
+                        clone_result.value_map_.find(incoming_value_id);
+                    if (new_value != clone_result.value_map_.end()) {
+                      incoming_value_id = new_value->second;
+                    }
+                    phi->AddOperand({SPV_OPERAND_TYPE_ID, {incoming_value_id}});
+                    phi->AddOperand({SPV_OPERAND_TYPE_ID, {pred}});
                   }
-                  phi->AddOperand({SPV_OPERAND_TYPE_ID, {incoming_value_id}});
-                  phi->AddOperand({SPV_OPERAND_TYPE_ID, {pred}});
                 }
-              }
-            }
-          });
+              });
         }
       }
       function_->AddBasicBlocks(clone_result.cloned_bb_.begin(),
@@ -377,38 +390,9 @@
                                 ++FindBasicBlockPosition(if_block));
     }
 
-    // Same as above but specialize the existing loop
-    {
-      std::unordered_set<uint32_t> dead_blocks;
-      std::unordered_set<uint32_t> unreachable_merges;
-      SimplifyLoop(make_range(function_->begin(), function_->end()), loop_,
-                   condition, original_loop_constant_value, &dead_blocks);
-
-      for (uint32_t merge_bb_id : if_merging_blocks) {
-        BasicBlock* merge = context_->cfg()->block(merge_bb_id);
-        // LCSSA, so we only care about phi instructions.
-        // If we the phi is reduced to a single incoming branch, do not
-        // propagate it to preserve LCSSA.
-        PatchPhis(merge, dead_blocks, true);
-      }
-      if (if_merge_block) {
-        bool has_live_pred = false;
-        for (uint32_t pid : cfg.preds(if_merge_block->id())) {
-          if (!dead_blocks.count(pid)) {
-            has_live_pred = true;
-            break;
-          }
-        }
-        if (!has_live_pred) unreachable_merges.insert(if_merge_block->id());
-      }
-      original_loop_target = loop_->GetPreHeaderBlock();
-      // We tagged dead blocks, prune the loop descriptor from any dead loops.
-      // After this call, |loop_| can be nullptr (i.e. the unswitch killed this
-      // loop).
-      loop_ = CleanLoopNest(loop_, dead_blocks, &unreachable_merges);
-
-      CleanUpCFG(function_->begin(), dead_blocks, unreachable_merges);
-    }
+    // Specialize the existing loop.
+    SpecializeLoop(loop_, condition, original_loop_constant_value);
+    BasicBlock* original_loop_target = loop_->GetPreHeaderBlock();
 
     /////////////////////////////////////
     // Finally: connect the new loops. //
@@ -441,9 +425,6 @@
         IRContext::Analysis::kAnalysisLoopAnalysis);
   }
 
-  // Returns true if the unswitch killed the original |loop_|.
-  bool WasLoopKilled() const { return loop_ == nullptr; }
-
  private:
   using ValueMapTy = std::unordered_map<uint32_t, uint32_t>;
   using BlockMapTy = std::unordered_map<uint32_t, BasicBlock*>;
@@ -465,96 +446,6 @@
     return context_->TakeNextId();
   }
 
-  // Patches |bb|'s phi instruction by removing incoming value from unexisting
-  // or tagged as dead branches.
-  void PatchPhis(BasicBlock* bb,
-                 const std::unordered_set<uint32_t>& dead_blocks,
-                 bool preserve_phi) {
-    CFG& cfg = *context_->cfg();
-
-    std::vector<Instruction*> phi_to_kill;
-    const std::vector<uint32_t>& bb_preds = cfg.preds(bb->id());
-    auto is_branch_dead = [&bb_preds, &dead_blocks](uint32_t id) {
-      return dead_blocks.count(id) ||
-             std::find(bb_preds.begin(), bb_preds.end(), id) == bb_preds.end();
-    };
-    bb->ForEachPhiInst(
-        [&phi_to_kill, &is_branch_dead, preserve_phi, this](Instruction* insn) {
-          uint32_t i = 0;
-          while (i < insn->NumInOperands()) {
-            uint32_t incoming_id = insn->GetSingleWordInOperand(i + 1);
-            if (is_branch_dead(incoming_id)) {
-              // Remove the incoming block id operand.
-              insn->RemoveInOperand(i + 1);
-              // Remove the definition id operand.
-              insn->RemoveInOperand(i);
-              continue;
-            }
-            i += 2;
-          }
-          // If there is only 1 remaining edge, propagate the value and
-          // kill the instruction.
-          if (insn->NumInOperands() == 2 && !preserve_phi) {
-            phi_to_kill.push_back(insn);
-            context_->ReplaceAllUsesWith(insn->result_id(),
-                                         insn->GetSingleWordInOperand(0));
-          }
-        });
-    for (Instruction* insn : phi_to_kill) {
-      context_->KillInst(insn);
-    }
-  }
-
-  // Removes any block that is tagged as dead, if the block is in
-  // |unreachable_merges| then all block's instructions are replaced by a
-  // OpUnreachable.
-  void CleanUpCFG(UptrVectorIterator<BasicBlock> bb_it,
-                  const std::unordered_set<uint32_t>& dead_blocks,
-                  const std::unordered_set<uint32_t>& unreachable_merges) {
-    CFG& cfg = *context_->cfg();
-
-    while (bb_it != bb_it.End()) {
-      BasicBlock& bb = *bb_it;
-
-      if (unreachable_merges.count(bb.id())) {
-        if (bb.begin() != bb.tail() ||
-            bb.terminator()->opcode() != SpvOpUnreachable) {
-          // Make unreachable, but leave the label.
-          bb.KillAllInsts(false);
-          InstructionBuilder(context_, &bb).AddUnreachable();
-          cfg.RemoveNonExistingEdges(bb.id());
-        }
-        ++bb_it;
-      } else if (dead_blocks.count(bb.id())) {
-        cfg.ForgetBlock(&bb);
-        // Kill this block.
-        bb.KillAllInsts(true);
-        bb_it = bb_it.Erase();
-      } else {
-        cfg.RemoveNonExistingEdges(bb.id());
-        ++bb_it;
-      }
-    }
-  }
-
-  // Return true if |c_inst| is a Boolean constant and set |cond_val| with the
-  // value that |c_inst|
-  bool GetConstCondition(const Instruction* c_inst, bool* cond_val) {
-    bool cond_is_const;
-    switch (c_inst->opcode()) {
-      case SpvOpConstantFalse: {
-        *cond_val = false;
-        cond_is_const = true;
-      } break;
-      case SpvOpConstantTrue: {
-        *cond_val = true;
-        cond_is_const = true;
-      } break;
-      default: { cond_is_const = false; } break;
-    }
-    return cond_is_const;
-  }
-
   // Simplifies |loop| assuming the instruction |to_version_insn| takes the
   // value |cst_value|. |block_range| is an iterator range returning the loop
   // basic blocks in a structured order (dominator first).
@@ -564,13 +455,9 @@
   //
   // Requirements:
   //   - |loop| must be in the LCSSA form;
-  //   - |cst_value| must be constant or null (to represent the default target
-  //   of an OpSwitch).
-  void SimplifyLoop(IteratorRange<UptrVectorIterator<BasicBlock>> block_range,
-                    Loop* loop, Instruction* to_version_insn,
-                    Instruction* cst_value,
-                    std::unordered_set<uint32_t>* dead_blocks) {
-    CFG& cfg = *context_->cfg();
+  //   - |cst_value| must be constant.
+  void SpecializeLoop(Loop* loop, Instruction* to_version_insn,
+                      Instruction* cst_value) {
     analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr();
 
     std::function<bool(uint32_t)> ignore_node;
@@ -595,192 +482,15 @@
     for (auto use : use_list) {
       Instruction* inst = use.first;
       uint32_t operand_index = use.second;
-      BasicBlock* bb = context_->get_instr_block(inst);
 
-      // If it is not a branch, simply inject the value.
-      if (!inst->IsBranch()) {
-        // To also handle switch, cst_value can be nullptr: this case
-        // means that we are looking to branch to the default target of
-        // the switch. We don't actually know its value so we don't touch
-        // it if it not a switch.
-        if (cst_value) {
-          inst->SetOperand(operand_index, {cst_value->result_id()});
-          def_use_mgr->AnalyzeInstUse(inst);
-        }
-      }
-
-      // The user is a branch, kill dead branches.
-      uint32_t live_target = 0;
-      std::unordered_set<uint32_t> dead_branches;
-      switch (inst->opcode()) {
-        case SpvOpBranchConditional: {
-          assert(cst_value && "No constant value to specialize !");
-          bool branch_cond = false;
-          if (GetConstCondition(cst_value, &branch_cond)) {
-            uint32_t true_label =
-                inst->GetSingleWordInOperand(kBranchCondTrueLabIdInIdx);
-            uint32_t false_label =
-                inst->GetSingleWordInOperand(kBranchCondFalseLabIdInIdx);
-            live_target = branch_cond ? true_label : false_label;
-            uint32_t dead_target = !branch_cond ? true_label : false_label;
-            cfg.RemoveEdge(bb->id(), dead_target);
-          }
-          break;
-        }
-        case SpvOpSwitch: {
-          live_target = inst->GetSingleWordInOperand(1);
-          if (cst_value) {
-            if (!cst_value->IsConstant()) break;
-            const Operand& cst = cst_value->GetInOperand(0);
-            for (uint32_t i = 2; i < inst->NumInOperands(); i += 2) {
-              const Operand& literal = inst->GetInOperand(i);
-              if (literal == cst) {
-                live_target = inst->GetSingleWordInOperand(i + 1);
-                break;
-              }
-            }
-          }
-          for (uint32_t i = 1; i < inst->NumInOperands(); i += 2) {
-            uint32_t id = inst->GetSingleWordInOperand(i);
-            if (id != live_target) {
-              cfg.RemoveEdge(bb->id(), id);
-            }
-          }
-        }
-        default:
-          break;
-      }
-      if (live_target != 0) {
-        // Check for the presence of the merge block.
-        if (Instruction* merge = bb->GetMergeInst()) context_->KillInst(merge);
-        context_->KillInst(&*bb->tail());
-        InstructionBuilder builder(context_, bb,
-                                   IRContext::kAnalysisDefUse |
-                                       IRContext::kAnalysisInstrToBlockMapping);
-        builder.AddBranch(live_target);
-      }
+      // To also handle switch, cst_value can be nullptr: this case
+      // means that we are looking to branch to the default target of
+      // the switch. We don't actually know its value so we don't touch
+      // it if it not a switch.
+      assert(cst_value && "We do not have a value to use.");
+      inst->SetOperand(operand_index, {cst_value->result_id()});
+      def_use_mgr->AnalyzeInstUse(inst);
     }
-
-    // Go through the loop basic block and tag all blocks that are obviously
-    // dead.
-    std::unordered_set<uint32_t> visited;
-    for (BasicBlock& bb : block_range) {
-      if (ignore_node(bb.id())) continue;
-      visited.insert(bb.id());
-
-      // Check if this block is dead, if so tag it as dead otherwise patch phi
-      // instructions.
-      bool has_live_pred = false;
-      for (uint32_t pid : cfg.preds(bb.id())) {
-        if (!dead_blocks->count(pid)) {
-          has_live_pred = true;
-          break;
-        }
-      }
-      if (!has_live_pred) {
-        dead_blocks->insert(bb.id());
-        const BasicBlock& cbb = bb;
-        // Patch the phis for any back-edge.
-        cbb.ForEachSuccessorLabel(
-            [dead_blocks, &visited, &cfg, this](uint32_t id) {
-              if (!visited.count(id) || dead_blocks->count(id)) return;
-              BasicBlock* succ = cfg.block(id);
-              PatchPhis(succ, *dead_blocks, false);
-            });
-        continue;
-      }
-      // Update the phi instructions, some incoming branch have/will disappear.
-      PatchPhis(&bb, *dead_blocks, /* preserve_phi = */ false);
-    }
-  }
-
-  // Returns true if the header is not reachable or tagged as dead or if we
-  // never loop back.
-  bool IsLoopDead(BasicBlock* header, BasicBlock* latch,
-                  const std::unordered_set<uint32_t>& dead_blocks) {
-    if (!header || dead_blocks.count(header->id())) return true;
-    if (!latch || dead_blocks.count(latch->id())) return true;
-    for (uint32_t pid : context_->cfg()->preds(header->id())) {
-      if (!dead_blocks.count(pid)) {
-        // Seems reachable.
-        return false;
-      }
-    }
-    return true;
-  }
-
-  // Cleans the loop nest under |loop| and reflect changes to the loop
-  // descriptor. This will kill all descriptors that represent dead loops.
-  // If |loop_| is killed, it will be set to nullptr.
-  // Any merge blocks that become unreachable will be added to
-  // |unreachable_merges|.
-  // The function returns the pointer to |loop| or nullptr if the loop was
-  // killed.
-  Loop* CleanLoopNest(Loop* loop,
-                      const std::unordered_set<uint32_t>& dead_blocks,
-                      std::unordered_set<uint32_t>* unreachable_merges) {
-    // This represent the pair of dead loop and nearest alive parent (nullptr if
-    // no parent).
-    std::unordered_map<Loop*, Loop*> dead_loops;
-    auto get_parent = [&dead_loops](Loop* l) -> Loop* {
-      std::unordered_map<Loop*, Loop*>::iterator it = dead_loops.find(l);
-      if (it != dead_loops.end()) return it->second;
-      return nullptr;
-    };
-
-    bool is_main_loop_dead =
-        IsLoopDead(loop->GetHeaderBlock(), loop->GetLatchBlock(), dead_blocks);
-    if (is_main_loop_dead) {
-      if (Instruction* merge = loop->GetHeaderBlock()->GetLoopMergeInst()) {
-        context_->KillInst(merge);
-      }
-      dead_loops[loop] = loop->GetParent();
-    } else {
-      dead_loops[loop] = loop;
-    }
-
-    // For each loop, check if we killed it. If we did, find a suitable parent
-    // for its children.
-    for (Loop& sub_loop :
-         make_range(++TreeDFIterator<Loop>(loop), TreeDFIterator<Loop>())) {
-      if (IsLoopDead(sub_loop.GetHeaderBlock(), sub_loop.GetLatchBlock(),
-                     dead_blocks)) {
-        if (Instruction* merge =
-                sub_loop.GetHeaderBlock()->GetLoopMergeInst()) {
-          context_->KillInst(merge);
-        }
-        dead_loops[&sub_loop] = get_parent(&sub_loop);
-      } else {
-        // The loop is alive, check if its merge block is dead, if it is, tag it
-        // as required.
-        if (sub_loop.GetMergeBlock()) {
-          uint32_t merge_id = sub_loop.GetMergeBlock()->id();
-          if (dead_blocks.count(merge_id)) {
-            unreachable_merges->insert(sub_loop.GetMergeBlock()->id());
-          }
-        }
-      }
-    }
-    if (!is_main_loop_dead) dead_loops.erase(loop);
-
-    // Remove dead blocks from live loops.
-    for (uint32_t bb_id : dead_blocks) {
-      Loop* l = loop_desc_[bb_id];
-      if (l) {
-        l->RemoveBasicBlock(bb_id);
-        loop_desc_.ForgetBasicBlock(bb_id);
-      }
-    }
-
-    std::for_each(
-        dead_loops.begin(), dead_loops.end(),
-        [&loop,
-         this](std::unordered_map<Loop*, Loop*>::iterator::reference it) {
-          if (it.first == loop) loop = nullptr;
-          loop_desc_.RemoveLoop(it.first);
-        });
-
-    return loop;
   }
 
   // Returns true if |var| is dynamically uniform.
@@ -839,17 +549,25 @@
     });
   }
 
-  // Returns true if |insn| is constant and dynamically uniform within the loop.
-  bool IsConditionLoopInvariant(Instruction* insn) {
+  // Returns true if |insn| is not a constant, but is loop invariant and
+  // dynamically uniform.
+  bool IsConditionNonConstantLoopInvariant(Instruction* insn) {
     assert(insn->IsBranch());
     assert(insn->opcode() != SpvOpBranch);
     analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr();
 
     Instruction* condition = def_use_mgr->GetDef(insn->GetOperand(0).words[0]);
-    return !loop_->IsInsideLoop(condition) &&
-           IsDynamicallyUniform(
-               condition, function_->entry().get(),
-               context_->GetPostDominatorAnalysis(function_)->GetDomTree());
+    if (condition->IsConstant()) {
+      return false;
+    }
+
+    if (loop_->IsInsideLoop(condition)) {
+      return false;
+    }
+
+    return IsDynamicallyUniform(
+        condition, function_->entry().get(),
+        context_->GetPostDominatorAnalysis(function_)->GetDomTree());
   }
 };
 
@@ -883,7 +601,7 @@
       processed_loop.insert(&loop);
 
       LoopUnswitch unswitcher(context(), f, &loop, &loop_descriptor);
-      while (!unswitcher.WasLoopKilled() && unswitcher.CanUnswitchLoop()) {
+      while (unswitcher.CanUnswitchLoop()) {
         if (!loop.IsLCSSA()) {
           LoopUtils(context(), &loop).MakeLoopClosedSSA();
         }
diff --git a/source/opt/merge_return_pass.cpp b/source/opt/merge_return_pass.cpp
index a13540c..a12b2ca 100644
--- a/source/opt/merge_return_pass.cpp
+++ b/source/opt/merge_return_pass.cpp
@@ -197,6 +197,7 @@
       tail_opcode == SpvOpUnreachable) {
     assert(CurrentState().InLoop() && "Should be in the dummy loop.");
     BranchToBlock(block, CurrentState().LoopMergeId());
+    return_blocks_.insert(block->id());
   }
 }
 
@@ -232,11 +233,19 @@
   const auto& target_pred = cfg()->preds(target->id());
   if (target_pred.size() == 1) {
     MarkForNewPhiNodes(target, context()->get_instr_block(target_pred[0]));
+  } else {
+    // If the loop contained a break and a return, OpPhi instructions may be
+    // required starting from the dominator of the loop merge.
+    DominatorAnalysis* dom_tree =
+        context()->GetDominatorAnalysis(target->GetParent());
+    auto idom = dom_tree->ImmediateDominator(target);
+    if (idom) {
+      MarkForNewPhiNodes(target, idom);
+    }
   }
 }
 
 void MergeReturnPass::CreatePhiNodesForInst(BasicBlock* merge_block,
-                                            uint32_t predecessor,
                                             Instruction& inst) {
   DominatorAnalysis* dom_tree =
       context()->GetDominatorAnalysis(merge_block->GetParent());
@@ -275,22 +284,22 @@
 
     // There is at least one values that needs to be replaced.
     // First create the OpPhi instruction.
-    InstructionBuilder builder(context(), &*merge_block->begin(),
-                               IRContext::kAnalysisDefUse);
+    InstructionBuilder builder(
+        context(), &*merge_block->begin(),
+        IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
     uint32_t undef_id = Type2Undef(inst.type_id());
     std::vector<uint32_t> phi_operands;
 
-    // Add the operands for the defining instructions.
-    phi_operands.push_back(inst.result_id());
-    phi_operands.push_back(predecessor);
-
-    // Add undef from all other blocks.
+    // Add the OpPhi operands. If the predecessor is a return block use undef,
+    // otherwise use |inst|'s id.
     std::vector<uint32_t> preds = cfg()->preds(merge_block->id());
     for (uint32_t pred_id : preds) {
-      if (pred_id != predecessor) {
+      if (return_blocks_.count(pred_id)) {
         phi_operands.push_back(undef_id);
-        phi_operands.push_back(pred_id);
+      } else {
+        phi_operands.push_back(inst.result_id());
       }
+      phi_operands.push_back(pred_id);
     }
 
     Instruction* new_phi = builder.AddPhi(inst.type_id(), phi_operands);
@@ -342,25 +351,28 @@
   while (block != nullptr && block != final_return_block_) {
     if (!predicated->insert(block).second) break;
     // Skip structured subgraphs.
-    BasicBlock* next = nullptr;
     assert(state->InLoop() && "Should be in the dummy loop at the very least.");
-    next = context()->get_instr_block(state->LoopMergeId());
-    while (state->LoopMergeId() == next->id()) {
+    Instruction* current_loop_merge_inst = state->LoopMergeInst();
+    uint32_t merge_block_id =
+        current_loop_merge_inst->GetSingleWordInOperand(0);
+    while (state->LoopMergeId() == merge_block_id) {
       state++;
     }
-    if (!BreakFromConstruct(block, next, predicated, order)) {
+    if (!BreakFromConstruct(block, predicated, order,
+                            current_loop_merge_inst)) {
       return false;
     }
-    block = next;
+    block = context()->get_instr_block(merge_block_id);
   }
   return true;
 }
 
 bool MergeReturnPass::BreakFromConstruct(
-    BasicBlock* block, BasicBlock* merge_block,
-    std::unordered_set<BasicBlock*>* predicated,
-    std::list<BasicBlock*>* order) {
-  // Make sure the cfg is build here.  If we don't then it becomes very hard
+    BasicBlock* block, std::unordered_set<BasicBlock*>* predicated,
+    std::list<BasicBlock*>* order, Instruction* loop_merge_inst) {
+  assert(loop_merge_inst->opcode() == SpvOpLoopMerge &&
+         "loop_merge_inst must be a loop merge instruction.");
+  // Make sure the CFG is build here.  If we don't then it becomes very hard
   // to know which new blocks need to be updated.
   context()->BuildInvalidAnalyses(IRContext::kAnalysisCFG);
 
@@ -380,6 +392,9 @@
       return false;
     }
   }
+
+  uint32_t merge_block_id = loop_merge_inst->GetSingleWordInOperand(0);
+  BasicBlock* merge_block = context()->get_instr_block(merge_block_id);
   if (merge_block->GetLoopMergeInst()) {
     cfg()->SplitLoopHeader(merge_block);
   }
@@ -393,8 +408,21 @@
   // Forget about the edges leaving block.  They will be removed.
   cfg()->RemoveSuccessorEdges(block);
 
-  BasicBlock* old_body = block->SplitBasicBlock(context(), TakeNextId(), iter);
+  auto old_body_id = TakeNextId();
+  BasicBlock* old_body = block->SplitBasicBlock(context(), old_body_id, iter);
   predicated->insert(old_body);
+  // If a return block is being split, mark the new body block also as a return
+  // block.
+  if (return_blocks_.count(block->id())) {
+    return_blocks_.insert(old_body_id);
+  }
+
+  // If |block| was a continue target for a loop |old_body| is now the correct
+  // continue target.
+  if (loop_merge_inst->GetSingleWordInOperand(1) == block->id()) {
+    loop_merge_inst->SetInOperand(1, {old_body->id()});
+    context()->UpdateDefUse(loop_merge_inst);
+  }
 
   // Update |order| so old_block will be traversed.
   InsertAfterElement(block, old_body, order);
@@ -403,6 +431,7 @@
   // 1. Load of the return status flag
   // 2. Branch to |merge_block| (true) or old body (false)
   // 3. Update OpPhi instructions in |merge_block|.
+  // 4. Update the CFG.
   //
   // Sine we are branching to the merge block of the current construct, there is
   // no need for an OpSelectionMerge.
@@ -421,10 +450,6 @@
   builder.AddConditionalBranch(load_id, merge_block->id(), old_body->id(),
                                old_body->id());
 
-  // Update the cfg
-  cfg()->AddEdges(block);
-  cfg()->RegisterBlock(old_body);
-
   // 3. Update OpPhi instructions in |merge_block|.
   BasicBlock* merge_original_pred = MarkedSinglePred(merge_block);
   if (merge_original_pred == nullptr) {
@@ -433,6 +458,12 @@
     MarkForNewPhiNodes(merge_block, old_body);
   }
 
+  // 4. Update the CFG.  We do this after updating the OpPhi instructions
+  // because |UpdatePhiNodes| assumes the edge from |block| has not been added
+  // to the CFG yet.
+  cfg()->AddEdges(block);
+  cfg()->RegisterBlock(old_body);
+
   assert(old_body->begin() != old_body->end());
   assert(block->begin() != block->end());
   return true;
@@ -643,7 +674,7 @@
   BasicBlock* current_bb = pred;
   while (current_bb != nullptr && current_bb->id() != header_id) {
     for (Instruction& inst : *current_bb) {
-      CreatePhiNodesForInst(bb, pred->id(), inst);
+      CreatePhiNodesForInst(bb, inst);
     }
     current_bb = dom_tree->ImmediateDominator(current_bb);
   }
diff --git a/source/opt/merge_return_pass.h b/source/opt/merge_return_pass.h
index 264e8b7..63094b7 100644
--- a/source/opt/merge_return_pass.h
+++ b/source/opt/merge_return_pass.h
@@ -104,7 +104,7 @@
   Status Process() override;
 
   IRContext::Analysis GetPreservedAnalyses() override {
-    return IRContext::kAnalysisNone;
+    return IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
   }
 
  private:
@@ -219,16 +219,18 @@
                        std::list<BasicBlock*>* order);
 
   // Add a conditional branch at the start of |block| that either jumps to
-  // |merge_block| or the original code in |block| depending on the value in
-  // |return_flag_|.
+  // the merge block of |loop_merge_inst| or the original code in |block|
+  // depending on the value in |return_flag_|.  The continue target in
+  // |loop_merge_inst| will be updated if needed.
   //
   // If new blocks that are created will be added to |order|.  This way a call
   // can traverse these new block in structured order.
   //
   // Returns true if successful.
-  bool BreakFromConstruct(BasicBlock* block, BasicBlock* merge_block,
+  bool BreakFromConstruct(BasicBlock* block,
                           std::unordered_set<BasicBlock*>* predicated,
-                          std::list<BasicBlock*>* order);
+                          std::list<BasicBlock*>* order,
+                          Instruction* loop_merge_inst);
 
   // Add an |OpReturn| or |OpReturnValue| to the end of |block|.  If an
   // |OpReturnValue| is needed, the return value is loaded from |return_value_|.
@@ -238,12 +240,11 @@
   // return block at the end of the pass.
   void CreateReturnBlock();
 
-  // Creates a Phi node in |merge_block| for the result of |inst| coming from
-  // |predecessor|.  Any uses of the result of |inst| that are no longer
+  // Creates a Phi node in |merge_block| for the result of |inst|.
+  // Any uses of the result of |inst| that are no longer
   // dominated by |inst|, are replaced with the result of the new |OpPhi|
   // instruction.
-  void CreatePhiNodesForInst(BasicBlock* merge_block, uint32_t predecessor,
-                             Instruction& inst);
+  void CreatePhiNodesForInst(BasicBlock* merge_block, Instruction& inst);
 
   // Traverse the nodes in |new_merge_nodes_|, and adds the OpPhi instructions
   // that are needed to make the code correct.  It is assumed that at this point
@@ -275,6 +276,8 @@
   // new edge from |new_source|.  The value for that edge will be an Undef. If
   // |target| only had a single predecessor, then it is marked as needing new
   // phi nodes.  See |MarkForNewPhiNodes|.
+  //
+  // The CFG must not include the edge from |new_source| to |target| yet.
   void UpdatePhiNodes(BasicBlock* new_source, BasicBlock* target);
 
   StructuredControlState& CurrentState() { return state_.back(); }
@@ -327,6 +330,11 @@
   // values that will need a phi on the new edges.
   std::unordered_map<BasicBlock*, BasicBlock*> new_merge_nodes_;
   bool HasNontrivialUnreachableBlocks(Function* function);
+
+  // Contains all return blocks that are merged. This is set is populated while
+  // processing structured blocks and used to properly construct OpPhi
+  // instructions.
+  std::unordered_set<uint32_t> return_blocks_;
 };
 
 }  // namespace opt
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index 856ede7..34ddedc 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -56,8 +56,8 @@
 struct Optimizer::Impl {
   explicit Impl(spv_target_env env) : target_env(env), pass_manager() {}
 
-  spv_target_env target_env;        // Target environment.
-  opt::PassManager pass_manager;    // Internal implementation pass manager.
+  spv_target_env target_env;      // Target environment.
+  opt::PassManager pass_manager;  // Internal implementation pass manager.
 };
 
 Optimizer::Optimizer(spv_target_env env) : impl_(new Impl(env)) {}
@@ -115,6 +115,10 @@
           // Make private variable function scope
           .RegisterPass(CreateEliminateDeadFunctionsPass())
           .RegisterPass(CreatePrivateToLocalPass())
+          // Fix up the storage classes that DXC may have purposely generated
+          // incorrectly.  All functions are inlined, and a lot of dead code has
+          // been removed.
+          .RegisterPass(CreateFixStorageClassPass())
           // Propagate the value stored to the loads in very simple cases.
           .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
           .RegisterPass(CreateLocalSingleStoreElimPass())
@@ -216,6 +220,21 @@
       .RegisterPass(CreateAggressiveDCEPass());
 }
 
+Optimizer& Optimizer::RegisterVulkanToWebGPUPasses() {
+  return RegisterPass(CreateStripDebugInfoPass())
+      .RegisterPass(CreateStripAtomicCounterMemoryPass())
+      .RegisterPass(CreateGenerateWebGPUInitializersPass())
+      .RegisterPass(CreateLegalizeVectorShufflePass())
+      .RegisterPass(CreateEliminateDeadConstantPass())
+      .RegisterPass(CreateFlattenDecorationPass())
+      .RegisterPass(CreateAggressiveDCEPass())
+      .RegisterPass(CreateDeadBranchElimPass());
+}
+
+Optimizer& Optimizer::RegisterWebGPUToVulkanPasses() {
+  return RegisterPass(CreateDecomposeInitializedVariablesPass());
+}
+
 bool Optimizer::RegisterPassesFromFlags(const std::vector<std::string>& flags) {
   for (const auto& flag : flags) {
     if (!RegisterPassFromFlag(flag)) {
@@ -258,7 +277,9 @@
   //
   // Both Pass::name() and Pass::desc() should be static class members so they
   // can be invoked without creating a pass instance.
-  if (pass_name == "strip-debug") {
+  if (pass_name == "strip-atomic-counter-memory") {
+    RegisterPass(CreateStripAtomicCounterMemoryPass());
+  } else if (pass_name == "strip-debug") {
     RegisterPass(CreateStripDebugInfoPass());
   } else if (pass_name == "strip-reflect") {
     RegisterPass(CreateStripReflectInfoPass());
@@ -324,6 +345,8 @@
     RegisterPass(CreateDeadInsertElimPass());
   } else if (pass_name == "eliminate-dead-variables") {
     RegisterPass(CreateDeadVariableEliminationPass());
+  } else if (pass_name == "eliminate-dead-members") {
+    RegisterPass(CreateEliminateDeadMembersPass());
   } else if (pass_name == "fold-spec-const-op-composite") {
     RegisterPass(CreateFoldSpecConstantOpAndCompositePass());
   } else if (pass_name == "loop-unswitch") {
@@ -373,7 +396,7 @@
   } else if (pass_name == "replace-invalid-opcode") {
     RegisterPass(CreateReplaceInvalidOpcodePass());
   } else if (pass_name == "inst-bindless-check") {
-    RegisterPass(CreateInstBindlessCheckPass(7, 23));
+    RegisterPass(CreateInstBindlessCheckPass(7, 23, true, true));
     RegisterPass(CreateSimplificationPass());
     RegisterPass(CreateDeadBranchElimPass());
     RegisterPass(CreateBlockMergePass());
@@ -434,12 +457,20 @@
     }
   } else if (pass_name == "ccp") {
     RegisterPass(CreateCCPPass());
+  } else if (pass_name == "code-sink") {
+    RegisterPass(CreateCodeSinkingPass());
+  } else if (pass_name == "fix-storage-class") {
+    RegisterPass(CreateFixStorageClassPass());
   } else if (pass_name == "O") {
     RegisterPerformancePasses();
   } else if (pass_name == "Os") {
     RegisterSizePasses();
   } else if (pass_name == "legalize-hlsl") {
     RegisterLegalizationPasses();
+  } else if (pass_name == "generate-webgpu-initializers") {
+    RegisterPass(CreateGenerateWebGPUInitializersPass());
+  } else if (pass_name == "legalize-vector-shuffle") {
+    RegisterPass(CreateLegalizeVectorShufflePass());
   } else {
     Errorf(consumer(), nullptr, {},
            "Unknown flag '--%s'. Use --help for a list of valid flags",
@@ -491,11 +522,25 @@
 
   context->set_max_id_bound(opt_options->max_id_bound_);
 
+  impl_->pass_manager.SetValidatorOptions(&opt_options->val_options_);
+  impl_->pass_manager.SetTargetEnv(impl_->target_env);
   auto status = impl_->pass_manager.Run(context.get());
-  if (status == opt::Pass::Status::SuccessWithChange ||
-      (status == opt::Pass::Status::SuccessWithoutChange &&
-       (optimized_binary->data() != original_binary ||
-        optimized_binary->size() != original_binary_size))) {
+
+  bool binary_changed = false;
+  if (status == opt::Pass::Status::SuccessWithChange) {
+    binary_changed = true;
+  } else if (status == opt::Pass::Status::SuccessWithoutChange) {
+    if (optimized_binary->size() != original_binary_size ||
+        (memcmp(optimized_binary->data(), original_binary,
+                original_binary_size) != 0)) {
+      binary_changed = true;
+      Log(consumer(), SPV_MSG_WARNING, nullptr, {},
+          "Binary unexpectedly changed despite optimizer saying there was no "
+          "change");
+    }
+  }
+
+  if (binary_changed) {
     optimized_binary->clear();
     context->module()->ToBinary(optimized_binary, /* skip_nop = */ true);
   }
@@ -513,10 +558,20 @@
   return *this;
 }
 
+Optimizer& Optimizer::SetValidateAfterAll(bool validate) {
+  impl_->pass_manager.SetValidateAfterAll(validate);
+  return *this;
+}
+
 Optimizer::PassToken CreateNullPass() {
   return MakeUnique<Optimizer::PassToken::Impl>(MakeUnique<opt::NullPass>());
 }
 
+Optimizer::PassToken CreateStripAtomicCounterMemoryPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::StripAtomicCounterMemoryPass>());
+}
+
 Optimizer::PassToken CreateStripDebugInfoPass() {
   return MakeUnique<Optimizer::PassToken::Impl>(
       MakeUnique<opt::StripDebugInfoPass>());
@@ -532,6 +587,11 @@
       MakeUnique<opt::EliminateDeadFunctionsPass>());
 }
 
+Optimizer::PassToken CreateEliminateDeadMembersPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::EliminateDeadMembersPass>());
+}
+
 Optimizer::PassToken CreateSetSpecConstantDefaultValuePass(
     const std::unordered_map<uint32_t, std::string>& id_value_map) {
   return MakeUnique<Optimizer::PassToken::Impl>(
@@ -780,9 +840,37 @@
 }
 
 Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
-                                                 uint32_t shader_id) {
+                                                 uint32_t shader_id,
+                                                 bool input_length_enable,
+                                                 bool input_init_enable) {
   return MakeUnique<Optimizer::PassToken::Impl>(
-      MakeUnique<opt::InstBindlessCheckPass>(desc_set, shader_id));
+      MakeUnique<opt::InstBindlessCheckPass>(
+          desc_set, shader_id, input_length_enable, input_init_enable));
+}
+
+Optimizer::PassToken CreateCodeSinkingPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::CodeSinkingPass>());
+}
+
+Optimizer::PassToken CreateGenerateWebGPUInitializersPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::GenerateWebGPUInitializersPass>());
+}
+
+Optimizer::PassToken CreateFixStorageClassPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::FixStorageClass>());
+}
+
+Optimizer::PassToken CreateLegalizeVectorShufflePass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::LegalizeVectorShufflePass>());
+}
+
+Optimizer::PassToken CreateDecomposeInitializedVariablesPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::DecomposeInitializedVariablesPass>());
 }
 
 }  // namespace spvtools
diff --git a/source/opt/pass.cpp b/source/opt/pass.cpp
index edcd245..80075db 100644
--- a/source/opt/pass.cpp
+++ b/source/opt/pass.cpp
@@ -16,6 +16,7 @@
 
 #include "source/opt/pass.h"
 
+#include "source/opt/ir_builder.h"
 #include "source/opt/iterator.h"
 
 namespace spvtools {
@@ -52,5 +53,72 @@
   return ptrTypeInst->GetSingleWordInOperand(kTypePointerTypeIdInIdx);
 }
 
+uint32_t Pass::GenerateCopy(Instruction* object_inst, uint32_t new_type_id,
+                            Instruction* insertion_position) {
+  analysis::TypeManager* type_mgr = context()->get_type_mgr();
+  analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
+
+  uint32_t original_type_id = object_inst->type_id();
+  if (original_type_id == new_type_id) {
+    return object_inst->result_id();
+  }
+
+  InstructionBuilder ir_builder(
+      context(), insertion_position,
+      IRContext::kAnalysisInstrToBlockMapping | IRContext::kAnalysisDefUse);
+
+  analysis::Type* original_type = type_mgr->GetType(original_type_id);
+  analysis::Type* new_type = type_mgr->GetType(new_type_id);
+
+  if (const analysis::Array* original_array_type = original_type->AsArray()) {
+    uint32_t original_element_type_id =
+        type_mgr->GetId(original_array_type->element_type());
+
+    analysis::Array* new_array_type = new_type->AsArray();
+    assert(new_array_type != nullptr && "Can't copy an array to a non-array.");
+    uint32_t new_element_type_id =
+        type_mgr->GetId(new_array_type->element_type());
+
+    std::vector<uint32_t> element_ids;
+    const analysis::Constant* length_const =
+        const_mgr->FindDeclaredConstant(original_array_type->LengthId());
+    assert(length_const->AsIntConstant());
+    uint32_t array_length = length_const->AsIntConstant()->GetU32();
+    for (uint32_t i = 0; i < array_length; i++) {
+      Instruction* extract = ir_builder.AddCompositeExtract(
+          original_element_type_id, object_inst->result_id(), {i});
+      element_ids.push_back(
+          GenerateCopy(extract, new_element_type_id, insertion_position));
+    }
+
+    return ir_builder.AddCompositeConstruct(new_type_id, element_ids)
+        ->result_id();
+  } else if (const analysis::Struct* original_struct_type =
+                 original_type->AsStruct()) {
+    analysis::Struct* new_struct_type = new_type->AsStruct();
+
+    const std::vector<const analysis::Type*>& original_types =
+        original_struct_type->element_types();
+    const std::vector<const analysis::Type*>& new_types =
+        new_struct_type->element_types();
+    std::vector<uint32_t> element_ids;
+    for (uint32_t i = 0; i < original_types.size(); i++) {
+      Instruction* extract = ir_builder.AddCompositeExtract(
+          type_mgr->GetId(original_types[i]), object_inst->result_id(), {i});
+      element_ids.push_back(GenerateCopy(extract, type_mgr->GetId(new_types[i]),
+                                         insertion_position));
+    }
+    return ir_builder.AddCompositeConstruct(new_type_id, element_ids)
+        ->result_id();
+  } else {
+    // If we do not have an aggregate type, then we have a problem.  Either we
+    // found multiple instances of the same type, or we are copying to an
+    // incompatible type.  Either way the code is illegal.
+    assert(false &&
+           "Don't know how to copy this type.  Code is likely illegal.");
+  }
+  return 0;
+}
+
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/pass.h b/source/opt/pass.h
index c95f502..0667c3d 100644
--- a/source/opt/pass.h
+++ b/source/opt/pass.h
@@ -125,6 +125,12 @@
   // TODO(1841): Handle id overflow.
   uint32_t TakeNextId() { return context_->TakeNextId(); }
 
+  // Returns the id whose value is the same as |object_to_copy| except its type
+  // is |new_type_id|.  Any instructions needed to generate this value will be
+  // inserted before |insertion_position|.
+  uint32_t GenerateCopy(Instruction* object_to_copy, uint32_t new_type_id,
+                        Instruction* insertion_position);
+
  private:
   MessageConsumer consumer_;  // Message consumer.
 
diff --git a/source/opt/pass_manager.cpp b/source/opt/pass_manager.cpp
index fa1e1d8..be53d34 100644
--- a/source/opt/pass_manager.cpp
+++ b/source/opt/pass_manager.cpp
@@ -51,6 +51,20 @@
     if (one_status == Pass::Status::Failure) return one_status;
     if (one_status == Pass::Status::SuccessWithChange) status = one_status;
 
+    if (validate_after_all_) {
+      spvtools::SpirvTools tools(target_env_);
+      tools.SetMessageConsumer(consumer());
+      std::vector<uint32_t> binary;
+      context->module()->ToBinary(&binary, true);
+      if (!tools.Validate(binary.data(), binary.size(), val_options_)) {
+        std::string msg = "Validation failed after pass ";
+        msg += pass->name();
+        spv_position_t null_pos{0, 0, 0};
+        consumer()(SPV_MSG_INTERNAL_ERROR, "", null_pos, msg.c_str());
+        return Pass::Status::Failure;
+      }
+    }
+
     // Reset the pass to free any memory used by the pass.
     pass.reset(nullptr);
   }
diff --git a/source/opt/pass_manager.h b/source/opt/pass_manager.h
index ed88aa1..9686ddd 100644
--- a/source/opt/pass_manager.h
+++ b/source/opt/pass_manager.h
@@ -43,7 +43,10 @@
   PassManager()
       : consumer_(nullptr),
         print_all_stream_(nullptr),
-        time_report_stream_(nullptr) {}
+        time_report_stream_(nullptr),
+        target_env_(SPV_ENV_UNIVERSAL_1_2),
+        val_options_(nullptr),
+        validate_after_all_(false) {}
 
   // Sets the message consumer to the given |consumer|.
   void SetMessageConsumer(MessageConsumer c) { consumer_ = std::move(c); }
@@ -89,6 +92,24 @@
     return *this;
   }
 
+  // Sets the target environment for validation.
+  PassManager& SetTargetEnv(spv_target_env env) {
+    target_env_ = env;
+    return *this;
+  }
+
+  // Sets the validation options.
+  PassManager& SetValidatorOptions(spv_validator_options options) {
+    val_options_ = options;
+    return *this;
+  }
+
+  // Sets the option to validate after each pass.
+  PassManager& SetValidateAfterAll(bool validate) {
+    validate_after_all_ = validate;
+    return *this;
+  }
+
  private:
   // Consumer for messages.
   MessageConsumer consumer_;
@@ -100,6 +121,12 @@
   // The output stream to write the resource utilization of each pass. If this
   // is null, no output is generated.
   std::ostream* time_report_stream_;
+  // The target environment.
+  spv_target_env target_env_;
+  // The validator options (used when validating each pass).
+  spv_validator_options val_options_;
+  // Controls whether validation occurs after every pass.
+  bool validate_after_all_;
 };
 
 inline void PassManager::AddPass(std::unique_ptr<Pass> pass) {
diff --git a/source/opt/passes.h b/source/opt/passes.h
index b9f30ae..06464b0 100644
--- a/source/opt/passes.h
+++ b/source/opt/passes.h
@@ -21,6 +21,7 @@
 #include "source/opt/block_merge_pass.h"
 #include "source/opt/ccp_pass.h"
 #include "source/opt/cfg_cleanup_pass.h"
+#include "source/opt/code_sink.h"
 #include "source/opt/combine_access_chains.h"
 #include "source/opt/common_uniform_elim_pass.h"
 #include "source/opt/compact_ids_pass.h"
@@ -28,15 +29,20 @@
 #include "source/opt/dead_branch_elim_pass.h"
 #include "source/opt/dead_insert_elim_pass.h"
 #include "source/opt/dead_variable_elimination.h"
+#include "source/opt/decompose_initialized_variables_pass.h"
 #include "source/opt/eliminate_dead_constant_pass.h"
 #include "source/opt/eliminate_dead_functions_pass.h"
+#include "source/opt/eliminate_dead_members_pass.h"
+#include "source/opt/fix_storage_class.h"
 #include "source/opt/flatten_decoration_pass.h"
 #include "source/opt/fold_spec_constant_op_and_composite_pass.h"
 #include "source/opt/freeze_spec_constant_value_pass.h"
+#include "source/opt/generate_webgpu_initializers_pass.h"
 #include "source/opt/if_conversion.h"
 #include "source/opt/inline_exhaustive_pass.h"
 #include "source/opt/inline_opaque_pass.h"
 #include "source/opt/inst_bindless_check_pass.h"
+#include "source/opt/legalize_vector_shuffle_pass.h"
 #include "source/opt/licm_pass.h"
 #include "source/opt/local_access_chain_convert_pass.h"
 #include "source/opt/local_redundancy_elimination.h"
@@ -61,6 +67,7 @@
 #include "source/opt/simplification_pass.h"
 #include "source/opt/ssa_rewrite_pass.h"
 #include "source/opt/strength_reduction_pass.h"
+#include "source/opt/strip_atomic_counter_memory_pass.h"
 #include "source/opt/strip_debug_info_pass.h"
 #include "source/opt/strip_reflect_info_pass.h"
 #include "source/opt/unify_const_pass.h"
diff --git a/source/opt/private_to_local_pass.cpp b/source/opt/private_to_local_pass.cpp
index 02909a7..d41d8f2 100644
--- a/source/opt/private_to_local_pass.cpp
+++ b/source/opt/private_to_local_pass.cpp
@@ -19,6 +19,7 @@
 #include <vector>
 
 #include "source/opt/ir_context.h"
+#include "source/spirv_constant.h"
 
 namespace spvtools {
 namespace opt {
@@ -38,6 +39,7 @@
     return Status::SuccessWithoutChange;
 
   std::vector<std::pair<Instruction*, Function*>> variables_to_move;
+  std::unordered_set<uint32_t> localized_variables;
   for (auto& inst : context()->types_values()) {
     if (inst.opcode() != SpvOpVariable) {
       continue;
@@ -57,6 +59,27 @@
   modified = !variables_to_move.empty();
   for (auto p : variables_to_move) {
     MoveVariable(p.first, p.second);
+    localized_variables.insert(p.first->result_id());
+  }
+
+  if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
+    // In SPIR-V 1.4 and later entry points must list private storage class
+    // variables that are statically used by the entry point. Go through the
+    // entry points and remove any references to variables that were localized.
+    for (auto& entry : get_module()->entry_points()) {
+      std::vector<Operand> new_operands;
+      for (uint32_t i = 0; i < entry.NumInOperands(); ++i) {
+        // Execution model, function id and name are always kept.
+        if (i < 3 ||
+            !localized_variables.count(entry.GetSingleWordInOperand(i))) {
+          new_operands.push_back(entry.GetInOperand(i));
+        }
+      }
+      if (new_operands.size() != entry.NumInOperands()) {
+        entry.SetInOperands(std::move(new_operands));
+        context()->AnalyzeUses(&entry);
+      }
+    }
   }
 
   return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
@@ -165,6 +188,7 @@
       UpdateUses(inst->result_id());
       break;
     case SpvOpName:
+    case SpvOpEntryPoint:  // entry points will be updated separately.
       break;
     default:
       assert(spvOpcodeIsDecoration(inst->opcode()) &&
diff --git a/source/opt/private_to_local_pass.h b/source/opt/private_to_local_pass.h
index f706e6e..4678530 100644
--- a/source/opt/private_to_local_pass.h
+++ b/source/opt/private_to_local_pass.h
@@ -35,7 +35,8 @@
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
            IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
-           IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/process_lines_pass.h b/source/opt/process_lines_pass.h
index 91b2136..c988bfd 100644
--- a/source/opt/process_lines_pass.h
+++ b/source/opt/process_lines_pass.h
@@ -51,7 +51,8 @@
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
            IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
-           IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/reduce_load_size.cpp b/source/opt/reduce_load_size.cpp
index b692c6b..7b5a015 100644
--- a/source/opt/reduce_load_size.cpp
+++ b/source/opt/reduce_load_size.cpp
@@ -139,7 +139,8 @@
 
   all_elements_used =
       !def_use_mgr->WhileEachUser(op_inst, [&elements_used](Instruction* use) {
-        if (use->opcode() != SpvOpCompositeExtract) {
+        if (use->opcode() != SpvOpCompositeExtract ||
+            use->NumInOperands() == 1) {
           return false;
         }
         elements_used.insert(use->GetSingleWordInOperand(1));
diff --git a/source/opt/reduce_load_size.h b/source/opt/reduce_load_size.h
index 724a430..ccac49b 100644
--- a/source/opt/reduce_load_size.h
+++ b/source/opt/reduce_load_size.h
@@ -36,7 +36,8 @@
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG |
            IRContext::kAnalysisDominatorAnalysis |
-           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/reflect.h b/source/opt/reflect.h
index 79d90bd..8106442 100644
--- a/source/opt/reflect.h
+++ b/source/opt/reflect.h
@@ -45,7 +45,8 @@
 inline bool IsTypeInst(SpvOp opcode) {
   return (opcode >= SpvOpTypeVoid && opcode <= SpvOpTypeForwardPointer) ||
          opcode == SpvOpTypePipeStorage || opcode == SpvOpTypeNamedBarrier ||
-         opcode == SpvOpTypeAccelerationStructureNV;
+         opcode == SpvOpTypeAccelerationStructureNV ||
+         opcode == SpvOpTypeCooperativeMatrixNV;
 }
 inline bool IsConstantInst(SpvOp opcode) {
   return opcode >= SpvOpConstantTrue && opcode <= SpvOpSpecConstantOp;
diff --git a/source/opt/scalar_replacement_pass.cpp b/source/opt/scalar_replacement_pass.cpp
index 66a97bd..bfa8598 100644
--- a/source/opt/scalar_replacement_pass.cpp
+++ b/source/opt/scalar_replacement_pass.cpp
@@ -225,12 +225,12 @@
   // indexes) or a direct use of the replacement variable.
   uint32_t indexId = chain->GetSingleWordInOperand(1u);
   const Instruction* index = get_def_use_mgr()->GetDef(indexId);
-  size_t indexValue = GetConstantInteger(index);
+  uint64_t indexValue = GetConstantInteger(index);
   if (indexValue > replacements.size()) {
     // Out of bounds access, this is illegal IR.
     return false;
   } else {
-    const Instruction* var = replacements[indexValue];
+    const Instruction* var = replacements[static_cast<size_t>(indexValue)];
     if (chain->NumInOperands() > 2) {
       // Replace input access chain with another access chain.
       BasicBlock::iterator chainIter(chain);
@@ -363,7 +363,7 @@
       context()->get_type_mgr()->GetTypeAndPointerType(id,
                                                        SpvStorageClassFunction);
   uint32_t ptrId = 0;
-  if (id == context()->get_type_mgr()->GetId(pointeeTy)) {
+  if (pointeeTy->IsUniqueType()) {
     // Non-ambiguous type, just ask the type manager for an id.
     ptrId = context()->get_type_mgr()->GetTypeInstruction(pointerTy.get());
     pointee_to_pointer_[id] = ptrId;
@@ -457,16 +457,16 @@
   }
 }
 
-size_t ScalarReplacementPass::GetIntegerLiteral(const Operand& op) const {
+uint64_t ScalarReplacementPass::GetIntegerLiteral(const Operand& op) const {
   assert(op.words.size() <= 2);
-  size_t len = 0;
+  uint64_t len = 0;
   for (uint32_t i = 0; i != op.words.size(); ++i) {
     len |= (op.words[i] << (32 * i));
   }
   return len;
 }
 
-size_t ScalarReplacementPass::GetConstantInteger(
+uint64_t ScalarReplacementPass::GetConstantInteger(
     const Instruction* constant) const {
   assert(get_def_use_mgr()->GetDef(constant->type_id())->opcode() ==
          SpvOpTypeInt);
@@ -480,7 +480,7 @@
   return GetIntegerLiteral(op);
 }
 
-size_t ScalarReplacementPass::GetArrayLength(
+uint64_t ScalarReplacementPass::GetArrayLength(
     const Instruction* arrayType) const {
   assert(arrayType->opcode() == SpvOpTypeArray);
   const Instruction* length =
@@ -488,14 +488,14 @@
   return GetConstantInteger(length);
 }
 
-size_t ScalarReplacementPass::GetNumElements(const Instruction* type) const {
+uint64_t ScalarReplacementPass::GetNumElements(const Instruction* type) const {
   assert(type->opcode() == SpvOpTypeVector ||
          type->opcode() == SpvOpTypeMatrix);
   const Operand& op = type->GetInOperand(1u);
   assert(op.words.size() <= 2);
-  size_t len = 0;
-  for (uint32_t i = 0; i != op.words.size(); ++i) {
-    len |= (op.words[i] << (32 * i));
+  uint64_t len = 0;
+  for (size_t i = 0; i != op.words.size(); ++i) {
+    len |= (static_cast<uint64_t>(op.words[i]) << (32ull * i));
   }
   return len;
 }
@@ -638,8 +638,8 @@
           switch (user->opcode()) {
             case SpvOpAccessChain:
             case SpvOpInBoundsAccessChain:
-              if (index == 2u) {
-                uint32_t id = user->GetSingleWordOperand(3u);
+              if (index == 2u && user->NumInOperands() > 1) {
+                uint32_t id = user->GetSingleWordInOperand(1u);
                 const Instruction* opInst = get_def_use_mgr()->GetDef(id);
                 if (!IsCompileTimeConstantInst(opInst->opcode())) {
                   ok = false;
@@ -717,7 +717,7 @@
     return false;
   return true;
 }
-bool ScalarReplacementPass::IsLargerThanSizeLimit(size_t length) const {
+bool ScalarReplacementPass::IsLargerThanSizeLimit(uint64_t length) const {
   if (max_num_elements_ == 0) {
     return false;
   }
@@ -751,8 +751,10 @@
           return false;
         }
       }
+      case SpvOpName:
+      case SpvOpMemberName:
       case SpvOpStore:
-        // No components are used.  Things are just stored to.
+        // No components are used.
         return true;
       case SpvOpAccessChain:
       case SpvOpInBoundsAccessChain: {
@@ -781,16 +783,6 @@
           return false;
         }
       }
-      case SpvOpCopyObject: {
-        // Follow the copy to see which components are used.
-        auto t = GetUsedComponents(use);
-        if (!t) {
-          result.reset(nullptr);
-          return false;
-        }
-        result->insert(t->begin(), t->end());
-        return true;
-      }
       default:
         // We do not know what is happening.  Have to assume the worst.
         result.reset(nullptr);
diff --git a/source/opt/scalar_replacement_pass.h b/source/opt/scalar_replacement_pass.h
index a82abf4..0ff9947 100644
--- a/source/opt/scalar_replacement_pass.h
+++ b/source/opt/scalar_replacement_pass.h
@@ -52,7 +52,8 @@
     return IRContext::kAnalysisDefUse |
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
-           IRContext::kAnalysisCFG | IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisCFG | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
   }
 
  private:
@@ -156,18 +157,18 @@
   // Returns the value of an OpConstant of integer type.
   //
   // |constant| must use two or fewer words to generate the value.
-  size_t GetConstantInteger(const Instruction* constant) const;
+  uint64_t GetConstantInteger(const Instruction* constant) const;
 
   // Returns the integer literal for |op|.
-  size_t GetIntegerLiteral(const Operand& op) const;
+  uint64_t GetIntegerLiteral(const Operand& op) const;
 
   // Returns the array length for |arrayInst|.
-  size_t GetArrayLength(const Instruction* arrayInst) const;
+  uint64_t GetArrayLength(const Instruction* arrayInst) const;
 
   // Returns the number of elements in |type|.
   //
   // |type| must be a vector or matrix type.
-  size_t GetNumElements(const Instruction* type) const;
+  uint64_t GetNumElements(const Instruction* type) const;
 
   // Returns true if |id| is a specialization constant.
   //
@@ -227,7 +228,7 @@
   // Limit on the number of members in an object that will be replaced.
   // 0 means there is no limit.
   uint32_t max_num_elements_;
-  bool IsLargerThanSizeLimit(size_t length) const;
+  bool IsLargerThanSizeLimit(uint64_t length) const;
   char name_[55];
 };
 
diff --git a/source/opt/simplification_pass.h b/source/opt/simplification_pass.h
index 348c96a..bcb88bf 100644
--- a/source/opt/simplification_pass.h
+++ b/source/opt/simplification_pass.h
@@ -33,7 +33,8 @@
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
            IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
-           IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
+           IRContext::kAnalysisTypes;
   }
 
  private:
diff --git a/source/opt/ssa_rewrite_pass.cpp b/source/opt/ssa_rewrite_pass.cpp
index 0a5d390..f2cb2da 100644
--- a/source/opt/ssa_rewrite_pass.cpp
+++ b/source/opt/ssa_rewrite_pass.cpp
@@ -435,12 +435,22 @@
         pass_->get_def_use_mgr()->GetDef(phi_candidate->var_id()));
     std::vector<Operand> phi_operands;
     uint32_t arg_ix = 0;
+    std::unordered_map<uint32_t, uint32_t> already_seen;
     for (uint32_t pred_label : pass_->cfg()->preds(phi_candidate->bb()->id())) {
       uint32_t op_val_id = GetPhiArgument(phi_candidate, arg_ix++);
-      phi_operands.push_back(
-          {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {op_val_id}});
-      phi_operands.push_back(
-          {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {pred_label}});
+      if (already_seen.count(pred_label) == 0) {
+        phi_operands.push_back(
+            {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {op_val_id}});
+        phi_operands.push_back(
+            {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {pred_label}});
+        already_seen[pred_label] = op_val_id;
+      } else {
+        // It is possible that there are two edges from the same parent block.
+        // Since the OpPhi can have only one entry for each parent, we have to
+        // make sure the two edges are consistent with each other.
+        assert(already_seen[pred_label] == op_val_id &&
+               "Inconsistent value for duplicate edges.");
+      }
     }
 
     // Generate a new OpPhi instruction and insert it in its basic
@@ -453,7 +463,6 @@
     pass_->context()->set_instr_block(&*phi_inst, phi_candidate->bb());
     auto insert_it = phi_candidate->bb()->begin();
     insert_it.InsertBefore(std::move(phi_inst));
-
     pass_->context()->get_decoration_mgr()->CloneDecorations(
         phi_candidate->var_id(), phi_candidate->result_id(),
         {SpvDecorationRelaxedPrecision});
diff --git a/source/opt/strip_atomic_counter_memory_pass.cpp b/source/opt/strip_atomic_counter_memory_pass.cpp
new file mode 100644
index 0000000..47714b7
--- /dev/null
+++ b/source/opt/strip_atomic_counter_memory_pass.cpp
@@ -0,0 +1,57 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/strip_atomic_counter_memory_pass.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status StripAtomicCounterMemoryPass::Process() {
+  bool changed = false;
+  context()->module()->ForEachInst([this, &changed](Instruction* inst) {
+    auto indices = spvOpcodeMemorySemanticsOperandIndices(inst->opcode());
+    if (indices.empty()) return;
+
+    for (auto idx : indices) {
+      auto mem_sem_id = inst->GetSingleWordOperand(idx);
+      const auto& mem_sem_inst =
+          context()->get_def_use_mgr()->GetDef(mem_sem_id);
+      // The spec explicitly says that this id must be an OpConstant
+      auto mem_sem_val = mem_sem_inst->GetSingleWordOperand(2);
+      if (!(mem_sem_val & SpvMemorySemanticsAtomicCounterMemoryMask)) {
+        continue;
+      }
+      mem_sem_val &= ~SpvMemorySemanticsAtomicCounterMemoryMask;
+
+      analysis::Integer int_type(32, false);
+      const analysis::Type* uint32_type =
+          context()->get_type_mgr()->GetRegisteredType(&int_type);
+      auto* new_const = context()->get_constant_mgr()->GetConstant(
+          uint32_type, {mem_sem_val});
+      auto* new_const_inst =
+          context()->get_constant_mgr()->GetDefiningInstruction(new_const);
+      auto new_const_id = new_const_inst->result_id();
+
+      inst->SetOperand(idx, {new_const_id});
+      context()->UpdateDefUse(inst);
+      changed = true;
+    }
+  });
+
+  return changed ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/strip_atomic_counter_memory_pass.h b/source/opt/strip_atomic_counter_memory_pass.h
new file mode 100644
index 0000000..62e274a
--- /dev/null
+++ b/source/opt/strip_atomic_counter_memory_pass.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_STRIP_ATOMIC_COUNT_MEMORY_PASS_H_
+#define SOURCE_OPT_STRIP_ATOMIC_COUNT_MEMORY_PASS_H_
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// Removes the AtomicCounterMemory bit from the value being passed into memory
+// semantics. This bit being set is ignored in Vulkan environments and
+// forbidden WebGPU ones.
+class StripAtomicCounterMemoryPass : public Pass {
+ public:
+  const char* name() const override { return "strip-atomic-counter-memory"; }
+  Status Process() override;
+
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
+           IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
+           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisScalarEvolution |
+           IRContext::kAnalysisRegisterPressure |
+           IRContext::kAnalysisValueNumberTable |
+           IRContext::kAnalysisStructuredCFG |
+           IRContext::kAnalysisBuiltinVarId |
+           IRContext::kAnalysisIdToFuncMapping | IRContext::kAnalysisTypes |
+           IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants;
+  }
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_STRIP_ATOMIC_COUNT_MEMORY_PASS_H_
diff --git a/source/opt/strip_reflect_info_pass.h b/source/opt/strip_reflect_info_pass.h
index 935a605..4e1999e 100644
--- a/source/opt/strip_reflect_info_pass.h
+++ b/source/opt/strip_reflect_info_pass.h
@@ -33,7 +33,8 @@
     return IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG |
            IRContext::kAnalysisDominatorAnalysis |
-           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
   }
 };
 
diff --git a/source/opt/struct_cfg_analysis.cpp b/source/opt/struct_cfg_analysis.cpp
index d78ec56..dcfd4a5 100644
--- a/source/opt/struct_cfg_analysis.cpp
+++ b/source/opt/struct_cfg_analysis.cpp
@@ -19,7 +19,7 @@
 namespace {
 const uint32_t kMergeNodeIndex = 0;
 const uint32_t kContinueNodeIndex = 1;
-}
+}  // namespace
 
 namespace spvtools {
 namespace opt {
@@ -37,6 +37,8 @@
 }
 
 void StructuredCFGAnalysis::AddBlocksInFunction(Function* func) {
+  if (func->begin() == func->end()) return;
+
   std::list<BasicBlock*> order;
   context_->cfg()->ComputeStructuredOrder(func, &*func->begin(), &order);
 
diff --git a/source/opt/type_manager.cpp b/source/opt/type_manager.cpp
index 722b9b9..001883c 100644
--- a/source/opt/type_manager.cpp
+++ b/source/opt/type_manager.cpp
@@ -744,6 +744,9 @@
     case SpvOpTypeNamedBarrier:
       type = new NamedBarrier();
       break;
+    case SpvOpTypeAccelerationStructureNV:
+      type = new AccelerationStructureNV();
+      break;
     default:
       SPIRV_UNIMPLEMENTED(consumer_, "unhandled type");
       break;
diff --git a/source/opt/types.h b/source/opt/types.h
index d77117d..fe0f39a 100644
--- a/source/opt/types.h
+++ b/source/opt/types.h
@@ -598,7 +598,7 @@
 DefineParameterlessType(Queue, queue);
 DefineParameterlessType(PipeStorage, pipe_storage);
 DefineParameterlessType(NamedBarrier, named_barrier);
-DefineParameterlessType(AccelerationStructureNV, acceleration_structure);
+DefineParameterlessType(AccelerationStructureNV, accelerationStructureNV);
 #undef DefineParameterlessType
 
 }  // namespace analysis
diff --git a/source/opt/upgrade_memory_model.cpp b/source/opt/upgrade_memory_model.cpp
index 8def55f..d8836a4 100644
--- a/source/opt/upgrade_memory_model.cpp
+++ b/source/opt/upgrade_memory_model.cpp
@@ -16,6 +16,7 @@
 
 #include <utility>
 
+#include "source/opt/ir_builder.h"
 #include "source/opt/ir_context.h"
 #include "source/util/make_unique.h"
 
@@ -68,6 +69,22 @@
   // instructions. Additionally, Workgroup storage class variables and function
   // parameters are implicitly coherent in GLSL450.
 
+  // Upgrade modf and frexp first since they generate new stores.
+  for (auto& func : *get_module()) {
+    func.ForEachInst([this](Instruction* inst) {
+      if (inst->opcode() == SpvOpExtInst) {
+        auto ext_inst = inst->GetSingleWordInOperand(1u);
+        if (ext_inst == GLSLstd450Modf || ext_inst == GLSLstd450Frexp) {
+          auto import =
+              get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(0u));
+          if (reinterpret_cast<char*>(import->GetInOperand(0u).words.data()) ==
+              std::string("GLSL.std.450")) {
+            UpgradeExtInst(inst);
+          }
+        }
+      }
+    });
+  }
   for (auto& func : *get_module()) {
     func.ForEachInst([this](Instruction* inst) {
       bool is_coherent = false;
@@ -104,11 +121,11 @@
 
       switch (inst->opcode()) {
         case SpvOpLoad:
-          UpgradeFlags(inst, 1u, is_coherent, is_volatile, kAvailability,
+          UpgradeFlags(inst, 1u, is_coherent, is_volatile, kVisibility,
                        kMemory);
           break;
         case SpvOpStore:
-          UpgradeFlags(inst, 2u, is_coherent, is_volatile, kVisibility,
+          UpgradeFlags(inst, 2u, is_coherent, is_volatile, kAvailability,
                        kMemory);
           break;
         case SpvOpCopyMemory:
@@ -125,11 +142,11 @@
           break;
         case SpvOpImageRead:
         case SpvOpImageSparseRead:
-          UpgradeFlags(inst, 2u, is_coherent, is_volatile, kAvailability,
-                       kImage);
+          UpgradeFlags(inst, 2u, is_coherent, is_volatile, kVisibility, kImage);
           break;
         case SpvOpImageWrite:
-          UpgradeFlags(inst, 3u, is_coherent, is_volatile, kVisibility, kImage);
+          UpgradeFlags(inst, 3u, is_coherent, is_volatile, kAvailability,
+                       kImage);
           break;
         default:
           break;
@@ -143,15 +160,15 @@
       }
       // According to SPV_KHR_vulkan_memory_model, if both available and
       // visible flags are used the first scope operand is for availability
-      // (reads) and the second is for visibiity (writes).
-      if (src_coherent) {
-        inst->AddOperand(
-            {SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(src_scope)}});
-      }
+      // (writes) and the second is for visibility (reads).
       if (dst_coherent) {
         inst->AddOperand(
             {SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(dst_scope)}});
       }
+      if (src_coherent) {
+        inst->AddOperand(
+            {SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(src_scope)}});
+      }
     });
   }
 }
@@ -581,5 +598,43 @@
   return false;
 }
 
+void UpgradeMemoryModel::UpgradeExtInst(Instruction* ext_inst) {
+  const bool is_modf = ext_inst->GetSingleWordInOperand(1u) == GLSLstd450Modf;
+  auto ptr_id = ext_inst->GetSingleWordInOperand(3u);
+  auto ptr_type_id = get_def_use_mgr()->GetDef(ptr_id)->type_id();
+  auto pointee_type_id =
+      get_def_use_mgr()->GetDef(ptr_type_id)->GetSingleWordInOperand(1u);
+  auto element_type_id = ext_inst->type_id();
+  std::vector<const analysis::Type*> element_types(2);
+  element_types[0] = context()->get_type_mgr()->GetType(element_type_id);
+  element_types[1] = context()->get_type_mgr()->GetType(pointee_type_id);
+  analysis::Struct struct_type(element_types);
+  uint32_t struct_id =
+      context()->get_type_mgr()->GetTypeInstruction(&struct_type);
+  // Change the operation
+  GLSLstd450 new_op = is_modf ? GLSLstd450ModfStruct : GLSLstd450FrexpStruct;
+  ext_inst->SetOperand(3u, {static_cast<uint32_t>(new_op)});
+  // Remove the pointer argument
+  ext_inst->RemoveOperand(5u);
+  // Set the type id to the new struct.
+  ext_inst->SetResultType(struct_id);
+
+  // The result is now a struct of the original result. The zero'th element is
+  // old result and should replace the old result. The one'th element needs to
+  // be stored via a new instruction.
+  auto where = ext_inst->NextNode();
+  InstructionBuilder builder(
+      context(), where,
+      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+  auto extract_0 =
+      builder.AddCompositeExtract(element_type_id, ext_inst->result_id(), {0});
+  context()->ReplaceAllUsesWith(ext_inst->result_id(), extract_0->result_id());
+  // The extract's input was just changed to itself, so fix that.
+  extract_0->SetInOperand(0u, {ext_inst->result_id()});
+  auto extract_1 =
+      builder.AddCompositeExtract(pointee_type_id, ext_inst->result_id(), {1});
+  builder.AddStore(ptr_id, extract_1->result_id());
+}
+
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/upgrade_memory_model.h b/source/opt/upgrade_memory_model.h
index 29c801c..9adc33b 100644
--- a/source/opt/upgrade_memory_model.h
+++ b/source/opt/upgrade_memory_model.h
@@ -118,6 +118,11 @@
   // Returns true if |scope_id| is SpvScopeDevice.
   bool IsDeviceScope(uint32_t scope_id);
 
+  // Upgrades GLSL.std.450 modf and frexp. Both instructions are replaced with
+  // their struct versions. New extracts and a store are added in order to
+  // facilitate adding memory model flags.
+  void UpgradeExtInst(Instruction* modf);
+
   // Caches the result of TraceInstruction. For a given result id and set of
   // indices, stores whether that combination is coherent and/or volatile.
   std::unordered_map<std::pair<uint32_t, std::vector<uint32_t>>,
diff --git a/source/opt/vector_dce.cpp b/source/opt/vector_dce.cpp
index 911242e..92532e3 100644
--- a/source/opt/vector_dce.cpp
+++ b/source/opt/vector_dce.cpp
@@ -66,7 +66,8 @@
 
     switch (current_inst->opcode()) {
       case SpvOpCompositeExtract:
-        MarkExtractUseAsLive(current_inst, live_components, &work_list);
+        MarkExtractUseAsLive(current_inst, current_item.components,
+                             live_components, &work_list);
         break;
       case SpvOpCompositeInsert:
         MarkInsertUsesAsLive(current_item, live_components, &work_list);
@@ -92,6 +93,7 @@
 }
 
 void VectorDCE::MarkExtractUseAsLive(const Instruction* current_inst,
+                                     const utils::BitVector& live_elements,
                                      LiveComponentMap* live_components,
                                      std::vector<WorkListItem>* work_list) {
   analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
@@ -102,7 +104,11 @@
   if (HasVectorOrScalarResult(operand_inst)) {
     WorkListItem new_item;
     new_item.instruction = operand_inst;
-    new_item.components.Set(current_inst->GetSingleWordInOperand(1));
+    if (current_inst->NumInOperands() < 2) {
+      new_item.components = live_elements;
+    } else {
+      new_item.components.Set(current_inst->GetSingleWordInOperand(1));
+    }
     AddItemToWorkListIfNeeded(new_item, live_components, work_list);
   }
 }
@@ -113,30 +119,44 @@
     std::vector<VectorDCE::WorkListItem>* work_list) {
   analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
 
-  uint32_t insert_position =
-      current_item.instruction->GetSingleWordInOperand(2);
+  if (current_item.instruction->NumInOperands() > 2) {
+    uint32_t insert_position =
+        current_item.instruction->GetSingleWordInOperand(2);
 
-  // Add the elements of the composite object that are used.
-  uint32_t operand_id =
-      current_item.instruction->GetSingleWordInOperand(kInsertCompositeIdInIdx);
-  Instruction* operand_inst = def_use_mgr->GetDef(operand_id);
+    // Add the elements of the composite object that are used.
+    uint32_t operand_id = current_item.instruction->GetSingleWordInOperand(
+        kInsertCompositeIdInIdx);
+    Instruction* operand_inst = def_use_mgr->GetDef(operand_id);
 
-  WorkListItem new_item;
-  new_item.instruction = operand_inst;
-  new_item.components = current_item.components;
-  new_item.components.Clear(insert_position);
+    WorkListItem new_item;
+    new_item.instruction = operand_inst;
+    new_item.components = current_item.components;
+    new_item.components.Clear(insert_position);
 
-  AddItemToWorkListIfNeeded(new_item, live_components, work_list);
+    AddItemToWorkListIfNeeded(new_item, live_components, work_list);
 
-  // Add the element being inserted if it is used.
-  if (current_item.components.Get(insert_position)) {
-    uint32_t obj_operand_id =
+    // Add the element being inserted if it is used.
+    if (current_item.components.Get(insert_position)) {
+      uint32_t obj_operand_id =
+          current_item.instruction->GetSingleWordInOperand(
+              kInsertObjectIdInIdx);
+      Instruction* obj_operand_inst = def_use_mgr->GetDef(obj_operand_id);
+      WorkListItem new_item_for_obj;
+      new_item_for_obj.instruction = obj_operand_inst;
+      new_item_for_obj.components.Set(0);
+      AddItemToWorkListIfNeeded(new_item_for_obj, live_components, work_list);
+    }
+  } else {
+    // If there are no indices, then this is a copy of the object being
+    // inserted.
+    uint32_t object_id =
         current_item.instruction->GetSingleWordInOperand(kInsertObjectIdInIdx);
-    Instruction* obj_operand_inst = def_use_mgr->GetDef(obj_operand_id);
-    WorkListItem new_item_for_obj;
-    new_item_for_obj.instruction = obj_operand_inst;
-    new_item_for_obj.components.Set(0);
-    AddItemToWorkListIfNeeded(new_item_for_obj, live_components, work_list);
+    Instruction* object_inst = def_use_mgr->GetDef(object_id);
+
+    WorkListItem new_item;
+    new_item.instruction = object_inst;
+    new_item.components = current_item.components;
+    AddItemToWorkListIfNeeded(new_item, live_components, work_list);
   }
 }
 
@@ -323,14 +343,23 @@
 bool VectorDCE::RewriteInsertInstruction(
     Instruction* current_inst, const utils::BitVector& live_components) {
   // If the value being inserted is not live, then we can skip the insert.
-  bool modified = false;
+
+  if (current_inst->NumInOperands() == 2) {
+    // If there are no indices, then this is the same as a copy.
+    context()->KillNamesAndDecorates(current_inst->result_id());
+    uint32_t object_id =
+        current_inst->GetSingleWordInOperand(kInsertObjectIdInIdx);
+    context()->ReplaceAllUsesWith(current_inst->result_id(), object_id);
+    return true;
+  }
+
   uint32_t insert_index = current_inst->GetSingleWordInOperand(2);
   if (!live_components.Get(insert_index)) {
-    modified = true;
     context()->KillNamesAndDecorates(current_inst->result_id());
     uint32_t composite_id =
         current_inst->GetSingleWordInOperand(kInsertCompositeIdInIdx);
     context()->ReplaceAllUsesWith(current_inst->result_id(), composite_id);
+    return true;
   }
 
   // If the values already in the composite are not used, then replace it with
@@ -342,9 +371,10 @@
     uint32_t undef_id = Type2Undef(current_inst->type_id());
     current_inst->SetInOperand(kInsertCompositeIdInIdx, {undef_id});
     context()->AnalyzeUses(current_inst);
+    return true;
   }
 
-  return modified;
+  return false;
 }
 
 void VectorDCE::AddItemToWorkListIfNeeded(
diff --git a/source/opt/vector_dce.h b/source/opt/vector_dce.h
index 4888699..4f039c5 100644
--- a/source/opt/vector_dce.h
+++ b/source/opt/vector_dce.h
@@ -53,7 +53,8 @@
     return IRContext::kAnalysisDefUse | IRContext::kAnalysisCFG |
            IRContext::kAnalysisInstrToBlockMapping |
            IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisDecorations |
-           IRContext::kAnalysisDominatorAnalysis | IRContext::kAnalysisNameMap;
+           IRContext::kAnalysisDominatorAnalysis | IRContext::kAnalysisNameMap |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
   }
 
  private:
@@ -128,6 +129,7 @@
   // live. If anything becomes live they are added to |work_list| and
   // |live_components| is updated accordingly.
   void MarkExtractUseAsLive(const Instruction* current_inst,
+                            const utils::BitVector& live_elements,
                             LiveComponentMap* live_components,
                             std::vector<WorkListItem>* work_list);
 
diff --git a/source/reduce/CMakeLists.txt b/source/reduce/CMakeLists.txt
index 57475ac..def4d21 100644
--- a/source/reduce/CMakeLists.txt
+++ b/source/reduce/CMakeLists.txt
@@ -13,29 +13,60 @@
 # limitations under the License.
 set(SPIRV_TOOLS_REDUCE_SOURCES
         change_operand_reduction_opportunity.h
-        operand_to_const_reduction_pass.h
-        operand_to_dominating_id_reduction_pass.h
+        change_operand_to_undef_reduction_opportunity.h
+        merge_blocks_reduction_opportunity.h
+        merge_blocks_reduction_opportunity_finder.h
+        operand_to_const_reduction_opportunity_finder.h
+        operand_to_undef_reduction_opportunity_finder.h
+        operand_to_dominating_id_reduction_opportunity_finder.h
         reducer.h
         reduction_opportunity.h
+        reduction_opportunity_finder.h
         reduction_pass.h
+        reduction_util.h
+        remove_block_reduction_opportunity.h
+        remove_block_reduction_opportunity_finder.h
         remove_instruction_reduction_opportunity.h
-        remove_opname_instruction_reduction_pass.h
-        remove_unreferenced_instruction_reduction_pass.h
+        remove_function_reduction_opportunity.h
+        remove_function_reduction_opportunity_finder.h
+        remove_opname_instruction_reduction_opportunity_finder.h
+        remove_selection_reduction_opportunity.h
+        remove_selection_reduction_opportunity_finder.h
+        remove_unreferenced_instruction_reduction_opportunity_finder.h
         structured_loop_to_selection_reduction_opportunity.h
-        structured_loop_to_selection_reduction_pass.h
+        structured_loop_to_selection_reduction_opportunity_finder.h
+        conditional_branch_to_simple_conditional_branch_opportunity_finder.h
+        conditional_branch_to_simple_conditional_branch_reduction_opportunity.h
+        simple_conditional_branch_to_branch_opportunity_finder.h
+        simple_conditional_branch_to_branch_reduction_opportunity.h
 
         change_operand_reduction_opportunity.cpp
-        operand_to_const_reduction_pass.cpp
-        operand_to_dominating_id_reduction_pass.cpp
+        change_operand_to_undef_reduction_opportunity.cpp
+        merge_blocks_reduction_opportunity.cpp
+        merge_blocks_reduction_opportunity_finder.cpp
+        operand_to_const_reduction_opportunity_finder.cpp
+        operand_to_undef_reduction_opportunity_finder.cpp
+        operand_to_dominating_id_reduction_opportunity_finder.cpp
         reducer.cpp
         reduction_opportunity.cpp
         reduction_pass.cpp
+        reduction_util.cpp
+        remove_block_reduction_opportunity.cpp
+        remove_block_reduction_opportunity_finder.cpp
+        remove_function_reduction_opportunity.cpp
+        remove_function_reduction_opportunity_finder.cpp
         remove_instruction_reduction_opportunity.cpp
-        remove_unreferenced_instruction_reduction_pass.cpp
-        remove_opname_instruction_reduction_pass.cpp
+        remove_selection_reduction_opportunity.cpp
+        remove_selection_reduction_opportunity_finder.cpp
+        remove_unreferenced_instruction_reduction_opportunity_finder.cpp
+        remove_opname_instruction_reduction_opportunity_finder.cpp
         structured_loop_to_selection_reduction_opportunity.cpp
-        structured_loop_to_selection_reduction_pass.cpp
-        )
+        structured_loop_to_selection_reduction_opportunity_finder.cpp
+        conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp
+        conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp
+        simple_conditional_branch_to_branch_opportunity_finder.cpp
+        simple_conditional_branch_to_branch_reduction_opportunity.cpp
+)
 
 if(MSVC)
   # Enable parallel builds across four cores for this lib
diff --git a/source/reduce/change_operand_reduction_opportunity.cpp b/source/reduce/change_operand_reduction_opportunity.cpp
index 5430d3e..c3f6fd7 100644
--- a/source/reduce/change_operand_reduction_opportunity.cpp
+++ b/source/reduce/change_operand_reduction_opportunity.cpp
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "change_operand_reduction_opportunity.h"
+#include "source/reduce/change_operand_reduction_opportunity.h"
 
 namespace spvtools {
 namespace reduce {
diff --git a/source/reduce/change_operand_reduction_opportunity.h b/source/reduce/change_operand_reduction_opportunity.h
index 9e43ec9..18e6ca1 100644
--- a/source/reduce/change_operand_reduction_opportunity.h
+++ b/source/reduce/change_operand_reduction_opportunity.h
@@ -15,40 +15,33 @@
 #ifndef SOURCE_REDUCE_CHANGE_OPERAND_REDUCTION_OPPORTUNITY_H_
 #define SOURCE_REDUCE_CHANGE_OPERAND_REDUCTION_OPPORTUNITY_H_
 
-#include "reduction_opportunity.h"
 #include "source/opt/instruction.h"
+#include "source/reduce/reduction_opportunity.h"
 #include "spirv-tools/libspirv.h"
 
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
-
-// Captures the opportunity to change an id operand of an instruction to some
-// other id.
+// An opportunity to replace an id operand of an instruction with some other id.
 class ChangeOperandReductionOpportunity : public ReductionOpportunity {
  public:
   // Constructs the opportunity to replace operand |operand_index| of |inst|
   // with |new_id|.
-  ChangeOperandReductionOpportunity(Instruction* inst, uint32_t operand_index,
-                                    uint32_t new_id)
+  ChangeOperandReductionOpportunity(opt::Instruction* inst,
+                                    uint32_t operand_index, uint32_t new_id)
       : inst_(inst),
         operand_index_(operand_index),
         original_id_(inst->GetOperand(operand_index).words[0]),
         original_type_(inst->GetOperand(operand_index).type),
         new_id_(new_id) {}
 
-  // Determines whether the opportunity can be applied; it may have been viable
-  // when discovered but later disabled by the application of some other
-  // reduction opportunity.
   bool PreconditionHolds() override;
 
  protected:
-  // Apply the change of operand.
   void Apply() override;
 
  private:
-  Instruction* const inst_;
+  opt::Instruction* const inst_;
   const uint32_t operand_index_;
   const uint32_t original_id_;
   const spv_operand_type_t original_type_;
diff --git a/source/reduce/change_operand_to_undef_reduction_opportunity.cpp b/source/reduce/change_operand_to_undef_reduction_opportunity.cpp
new file mode 100644
index 0000000..8e33da6
--- /dev/null
+++ b/source/reduce/change_operand_to_undef_reduction_opportunity.cpp
@@ -0,0 +1,41 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/change_operand_to_undef_reduction_opportunity.h"
+
+#include "source/opt/ir_context.h"
+#include "source/reduce/reduction_util.h"
+
+namespace spvtools {
+namespace reduce {
+
+bool ChangeOperandToUndefReductionOpportunity::PreconditionHolds() {
+  // Check that the instruction still has the original operand.
+  return inst_->NumOperands() > operand_index_ &&
+         inst_->GetOperand(operand_index_).words[0] == original_id_;
+}
+
+void ChangeOperandToUndefReductionOpportunity::Apply() {
+  auto operand = inst_->GetOperand(operand_index_);
+  auto operand_id = operand.words[0];
+  auto operand_id_def = context_->get_def_use_mgr()->GetDef(operand_id);
+  auto operand_type_id = operand_id_def->type_id();
+  // The opportunity should not exist unless this holds.
+  assert(operand_type_id);
+  auto undef_id = FindOrCreateGlobalUndef(context_, operand_type_id);
+  inst_->SetOperand(operand_index_, {undef_id});
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/change_operand_to_undef_reduction_opportunity.h b/source/reduce/change_operand_to_undef_reduction_opportunity.h
new file mode 100644
index 0000000..ffd3155
--- /dev/null
+++ b/source/reduce/change_operand_to_undef_reduction_opportunity.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_CHANGE_OPERAND_TO_UNDEF_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_CHANGE_OPERAND_TO_UNDEF_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/instruction.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "spirv-tools/libspirv.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to replace an id operand of an instruction with undef.
+class ChangeOperandToUndefReductionOpportunity : public ReductionOpportunity {
+ public:
+  // Constructs the opportunity to replace operand |operand_index| of |inst|
+  // with undef.
+  ChangeOperandToUndefReductionOpportunity(opt::IRContext* context,
+                                           opt::Instruction* inst,
+                                           uint32_t operand_index)
+      : context_(context),
+        inst_(inst),
+        operand_index_(operand_index),
+        original_id_(inst->GetOperand(operand_index).words[0]) {}
+
+  bool PreconditionHolds() override;
+
+ protected:
+  void Apply() override;
+
+ private:
+  opt::IRContext* context_;
+  opt::Instruction* const inst_;
+  const uint32_t operand_index_;
+  const uint32_t original_id_;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_CHANGE_OPERAND_TO_UNDEF_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp
new file mode 100644
index 0000000..2feca4a
--- /dev/null
+++ b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp
@@ -0,0 +1,89 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h"
+
+#include "source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h"
+#include "source/reduce/reduction_util.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::IRContext;
+using opt::Instruction;
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+ConditionalBranchToSimpleConditionalBranchOpportunityFinder::
+    GetAvailableOpportunities(IRContext* context) const {
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+  // Find the opportunities for redirecting all false targets before the
+  // opportunities for redirecting all true targets because the former
+  // opportunities disable the latter, and vice versa, and the efficiency of the
+  // reducer is improved by avoiding contiguous opportunities that disable one
+  // another.
+  for (bool redirect_to_true : {true, false}) {
+    // Consider every function.
+    for (auto& function : *context->module()) {
+      // Consider every block in the function.
+      for (auto& block : function) {
+        // The terminator must be SpvOpBranchConditional.
+        Instruction* terminator = block.terminator();
+        if (terminator->opcode() != SpvOpBranchConditional) {
+          continue;
+        }
+
+        uint32_t true_block_id =
+            terminator->GetSingleWordInOperand(kTrueBranchOperandIndex);
+        uint32_t false_block_id =
+            terminator->GetSingleWordInOperand(kFalseBranchOperandIndex);
+
+        // The conditional branch must not already be simplified.
+        if (true_block_id == false_block_id) {
+          continue;
+        }
+
+        // The redirected target must not be a back-edge to a structured loop
+        // header.
+        uint32_t redirected_block_id =
+            redirect_to_true ? false_block_id : true_block_id;
+        uint32_t containing_loop_header =
+            context->GetStructuredCFGAnalysis()->ContainingLoop(block.id());
+        // The structured CFG analysis does not include a loop header as part
+        // of the loop construct, but we want to include it, so handle this
+        // special case:
+        if (block.GetLoopMergeInst() != nullptr) {
+          containing_loop_header = block.id();
+        }
+        if (redirected_block_id == containing_loop_header) {
+          continue;
+        }
+
+        result.push_back(
+            MakeUnique<
+                ConditionalBranchToSimpleConditionalBranchReductionOpportunity>(
+                block.terminator(), redirect_to_true));
+      }
+    }
+  }
+  return result;
+}
+
+std::string
+ConditionalBranchToSimpleConditionalBranchOpportunityFinder::GetName() const {
+  return "ConditionalBranchToSimpleConditionalBranchOpportunityFinder";
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h
new file mode 100644
index 0000000..c582a88
--- /dev/null
+++ b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_SIMPLIFY_SELECTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_SIMPLIFY_SELECTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities to simplify conditional branches into simple
+// conditional branches (conditional branches with one target).
+class ConditionalBranchToSimpleConditionalBranchOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const override;
+
+  std::string GetName() const override;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_SIMPLIFY_SELECTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp b/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp
new file mode 100644
index 0000000..92c621e
--- /dev/null
+++ b/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp
@@ -0,0 +1,54 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h"
+
+#include "source/reduce/reduction_util.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::Instruction;
+
+ConditionalBranchToSimpleConditionalBranchReductionOpportunity::
+    ConditionalBranchToSimpleConditionalBranchReductionOpportunity(
+        Instruction* conditional_branch_instruction, bool redirect_to_true)
+    : conditional_branch_instruction_(conditional_branch_instruction),
+      redirect_to_true_(redirect_to_true) {}
+
+bool ConditionalBranchToSimpleConditionalBranchReductionOpportunity::
+    PreconditionHolds() {
+  // Another opportunity may have already simplified this conditional branch,
+  // which should disable this opportunity.
+  return conditional_branch_instruction_->GetSingleWordInOperand(
+             kTrueBranchOperandIndex) !=
+         conditional_branch_instruction_->GetSingleWordInOperand(
+             kFalseBranchOperandIndex);
+}
+
+void ConditionalBranchToSimpleConditionalBranchReductionOpportunity::Apply() {
+  uint32_t operand_to_modify =
+      redirect_to_true_ ? kFalseBranchOperandIndex : kTrueBranchOperandIndex;
+  uint32_t operand_to_copy =
+      redirect_to_true_ ? kTrueBranchOperandIndex : kFalseBranchOperandIndex;
+
+  // Do the branch redirection.
+  conditional_branch_instruction_->SetInOperand(
+      operand_to_modify,
+      {conditional_branch_instruction_->GetSingleWordInOperand(
+          operand_to_copy)});
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h b/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h
new file mode 100644
index 0000000..421906b
--- /dev/null
+++ b/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_SIMPLIFY_CONDITIONAL_BRANCH_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_SIMPLIFY_CONDITIONAL_BRANCH_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/basic_block.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to simplify a conditional branch to a simple conditional
+// branch (a conditional branch with one target).
+class ConditionalBranchToSimpleConditionalBranchReductionOpportunity
+    : public ReductionOpportunity {
+ public:
+  // Constructs an opportunity to simplify |conditional_branch_instruction|. If
+  // |redirect_to_true| is true, the false target will be changed to also point
+  // to the true target; otherwise, the true target will be changed to also
+  // point to the false target.
+  explicit ConditionalBranchToSimpleConditionalBranchReductionOpportunity(
+      opt::Instruction* conditional_branch_instruction, bool redirect_to_true);
+
+  bool PreconditionHolds() override;
+
+ protected:
+  void Apply() override;
+
+ private:
+  opt::Instruction* conditional_branch_instruction_;
+
+  // If true, the false target will be changed to point to the true target;
+  // otherwise, the true target will be changed to point to the false target.
+  bool redirect_to_true_;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_SIMPLIFY_CONDITIONAL_BRANCH_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/merge_blocks_reduction_opportunity.cpp b/source/reduce/merge_blocks_reduction_opportunity.cpp
new file mode 100644
index 0000000..42c7843
--- /dev/null
+++ b/source/reduce/merge_blocks_reduction_opportunity.cpp
@@ -0,0 +1,83 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/merge_blocks_reduction_opportunity.h"
+
+#include "source/opt/block_merge_util.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::BasicBlock;
+using opt::Function;
+using opt::IRContext;
+
+MergeBlocksReductionOpportunity::MergeBlocksReductionOpportunity(
+    IRContext* context, Function* function, BasicBlock* block) {
+  // Precondition: the terminator has to be OpBranch.
+  assert(block->terminator()->opcode() == SpvOpBranch);
+  context_ = context;
+  function_ = function;
+  // Get the successor block associated with the OpBranch.
+  successor_block_ =
+      context->cfg()->block(block->terminator()->GetSingleWordInOperand(0));
+}
+
+bool MergeBlocksReductionOpportunity::PreconditionHolds() {
+  // Merge block opportunities can disable each other.
+  // Example: Given blocks: A->B->C.
+  // A is a loop header; B and C are blocks in the loop; C ends with OpReturn.
+  // There are two opportunities: B and C can be merged with their predecessors.
+  // Merge C. B now ends with OpReturn. We now just have: A->B.
+  // Merge B is now disabled, as this would lead to A, a loop header, ending
+  // with an OpReturn, which is invalid.
+
+  const auto predecessors = context_->cfg()->preds(successor_block_->id());
+  assert(1 == predecessors.size() &&
+         "For a successor to be merged into its predecessor, exactly one "
+         "predecessor must be present.");
+  const uint32_t predecessor_id = predecessors[0];
+  BasicBlock* predecessor_block = context_->get_instr_block(predecessor_id);
+  return opt::blockmergeutil::CanMergeWithSuccessor(context_,
+                                                    predecessor_block);
+}
+
+void MergeBlocksReductionOpportunity::Apply() {
+  // While the original block that targeted the successor may not exist anymore
+  // (it might have been merged with another block), some block must exist that
+  // targets the successor.  Find it.
+
+  const auto predecessors = context_->cfg()->preds(successor_block_->id());
+  assert(1 == predecessors.size() &&
+         "For a successor to be merged into its predecessor, exactly one "
+         "predecessor must be present.");
+  const uint32_t predecessor_id = predecessors[0];
+
+  // We need an iterator pointing to the predecessor, hence the loop.
+  for (auto bi = function_->begin(); bi != function_->end(); ++bi) {
+    if (bi->id() == predecessor_id) {
+      opt::blockmergeutil::MergeWithSuccessor(context_, function_, bi);
+      // Block merging changes the control flow graph, so invalidate it.
+      context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone);
+      return;
+    }
+  }
+
+  assert(false &&
+         "Unreachable: we should have found a block with the desired id.");
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/merge_blocks_reduction_opportunity.h b/source/reduce/merge_blocks_reduction_opportunity.h
new file mode 100644
index 0000000..5c9180b
--- /dev/null
+++ b/source/reduce/merge_blocks_reduction_opportunity.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_MERGE_BLOCKS_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_MERGE_BLOCKS_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/basic_block.h"
+#include "source/opt/function.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to merge two blocks into one.
+class MergeBlocksReductionOpportunity : public ReductionOpportunity {
+ public:
+  // Creates the opportunity to merge |block| with its successor, where |block|
+  // is inside |function|, and |context| is the enclosing IR context.
+  MergeBlocksReductionOpportunity(opt::IRContext* context,
+                                  opt::Function* function,
+                                  opt::BasicBlock* block);
+
+  bool PreconditionHolds() override;
+
+ protected:
+  void Apply() override;
+
+ private:
+  opt::IRContext* context_;
+  opt::Function* function_;
+
+  // Rather than holding on to the block that can be merged with its successor,
+  // we hold on to its successor. This is because the predecessor block might
+  // get merged with *its* predecessor, and so will no longer exist, while the
+  // successor will continue to exist until this opportunity gets applied.
+  opt::BasicBlock* successor_block_;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_MERGE_BLOCKS_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/merge_blocks_reduction_opportunity_finder.cpp b/source/reduce/merge_blocks_reduction_opportunity_finder.cpp
new file mode 100644
index 0000000..89d6263
--- /dev/null
+++ b/source/reduce/merge_blocks_reduction_opportunity_finder.cpp
@@ -0,0 +1,48 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/merge_blocks_reduction_opportunity_finder.h"
+#include "source/opt/block_merge_util.h"
+#include "source/reduce/merge_blocks_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::IRContext;
+
+std::string MergeBlocksReductionOpportunityFinder::GetName() const {
+  return "MergeBlocksReductionOpportunityFinder";
+}
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+MergeBlocksReductionOpportunityFinder::GetAvailableOpportunities(
+    IRContext* context) const {
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+  // Consider every block in every function.
+  for (auto& function : *context->module()) {
+    for (auto& block : function) {
+      // See whether it is possible to merge this block with its successor.
+      if (opt::blockmergeutil::CanMergeWithSuccessor(context, &block)) {
+        // It is, so record an opportunity to do this.
+        result.push_back(spvtools::MakeUnique<MergeBlocksReductionOpportunity>(
+            context, &function, &block));
+      }
+    }
+  }
+  return result;
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/merge_blocks_reduction_opportunity_finder.h b/source/reduce/merge_blocks_reduction_opportunity_finder.h
new file mode 100644
index 0000000..dbf82fe
--- /dev/null
+++ b/source/reduce/merge_blocks_reduction_opportunity_finder.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_MERGE_BLOCKS_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_MERGE_BLOCKS_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder of opportunities to merge blocks together.
+class MergeBlocksReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  MergeBlocksReductionOpportunityFinder() = default;
+
+  ~MergeBlocksReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_MERGE_BLOCKS_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/operand_to_const_reduction_pass.cpp b/source/reduce/operand_to_const_reduction_opportunity_finder.cpp
similarity index 88%
rename from source/reduce/operand_to_const_reduction_pass.cpp
rename to source/reduce/operand_to_const_reduction_opportunity_finder.cpp
index 35c0f4b..3e0a224 100644
--- a/source/reduce/operand_to_const_reduction_pass.cpp
+++ b/source/reduce/operand_to_const_reduction_opportunity_finder.cpp
@@ -12,18 +12,19 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "operand_to_const_reduction_pass.h"
-#include "change_operand_reduction_opportunity.h"
+#include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
+
 #include "source/opt/instruction.h"
+#include "source/reduce/change_operand_reduction_opportunity.h"
 
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
+using opt::IRContext;
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
-OperandToConstReductionPass::GetAvailableOpportunities(
-    opt::IRContext* context) const {
+OperandToConstReductionOpportunityFinder::GetAvailableOpportunities(
+    IRContext* context) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
   assert(result.empty());
 
@@ -74,8 +75,8 @@
   return result;
 }
 
-std::string OperandToConstReductionPass::GetName() const {
-  return "OperandToConstReductionPass";
+std::string OperandToConstReductionOpportunityFinder::GetName() const {
+  return "OperandToConstReductionOpportunityFinder";
 }
 
 }  // namespace reduce
diff --git a/source/reduce/operand_to_const_reduction_opportunity_finder.h b/source/reduce/operand_to_const_reduction_opportunity_finder.h
new file mode 100644
index 0000000..93c0dcd
--- /dev/null
+++ b/source/reduce/operand_to_const_reduction_opportunity_finder.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities to replace id operands of instructions with ids of
+// constants.  This reduces the extent to which ids of non-constants are used,
+// paving the way for instructions that generate them to be eliminated.
+class OperandToConstReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  OperandToConstReductionOpportunityFinder() = default;
+
+  ~OperandToConstReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/operand_to_const_reduction_pass.h b/source/reduce/operand_to_const_reduction_pass.h
deleted file mode 100644
index fd7cfa0..0000000
--- a/source/reduce/operand_to_const_reduction_pass.h
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright (c) 2018 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_PASS_H_
-#define SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_PASS_H_
-
-#include "reduction_pass.h"
-
-namespace spvtools {
-namespace reduce {
-
-// A reduction pass for turning id operands of instructions into ids of
-// constants.  This reduces the extent to which ids of non-constants are used,
-// paving the way for instructions that generate them to be eliminated by other
-// passes.
-class OperandToConstReductionPass : public ReductionPass {
- public:
-  // Creates the reduction pass in the context of the given target environment
-  // |target_env|
-  explicit OperandToConstReductionPass(const spv_target_env target_env)
-      : ReductionPass(target_env) {}
-
-  ~OperandToConstReductionPass() override = default;
-
-  // The name of this pass.
-  std::string GetName() const final;
-
- protected:
-  // Finds all opportunities for replacing an operand with a constant in the
-  // given module.
-  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
-
- private:
-};
-
-}  // namespace reduce
-}  // namespace spvtools
-
-#endif  // SOURCE_REDUCE_OPERAND_TO_CONST_REDUCTION_PASS_H_
diff --git a/source/reduce/operand_to_dominating_id_reduction_pass.cpp b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp
similarity index 84%
rename from source/reduce/operand_to_dominating_id_reduction_pass.cpp
rename to source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp
index 9280a41..13beb89 100644
--- a/source/reduce/operand_to_dominating_id_reduction_pass.cpp
+++ b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp
@@ -12,18 +12,21 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "operand_to_dominating_id_reduction_pass.h"
-#include "change_operand_reduction_opportunity.h"
+#include "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h"
+
 #include "source/opt/instruction.h"
+#include "source/reduce/change_operand_reduction_opportunity.h"
 
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
+using opt::Function;
+using opt::IRContext;
+using opt::Instruction;
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
-OperandToDominatingIdReductionPass::GetAvailableOpportunities(
-    opt::IRContext* context) const {
+OperandToDominatingIdReductionOpportunityFinder::GetAvailableOpportunities(
+    IRContext* context) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
   // Go through every instruction in every block, considering it as a potential
@@ -55,11 +58,12 @@
   return result;
 }
 
-void OperandToDominatingIdReductionPass::GetOpportunitiesForDominatingInst(
-    std::vector<std::unique_ptr<ReductionOpportunity>>* opportunities,
-    opt::Instruction* candidate_dominator,
-    opt::Function::iterator candidate_dominator_block, opt::Function* function,
-    opt::IRContext* context) const {
+void OperandToDominatingIdReductionOpportunityFinder::
+    GetOpportunitiesForDominatingInst(
+        std::vector<std::unique_ptr<ReductionOpportunity>>* opportunities,
+        Instruction* candidate_dominator,
+        Function::iterator candidate_dominator_block, Function* function,
+        IRContext* context) const {
   assert(candidate_dominator->HasResultId());
   assert(candidate_dominator->type_id());
   auto dominator_analysis = context->GetDominatorAnalysis(function);
@@ -106,8 +110,8 @@
   }
 }
 
-std::string OperandToDominatingIdReductionPass::GetName() const {
-  return "OperandToDominatingIdReductionPass";
+std::string OperandToDominatingIdReductionOpportunityFinder::GetName() const {
+  return "OperandToDominatingIdReductionOpportunityFinder";
 }
 
 }  // namespace reduce
diff --git a/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h
new file mode 100644
index 0000000..7745ff7
--- /dev/null
+++ b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder that aims to bring to SPIR-V (and generalize) the idea from
+// human-readable languages of e.g. finding opportunities to replace an
+// expression with one of its arguments, (x + y) -> x, or with a reference to an
+// identifier that was assigned to higher up in the program.  The generalization
+// of this is to replace an id with a different id of the same type defined in
+// some dominating instruction.
+//
+// If id x is defined and then used several times, changing each use of x to
+// some dominating definition may eventually allow the statement defining x
+// to be eliminated by another pass.
+class OperandToDominatingIdReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  OperandToDominatingIdReductionOpportunityFinder() = default;
+
+  ~OperandToDominatingIdReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+  void GetOpportunitiesForDominatingInst(
+      std::vector<std::unique_ptr<ReductionOpportunity>>* opportunities,
+      opt::Instruction* dominating_instruction,
+      opt::Function::iterator candidate_dominator_block,
+      opt::Function* function, opt::IRContext* context) const;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/operand_to_dominating_id_reduction_pass.h b/source/reduce/operand_to_dominating_id_reduction_pass.h
deleted file mode 100644
index b62b6ae..0000000
--- a/source/reduce/operand_to_dominating_id_reduction_pass.h
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (c) 2018 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_PASS_H_
-#define SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_PASS_H_
-
-#include "reduction_pass.h"
-
-namespace spvtools {
-namespace reduce {
-
-// A reduction pass that aims to bring to SPIR-V (and generalize) the idea from
-// human-readable languages of e.g. replacing an expression with one of its
-// arguments, (x + y) -> x, or with a reference to an identifier that was
-// assigned to higher up in the program.  The generalization of this is to
-// replace an id with a different id of the same type defined in some
-// dominating instruction.
-//
-// If id x is defined and then used several times, changing each use of x to
-// some dominating definition may eventually allow the statement defining x
-// to be eliminated by another pass.
-class OperandToDominatingIdReductionPass : public ReductionPass {
- public:
-  // Creates the reduction pass in the context of the given target environment
-  // |target_env|
-  explicit OperandToDominatingIdReductionPass(const spv_target_env target_env)
-      : ReductionPass(target_env) {}
-
-  ~OperandToDominatingIdReductionPass() override = default;
-
-  // The name of this pass.
-  std::string GetName() const final;
-
- protected:
-  // Finds all opportunities for replacing an operand with a dominating
-  // instruction in a given module.
-  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
-
- private:
-  void GetOpportunitiesForDominatingInst(
-      std::vector<std::unique_ptr<ReductionOpportunity>>* opportunities,
-      opt::Instruction* dominating_instruction,
-      opt::Function::iterator candidate_dominator_block,
-      opt::Function* function, opt::IRContext* context) const;
-};
-
-}  // namespace reduce
-}  // namespace spvtools
-
-#endif  // SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_PASS_H_
diff --git a/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp b/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp
new file mode 100644
index 0000000..579b7df
--- /dev/null
+++ b/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp
@@ -0,0 +1,94 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/operand_to_undef_reduction_opportunity_finder.h"
+
+#include "source/opt/instruction.h"
+#include "source/reduce/change_operand_to_undef_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::IRContext;
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+OperandToUndefReductionOpportunityFinder::GetAvailableOpportunities(
+    IRContext* context) const {
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+  for (auto& function : *context->module()) {
+    for (auto& block : function) {
+      for (auto& inst : block) {
+        // Skip instructions that result in a pointer type.
+        auto type_id = inst.type_id();
+        if (type_id) {
+          auto type_id_def = context->get_def_use_mgr()->GetDef(type_id);
+          if (type_id_def->opcode() == SpvOpTypePointer) {
+            continue;
+          }
+        }
+
+        // We iterate through the operands using an explicit index (rather
+        // than using a lambda) so that we use said index in the construction
+        // of a ChangeOperandToUndefReductionOpportunity
+        for (uint32_t index = 0; index < inst.NumOperands(); index++) {
+          const auto& operand = inst.GetOperand(index);
+
+          if (spvIsInIdType(operand.type)) {
+            const auto operand_id = operand.words[0];
+            auto operand_id_def =
+                context->get_def_use_mgr()->GetDef(operand_id);
+
+            // Skip constant and undef operands.
+            // We always want the reducer to make the module "smaller", which
+            // ensures termination.
+            // Therefore, we assume: id > undef id > constant id.
+            if (spvOpcodeIsConstantOrUndef(operand_id_def->opcode())) {
+              continue;
+            }
+
+            // Don't replace function operands with undef.
+            if (operand_id_def->opcode() == SpvOpFunction) {
+              continue;
+            }
+
+            // Only consider operands that have a type.
+            auto operand_type_id = operand_id_def->type_id();
+            if (operand_type_id) {
+              auto operand_type_id_def =
+                  context->get_def_use_mgr()->GetDef(operand_type_id);
+
+              // Skip pointer operands.
+              if (operand_type_id_def->opcode() == SpvOpTypePointer) {
+                continue;
+              }
+
+              result.push_back(
+                  MakeUnique<ChangeOperandToUndefReductionOpportunity>(
+                      context, &inst, index));
+            }
+          }
+        }
+      }
+    }
+  }
+  return result;
+}
+
+std::string OperandToUndefReductionOpportunityFinder::GetName() const {
+  return "OperandToUndefReductionOpportunityFinder";
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/operand_to_undef_reduction_opportunity_finder.h b/source/reduce/operand_to_undef_reduction_opportunity_finder.h
new file mode 100644
index 0000000..9cdd8cd
--- /dev/null
+++ b/source/reduce/operand_to_undef_reduction_opportunity_finder.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_OPERAND_TO_UNDEF_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_OPERAND_TO_UNDEF_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder of opportunities to replace id operands of instructions with ids of
+// undef.
+class OperandToUndefReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  OperandToUndefReductionOpportunityFinder() = default;
+
+  ~OperandToUndefReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_OPERAND_TO_UNDEF_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/pch_source_reduce.h b/source/reduce/pch_source_reduce.h
index 823b55a..6c0da0c 100644
--- a/source/reduce/pch_source_reduce.h
+++ b/source/reduce/pch_source_reduce.h
@@ -16,8 +16,8 @@
 #include <functional>
 #include <string>
 #include "source/reduce/change_operand_reduction_opportunity.h"
-#include "source/reduce/operand_to_const_reduction_pass.h"
+#include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
 #include "source/reduce/reduction_opportunity.h"
 #include "source/reduce/reduction_pass.h"
 #include "source/reduce/remove_instruction_reduction_opportunity.h"
-#include "source/reduce/remove_unreferenced_instruction_reduction_pass.h"
+#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
diff --git a/source/reduce/reducer.cpp b/source/reduce/reducer.cpp
index 4f4429a..a677be3 100644
--- a/source/reduce/reducer.cpp
+++ b/source/reduce/reducer.cpp
@@ -12,14 +12,25 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "source/reduce/reducer.h"
+
 #include <cassert>
 #include <sstream>
 
+#include "source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h"
+#include "source/reduce/merge_blocks_reduction_opportunity_finder.h"
+#include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
+#include "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h"
+#include "source/reduce/operand_to_undef_reduction_opportunity_finder.h"
+#include "source/reduce/remove_block_reduction_opportunity_finder.h"
+#include "source/reduce/remove_function_reduction_opportunity_finder.h"
+#include "source/reduce/remove_opname_instruction_reduction_opportunity_finder.h"
+#include "source/reduce/remove_selection_reduction_opportunity_finder.h"
+#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
+#include "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h"
+#include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h"
 #include "source/spirv_reducer_options.h"
 
-#include "reducer.h"
-#include "reduction_pass.h"
-
 namespace spvtools {
 namespace reduce {
 
@@ -53,13 +64,25 @@
 
 Reducer::ReductionResultStatus Reducer::Run(
     std::vector<uint32_t>&& binary_in, std::vector<uint32_t>* binary_out,
-    spv_const_reducer_options options) const {
-  std::vector<uint32_t> current_binary = binary_in;
+    spv_const_reducer_options options,
+    spv_validator_options validator_options) const {
+  std::vector<uint32_t> current_binary(std::move(binary_in));
+
+  spvtools::SpirvTools tools(impl_->target_env);
+  assert(tools.IsValid() && "Failed to create SPIRV-Tools interface");
 
   // Keeps track of how many reduction attempts have been tried.  Reduction
   // bails out if this reaches a given limit.
   uint32_t reductions_applied = 0;
 
+  // Initial state should be valid.
+  if (!tools.Validate(&current_binary[0], current_binary.size(),
+                      validator_options)) {
+    impl_->consumer(SPV_MSG_INFO, nullptr, {},
+                    "Initial binary is invalid; stopping.");
+    return Reducer::ReductionResultStatus::kInitialStateInvalid;
+  }
+
   // Initial state should be interesting.
   if (!impl_->interestingness_function(current_binary, reductions_applied)) {
     impl_->consumer(SPV_MSG_INFO, nullptr, {},
@@ -93,25 +116,31 @@
       do {
         auto maybe_result = pass->TryApplyReduction(current_binary);
         if (maybe_result.empty()) {
-          // This pass did not have any impact, so move on to the next pass.
+          // For this round, the pass has no more opportunities (chunks) to
+          // apply, so move on to the next pass.
           impl_->consumer(
               SPV_MSG_INFO, nullptr, {},
               ("Pass " + pass->GetName() + " did not make a reduction step.")
                   .c_str());
           break;
         }
+        bool interesting = false;
         std::stringstream stringstream;
         reductions_applied++;
         stringstream << "Pass " << pass->GetName() << " made reduction step "
                      << reductions_applied << ".";
         impl_->consumer(SPV_MSG_INFO, nullptr, {},
                         (stringstream.str().c_str()));
-        if (!spvtools::SpirvTools(impl_->target_env).Validate(maybe_result)) {
+        if (!tools.Validate(&maybe_result[0], maybe_result.size(),
+                            validator_options)) {
           // The reduction step went wrong and an invalid binary was produced.
           // By design, this shouldn't happen; this is a safeguard to stop an
           // invalid binary from being regarded as interesting.
           impl_->consumer(SPV_MSG_INFO, nullptr, {},
                           "Reduction step produced an invalid binary.");
+          if (options->fail_on_validation_error) {
+            return Reducer::ReductionResultStatus::kStateInvalid;
+          }
         } else if (impl_->interestingness_function(maybe_result,
                                                    reductions_applied)) {
           // Success!  The binary produced by this reduction step is
@@ -120,8 +149,11 @@
           impl_->consumer(SPV_MSG_INFO, nullptr, {},
                           "Reduction step succeeded.");
           current_binary = std::move(maybe_result);
+          interesting = true;
           another_round_worthwhile = true;
         }
+        // We must call this before the next call to TryApplyReduction.
+        pass->NotifyInteresting(interesting);
         // Bail out if the reduction step limit has been reached.
       } while (!impl_->ReachedStepLimit(reductions_applied, options));
     }
@@ -140,9 +172,38 @@
   return Reducer::ReductionResultStatus::kComplete;
 }
 
+void Reducer::AddDefaultReductionPasses() {
+  AddReductionPass(spvtools::MakeUnique<
+                   RemoveOpNameInstructionReductionOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<OperandToUndefReductionOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<OperandToConstReductionOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<OperandToDominatingIdReductionOpportunityFinder>());
+  AddReductionPass(spvtools::MakeUnique<
+                   RemoveUnreferencedInstructionReductionOpportunityFinder>());
+  AddReductionPass(spvtools::MakeUnique<
+                   StructuredLoopToSelectionReductionOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<MergeBlocksReductionOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<RemoveFunctionReductionOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<RemoveBlockReductionOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<RemoveSelectionReductionOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<
+          ConditionalBranchToSimpleConditionalBranchOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<SimpleConditionalBranchToBranchOpportunityFinder>());
+}
+
 void Reducer::AddReductionPass(
-    std::unique_ptr<ReductionPass>&& reduction_pass) {
-  impl_->passes.push_back(std::move(reduction_pass));
+    std::unique_ptr<ReductionOpportunityFinder>&& finder) {
+  impl_->passes.push_back(spvtools::MakeUnique<ReductionPass>(
+      impl_->target_env, std::move(finder)));
 }
 
 bool Reducer::Impl::ReachedStepLimit(uint32_t current_step,
diff --git a/source/reduce/reducer.h b/source/reduce/reducer.h
index 3a4c26c..a9b28c3 100644
--- a/source/reduce/reducer.h
+++ b/source/reduce/reducer.h
@@ -18,10 +18,9 @@
 #include <functional>
 #include <string>
 
+#include "source/reduce/reduction_pass.h"
 #include "spirv-tools/libspirv.hpp"
 
-#include "reduction_pass.h"
-
 namespace spvtools {
 namespace reduce {
 
@@ -33,7 +32,12 @@
   enum ReductionResultStatus {
     kInitialStateNotInteresting,
     kReachedStepLimit,
-    kComplete
+    kComplete,
+    kInitialStateInvalid,
+
+    // Returned when the fail-on-validation-error option is set and a
+    // reduction step yields a state that fails validation.
+    kStateInvalid,
   };
 
   // The type for a function that will take a binary and return true if and
@@ -75,16 +79,20 @@
   void SetInterestingnessFunction(
       InterestingnessFunction interestingness_function);
 
-  // Adds a reduction pass to the sequence of passes that will be iterated
-  // over.
-  void AddReductionPass(std::unique_ptr<ReductionPass>&& reduction_pass);
+  // Adds all default reduction passes.
+  void AddDefaultReductionPasses();
+
+  // Adds a reduction pass based on the given finder to the sequence of passes
+  // that will be iterated over.
+  void AddReductionPass(std::unique_ptr<ReductionOpportunityFinder>&& finder);
 
   // Reduces the given SPIR-V module |binary_out|.
   // The reduced binary ends up in |binary_out|.
   // A status is returned.
   ReductionResultStatus Run(std::vector<uint32_t>&& binary_in,
                             std::vector<uint32_t>* binary_out,
-                            spv_const_reducer_options options) const;
+                            spv_const_reducer_options options,
+                            spv_validator_options validator_options) const;
 
  private:
   struct Impl;                  // Opaque struct for holding internal data.
diff --git a/source/reduce/reduction_opportunity.cpp b/source/reduce/reduction_opportunity.cpp
index f562678..77be784 100644
--- a/source/reduce/reduction_opportunity.cpp
+++ b/source/reduce/reduction_opportunity.cpp
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "reduction_opportunity.h"
+#include "source/reduce/reduction_opportunity.h"
 
 namespace spvtools {
 namespace reduce {
diff --git a/source/reduce/reduction_opportunity.h b/source/reduce/reduction_opportunity.h
index da382a2..703a50a 100644
--- a/source/reduce/reduction_opportunity.h
+++ b/source/reduce/reduction_opportunity.h
@@ -20,23 +20,24 @@
 namespace spvtools {
 namespace reduce {
 
-// Abstract class capturing an opportunity to apply a reducing transformation.
+// Abstract class: an opportunity to apply a reducing transformation.
 class ReductionOpportunity {
  public:
   ReductionOpportunity() = default;
   virtual ~ReductionOpportunity() = default;
 
-  // Determines whether the opportunity can be applied; it may have been viable
-  // when discovered but later disabled by the application of some other
-  // reduction opportunity.
+  // Returns true if this opportunity has not been disabled by the application
+  // of another conflicting opportunity.
   virtual bool PreconditionHolds() = 0;
 
-  // A no-op if PreconditoinHolds() returns false; otherwise applies the
-  // opportunity.
+  // Applies the opportunity, mutating the module from which the opportunity was
+  // created. It is a no-op if PreconditionHolds() returns false.
   void TryToApply();
 
  protected:
-  // Apply the reduction opportunity.
+  // Applies the opportunity, mutating the module from which the opportunity was
+  // created.
+  // Precondition: PreconditionHolds() must return true.
   virtual void Apply() = 0;
 };
 
diff --git a/source/reduce/reduction_opportunity_finder.h b/source/reduce/reduction_opportunity_finder.h
new file mode 100644
index 0000000..1837484
--- /dev/null
+++ b/source/reduce/reduction_opportunity_finder.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/opt/ir_context.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// Abstract class for finding opportunities for reducing a SPIR-V module.
+class ReductionOpportunityFinder {
+ public:
+  ReductionOpportunityFinder() = default;
+
+  virtual ~ReductionOpportunityFinder() = default;
+
+  // Finds and returns the reduction opportunities relevant to this pass that
+  // could be applied to the given SPIR-V module.
+  virtual std::vector<std::unique_ptr<ReductionOpportunity>>
+  GetAvailableOpportunities(opt::IRContext* context) const = 0;
+
+  // Provides a name for the finder.
+  virtual std::string GetName() const = 0;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/reduction_pass.cpp b/source/reduce/reduction_pass.cpp
index befba8b..2cb986d 100644
--- a/source/reduce/reduction_pass.cpp
+++ b/source/reduce/reduction_pass.cpp
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <algorithm>
+#include "source/reduce/reduction_pass.h"
 
-#include "reduction_pass.h"
+#include <algorithm>
 
 #include "source/opt/build_module.h"
 
@@ -34,22 +34,21 @@
   assert(context);
 
   std::vector<std::unique_ptr<ReductionOpportunity>> opportunities =
-      GetAvailableOpportunities(context.get());
+      finder_->GetAvailableOpportunities(context.get());
 
-  if (!is_initialized_) {
-    is_initialized_ = true;
-    index_ = 0;
-    granularity_ = (uint32_t)opportunities.size();
-  }
-
-  if (opportunities.empty()) {
-    granularity_ = 1;
-    return std::vector<uint32_t>();
+  // There is no point in having a granularity larger than the number of
+  // opportunities, so reduce the granularity in this case.
+  if (granularity_ > opportunities.size()) {
+    granularity_ = std::max((uint32_t)1, (uint32_t)opportunities.size());
   }
 
   assert(granularity_ > 0);
 
   if (index_ >= opportunities.size()) {
+    // We have reached the end of the available opportunities and, therefore,
+    // the end of the round for this pass, so reset the index and decrease the
+    // granularity for the next round. Return an empty vector to signal the end
+    // of the round.
     index_ = 0;
     granularity_ = std::max((uint32_t)1, granularity_ / 2);
     return std::vector<uint32_t>();
@@ -61,8 +60,6 @@
     opportunities[i]->TryToApply();
   }
 
-  index_ += granularity_;
-
   std::vector<uint32_t> result;
   context->module()->ToBinary(&result, false);
   return result;
@@ -73,14 +70,17 @@
 }
 
 bool ReductionPass::ReachedMinimumGranularity() const {
-  if (!is_initialized_) {
-    // Conceptually we can think that if the pass has not yet been initialized,
-    // it is operating at unbounded granularity.
-    return false;
-  }
   assert(granularity_ != 0);
   return granularity_ == 1;
 }
 
+std::string ReductionPass::GetName() const { return finder_->GetName(); }
+
+void ReductionPass::NotifyInteresting(bool interesting) {
+  if (!interesting) {
+    index_ += granularity_;
+  }
+}
+
 }  // namespace reduce
 }  // namespace spvtools
diff --git a/source/reduce/reduction_pass.h b/source/reduce/reduction_pass.h
index afb95cc..f2d937b 100644
--- a/source/reduce/reduction_pass.h
+++ b/source/reduce/reduction_pass.h
@@ -15,10 +15,11 @@
 #ifndef SOURCE_REDUCE_REDUCTION_PASS_H_
 #define SOURCE_REDUCE_REDUCTION_PASS_H_
 
-#include "spirv-tools/libspirv.hpp"
+#include <limits>
 
-#include "reduction_opportunity.h"
 #include "source/opt/ir_context.h"
+#include "source/reduce/reduction_opportunity_finder.h"
+#include "spirv-tools/libspirv.hpp"
 
 namespace spvtools {
 namespace reduce {
@@ -32,37 +33,44 @@
 // again, until the minimum granularity is reached.
 class ReductionPass {
  public:
-  // Constructs a reduction pass with a given target environment, |target_env|.
-  // Initially the pass is uninitialized.
-  explicit ReductionPass(const spv_target_env target_env)
-      : target_env_(target_env), is_initialized_(false) {}
+  // Constructs a reduction pass with a given target environment, |target_env|,
+  // and a given finder of reduction opportunities, |finder|.
+  explicit ReductionPass(const spv_target_env target_env,
+                         std::unique_ptr<ReductionOpportunityFinder> finder)
+      : target_env_(target_env),
+        finder_(std::move(finder)),
+        index_(0),
+        granularity_(std::numeric_limits<uint32_t>::max()) {}
 
-  virtual ~ReductionPass() = default;
-
-  // Apply the reduction pass to the given binary.
+  // Applies the reduction pass to the given binary by applying a "chunk" of
+  // reduction opportunities. Returns the new binary if a chunk was applied; in
+  // this case, before the next call the caller must invoke
+  // NotifyInteresting(...) to indicate whether the new binary is interesting.
+  // Returns an empty vector if there are no more chunks left to apply; in this
+  // case, the index will be reset and the granularity lowered for the next
+  // round.
   std::vector<uint32_t> TryApplyReduction(const std::vector<uint32_t>& binary);
 
-  // Set a consumer to which relevant messages will be directed.
+  // Notifies the reduction pass whether the binary returned from
+  // TryApplyReduction is interesting, so that the next call to
+  // TryApplyReduction will avoid applying the same chunk of opportunities.
+  void NotifyInteresting(bool interesting);
+
+  // Sets a consumer to which relevant messages will be directed.
   void SetMessageConsumer(MessageConsumer consumer);
 
-  // Determines whether the granularity with which reduction opportunities are
+  // Returns true if the granularity with which reduction opportunities are
   // applied has reached a minimum.
   bool ReachedMinimumGranularity() const;
 
-  // Returns the name of the reduction pass (useful for monitoring reduction
-  // progress).
-  virtual std::string GetName() const = 0;
-
- protected:
-  // Finds the reduction opportunities relevant to this pass that could be
-  // applied to a given SPIR-V module.
-  virtual std::vector<std::unique_ptr<ReductionOpportunity>>
-  GetAvailableOpportunities(opt::IRContext* context) const = 0;
+  // Returns the name associated with this reduction pass (based on its
+  // associated finder).
+  std::string GetName() const;
 
  private:
   const spv_target_env target_env_;
+  const std::unique_ptr<ReductionOpportunityFinder> finder_;
   MessageConsumer consumer_;
-  bool is_initialized_;
   uint32_t index_;
   uint32_t granularity_;
 };
diff --git a/source/reduce/reduction_util.cpp b/source/reduce/reduction_util.cpp
new file mode 100644
index 0000000..2b2b7e6
--- /dev/null
+++ b/source/reduce/reduction_util.cpp
@@ -0,0 +1,48 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/reduction_util.h"
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::IRContext;
+using opt::Instruction;
+
+const uint32_t kTrueBranchOperandIndex = 1;
+const uint32_t kFalseBranchOperandIndex = 2;
+
+uint32_t FindOrCreateGlobalUndef(IRContext* context, uint32_t type_id) {
+  for (auto& inst : context->module()->types_values()) {
+    if (inst.opcode() != SpvOpUndef) {
+      continue;
+    }
+    if (inst.type_id() == type_id) {
+      return inst.result_id();
+    }
+  }
+  // TODO(2182): this is adapted from MemPass::Type2Undef.  In due course it
+  // would be good to factor out this duplication.
+  const uint32_t undef_id = context->TakeNextId();
+  std::unique_ptr<Instruction> undef_inst(
+      new Instruction(context, SpvOpUndef, type_id, undef_id, {}));
+  assert(undef_id == undef_inst->result_id());
+  context->module()->AddGlobalValue(std::move(undef_inst));
+  return undef_id;
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/reduction_util.h b/source/reduce/reduction_util.h
new file mode 100644
index 0000000..b8ffb6e
--- /dev/null
+++ b/source/reduce/reduction_util.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REDUCTION_UTIL_H_
+#define SOURCE_REDUCE_REDUCTION_UTIL_H_
+
+#include "spirv-tools/libspirv.hpp"
+
+#include "source/opt/ir_context.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+extern const uint32_t kTrueBranchOperandIndex;
+extern const uint32_t kFalseBranchOperandIndex;
+
+// Returns an OpUndef id from the global value list that is of the given type,
+// adding one if it does not exist.
+uint32_t FindOrCreateGlobalUndef(opt::IRContext* context, uint32_t type_id);
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_REDUCTION_UTIL_H_
diff --git a/source/reduce/remove_block_reduction_opportunity.cpp b/source/reduce/remove_block_reduction_opportunity.cpp
new file mode 100644
index 0000000..3ad7f72
--- /dev/null
+++ b/source/reduce/remove_block_reduction_opportunity.cpp
@@ -0,0 +1,57 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_block_reduction_opportunity.h"
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::BasicBlock;
+using opt::Function;
+
+RemoveBlockReductionOpportunity::RemoveBlockReductionOpportunity(
+    Function* function, BasicBlock* block)
+    : function_(function), block_(block) {
+  // precondition:
+  assert(block_->begin() != block_->end() &&
+         block_->begin()->context()->get_def_use_mgr()->NumUsers(
+             block_->id()) == 0 &&
+         "RemoveBlockReductionOpportunity block must have 0 references");
+}
+
+bool RemoveBlockReductionOpportunity::PreconditionHolds() {
+  // Removing other blocks cannot disable this opportunity.
+  return true;
+}
+
+void RemoveBlockReductionOpportunity::Apply() {
+  // We need an iterator pointing to the block, hence the loop.
+  for (auto bi = function_->begin(); bi != function_->end(); ++bi) {
+    if (bi->id() == block_->id()) {
+      bi->KillAllInsts(true);
+      bi.Erase();
+      // Block removal changes the function, but we don't use analyses, so no
+      // need to invalidate them.
+      return;
+    }
+  }
+
+  assert(false &&
+         "Unreachable: we should have found a block with the desired id.");
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/remove_block_reduction_opportunity.h b/source/reduce/remove_block_reduction_opportunity.h
new file mode 100644
index 0000000..4b358ab
--- /dev/null
+++ b/source/reduce/remove_block_reduction_opportunity.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/basic_block.h"
+#include "source/opt/function.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to remove an unreferenced block.
+// See RemoveBlockReductionOpportunityFinder.
+class RemoveBlockReductionOpportunity : public ReductionOpportunity {
+ public:
+  // Creates the opportunity to remove |block| in |function| in |context|.
+  RemoveBlockReductionOpportunity(opt::Function* function,
+                                  opt::BasicBlock* block);
+
+  bool PreconditionHolds() override;
+
+ protected:
+  void Apply() override;
+
+ private:
+  opt::Function* function_;
+  opt::BasicBlock* block_;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/remove_block_reduction_opportunity_finder.cpp b/source/reduce/remove_block_reduction_opportunity_finder.cpp
new file mode 100644
index 0000000..a3f873f
--- /dev/null
+++ b/source/reduce/remove_block_reduction_opportunity_finder.cpp
@@ -0,0 +1,98 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_block_reduction_opportunity_finder.h"
+
+#include "source/reduce/remove_block_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::Function;
+using opt::IRContext;
+using opt::Instruction;
+
+std::string RemoveBlockReductionOpportunityFinder::GetName() const {
+  return "RemoveBlockReductionOpportunityFinder";
+}
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+RemoveBlockReductionOpportunityFinder::GetAvailableOpportunities(
+    IRContext* context) const {
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+  // Consider every block in every function.
+  for (auto& function : *context->module()) {
+    for (auto bi = function.begin(); bi != function.end(); ++bi) {
+      if (IsBlockValidOpportunity(context, function, bi)) {
+        result.push_back(spvtools::MakeUnique<RemoveBlockReductionOpportunity>(
+            &function, &*bi));
+      }
+    }
+  }
+  return result;
+}
+
+bool RemoveBlockReductionOpportunityFinder::IsBlockValidOpportunity(
+    IRContext* context, Function& function, Function::iterator& bi) {
+  assert(bi != function.end() && "Block iterator was out of bounds");
+
+  // Don't remove first block; we don't want to end up with no blocks.
+  if (bi == function.begin()) {
+    return false;
+  }
+
+  // Don't remove blocks with references.
+  if (context->get_def_use_mgr()->NumUsers(bi->id()) > 0) {
+    return false;
+  }
+
+  // Don't remove blocks whose instructions have outside references.
+  if (!BlockInstructionsHaveNoOutsideReferences(context, bi)) {
+    return false;
+  }
+
+  return true;
+}
+
+bool RemoveBlockReductionOpportunityFinder::
+    BlockInstructionsHaveNoOutsideReferences(IRContext* context,
+                                             const Function::iterator& bi) {
+  // Get all instructions in block.
+  std::unordered_set<uint32_t> instructions_in_block;
+  for (const Instruction& instruction : *bi) {
+    instructions_in_block.insert(instruction.unique_id());
+  }
+
+  // For each instruction...
+  for (const Instruction& instruction : *bi) {
+    // For each use of the instruction...
+    bool no_uses_outside_block = context->get_def_use_mgr()->WhileEachUser(
+        &instruction, [&instructions_in_block](Instruction* user) -> bool {
+          // If the use is in this block, continue (return true). Otherwise, we
+          // found an outside use; return false (and stop).
+          return instructions_in_block.find(user->unique_id()) !=
+                 instructions_in_block.end();
+        });
+
+    if (!no_uses_outside_block) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/remove_block_reduction_opportunity_finder.h b/source/reduce/remove_block_reduction_opportunity_finder.h
new file mode 100644
index 0000000..83cd04b
--- /dev/null
+++ b/source/reduce/remove_block_reduction_opportunity_finder.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/opt/function.h"
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder of opportunities to remove a block. The optimizer can remove dead
+// code. However, the reducer needs to be able to remove at a fine-grained
+// level.
+class RemoveBlockReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  RemoveBlockReductionOpportunityFinder() = default;
+
+  ~RemoveBlockReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+  // Returns true if the block |bi| in function |function| is a valid
+  // opportunity according to various restrictions.
+  static bool IsBlockValidOpportunity(opt::IRContext* context,
+                                      opt::Function& function,
+                                      opt::Function::iterator& bi);
+
+  // Returns true if the instructions (definitions) in block |bi| have no
+  // references, except for references from inside the block itself.
+  static bool BlockInstructionsHaveNoOutsideReferences(
+      opt::IRContext* context, const opt::Function::iterator& bi);
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/remove_function_reduction_opportunity.cpp b/source/reduce/remove_function_reduction_opportunity.cpp
new file mode 100644
index 0000000..ecad670
--- /dev/null
+++ b/source/reduce/remove_function_reduction_opportunity.cpp
@@ -0,0 +1,41 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_function_reduction_opportunity.h"
+
+#include "source/opt/eliminate_dead_functions_util.h"
+
+namespace spvtools {
+namespace reduce {
+
+bool RemoveFunctionReductionOpportunity::PreconditionHolds() {
+  // Removing one function cannot influence whether another function can be
+  // removed.
+  return true;
+}
+
+void RemoveFunctionReductionOpportunity::Apply() {
+  for (opt::Module::iterator function_it = context_->module()->begin();
+       function_it != context_->module()->end(); ++function_it) {
+    if (&*function_it == function_) {
+      opt::eliminatedeadfunctionsutil::EliminateFunction(context_,
+                                                         &function_it);
+      return;
+    }
+  }
+  assert(0 && "Function to be removed was not found.");
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/remove_function_reduction_opportunity.h b/source/reduce/remove_function_reduction_opportunity.h
new file mode 100644
index 0000000..d8c57db
--- /dev/null
+++ b/source/reduce/remove_function_reduction_opportunity.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/function.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to remove an unreferenced function.
+class RemoveFunctionReductionOpportunity : public ReductionOpportunity {
+ public:
+  // Creates an opportunity to remove |function| from the module represented by
+  // |context|.
+  RemoveFunctionReductionOpportunity(opt::IRContext* context,
+                                     opt::Function* function)
+      : context_(context), function_(function) {}
+
+  bool PreconditionHolds() override;
+
+ protected:
+  void Apply() override;
+
+ private:
+  // The IR context for the module under analysis.
+  opt::IRContext* context_;
+
+  // The function that can be removed.
+  opt::Function* function_;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  //   SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/remove_function_reduction_opportunity_finder.cpp b/source/reduce/remove_function_reduction_opportunity_finder.cpp
new file mode 100644
index 0000000..1edb973
--- /dev/null
+++ b/source/reduce/remove_function_reduction_opportunity_finder.cpp
@@ -0,0 +1,43 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_function_reduction_opportunity_finder.h"
+
+#include "source/reduce/remove_function_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+RemoveFunctionReductionOpportunityFinder::GetAvailableOpportunities(
+    opt::IRContext* context) const {
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+  // Consider each function.
+  for (auto& function : *context->module()) {
+    if (context->get_def_use_mgr()->NumUses(function.result_id()) > 0) {
+      // If the function is referenced, ignore it.
+      continue;
+    }
+    result.push_back(
+        MakeUnique<RemoveFunctionReductionOpportunity>(context, &function));
+  }
+  return result;
+}
+
+std::string RemoveFunctionReductionOpportunityFinder::GetName() const {
+  return "RemoveFunctionReductionOpportunityFinder";
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/remove_function_reduction_opportunity_finder.h b/source/reduce/remove_function_reduction_opportunity_finder.h
new file mode 100644
index 0000000..7952a22
--- /dev/null
+++ b/source/reduce/remove_function_reduction_opportunity_finder.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder of opportunities to remove unreferenced functions.
+class RemoveFunctionReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  RemoveFunctionReductionOpportunityFinder() = default;
+
+  ~RemoveFunctionReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  //   SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/remove_instruction_reduction_opportunity.cpp b/source/reduce/remove_instruction_reduction_opportunity.cpp
index 7b7a74e..9ca093b 100644
--- a/source/reduce/remove_instruction_reduction_opportunity.cpp
+++ b/source/reduce/remove_instruction_reduction_opportunity.cpp
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "source/opt/ir_context.h"
+#include "source/reduce/remove_instruction_reduction_opportunity.h"
 
-#include "remove_instruction_reduction_opportunity.h"
+#include "source/opt/ir_context.h"
 
 namespace spvtools {
 namespace reduce {
diff --git a/source/reduce/remove_instruction_reduction_opportunity.h b/source/reduce/remove_instruction_reduction_opportunity.h
index 471ff15..07bef50 100644
--- a/source/reduce/remove_instruction_reduction_opportunity.h
+++ b/source/reduce/remove_instruction_reduction_opportunity.h
@@ -15,30 +15,27 @@
 #ifndef SOURCE_REDUCE_REMOVE_INSTRUCTION_REDUCTION_OPPORTUNITY_H_
 #define SOURCE_REDUCE_REMOVE_INSTRUCTION_REDUCTION_OPPORTUNITY_H_
 
-#include "reduction_opportunity.h"
 #include "source/opt/instruction.h"
+#include "source/reduce/reduction_opportunity.h"
 
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
-
-// Captures the opportunity to remove an instruction from the SPIR-V module.
+// An opportunity to remove an instruction from the SPIR-V module.
 class RemoveInstructionReductionOpportunity : public ReductionOpportunity {
  public:
   // Constructs the opportunity to remove |inst|.
-  explicit RemoveInstructionReductionOpportunity(Instruction* inst)
+  explicit RemoveInstructionReductionOpportunity(opt::Instruction* inst)
       : inst_(inst) {}
 
-  // This kind of opportunity can be unconditionally applied.
+  // Always returns true, as this opportunity can always be applied.
   bool PreconditionHolds() override;
 
  protected:
-  // Remove the instruction.
   void Apply() override;
 
  private:
-  Instruction* inst_;
+  opt::Instruction* inst_;
 };
 
 }  // namespace reduce
diff --git a/source/reduce/remove_opname_instruction_reduction_pass.cpp b/source/reduce/remove_opname_instruction_reduction_opportunity_finder.cpp
similarity index 72%
rename from source/reduce/remove_opname_instruction_reduction_pass.cpp
rename to source/reduce/remove_opname_instruction_reduction_opportunity_finder.cpp
index bf99bc5..f687d71 100644
--- a/source/reduce/remove_opname_instruction_reduction_pass.cpp
+++ b/source/reduce/remove_opname_instruction_reduction_opportunity_finder.cpp
@@ -12,19 +12,20 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "remove_opname_instruction_reduction_pass.h"
-#include "remove_instruction_reduction_opportunity.h"
+#include "source/reduce/remove_opname_instruction_reduction_opportunity_finder.h"
+
 #include "source/opcode.h"
 #include "source/opt/instruction.h"
+#include "source/reduce/remove_instruction_reduction_opportunity.h"
 
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
+using opt::IRContext;
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
-RemoveOpNameInstructionReductionPass::GetAvailableOpportunities(
-    opt::IRContext* context) const {
+RemoveOpNameInstructionReductionOpportunityFinder::GetAvailableOpportunities(
+    IRContext* context) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
   for (auto& inst : context->module()->debugs2()) {
@@ -36,8 +37,8 @@
   return result;
 }
 
-std::string RemoveOpNameInstructionReductionPass::GetName() const {
-  return "RemoveOpNameInstructionReductionPass";
+std::string RemoveOpNameInstructionReductionOpportunityFinder::GetName() const {
+  return "RemoveOpNameInstructionReductionOpportunityFinder";
 }
 
 }  // namespace reduce
diff --git a/source/reduce/remove_opname_instruction_reduction_opportunity_finder.h b/source/reduce/remove_opname_instruction_reduction_opportunity_finder.h
new file mode 100644
index 0000000..8b9fd6f
--- /dev/null
+++ b/source/reduce/remove_opname_instruction_reduction_opportunity_finder.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities to remove OpName instructions.  As well as making
+// the module smaller, removing an OpName instruction may create opportunities
+// for subsequently removing the instructions that create the ids to which the
+// OpName applies.
+class RemoveOpNameInstructionReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  RemoveOpNameInstructionReductionOpportunityFinder() = default;
+
+  ~RemoveOpNameInstructionReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/remove_opname_instruction_reduction_pass.h b/source/reduce/remove_opname_instruction_reduction_pass.h
deleted file mode 100644
index d20b6e1..0000000
--- a/source/reduce/remove_opname_instruction_reduction_pass.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (c) 2018 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_PASS_H_
-#define SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_PASS_H_
-
-#include "reduction_pass.h"
-
-namespace spvtools {
-namespace reduce {
-
-// A reduction pass for removing OpName instructions.  As well as making the
-// module smaller, removing an OpName instruction may create opportunities to
-// remove the instruction that create the id to which the OpName applies.
-class RemoveOpNameInstructionReductionPass : public ReductionPass {
- public:
-  // Creates the reduction pass in the context of the given target environment
-  // |target_env|
-  explicit RemoveOpNameInstructionReductionPass(const spv_target_env target_env)
-      : ReductionPass(target_env) {}
-
-  ~RemoveOpNameInstructionReductionPass() override = default;
-
-  // The name of this pass.
-  std::string GetName() const final;
-
- protected:
-  // Finds all opportunities for removing opName instructions in the
-  // given module.
-  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
-
- private:
-};
-
-}  // namespace reduce
-}  // namespace spvtools
-
-#endif  // SOURCE_REDUCE_REMOVE_OpName_INSTRUCTION_REDUCTION_PASS_H_
diff --git a/source/reduce/remove_selection_reduction_opportunity.cpp b/source/reduce/remove_selection_reduction_opportunity.cpp
new file mode 100644
index 0000000..96f0147
--- /dev/null
+++ b/source/reduce/remove_selection_reduction_opportunity.cpp
@@ -0,0 +1,31 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_selection_reduction_opportunity.h"
+
+#include "source/opt/basic_block.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace reduce {
+
+bool RemoveSelectionReductionOpportunity::PreconditionHolds() { return true; }
+
+void RemoveSelectionReductionOpportunity::Apply() {
+  auto merge_instruction = header_block_->GetMergeInst();
+  merge_instruction->context()->KillInst(merge_instruction);
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/remove_selection_reduction_opportunity.h b/source/reduce/remove_selection_reduction_opportunity.h
new file mode 100644
index 0000000..892618e
--- /dev/null
+++ b/source/reduce/remove_selection_reduction_opportunity.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_SELECTION_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_REMOVE_SELECTION_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/basic_block.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity for removing a selection construct by simply removing the
+// OpSelectionMerge instruction; thus, the selection must have already been
+// simplified to a point where the instruction can be trivially removed.
+class RemoveSelectionReductionOpportunity : public ReductionOpportunity {
+ public:
+  // Constructs a reduction opportunity from the selection header |block| in
+  // |function|.
+  RemoveSelectionReductionOpportunity(opt::BasicBlock* header_block)
+      : header_block_(header_block) {}
+
+  bool PreconditionHolds() override;
+
+ protected:
+  void Apply() override;
+
+ private:
+  // The header block of the selection.
+  opt::BasicBlock* header_block_;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  //   SOURCE_REDUCE_REMOVE_SELECTION_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/remove_selection_reduction_opportunity_finder.cpp b/source/reduce/remove_selection_reduction_opportunity_finder.cpp
new file mode 100644
index 0000000..45821e2
--- /dev/null
+++ b/source/reduce/remove_selection_reduction_opportunity_finder.cpp
@@ -0,0 +1,150 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_selection_reduction_opportunity_finder.h"
+
+#include "source/reduce/remove_selection_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::BasicBlock;
+using opt::IRContext;
+using opt::Instruction;
+
+namespace {
+const uint32_t kMergeNodeIndex = 0;
+const uint32_t kContinueNodeIndex = 1;
+}  // namespace
+
+std::string RemoveSelectionReductionOpportunityFinder::GetName() const {
+  return "RemoveSelectionReductionOpportunityFinder";
+}
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+RemoveSelectionReductionOpportunityFinder::GetAvailableOpportunities(
+    IRContext* context) const {
+  // Get all loop merge and continue blocks so we can check for these later.
+  std::unordered_set<uint32_t> merge_and_continue_blocks_from_loops;
+  for (auto& function : *context->module()) {
+    for (auto& block : function) {
+      if (auto merge_instruction = block.GetMergeInst()) {
+        if (merge_instruction->opcode() == SpvOpLoopMerge) {
+          uint32_t merge_block_id =
+              merge_instruction->GetSingleWordOperand(kMergeNodeIndex);
+          uint32_t continue_block_id =
+              merge_instruction->GetSingleWordOperand(kContinueNodeIndex);
+          merge_and_continue_blocks_from_loops.insert(merge_block_id);
+          merge_and_continue_blocks_from_loops.insert(continue_block_id);
+        }
+      }
+    }
+  }
+
+  // Return all selection headers where the OpSelectionMergeInstruction can be
+  // removed.
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+  for (auto& function : *context->module()) {
+    for (auto& block : function) {
+      if (auto merge_instruction = block.GetMergeInst()) {
+        if (merge_instruction->opcode() == SpvOpSelectionMerge) {
+          if (CanOpSelectionMergeBeRemoved(
+                  context, block, merge_instruction,
+                  merge_and_continue_blocks_from_loops)) {
+            result.push_back(
+                MakeUnique<RemoveSelectionReductionOpportunity>(&block));
+          }
+        }
+      }
+    }
+  }
+  return result;
+}
+
+bool RemoveSelectionReductionOpportunityFinder::CanOpSelectionMergeBeRemoved(
+    IRContext* context, const BasicBlock& header_block,
+    Instruction* merge_instruction,
+    std::unordered_set<uint32_t> merge_and_continue_blocks_from_loops) {
+  assert(header_block.GetMergeInst() == merge_instruction &&
+         "CanOpSelectionMergeBeRemoved(...): header block and merge "
+         "instruction mismatch");
+
+  // The OpSelectionMerge instruction is needed if either of the following are
+  // true.
+  //
+  // 1. The header block has at least two (unique) successors that are not
+  // merge or continue blocks of a loop.
+  //
+  // 2. The predecessors of the merge block are "using" the merge block to avoid
+  // divergence. In other words, there exists a predecessor of the merge block
+  // that has a successor that is not the merge block of this construct and not
+  // a merge or continue block of a loop.
+
+  // 1.
+  {
+    uint32_t divergent_successor_count = 0;
+
+    std::unordered_set<uint32_t> seen_successors;
+
+    header_block.ForEachSuccessorLabel(
+        [&seen_successors, &merge_and_continue_blocks_from_loops,
+         &divergent_successor_count](uint32_t successor) {
+          // Not already seen.
+          if (seen_successors.find(successor) == seen_successors.end()) {
+            seen_successors.insert(successor);
+            // Not a loop continue or merge.
+            if (merge_and_continue_blocks_from_loops.find(successor) ==
+                merge_and_continue_blocks_from_loops.end()) {
+              ++divergent_successor_count;
+            }
+          }
+        });
+
+    if (divergent_successor_count > 1) {
+      return false;
+    }
+  }
+
+  // 2.
+  {
+    uint32_t merge_block_id =
+        merge_instruction->GetSingleWordOperand(kMergeNodeIndex);
+    for (uint32_t predecessor_block_id :
+         context->cfg()->preds(merge_block_id)) {
+      const BasicBlock* predecessor_block =
+          context->cfg()->block(predecessor_block_id);
+      assert(predecessor_block);
+      bool found_divergent_successor = false;
+      predecessor_block->ForEachSuccessorLabel(
+          [&found_divergent_successor, merge_block_id,
+           &merge_and_continue_blocks_from_loops](uint32_t successor_id) {
+            // The successor is not the merge block, nor a loop merge or
+            // continue.
+            if (successor_id != merge_block_id &&
+                merge_and_continue_blocks_from_loops.find(successor_id) ==
+                    merge_and_continue_blocks_from_loops.end()) {
+              found_divergent_successor = true;
+            }
+          });
+      if (found_divergent_successor) {
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/remove_selection_reduction_opportunity_finder.h b/source/reduce/remove_selection_reduction_opportunity_finder.h
new file mode 100644
index 0000000..848122b
--- /dev/null
+++ b/source/reduce/remove_selection_reduction_opportunity_finder.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_REMOVE_SELECTION_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REMOVE_SELECTION_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities for removing a selection construct by simply
+// removing the OpSelectionMerge instruction; thus, the selections must have
+// already been simplified to a point where they can be trivially removed.
+class RemoveSelectionReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  RemoveSelectionReductionOpportunityFinder() = default;
+
+  ~RemoveSelectionReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+  // Returns true if the OpSelectionMerge instruction |merge_instruction| in
+  // block |header_block| can be removed.
+  static bool CanOpSelectionMergeBeRemoved(
+      opt::IRContext* context, const opt::BasicBlock& header_block,
+      opt::Instruction* merge_instruction,
+      std::unordered_set<uint32_t> merge_and_continue_blocks_from_loops);
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_REMOVE_SELECTION_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/remove_unreferenced_instruction_reduction_pass.cpp b/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp
similarity index 79%
rename from source/reduce/remove_unreferenced_instruction_reduction_pass.cpp
rename to source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp
index bc4998d..8f32435 100644
--- a/source/reduce/remove_unreferenced_instruction_reduction_pass.cpp
+++ b/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp
@@ -12,19 +12,20 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "remove_unreferenced_instruction_reduction_pass.h"
-#include "remove_instruction_reduction_opportunity.h"
+#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
+
 #include "source/opcode.h"
 #include "source/opt/instruction.h"
+#include "source/reduce/remove_instruction_reduction_opportunity.h"
 
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
+using opt::IRContext;
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
-RemoveUnreferencedInstructionReductionPass::GetAvailableOpportunities(
-    opt::IRContext* context) const {
+RemoveUnreferencedInstructionReductionOpportunityFinder::
+    GetAvailableOpportunities(IRContext* context) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
   for (auto& function : *context->module()) {
@@ -52,8 +53,9 @@
   return result;
 }
 
-std::string RemoveUnreferencedInstructionReductionPass::GetName() const {
-  return "RemoveUnreferencedInstructionReductionPass";
+std::string RemoveUnreferencedInstructionReductionOpportunityFinder::GetName()
+    const {
+  return "RemoveUnreferencedInstructionReductionOpportunityFinder";
 }
 
 }  // namespace reduce
diff --git a/source/reduce/remove_unreferenced_instruction_reduction_pass.h b/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h
similarity index 60%
rename from source/reduce/remove_unreferenced_instruction_reduction_pass.h
rename to source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h
index 27c3fc2..30f460b 100644
--- a/source/reduce/remove_unreferenced_instruction_reduction_pass.h
+++ b/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h
@@ -12,35 +12,28 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_PASS_H_
-#define SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_PASS_H_
+#ifndef SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
 
-#include "reduction_pass.h"
+#include "source/reduce/reduction_opportunity_finder.h"
 
 namespace spvtools {
 namespace reduce {
 
-// A reduction pass for removing non-control-flow instructions in blocks in
-// cases where the instruction's id is not referenced.  As well as making the
-// module smaller, removing an instruction that referenced particular ids may
+// A finder for opportunities to remove non-control-flow instructions in blocks
+// in cases where the instruction's id is not referenced.  As well as making the
+// module smaller, removing an instruction that references particular ids may
 // create opportunities for subsequently removing the instructions that
 // generated those ids.
-class RemoveUnreferencedInstructionReductionPass : public ReductionPass {
+class RemoveUnreferencedInstructionReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
  public:
-  // Creates the reduction pass in the context of the given target environment
-  // |target_env|
-  explicit RemoveUnreferencedInstructionReductionPass(
-      const spv_target_env target_env)
-      : ReductionPass(target_env) {}
+  RemoveUnreferencedInstructionReductionOpportunityFinder() = default;
 
-  ~RemoveUnreferencedInstructionReductionPass() override = default;
+  ~RemoveUnreferencedInstructionReductionOpportunityFinder() override = default;
 
-  // The name of this pass.
   std::string GetName() const final;
 
- protected:
-  // Finds all opportunities for removing unreferenced instructions in the
-  // given module.
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
       opt::IRContext* context) const final;
 
@@ -50,4 +43,4 @@
 }  // namespace reduce
 }  // namespace spvtools
 
-#endif  // SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_PASS_H_
+#endif  // SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp
new file mode 100644
index 0000000..17a5c7e
--- /dev/null
+++ b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp
@@ -0,0 +1,65 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h"
+
+#include "source/reduce/reduction_util.h"
+#include "source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+using opt::IRContext;
+using opt::Instruction;
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+SimpleConditionalBranchToBranchOpportunityFinder::GetAvailableOpportunities(
+    IRContext* context) const {
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+  // Consider every function.
+  for (auto& function : *context->module()) {
+    // Consider every block in the function.
+    for (auto& block : function) {
+      // The terminator must be SpvOpBranchConditional.
+      Instruction* terminator = block.terminator();
+      if (terminator->opcode() != SpvOpBranchConditional) {
+        continue;
+      }
+      // It must not be a selection header, as these cannot be followed by
+      // OpBranch.
+      if (block.GetMergeInst() &&
+          block.GetMergeInst()->opcode() == SpvOpSelectionMerge) {
+        continue;
+      }
+      // The conditional branch must be simplified.
+      if (terminator->GetSingleWordInOperand(kTrueBranchOperandIndex) !=
+          terminator->GetSingleWordInOperand(kFalseBranchOperandIndex)) {
+        continue;
+      }
+
+      result.push_back(
+          MakeUnique<SimpleConditionalBranchToBranchReductionOpportunity>(
+              block.terminator()));
+    }
+  }
+  return result;
+}
+
+std::string SimpleConditionalBranchToBranchOpportunityFinder::GetName() const {
+  return "SimpleConditionalBranchToBranchOpportunityFinder";
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h
new file mode 100644
index 0000000..10b9dce
--- /dev/null
+++ b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities to change simple conditional branches (conditional
+// branches with one target) to an OpBranch.
+class SimpleConditionalBranchToBranchOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const override;
+
+  std::string GetName() const override;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp b/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp
new file mode 100644
index 0000000..8968b96
--- /dev/null
+++ b/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp
@@ -0,0 +1,59 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.h"
+
+#include "source/reduce/reduction_util.h"
+
+namespace spvtools {
+namespace reduce {
+
+using namespace opt;
+
+SimpleConditionalBranchToBranchReductionOpportunity::
+    SimpleConditionalBranchToBranchReductionOpportunity(
+        Instruction* conditional_branch_instruction)
+    : conditional_branch_instruction_(conditional_branch_instruction) {}
+
+bool SimpleConditionalBranchToBranchReductionOpportunity::PreconditionHolds() {
+  // We find at most one opportunity per conditional branch and simplifying
+  // another branch cannot disable this opportunity.
+  return true;
+}
+
+void SimpleConditionalBranchToBranchReductionOpportunity::Apply() {
+  assert(conditional_branch_instruction_->opcode() == SpvOpBranchConditional &&
+         "SimpleConditionalBranchToBranchReductionOpportunity: branch was not "
+         "a conditional branch");
+
+  assert(conditional_branch_instruction_->GetSingleWordInOperand(
+             kTrueBranchOperandIndex) ==
+             conditional_branch_instruction_->GetSingleWordInOperand(
+                 kFalseBranchOperandIndex) &&
+         "SimpleConditionalBranchToBranchReductionOpportunity: branch was not "
+         "simple");
+
+  // OpBranchConditional %condition %block_id %block_id ...
+  // ->
+  // OpBranch %block_id
+
+  conditional_branch_instruction_->SetOpcode(SpvOpBranch);
+  conditional_branch_instruction_->ReplaceOperands(
+      {{SPV_OPERAND_TYPE_ID,
+        {conditional_branch_instruction_->GetSingleWordInOperand(
+            kTrueBranchOperandIndex)}}});
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.h b/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.h
new file mode 100644
index 0000000..eddb464
--- /dev/null
+++ b/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/instruction.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to change simple conditional branches (conditional branches
+// with one target) to an OpBranch.
+class SimpleConditionalBranchToBranchReductionOpportunity
+    : public ReductionOpportunity {
+ public:
+  // Constructs an opportunity to simplify |conditional_branch_instruction|.
+  explicit SimpleConditionalBranchToBranchReductionOpportunity(
+      opt::Instruction* conditional_branch_instruction);
+
+  bool PreconditionHolds() override;
+
+ protected:
+  void Apply() override;
+
+ private:
+  opt::Instruction* conditional_branch_instruction_;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_SIMPLE_CONDITIONAL_BRANCH_TO_BRANCH_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp b/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
index 679cfc1..afc1298 100644
--- a/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
@@ -12,16 +12,22 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "structured_loop_to_selection_reduction_opportunity.h"
+#include "source/reduce/structured_loop_to_selection_reduction_opportunity.h"
+
 #include "source/opt/aggressive_dead_code_elim_pass.h"
 #include "source/opt/ir_context.h"
+#include "source/reduce/reduction_util.h"
 
 namespace spvtools {
 namespace reduce {
 
+using opt::BasicBlock;
+using opt::IRContext;
+using opt::Instruction;
+using opt::Operand;
+
 namespace {
 const uint32_t kMergeNodeIndex = 0;
-const uint32_t kContinueNodeIndex = 1;
 }  // namespace
 
 bool StructuredLoopToSelectionReductionOpportunity::PreconditionHolds() {
@@ -41,15 +47,11 @@
 
   // (1) Redirect edges that point to the loop's continue target to their
   // closest merge block.
-  RedirectToClosestMergeBlock(
-      loop_construct_header_->GetLoopMergeInst()->GetSingleWordOperand(
-          kContinueNodeIndex));
+  RedirectToClosestMergeBlock(loop_construct_header_->ContinueBlockId());
 
   // (2) Redirect edges that point to the loop's merge block to their closest
   // merge block (which might be that of an enclosing selection, for instance).
-  RedirectToClosestMergeBlock(
-      loop_construct_header_->GetLoopMergeInst()->GetSingleWordOperand(
-          kMergeNodeIndex));
+  RedirectToClosestMergeBlock(loop_construct_header_->MergeBlockId());
 
   // (3) Turn the loop construct header into a selection.
   ChangeLoopToSelection();
@@ -125,12 +127,8 @@
 
   // original_target_id must either be the merge target or continue construct
   // for the loop being operated on.
-  assert(original_target_id ==
-             loop_construct_header_->GetMergeInst()->GetSingleWordOperand(
-                 kMergeNodeIndex) ||
-         original_target_id ==
-             loop_construct_header_->GetMergeInst()->GetSingleWordOperand(
-                 kContinueNodeIndex));
+  assert(original_target_id == loop_construct_header_->MergeBlockId() ||
+         original_target_id == loop_construct_header_->ContinueBlockId());
 
   auto terminator = context_->cfg()->block(source_id)->terminator();
 
@@ -191,7 +189,7 @@
   to_block->ForEachPhiInst([this, &from_id](Instruction* phi_inst) {
     // Add to the phi operand an (undef, from_id) pair to reflect the added
     // edge.
-    auto undef_id = FindOrCreateGlobalUndef(phi_inst->type_id());
+    auto undef_id = FindOrCreateGlobalUndef(context_, phi_inst->type_id());
     phi_inst->AddOperand(Operand(SPV_OPERAND_TYPE_ID, {undef_id}));
     phi_inst->AddOperand(Operand(SPV_OPERAND_TYPE_ID, {from_id}));
   });
@@ -215,11 +213,11 @@
   // the "else" branch be the merge block.
   auto terminator = loop_construct_header_->terminator();
   if (terminator->opcode() == SpvOpBranch) {
-    analysis::Bool temp;
-    const analysis::Bool* bool_type =
+    opt::analysis::Bool temp;
+    const opt::analysis::Bool* bool_type =
         context_->get_type_mgr()->GetRegisteredType(&temp)->AsBool();
     auto const_mgr = context_->get_constant_mgr();
-    auto true_const = const_mgr->GetConstant(bool_type, {true});
+    auto true_const = const_mgr->GetConstant(bool_type, {1});
     auto true_const_result_id =
         const_mgr->GetDefiningInstruction(true_const)->result_id();
     auto original_branch_id = terminator->GetSingleWordOperand(0);
@@ -248,6 +246,10 @@
       context_->get_def_use_mgr()->ForEachUse(&def, [this, &block, &def](
                                                         Instruction* use,
                                                         uint32_t index) {
+        // Ignore uses outside of blocks, such as in OpDecorate.
+        if (context_->get_instr_block(use) == nullptr) {
+          return;
+        }
         // If a use is not appropriately dominated by its definition,
         // replace the use with an OpUndef, unless the definition is an
         // access chain, in which case replace it with some (possibly fresh)
@@ -273,7 +275,8 @@
                 break;
             }
           } else {
-            use->SetOperand(index, {FindOrCreateGlobalUndef(def.type_id())});
+            use->SetOperand(index,
+                            {FindOrCreateGlobalUndef(context_, def.type_id())});
           }
         }
       });
@@ -296,26 +299,6 @@
       ->Dominates(def, use);
 }
 
-uint32_t StructuredLoopToSelectionReductionOpportunity::FindOrCreateGlobalUndef(
-    uint32_t type_id) {
-  for (auto& inst : context_->module()->types_values()) {
-    if (inst.opcode() != SpvOpUndef) {
-      continue;
-    }
-    if (inst.type_id() == type_id) {
-      return inst.result_id();
-    }
-  }
-  // TODO(2182): this is adapted from MemPass::Type2Undef.  In due course it
-  // would be good to factor out this duplication.
-  const uint32_t undef_id = context_->TakeNextId();
-  std::unique_ptr<Instruction> undef_inst(
-      new Instruction(context_, SpvOpUndef, type_id, undef_id, {}));
-  assert(undef_id == undef_inst->result_id());
-  context_->module()->AddGlobalValue(std::move(undef_inst));
-  return undef_id;
-}
-
 uint32_t
 StructuredLoopToSelectionReductionOpportunity::FindOrCreateGlobalVariable(
     uint32_t pointer_type_id) {
diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity.h b/source/reduce/structured_loop_to_selection_reduction_opportunity.h
index b139016..f6c065b 100644
--- a/source/reduce/structured_loop_to_selection_reduction_opportunity.h
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity.h
@@ -15,36 +15,33 @@
 #ifndef SOURCE_REDUCE_CUT_LOOP_REDUCTION_OPPORTUNITY_H_
 #define SOURCE_REDUCE_CUT_LOOP_REDUCTION_OPPORTUNITY_H_
 
-#include <source/opt/def_use_manager.h>
-#include "reduction_opportunity.h"
+#include "source/opt/def_use_manager.h"
 #include "source/opt/dominator_analysis.h"
 #include "source/opt/function.h"
+#include "source/reduce/reduction_opportunity.h"
 
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
-
-// Captures an opportunity to replace a structured loop with a selection.
+// An opportunity to replace a structured loop with a selection.
 class StructuredLoopToSelectionReductionOpportunity
     : public ReductionOpportunity {
  public:
   // Constructs an opportunity from a loop header block and the function that
   // encloses it.
   explicit StructuredLoopToSelectionReductionOpportunity(
-      IRContext* context, BasicBlock* loop_construct_header,
-      Function* enclosing_function)
+      opt::IRContext* context, opt::BasicBlock* loop_construct_header,
+      opt::Function* enclosing_function)
       : context_(context),
         loop_construct_header_(loop_construct_header),
         enclosing_function_(enclosing_function) {}
 
-  // We require the loop header to be reachable.  A structured loop might
+  // Returns true if the loop header is reachable.  A structured loop might
   // become unreachable as a result of turning another structured loop into
   // a selection.
   bool PreconditionHolds() override;
 
  protected:
-  // Perform the structured loop to selection transformation.
   void Apply() override;
 
  private:
@@ -68,11 +65,12 @@
   // Removes any components of |to_block|'s phi instructions relating to
   // |from_id|.
   void AdaptPhiInstructionsForRemovedEdge(uint32_t from_id,
-                                          BasicBlock* to_block);
+                                          opt::BasicBlock* to_block);
 
   // Adds components to |to_block|'s phi instructions to account for a new
   // incoming edge from |from_id|.
-  void AdaptPhiInstructionsForAddedEdge(uint32_t from_id, BasicBlock* to_block);
+  void AdaptPhiInstructionsForAddedEdge(uint32_t from_id,
+                                        opt::BasicBlock* to_block);
 
   // Turns the OpLoopMerge for the loop into OpSelectionMerge, and adapts the
   // following branch instruction accordingly.
@@ -88,17 +86,10 @@
   // 2) |def| is an OpVariable
   // 3) |use| is part of an OpPhi, with associated incoming block b, and |def|
   // dominates b.
-  bool DefinitionSufficientlyDominatesUse(Instruction* def, Instruction* use,
+  bool DefinitionSufficientlyDominatesUse(opt::Instruction* def,
+                                          opt::Instruction* use,
                                           uint32_t use_index,
-                                          BasicBlock& def_block);
-
-  // Checks whether the global value list has an OpUndef of the given type,
-  // adding one if not, and returns the id of such an OpUndef.
-  //
-  // TODO(2184): This will likely be used by other reduction passes, so should
-  // be factored out in due course.  Parts of the spirv-opt framework provide
-  // similar functionality, so there may be a case for further refactoring.
-  uint32_t FindOrCreateGlobalUndef(uint32_t type_id);
+                                          opt::BasicBlock& def_block);
 
   // Checks whether the global value list has an OpVariable of the given pointer
   // type, adding one if not, and returns the id of such an OpVariable.
@@ -114,9 +105,9 @@
   // be factored out in due course.
   uint32_t FindOrCreateFunctionVariable(uint32_t pointer_type_id);
 
-  IRContext* context_;
-  BasicBlock* loop_construct_header_;
-  Function* enclosing_function_;
+  opt::IRContext* context_;
+  opt::BasicBlock* loop_construct_header_;
+  opt::Function* enclosing_function_;
 };
 
 }  // namespace reduce
diff --git a/source/reduce/structured_loop_to_selection_reduction_pass.cpp b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp
similarity index 73%
rename from source/reduce/structured_loop_to_selection_reduction_pass.cpp
rename to source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp
index 768a2e8..085b267 100644
--- a/source/reduce/structured_loop_to_selection_reduction_pass.cpp
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp
@@ -12,13 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "structured_loop_to_selection_reduction_pass.h"
-#include "structured_loop_to_selection_reduction_opportunity.h"
+#include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h"
+
+#include "source/reduce/structured_loop_to_selection_reduction_opportunity.h"
 
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
+using opt::IRContext;
 
 namespace {
 const uint32_t kMergeNodeIndex = 0;
@@ -26,17 +27,16 @@
 }  // namespace
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
-StructuredLoopToSelectionReductionPass::GetAvailableOpportunities(
-    opt::IRContext* context) const {
+StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities(
+    IRContext* context) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
   std::set<uint32_t> merge_block_ids;
   for (auto& function : *context->module()) {
     for (auto& block : function) {
-      auto merge_inst = block.GetMergeInst();
-      if (merge_inst) {
-        merge_block_ids.insert(
-            merge_inst->GetSingleWordOperand(kMergeNodeIndex));
+      auto merge_block_id = block.MergeBlockIdIfAny();
+      if (merge_block_id) {
+        merge_block_ids.insert(merge_block_id);
       }
     }
   }
@@ -50,11 +50,19 @@
         continue;
       }
 
+      uint32_t continue_block_id =
+          loop_merge_inst->GetSingleWordOperand(kContinueNodeIndex);
+
       // Check whether the loop construct's continue target is the merge block
       // of some structured control flow construct.  If it is, we cautiously do
       // not consider applying a transformation.
-      if (merge_block_ids.find(loop_merge_inst->GetSingleWordOperand(
-              kContinueNodeIndex)) != merge_block_ids.end()) {
+      if (merge_block_ids.find(continue_block_id) != merge_block_ids.end()) {
+        continue;
+      }
+
+      // Check whether the loop header block is also the continue target. If it
+      // is, we cautiously do not consider applying a transformation.
+      if (block.id() == continue_block_id) {
         continue;
       }
 
@@ -87,8 +95,9 @@
   return result;
 }
 
-std::string StructuredLoopToSelectionReductionPass::GetName() const {
-  return "StructuredLoopToSelectionReductionPass";
+std::string StructuredLoopToSelectionReductionOpportunityFinder::GetName()
+    const {
+  return "StructuredLoopToSelectionReductionOpportunityFinder";
 }
 
 }  // namespace reduce
diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h
new file mode 100644
index 0000000..d63d434
--- /dev/null
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_REDUCE_STRUCTURED_LOOP_TO_SELECTION_REDUCTION_OPPORTUNITY_FINDER_H
+#define SOURCE_REDUCE_STRUCTURED_LOOP_TO_SELECTION_REDUCTION_OPPORTUNITY_FINDER_H
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities to turn structured loops into selections,
+// generalizing from a human-writable language the idea of turning a loop:
+//
+// while (c) {
+//   body;
+// }
+//
+// into:
+//
+// if (c) {
+//   body;
+// }
+//
+// Applying such opportunities results in continue constructs of transformed
+// loops becoming unreachable, so that it may be possible to remove them
+// subsequently.
+class StructuredLoopToSelectionReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  StructuredLoopToSelectionReductionOpportunityFinder() = default;
+
+  ~StructuredLoopToSelectionReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_STRUCTURED_LOOP_TO_SELECTION_REDUCTION_OPPORTUNITY_FINDER_H
diff --git a/source/reduce/structured_loop_to_selection_reduction_pass.h b/source/reduce/structured_loop_to_selection_reduction_pass.h
deleted file mode 100644
index 9c4d4ca..0000000
--- a/source/reduce/structured_loop_to_selection_reduction_pass.h
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (c) 2018 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef SOURCE_REDUCE_CUT_LOOP_REDUCTION_PASS_H_
-#define SOURCE_REDUCE_CUT_LOOP_REDUCTION_PASS_H_
-
-#include "reduction_pass.h"
-
-namespace spvtools {
-namespace reduce {
-
-// Turns structured loops into selections, generalizing from a human-writable
-// language the idea of turning a loop:
-//
-// while (c) {
-//   body;
-// }
-//
-// into:
-//
-// if (c) {
-//   body;
-// }
-//
-// The pass results in continue constructs of transformed loops becoming
-// unreachable; another pass for eliminating blocks may end up being able to
-// remove them.
-class StructuredLoopToSelectionReductionPass : public ReductionPass {
- public:
-  // Creates the reduction pass in the context of the given target environment
-  // |target_env|
-  explicit StructuredLoopToSelectionReductionPass(
-      const spv_target_env target_env)
-      : ReductionPass(target_env) {}
-
-  ~StructuredLoopToSelectionReductionPass() override = default;
-
-  // The name of this pass.
-  std::string GetName() const final;
-
- protected:
-  // Finds all opportunities for transforming a structured loop to a selection
-  // in the given module.
-  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
-
- private:
-};
-
-}  // namespace reduce
-}  // namespace spvtools
-
-#endif  // SOURCE_REDUCE_CUT_LOOP_REDUCTION_PASS_H_
diff --git a/source/spirv_reducer_options.cpp b/source/spirv_reducer_options.cpp
index 110ea3e..0426800 100644
--- a/source/spirv_reducer_options.cpp
+++ b/source/spirv_reducer_options.cpp
@@ -29,3 +29,8 @@
     spv_reducer_options options, uint32_t step_limit) {
   options->step_limit = step_limit;
 }
+
+SPIRV_TOOLS_EXPORT void spvReducerOptionsSetFailOnValidationError(
+    spv_reducer_options options, bool fail_on_validation_error) {
+  options->fail_on_validation_error = fail_on_validation_error;
+}
diff --git a/source/spirv_reducer_options.h b/source/spirv_reducer_options.h
index d48303c..363517b 100644
--- a/source/spirv_reducer_options.h
+++ b/source/spirv_reducer_options.h
@@ -26,10 +26,14 @@
 // Manages command line options passed to the SPIR-V Reducer. New struct
 // members may be added for any new option.
 struct spv_reducer_options_t {
-  spv_reducer_options_t() : step_limit(kDefaultStepLimit) {}
+  spv_reducer_options_t()
+      : step_limit(kDefaultStepLimit), fail_on_validation_error(false) {}
 
-  // The number of steps the reducer will run for before giving up.
+  // See spvReducerOptionsSetStepLimit.
   uint32_t step_limit;
+
+  // See spvReducerOptionsSetFailOnValidationError.
+  bool fail_on_validation_error;
 };
 
 #endif  // SOURCE_SPIRV_REDUCER_OPTIONS_H_
diff --git a/source/spirv_target_env.cpp b/source/spirv_target_env.cpp
index a3aa0af..09a1219 100644
--- a/source/spirv_target_env.cpp
+++ b/source/spirv_target_env.cpp
@@ -15,6 +15,7 @@
 #include "source/spirv_target_env.h"
 
 #include <cstring>
+#include <string>
 
 #include "source/spirv_constant.h"
 #include "spirv-tools/libspirv.h"
@@ -61,6 +62,10 @@
       return "SPIR-V 1.3 (under Vulkan 1.1 semantics)";
     case SPV_ENV_WEBGPU_0:
       return "SPIR-V 1.3 (under WIP WebGPU semantics)";
+    case SPV_ENV_UNIVERSAL_1_4:
+      return "SPIR-V 1.4";
+    case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
+      return "SPIR-V 1.4 (under Vulkan 1.1 semantics)";
   }
   return "";
 }
@@ -91,6 +96,9 @@
     case SPV_ENV_VULKAN_1_1:
     case SPV_ENV_WEBGPU_0:
       return SPV_SPIRV_VERSION_WORD(1, 3);
+    case SPV_ENV_UNIVERSAL_1_4:
+    case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
+      return SPV_SPIRV_VERSION_WORD(1, 4);
   }
   return SPV_SPIRV_VERSION_WORD(0, 0);
 }
@@ -99,7 +107,10 @@
   auto match = [s](const char* b) {
     return s && (0 == strncmp(s, b, strlen(b)));
   };
-  if (match("vulkan1.0")) {
+  if (match("vulkan1.1spv1.4")) {
+    if (env) *env = SPV_ENV_VULKAN_1_1_SPIRV_1_4;
+    return true;
+  } else if (match("vulkan1.0")) {
     if (env) *env = SPV_ENV_VULKAN_1_0;
     return true;
   } else if (match("vulkan1.1")) {
@@ -117,6 +128,9 @@
   } else if (match("spv1.3")) {
     if (env) *env = SPV_ENV_UNIVERSAL_1_3;
     return true;
+  } else if (match("spv1.4")) {
+    if (env) *env = SPV_ENV_UNIVERSAL_1_4;
+    return true;
   } else if (match("opencl1.2embedded")) {
     if (env) *env = SPV_ENV_OPENCL_EMBEDDED_1_2;
     return true;
@@ -185,9 +199,11 @@
     case SPV_ENV_OPENCL_EMBEDDED_2_2:
     case SPV_ENV_UNIVERSAL_1_3:
     case SPV_ENV_WEBGPU_0:
+    case SPV_ENV_UNIVERSAL_1_4:
       return false;
     case SPV_ENV_VULKAN_1_0:
     case SPV_ENV_VULKAN_1_1:
+    case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
       return true;
   }
   return false;
@@ -207,6 +223,8 @@
     case SPV_ENV_UNIVERSAL_1_3:
     case SPV_ENV_VULKAN_1_1:
     case SPV_ENV_WEBGPU_0:
+    case SPV_ENV_UNIVERSAL_1_4:
+    case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
       return false;
     case SPV_ENV_OPENCL_1_2:
     case SPV_ENV_OPENCL_EMBEDDED_1_2:
@@ -242,9 +260,53 @@
     case SPV_ENV_OPENCL_EMBEDDED_2_2:
     case SPV_ENV_OPENCL_2_1:
     case SPV_ENV_OPENCL_2_2:
+    case SPV_ENV_UNIVERSAL_1_4:
+    case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
       return false;
     case SPV_ENV_WEBGPU_0:
       return true;
   }
   return false;
 }
+
+bool spvIsVulkanOrWebGPUEnv(spv_target_env env) {
+  return spvIsVulkanEnv(env) || spvIsWebGPUEnv(env);
+}
+
+std::string spvLogStringForEnv(spv_target_env env) {
+  switch (env) {
+    case SPV_ENV_OPENCL_1_2:
+    case SPV_ENV_OPENCL_2_0:
+    case SPV_ENV_OPENCL_2_1:
+    case SPV_ENV_OPENCL_2_2:
+    case SPV_ENV_OPENCL_EMBEDDED_1_2:
+    case SPV_ENV_OPENCL_EMBEDDED_2_0:
+    case SPV_ENV_OPENCL_EMBEDDED_2_1:
+    case SPV_ENV_OPENCL_EMBEDDED_2_2: {
+      return "OpenCL";
+    }
+    case SPV_ENV_OPENGL_4_0:
+    case SPV_ENV_OPENGL_4_1:
+    case SPV_ENV_OPENGL_4_2:
+    case SPV_ENV_OPENGL_4_3:
+    case SPV_ENV_OPENGL_4_5: {
+      return "OpenGL";
+    }
+    case SPV_ENV_VULKAN_1_0:
+    case SPV_ENV_VULKAN_1_1:
+    case SPV_ENV_VULKAN_1_1_SPIRV_1_4: {
+      return "Vulkan";
+    }
+    case SPV_ENV_WEBGPU_0: {
+      return "WebGPU";
+    }
+    case SPV_ENV_UNIVERSAL_1_0:
+    case SPV_ENV_UNIVERSAL_1_1:
+    case SPV_ENV_UNIVERSAL_1_2:
+    case SPV_ENV_UNIVERSAL_1_3:
+    case SPV_ENV_UNIVERSAL_1_4: {
+      return "Universal";
+    }
+  }
+  return "Unknown";
+}
diff --git a/source/spirv_target_env.h b/source/spirv_target_env.h
index d1bd835..c19d169 100644
--- a/source/spirv_target_env.h
+++ b/source/spirv_target_env.h
@@ -15,11 +15,9 @@
 #ifndef SOURCE_SPIRV_TARGET_ENV_H_
 #define SOURCE_SPIRV_TARGET_ENV_H_
 
-#include "spirv-tools/libspirv.h"
+#include <string>
 
-// Parses s into *env and returns true if successful.  If unparsable, returns
-// false and sets *env to SPV_ENV_UNIVERSAL_1_0.
-bool spvParseTargetEnv(const char* s, spv_target_env* env);
+#include "spirv-tools/libspirv.h"
 
 // Returns true if |env| is a VULKAN environment, false otherwise.
 bool spvIsVulkanEnv(spv_target_env env);
@@ -30,7 +28,14 @@
 // Returns true if |env| is an WEBGPU environment, false otherwise.
 bool spvIsWebGPUEnv(spv_target_env env);
 
+// Returns true if |env| is a VULKAN or WEBGPU environment, false otherwise.
+bool spvIsVulkanOrWebGPUEnv(spv_target_env env);
+
 // Returns the version number for the given SPIR-V target environment.
 uint32_t spvVersionForTargetEnv(spv_target_env env);
 
+// Returns a string to use in logging messages that indicates the class of
+// environment, i.e. "Vulkan", "WebGPU", "OpenCL", etc.
+std::string spvLogStringForEnv(spv_target_env env);
+
 #endif  // SOURCE_SPIRV_TARGET_ENV_H_
diff --git a/source/spirv_validator_options.cpp b/source/spirv_validator_options.cpp
index 2e9cf26..9b522fb 100644
--- a/source/spirv_validator_options.cpp
+++ b/source/spirv_validator_options.cpp
@@ -12,11 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "source/spirv_validator_options.h"
+
 #include <cassert>
 #include <cstring>
 
-#include "source/spirv_validator_options.h"
-
 bool spvParseUniversalLimitsOptions(const char* s, spv_validator_limit* type) {
   auto match = [s](const char* b) {
     return s && (0 == strncmp(s, b, strlen(b)));
@@ -95,6 +95,11 @@
   options->relax_block_layout = val;
 }
 
+void spvValidatorOptionsSetUniformBufferStandardLayout(
+    spv_validator_options options, bool val) {
+  options->uniform_buffer_standard_layout = val;
+}
+
 void spvValidatorOptionsSetScalarBlockLayout(spv_validator_options options,
                                              bool val) {
   options->scalar_block_layout = val;
diff --git a/source/spirv_validator_options.h b/source/spirv_validator_options.h
index f426ebf..5b27de6 100644
--- a/source/spirv_validator_options.h
+++ b/source/spirv_validator_options.h
@@ -43,6 +43,7 @@
         relax_struct_store(false),
         relax_logical_pointer(false),
         relax_block_layout(false),
+        uniform_buffer_standard_layout(false),
         scalar_block_layout(false),
         skip_block_layout(false) {}
 
@@ -50,6 +51,7 @@
   bool relax_struct_store;
   bool relax_logical_pointer;
   bool relax_block_layout;
+  bool uniform_buffer_standard_layout;
   bool scalar_block_layout;
   bool skip_block_layout;
 };
diff --git a/source/table.cpp b/source/table.cpp
index b10d776..7890305 100644
--- a/source/table.cpp
+++ b/source/table.cpp
@@ -37,7 +37,9 @@
     case SPV_ENV_UNIVERSAL_1_2:
     case SPV_ENV_UNIVERSAL_1_3:
     case SPV_ENV_VULKAN_1_1:
+    case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
     case SPV_ENV_WEBGPU_0:
+    case SPV_ENV_UNIVERSAL_1_4:
       break;
     default:
       return nullptr;
diff --git a/source/text.cpp b/source/text.cpp
index adaf796..9eadf73 100644
--- a/source/text.cpp
+++ b/source/text.cpp
@@ -778,7 +778,7 @@
                              const size_t input_text_size, spv_binary* pBinary,
                              spv_diagnostic* pDiagnostic) {
   return spvTextToBinaryWithOptions(context, input_text, input_text_size,
-                                    SPV_BINARY_TO_TEXT_OPTION_NONE, pBinary,
+                                    SPV_TEXT_TO_BINARY_OPTION_NONE, pBinary,
                                     pDiagnostic);
 }
 
diff --git a/source/val/construct.cpp b/source/val/construct.cpp
index 7b0cb2d..e0053ee 100644
--- a/source/val/construct.cpp
+++ b/source/val/construct.cpp
@@ -73,7 +73,6 @@
   auto header = entry_block();
   auto merge = exit_block();
   assert(header);
-  assert(merge);
   int header_depth = function->GetBlockDepth(const_cast<BasicBlock*>(header));
   ConstructBlockSet construct_blocks;
   std::unordered_set<BasicBlock*> corresponding_headers;
diff --git a/source/val/function.cpp b/source/val/function.cpp
index f638fb5..90893b3 100644
--- a/source/val/function.cpp
+++ b/source/val/function.cpp
@@ -86,6 +86,12 @@
   continue_construct.set_corresponding_constructs({&loop_construct});
   loop_construct.set_corresponding_constructs({&continue_construct});
   merge_block_header_[&merge_block] = current_block_;
+  if (continue_target_headers_.find(&continue_target_block) ==
+      continue_target_headers_.end()) {
+    continue_target_headers_[&continue_target_block] = {current_block_};
+  } else {
+    continue_target_headers_[&continue_target_block].push_back(current_block_);
+  }
 
   return SPV_SUCCESS;
 }
diff --git a/source/val/function.h b/source/val/function.h
index a052bbd..9cda2ff 100644
--- a/source/val/function.h
+++ b/source/val/function.h
@@ -232,6 +232,28 @@
     return function_call_targets_;
   }
 
+  // Returns the block containing the OpSelectionMerge or OpLoopMerge that
+  // references |merge_block|.
+  // Values of |merge_block_header_| inserted by CFGPass, so do not call before
+  // the first iteration of ordered instructions in
+  // ValidateBinaryUsingContextAndValidationState has completed.
+  BasicBlock* GetMergeHeader(BasicBlock* merge_block) {
+    return merge_block_header_[merge_block];
+  }
+
+  // Returns vector of the blocks containing a OpLoopMerge that references
+  // |continue_target|.
+  // Values of |continue_target_headers_| inserted by CFGPass, so do not call
+  // before the first iteration of ordered instructions in
+  // ValidateBinaryUsingContextAndValidationState has completed.
+  std::vector<BasicBlock*> GetContinueHeaders(BasicBlock* continue_target) {
+    if (continue_target_headers_.find(continue_target) ==
+        continue_target_headers_.end()) {
+      return {};
+    }
+    return continue_target_headers_[continue_target];
+  }
+
  private:
   // Computes the representation of the augmented CFG.
   // Populates augmented_successors_map_ and augmented_predecessors_map_.
@@ -340,6 +362,10 @@
   /// This map provides the header block for a given merge block.
   std::unordered_map<BasicBlock*, BasicBlock*> merge_block_header_;
 
+  /// This map provides the header blocks for a given continue target.
+  std::unordered_map<BasicBlock*, std::vector<BasicBlock*>>
+      continue_target_headers_;
+
   /// Stores the control flow nesting depth of a given basic block
   std::unordered_map<BasicBlock*, int> block_depth_;
 
diff --git a/source/val/validate.cpp b/source/val/validate.cpp
index 5d0c624..4a730da 100644
--- a/source/val/validate.cpp
+++ b/source/val/validate.cpp
@@ -14,10 +14,9 @@
 
 #include "source/val/validate.h"
 
+#include <algorithm>
 #include <cassert>
 #include <cstdio>
-
-#include <algorithm>
 #include <functional>
 #include <iterator>
 #include <memory>
@@ -52,21 +51,6 @@
 namespace val {
 namespace {
 
-// TODO(umar): Validate header
-// TODO(umar): The binary parser validates the magic word, and the length of the
-// header, but nothing else.
-spv_result_t setHeader(void* user_data, spv_endianness_t, uint32_t,
-                       uint32_t version, uint32_t generator, uint32_t id_bound,
-                       uint32_t) {
-  // Record the ID bound so that the validator can ensure no ID is out of bound.
-  ValidationState_t& _ = *(reinterpret_cast<ValidationState_t*>(user_data));
-  _.setIdBound(id_bound);
-  _.setGenerator(generator);
-  _.setVersion(version);
-
-  return SPV_SUCCESS;
-}
-
 // Parses OpExtension instruction and registers extension.
 void RegisterExtension(ValidationState_t& _,
                        const spv_parsed_instruction_t* inst) {
@@ -110,48 +94,6 @@
   return SPV_SUCCESS;
 }
 
-void printDot(const ValidationState_t& _, const BasicBlock& other) {
-  std::string block_string;
-  if (other.successors()->empty()) {
-    block_string += "end ";
-  } else {
-    for (auto block : *other.successors()) {
-      block_string += _.getIdName(block->id()) + " ";
-    }
-  }
-  printf("%10s -> {%s\b}\n", _.getIdName(other.id()).c_str(),
-         block_string.c_str());
-}
-
-void PrintBlocks(ValidationState_t& _, Function func) {
-  assert(func.first_block());
-
-  printf("%10s -> %s\n", _.getIdName(func.id()).c_str(),
-         _.getIdName(func.first_block()->id()).c_str());
-  for (const auto& block : func.ordered_blocks()) {
-    printDot(_, *block);
-  }
-}
-
-#ifdef __clang__
-#define UNUSED(func) [[gnu::unused]] func
-#elif defined(__GNUC__)
-#define UNUSED(func)            \
-  func __attribute__((unused)); \
-  func
-#elif defined(_MSC_VER)
-#define UNUSED(func) func
-#endif
-
-UNUSED(void PrintDotGraph(ValidationState_t& _, Function func)) {
-  if (func.first_block()) {
-    std::string func_name(_.getIdName(func.id()));
-    printf("digraph %s {\n", func_name.c_str());
-    PrintBlocks(_, func);
-    printf("}\n");
-  }
-}
-
 spv_result_t ValidateForwardDecls(ValidationState_t& _) {
   if (_.unresolved_forward_id_count() == 0) return SPV_SUCCESS;
 
@@ -169,6 +111,57 @@
          << id_str.substr(0, id_str.size() - 1);
 }
 
+std::vector<std::string> CalculateNamesForEntryPoint(ValidationState_t& _,
+                                                     const uint32_t id) {
+  auto id_descriptions = _.entry_point_descriptions(id);
+  auto id_names = std::vector<std::string>();
+  id_names.reserve((id_descriptions.size()));
+
+  for (auto description : id_descriptions) id_names.push_back(description.name);
+
+  return id_names;
+}
+
+spv_result_t ValidateEntryPointNameUnique(ValidationState_t& _,
+                                          const uint32_t id) {
+  auto id_names = CalculateNamesForEntryPoint(_, id);
+  const auto names =
+      std::unordered_set<std::string>(id_names.begin(), id_names.end());
+
+  if (id_names.size() != names.size()) {
+    std::sort(id_names.begin(), id_names.end());
+    for (size_t i = 0; i < id_names.size() - 1; i++) {
+      if (id_names[i] == id_names[i + 1]) {
+        return _.diag(SPV_ERROR_INVALID_BINARY, _.FindDef(id))
+               << "Entry point name \"" << id_names[i]
+               << "\" is not unique, which is not allow in WebGPU env.";
+      }
+    }
+  }
+
+  for (const auto other_id : _.entry_points()) {
+    if (other_id == id) continue;
+    const auto other_id_names = CalculateNamesForEntryPoint(_, other_id);
+    for (const auto other_id_name : other_id_names) {
+      if (names.find(other_id_name) != names.end()) {
+        return _.diag(SPV_ERROR_INVALID_BINARY, _.FindDef(id))
+               << "Entry point name \"" << other_id_name
+               << "\" is not unique, which is not allow in WebGPU env.";
+      }
+    }
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateEntryPointNamesUnique(ValidationState_t& _) {
+  for (const auto id : _.entry_points()) {
+    auto result = ValidateEntryPointNameUnique(_, id);
+    if (result != SPV_SUCCESS) return result;
+  }
+  return SPV_SUCCESS;
+}
+
 // Entry point validation. Based on 2.16.1 (Universal Validation Rules) of the
 // SPIRV spec:
 // * There is at least one OpEntryPoint instruction, unless the Linkage
@@ -177,7 +170,7 @@
 //   OpFunctionCall instruction.
 //
 // Additionally enforces that entry points for Vulkan and WebGPU should not have
-// recursion.
+// recursion. And that entry names should be unique for WebGPU.
 spv_result_t ValidateEntryPoints(ValidationState_t& _) {
   _.ComputeFunctionToEntryPointMapping();
   _.ComputeRecursiveEntryPoints();
@@ -198,14 +191,19 @@
 
     // For Vulkan and WebGPU, the static function-call graph for an entry point
     // must not contain cycles.
-    if (spvIsWebGPUEnv(_.context()->target_env) ||
-        spvIsVulkanEnv(_.context()->target_env)) {
+    if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
       if (_.recursive_entry_points().find(entry_point) !=
           _.recursive_entry_points().end()) {
         return _.diag(SPV_ERROR_INVALID_BINARY, _.FindDef(entry_point))
                << "Entry points may not have a call graph with cycles.";
       }
     }
+
+    // For WebGPU all entry point names must be unique.
+    if (spvIsWebGPUEnv(_.context()->target_env)) {
+      const auto result = ValidateEntryPointNamesUnique(_);
+      if (result != SPV_SUCCESS) return result;
+    }
   }
 
   return SPV_SUCCESS;
@@ -225,6 +223,12 @@
            << "Invalid SPIR-V magic number.";
   }
 
+  if (spvIsWebGPUEnv(context.target_env) && endian != SPV_ENDIANNESS_LITTLE) {
+    return DiagnosticStream(position, context.consumer, "",
+                            SPV_ERROR_INVALID_BINARY)
+           << "WebGPU requires SPIR-V to be little endian.";
+  }
+
   spv_header_t header;
   if (spvBinaryHeaderGet(binary.get(), endian, &header)) {
     return DiagnosticStream(position, context.consumer, "",
@@ -262,7 +266,8 @@
 
   // Parse the module and perform inline validation checks. These checks do
   // not require the the knowledge of the whole module.
-  if (auto error = spvBinaryParse(&context, vstate, words, num_words, setHeader,
+  if (auto error = spvBinaryParse(&context, vstate, words, num_words,
+                                  /*parsed_header =*/nullptr,
                                   ProcessInstruction, pDiagnostic)) {
     return error;
   }
@@ -329,7 +334,6 @@
       Instruction* inst = const_cast<Instruction*>(&instruction);
       vstate->RegisterInstruction(inst);
     }
-    if (auto error = UpdateIdUse(*vstate, &instruction)) return error;
   }
 
   if (!vstate->has_memory_model_specified())
@@ -343,6 +347,19 @@
   // Catch undefined forward references before performing further checks.
   if (auto error = ValidateForwardDecls(*vstate)) return error;
 
+  // ID usage needs be handled in its own iteration of the instructions,
+  // between the two others. It depends on the first loop to have been
+  // finished, so that all instructions have been registered. And the following
+  // loop depends on all of the usage data being populated. Thus it cannot live
+  // in either of those iterations.
+  // It should also live after the forward declaration check, since it will
+  // have problems with missing forward declarations, but give less useful error
+  // messages.
+  for (size_t i = 0; i < vstate->ordered_instructions().size(); ++i) {
+    auto& instruction = vstate->ordered_instructions()[i];
+    if (auto error = UpdateIdUse(*vstate, &instruction)) return error;
+  }
+
   // Validate individual opcodes.
   for (size_t i = 0; i < vstate->ordered_instructions().size(); ++i) {
     auto& instruction = vstate->ordered_instructions()[i];
diff --git a/source/val/validate_annotation.cpp b/source/val/validate_annotation.cpp
index 621ace2..4e8a2cd 100644
--- a/source/val/validate_annotation.cpp
+++ b/source/val/validate_annotation.cpp
@@ -23,6 +23,183 @@
 namespace val {
 namespace {
 
+bool IsValidWebGPUDecoration(uint32_t decoration) {
+  switch (decoration) {
+    case SpvDecorationSpecId:
+    case SpvDecorationBlock:
+    case SpvDecorationRowMajor:
+    case SpvDecorationColMajor:
+    case SpvDecorationArrayStride:
+    case SpvDecorationMatrixStride:
+    case SpvDecorationBuiltIn:
+    case SpvDecorationNoPerspective:
+    case SpvDecorationFlat:
+    case SpvDecorationCentroid:
+    case SpvDecorationRestrict:
+    case SpvDecorationAliased:
+    case SpvDecorationNonWritable:
+    case SpvDecorationNonReadable:
+    case SpvDecorationUniform:
+    case SpvDecorationLocation:
+    case SpvDecorationComponent:
+    case SpvDecorationIndex:
+    case SpvDecorationBinding:
+    case SpvDecorationDescriptorSet:
+    case SpvDecorationOffset:
+    case SpvDecorationNoContraction:
+      return true;
+    default:
+      return false;
+  }
+}
+
+std::string LogStringForDecoration(uint32_t decoration) {
+  switch (decoration) {
+    case SpvDecorationRelaxedPrecision:
+      return "RelaxedPrecision";
+    case SpvDecorationSpecId:
+      return "SpecId";
+    case SpvDecorationBlock:
+      return "Block";
+    case SpvDecorationBufferBlock:
+      return "BufferBlock";
+    case SpvDecorationRowMajor:
+      return "RowMajor";
+    case SpvDecorationColMajor:
+      return "ColMajor";
+    case SpvDecorationArrayStride:
+      return "ArrayStride";
+    case SpvDecorationMatrixStride:
+      return "MatrixStride";
+    case SpvDecorationGLSLShared:
+      return "GLSLShared";
+    case SpvDecorationGLSLPacked:
+      return "GLSLPacked";
+    case SpvDecorationCPacked:
+      return "CPacked";
+    case SpvDecorationBuiltIn:
+      return "BuiltIn";
+    case SpvDecorationNoPerspective:
+      return "NoPerspective";
+    case SpvDecorationFlat:
+      return "Flat";
+    case SpvDecorationPatch:
+      return "Patch";
+    case SpvDecorationCentroid:
+      return "Centroid";
+    case SpvDecorationSample:
+      return "Sample";
+    case SpvDecorationInvariant:
+      return "Invariant";
+    case SpvDecorationRestrict:
+      return "Restrict";
+    case SpvDecorationAliased:
+      return "Aliased";
+    case SpvDecorationVolatile:
+      return "Volatile";
+    case SpvDecorationConstant:
+      return "Constant";
+    case SpvDecorationCoherent:
+      return "Coherent";
+    case SpvDecorationNonWritable:
+      return "NonWritable";
+    case SpvDecorationNonReadable:
+      return "NonReadable";
+    case SpvDecorationUniform:
+      return "Uniform";
+    case SpvDecorationSaturatedConversion:
+      return "SaturatedConversion";
+    case SpvDecorationStream:
+      return "Stream";
+    case SpvDecorationLocation:
+      return "Location";
+    case SpvDecorationComponent:
+      return "Component";
+    case SpvDecorationIndex:
+      return "Index";
+    case SpvDecorationBinding:
+      return "Binding";
+    case SpvDecorationDescriptorSet:
+      return "DescriptorSet";
+    case SpvDecorationOffset:
+      return "Offset";
+    case SpvDecorationXfbBuffer:
+      return "XfbBuffer";
+    case SpvDecorationXfbStride:
+      return "XfbStride";
+    case SpvDecorationFuncParamAttr:
+      return "FuncParamAttr";
+    case SpvDecorationFPRoundingMode:
+      return "FPRoundingMode";
+    case SpvDecorationFPFastMathMode:
+      return "FPFastMathMode";
+    case SpvDecorationLinkageAttributes:
+      return "LinkageAttributes";
+    case SpvDecorationNoContraction:
+      return "NoContraction";
+    case SpvDecorationInputAttachmentIndex:
+      return "InputAttachmentIndex";
+    case SpvDecorationAlignment:
+      return "Alignment";
+    case SpvDecorationMaxByteOffset:
+      return "MaxByteOffset";
+    case SpvDecorationAlignmentId:
+      return "AlignmentId";
+    case SpvDecorationMaxByteOffsetId:
+      return "MaxByteOffsetId";
+    case SpvDecorationNoSignedWrap:
+      return "NoSignedWrap";
+    case SpvDecorationNoUnsignedWrap:
+      return "NoUnsignedWrap";
+    case SpvDecorationExplicitInterpAMD:
+      return "ExplicitInterpAMD";
+    case SpvDecorationOverrideCoverageNV:
+      return "OverrideCoverageNV";
+    case SpvDecorationPassthroughNV:
+      return "PassthroughNV";
+    case SpvDecorationViewportRelativeNV:
+      return "ViewportRelativeNV";
+    case SpvDecorationSecondaryViewportRelativeNV:
+      return "SecondaryViewportRelativeNV";
+    case SpvDecorationPerPrimitiveNV:
+      return "PerPrimitiveNV";
+    case SpvDecorationPerViewNV:
+      return "PerViewNV";
+    case SpvDecorationPerTaskNV:
+      return "PerTaskNV";
+    case SpvDecorationPerVertexNV:
+      return "PerVertexNV";
+    case SpvDecorationNonUniformEXT:
+      return "NonUniformEXT";
+    case SpvDecorationRestrictPointerEXT:
+      return "RestrictPointerEXT";
+    case SpvDecorationAliasedPointerEXT:
+      return "AliasedPointerEXT";
+    case SpvDecorationHlslCounterBufferGOOGLE:
+      return "HlslCounterBufferGOOGLE";
+    case SpvDecorationHlslSemanticGOOGLE:
+      return "HlslSemanticGOOGLE";
+    default:
+      break;
+  }
+  return "Unknown";
+}
+
+// Returns true if the decoration takes ID parameters.
+// TODO(dneto): This can be generated from the grammar.
+bool DecorationTakesIdParameters(uint32_t type) {
+  switch (static_cast<SpvDecoration>(type)) {
+    case SpvDecorationUniformId:
+    case SpvDecorationAlignmentId:
+    case SpvDecorationMaxByteOffsetId:
+    case SpvDecorationHlslCounterBufferGOOGLE:
+      return true;
+    default:
+      break;
+  }
+  return false;
+}
+
 spv_result_t ValidateDecorate(ValidationState_t& _, const Instruction* inst) {
   const auto decoration = inst->GetOperandAs<uint32_t>(1);
   if (decoration == SpvDecorationSpecId) {
@@ -31,14 +208,39 @@
     if (!target || !spvOpcodeIsScalarSpecConstant(target->opcode())) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
              << "OpDecorate SpecId decoration target <id> '"
-             << _.getIdName(decoration)
+             << _.getIdName(target_id)
              << "' is not a scalar specialization constant.";
     }
   }
+
+  if (spvIsWebGPUEnv(_.context()->target_env) &&
+      !IsValidWebGPUDecoration(decoration)) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "OpDecorate decoration '" << LogStringForDecoration(decoration)
+           << "' is not valid for the WebGPU execution environment.";
+  }
+
+  if (DecorationTakesIdParameters(decoration)) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Decorations taking ID parameters may not be used with "
+              "OpDecorateId";
+  }
   // TODO: Add validations for all decorations.
   return SPV_SUCCESS;
 }
 
+spv_result_t ValidateDecorateId(ValidationState_t& _, const Instruction* inst) {
+  const auto decoration = inst->GetOperandAs<uint32_t>(1);
+  if (!DecorationTakesIdParameters(decoration)) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Decorations that don't take ID parameters may not be used with "
+              "OpDecorateId";
+  }
+  // TODO: Add validations for these decorations.
+  // UniformId is covered elsewhere.
+  return SPV_SUCCESS;
+}
+
 spv_result_t ValidateMemberDecorate(ValidationState_t& _,
                                     const Instruction* inst) {
   const auto struct_type_id = inst->GetOperandAs<uint32_t>(0);
@@ -59,6 +261,15 @@
            << " is out of bounds. The structure has " << member_count
            << " members. Largest valid index is " << member_count - 1 << ".";
   }
+
+  const auto decoration = inst->GetOperandAs<uint32_t>(2);
+  if (spvIsWebGPUEnv(_.context()->target_env) &&
+      !IsValidWebGPUDecoration(decoration)) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "OpMemberDecorate decoration  '" << _.getIdName(decoration)
+           << "' is not valid for the WebGPU execution environment.";
+  }
+
   return SPV_SUCCESS;
 }
 
@@ -161,7 +372,8 @@
 spv_result_t RegisterDecorations(ValidationState_t& _,
                                  const Instruction* inst) {
   switch (inst->opcode()) {
-    case SpvOpDecorate: {
+    case SpvOpDecorate:
+    case SpvOpDecorateId: {
       const uint32_t target_id = inst->word(1);
       const SpvDecoration dec_type = static_cast<SpvDecoration>(inst->word(2));
       std::vector<uint32_t> dec_params;
@@ -236,6 +448,11 @@
     case SpvOpDecorate:
       if (auto error = ValidateDecorate(_, inst)) return error;
       break;
+    case SpvOpDecorateId:
+      if (auto error = ValidateDecorateId(_, inst)) return error;
+      break;
+    // TODO(dneto): SpvOpDecorateStringGOOGLE
+    // See https://github.com/KhronosGroup/SPIRV-Tools/issues/2253
     case SpvOpMemberDecorate:
       if (auto error = ValidateMemberDecorate(_, inst)) return error;
       break;
diff --git a/source/val/validate_arithmetics.cpp b/source/val/validate_arithmetics.cpp
index 2314e7d..433330d 100644
--- a/source/val/validate_arithmetics.cpp
+++ b/source/val/validate_arithmetics.cpp
@@ -39,8 +39,11 @@
     case SpvOpFRem:
     case SpvOpFMod:
     case SpvOpFNegate: {
+      bool supportsCoopMat =
+          (opcode != SpvOpFMul && opcode != SpvOpFRem && opcode != SpvOpFMod);
       if (!_.IsFloatScalarType(result_type) &&
-          !_.IsFloatVectorType(result_type))
+          !_.IsFloatVectorType(result_type) &&
+          !(supportsCoopMat && _.IsFloatCooperativeMatrixType(result_type)))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected floating scalar or vector type as Result Type: "
                << spvOpcodeString(opcode);
@@ -58,8 +61,11 @@
 
     case SpvOpUDiv:
     case SpvOpUMod: {
+      bool supportsCoopMat = (opcode == SpvOpUDiv);
       if (!_.IsUnsignedIntScalarType(result_type) &&
-          !_.IsUnsignedIntVectorType(result_type))
+          !_.IsUnsignedIntVectorType(result_type) &&
+          !(supportsCoopMat &&
+            _.IsUnsignedIntCooperativeMatrixType(result_type)))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected unsigned int scalar or vector type as Result Type: "
                << spvOpcodeString(opcode);
@@ -82,7 +88,10 @@
     case SpvOpSMod:
     case SpvOpSRem:
     case SpvOpSNegate: {
-      if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type))
+      bool supportsCoopMat =
+          (opcode != SpvOpIMul && opcode != SpvOpSRem && opcode != SpvOpSMod);
+      if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type) &&
+          !(supportsCoopMat && _.IsIntCooperativeMatrixType(result_type)))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected int scalar or vector type as Result Type: "
                << spvOpcodeString(opcode);
@@ -94,7 +103,8 @@
            ++operand_index) {
         const uint32_t type_id = _.GetOperandTypeId(inst, operand_index);
         if (!type_id ||
-            (!_.IsIntScalarType(type_id) && !_.IsIntVectorType(type_id)))
+            (!_.IsIntScalarType(type_id) && !_.IsIntVectorType(type_id) &&
+             !(supportsCoopMat && _.IsIntCooperativeMatrixType(result_type))))
           return _.diag(SPV_ERROR_INVALID_DATA, inst)
                  << "Expected int scalar or vector type as operand: "
                  << spvOpcodeString(opcode) << " operand index "
@@ -176,7 +186,8 @@
     }
 
     case SpvOpMatrixTimesScalar: {
-      if (!_.IsFloatMatrixType(result_type))
+      if (!_.IsFloatMatrixType(result_type) &&
+          !_.IsCooperativeMatrixType(result_type))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected float matrix type as Result Type: "
                << spvOpcodeString(opcode);
@@ -442,6 +453,92 @@
       break;
     }
 
+    case SpvOpCooperativeMatrixMulAddNV: {
+      const uint32_t D_type_id = _.GetOperandTypeId(inst, 1);
+      const uint32_t A_type_id = _.GetOperandTypeId(inst, 2);
+      const uint32_t B_type_id = _.GetOperandTypeId(inst, 3);
+      const uint32_t C_type_id = _.GetOperandTypeId(inst, 4);
+
+      if (!_.IsCooperativeMatrixType(A_type_id)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Expected cooperative matrix type as A Type: "
+               << spvOpcodeString(opcode);
+      }
+      if (!_.IsCooperativeMatrixType(B_type_id)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Expected cooperative matrix type as B Type: "
+               << spvOpcodeString(opcode);
+      }
+      if (!_.IsCooperativeMatrixType(C_type_id)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Expected cooperative matrix type as C Type: "
+               << spvOpcodeString(opcode);
+      }
+      if (!_.IsCooperativeMatrixType(D_type_id)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Expected cooperative matrix type as Result Type: "
+               << spvOpcodeString(opcode);
+      }
+
+      const auto A = _.FindDef(A_type_id);
+      const auto B = _.FindDef(B_type_id);
+      const auto C = _.FindDef(C_type_id);
+      const auto D = _.FindDef(D_type_id);
+
+      std::tuple<bool, bool, uint32_t> A_scope, B_scope, C_scope, D_scope,
+          A_rows, B_rows, C_rows, D_rows, A_cols, B_cols, C_cols, D_cols;
+
+      A_scope = _.EvalInt32IfConst(A->GetOperandAs<uint32_t>(2));
+      B_scope = _.EvalInt32IfConst(B->GetOperandAs<uint32_t>(2));
+      C_scope = _.EvalInt32IfConst(C->GetOperandAs<uint32_t>(2));
+      D_scope = _.EvalInt32IfConst(D->GetOperandAs<uint32_t>(2));
+
+      A_rows = _.EvalInt32IfConst(A->GetOperandAs<uint32_t>(3));
+      B_rows = _.EvalInt32IfConst(B->GetOperandAs<uint32_t>(3));
+      C_rows = _.EvalInt32IfConst(C->GetOperandAs<uint32_t>(3));
+      D_rows = _.EvalInt32IfConst(D->GetOperandAs<uint32_t>(3));
+
+      A_cols = _.EvalInt32IfConst(A->GetOperandAs<uint32_t>(4));
+      B_cols = _.EvalInt32IfConst(B->GetOperandAs<uint32_t>(4));
+      C_cols = _.EvalInt32IfConst(C->GetOperandAs<uint32_t>(4));
+      D_cols = _.EvalInt32IfConst(D->GetOperandAs<uint32_t>(4));
+
+      const auto notEqual = [](std::tuple<bool, bool, uint32_t> X,
+                               std::tuple<bool, bool, uint32_t> Y) {
+        return (std::get<1>(X) && std::get<1>(Y) &&
+                std::get<2>(X) != std::get<2>(Y));
+      };
+
+      if (notEqual(A_scope, B_scope) || notEqual(A_scope, C_scope) ||
+          notEqual(A_scope, D_scope) || notEqual(B_scope, C_scope) ||
+          notEqual(B_scope, D_scope) || notEqual(C_scope, D_scope)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Cooperative matrix scopes must match: "
+               << spvOpcodeString(opcode);
+      }
+
+      if (notEqual(A_rows, C_rows) || notEqual(A_rows, D_rows) ||
+          notEqual(C_rows, D_rows)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Cooperative matrix 'M' mismatch: "
+               << spvOpcodeString(opcode);
+      }
+
+      if (notEqual(B_cols, C_cols) || notEqual(B_cols, D_cols) ||
+          notEqual(C_cols, D_cols)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Cooperative matrix 'N' mismatch: "
+               << spvOpcodeString(opcode);
+      }
+
+      if (notEqual(A_cols, B_rows)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Cooperative matrix 'K' mismatch: "
+               << spvOpcodeString(opcode);
+      }
+      break;
+    }
+
     default:
       break;
   }
diff --git a/source/val/validate_atomics.cpp b/source/val/validate_atomics.cpp
index 6bfd06d..38c7053 100644
--- a/source/val/validate_atomics.cpp
+++ b/source/val/validate_atomics.cpp
@@ -127,6 +127,7 @@
         case SpvStorageClassAtomicCounter:
         case SpvStorageClassImage:
         case SpvStorageClassStorageBuffer:
+        case SpvStorageClassPhysicalStorageBufferEXT:
           break;
         default:
           if (spvIsOpenCLEnv(_.context()->target_env)) {
diff --git a/source/val/validate_barriers.cpp b/source/val/validate_barriers.cpp
index 4fbe9c9..b499c8c 100644
--- a/source/val/validate_barriers.cpp
+++ b/source/val/validate_barriers.cpp
@@ -14,8 +14,6 @@
 
 // Validates correctness of barrier SPIR-V instructions.
 
-#include "source/val/validate.h"
-
 #include <string>
 
 #include "source/diagnostic.h"
@@ -24,6 +22,7 @@
 #include "source/spirv_target_env.h"
 #include "source/util/bitutils.h"
 #include "source/val/instruction.h"
+#include "source/val/validate.h"
 #include "source/val/validate_memory_semantics.h"
 #include "source/val/validate_scopes.h"
 #include "source/val/validation_state.h"
@@ -38,8 +37,7 @@
 
   switch (opcode) {
     case SpvOpControlBarrier: {
-      if (spvVersionForTargetEnv(_.context()->target_env) <
-          SPV_SPIRV_VERSION_WORD(1, 3)) {
+      if (_.version() < SPV_SPIRV_VERSION_WORD(1, 3)) {
         _.function(inst->function()->id())
             ->RegisterExecutionModelLimitation(
                 [](SpvExecutionModel model, std::string* message) {
diff --git a/source/val/validate_builtins.cpp b/source/val/validate_builtins.cpp
index aaba324..dbe7bef 100644
--- a/source/val/validate_builtins.cpp
+++ b/source/val/validate_builtins.cpp
@@ -101,6 +101,28 @@
   return SpvStorageClassMax;
 }
 
+bool IsBuiltInValidForWebGPU(SpvBuiltIn label) {
+  switch (label) {
+    case SpvBuiltInPosition:
+    case SpvBuiltInVertexIndex:
+    case SpvBuiltInInstanceIndex:
+    case SpvBuiltInFrontFacing:
+    case SpvBuiltInFragCoord:
+    case SpvBuiltInFragDepth:
+    case SpvBuiltInNumWorkgroups:
+    case SpvBuiltInWorkgroupSize:
+    case SpvBuiltInLocalInvocationId:
+    case SpvBuiltInGlobalInvocationId:
+    case SpvBuiltInLocalInvocationIndex: {
+      return true;
+    }
+    default:
+      break;
+  }
+
+  return false;
+}
+
 // Helper class managing validation of built-ins.
 // TODO: Generic functionality of this class can be moved into
 // ValidationState_t to be made available to other users.
@@ -169,6 +191,8 @@
                                                const Instruction& inst);
   spv_result_t ValidateVertexIdOrInstanceIdAtDefinition(
       const Decoration& decoration, const Instruction& inst);
+  spv_result_t ValidateLocalInvocationIndexAtDefinition(
+      const Decoration& decoration, const Instruction& inst);
   spv_result_t ValidateWorkgroupSizeAtDefinition(const Decoration& decoration,
                                                  const Instruction& inst);
   // Used for GlobalInvocationId, LocalInvocationId, NumWorkgroups, WorkgroupId.
@@ -267,6 +291,11 @@
       const Instruction& referenced_inst,
       const Instruction& referenced_from_inst);
 
+  spv_result_t ValidateLocalInvocationIndexAtReference(
+      const Decoration& decoration, const Instruction& built_in_inst,
+      const Instruction& referenced_inst,
+      const Instruction& referenced_from_inst);
+
   spv_result_t ValidateVertexIndexAtReference(
       const Decoration& decoration, const Instruction& built_in_inst,
       const Instruction& referenced_inst,
@@ -937,12 +966,14 @@
 
 spv_result_t BuiltInsValidator::ValidateFragCoordAtDefinition(
     const Decoration& decoration, const Instruction& inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     if (spv_result_t error = ValidateF32Vec(
             decoration, inst, 4,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
-                     << "According to the Vulkan spec BuiltIn FragCoord "
+                     << "According to the "
+                     << spvLogStringForEnv(_.context()->target_env)
+                     << " spec BuiltIn FragCoord "
                         "variable needs to be a 4-component 32-bit float "
                         "vector. "
                      << message;
@@ -959,12 +990,13 @@
     const Decoration& decoration, const Instruction& built_in_inst,
     const Instruction& referenced_inst,
     const Instruction& referenced_from_inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
     if (storage_class != SpvStorageClassMax &&
         storage_class != SpvStorageClassInput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-             << "Vulkan spec allows BuiltIn FragCoord to be only used for "
+             << spvLogStringForEnv(_.context()->target_env)
+             << " spec allows BuiltIn FragCoord to be only used for "
                 "variables with Input storage class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                  referenced_from_inst)
@@ -974,7 +1006,8 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelFragment) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-               << "Vulkan spec allows BuiltIn FragCoord to be used only with "
+               << spvLogStringForEnv(_.context()->target_env)
+               << " spec allows BuiltIn FragCoord to be used only with "
                   "Fragment execution model. "
                << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                    referenced_from_inst, execution_model);
@@ -994,12 +1027,14 @@
 
 spv_result_t BuiltInsValidator::ValidateFragDepthAtDefinition(
     const Decoration& decoration, const Instruction& inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     if (spv_result_t error = ValidateF32(
             decoration, inst,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
-                     << "According to the Vulkan spec BuiltIn FragDepth "
+                     << "According to the "
+                     << spvLogStringForEnv(_.context()->target_env)
+                     << " spec BuiltIn FragDepth "
                         "variable needs to be a 32-bit float scalar. "
                      << message;
             })) {
@@ -1015,12 +1050,13 @@
     const Decoration& decoration, const Instruction& built_in_inst,
     const Instruction& referenced_inst,
     const Instruction& referenced_from_inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
     if (storage_class != SpvStorageClassMax &&
         storage_class != SpvStorageClassOutput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-             << "Vulkan spec allows BuiltIn FragDepth to be only used for "
+             << spvLogStringForEnv(_.context()->target_env)
+             << " spec allows BuiltIn FragDepth to be only used for "
                 "variables with Output storage class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                  referenced_from_inst)
@@ -1030,7 +1066,8 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelFragment) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-               << "Vulkan spec allows BuiltIn FragDepth to be used only with "
+               << spvLogStringForEnv(_.context()->target_env)
+               << " spec allows BuiltIn FragDepth to be used only with "
                   "Fragment execution model. "
                << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                    referenced_from_inst, execution_model);
@@ -1043,7 +1080,8 @@
       const auto* modes = _.GetExecutionModes(entry_point);
       if (!modes || !modes->count(SpvExecutionModeDepthReplacing)) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-               << "Vulkan spec requires DepthReplacing execution mode to be "
+               << spvLogStringForEnv(_.context()->target_env)
+               << " spec requires DepthReplacing execution mode to be "
                   "declared when using BuiltIn FragDepth. "
                << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                    referenced_from_inst);
@@ -1063,12 +1101,14 @@
 
 spv_result_t BuiltInsValidator::ValidateFrontFacingAtDefinition(
     const Decoration& decoration, const Instruction& inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     if (spv_result_t error = ValidateBool(
             decoration, inst,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
-                     << "According to the Vulkan spec BuiltIn FrontFacing "
+                     << "According to the "
+                     << spvLogStringForEnv(_.context()->target_env)
+                     << " spec BuiltIn FrontFacing "
                         "variable needs to be a bool scalar. "
                      << message;
             })) {
@@ -1084,12 +1124,13 @@
     const Decoration& decoration, const Instruction& built_in_inst,
     const Instruction& referenced_inst,
     const Instruction& referenced_from_inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
     if (storage_class != SpvStorageClassMax &&
         storage_class != SpvStorageClassInput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-             << "Vulkan spec allows BuiltIn FrontFacing to be only used for "
+             << spvLogStringForEnv(_.context()->target_env)
+             << " spec allows BuiltIn FrontFacing to be only used for "
                 "variables with Input storage class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                  referenced_from_inst)
@@ -1099,7 +1140,8 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelFragment) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-               << "Vulkan spec allows BuiltIn FrontFacing to be used only with "
+               << spvLogStringForEnv(_.context()->target_env)
+               << " spec allows BuiltIn FrontFacing to be used only with "
                   "Fragment execution model. "
                << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                    referenced_from_inst, execution_model);
@@ -1233,12 +1275,14 @@
 
 spv_result_t BuiltInsValidator::ValidateInstanceIndexAtDefinition(
     const Decoration& decoration, const Instruction& inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     if (spv_result_t error = ValidateI32(
             decoration, inst,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
-                     << "According to the Vulkan spec BuiltIn InstanceIndex "
+                     << "According to the "
+                     << spvLogStringForEnv(_.context()->target_env)
+                     << " spec BuiltIn InstanceIndex "
                         "variable needs to be a 32-bit int scalar. "
                      << message;
             })) {
@@ -1254,12 +1298,13 @@
     const Decoration& decoration, const Instruction& built_in_inst,
     const Instruction& referenced_inst,
     const Instruction& referenced_from_inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
     if (storage_class != SpvStorageClassMax &&
         storage_class != SpvStorageClassInput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-             << "Vulkan spec allows BuiltIn InstanceIndex to be only used for "
+             << spvLogStringForEnv(_.context()->target_env)
+             << " spec allows BuiltIn InstanceIndex to be only used for "
                 "variables with Input storage class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                  referenced_from_inst)
@@ -1269,7 +1314,8 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelVertex) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-               << "Vulkan spec allows BuiltIn InstanceIndex to be used only "
+               << spvLogStringForEnv(_.context()->target_env)
+               << " spec allows BuiltIn InstanceIndex to be used only "
                   "with Vertex execution model. "
                << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                    referenced_from_inst, execution_model);
@@ -1617,6 +1663,46 @@
     }
   }
 
+  if (spvIsWebGPUEnv(_.context()->target_env)) {
+    const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
+    if (storage_class != SpvStorageClassMax &&
+        storage_class != SpvStorageClassOutput) {
+      return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << "WebGPU spec allows BuiltIn Position to be only used for "
+                "variables with Output storage class. "
+             << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                 referenced_from_inst)
+             << " " << GetStorageClassDesc(referenced_from_inst);
+    }
+
+    for (const SpvExecutionModel execution_model : execution_models_) {
+      switch (execution_model) {
+        case SpvExecutionModelVertex: {
+          if (spv_result_t error = ValidateF32Vec(
+                  decoration, built_in_inst, 4,
+                  [this, &referenced_from_inst](
+                      const std::string& message) -> spv_result_t {
+                    return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+                           << "According to the WebGPU spec BuiltIn Position "
+                              "variable needs to be a 4-component 32-bit float "
+                              "vector. "
+                           << message;
+                  })) {
+            return error;
+          }
+          break;
+        }
+        default: {
+          return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+                 << "WebGPU spec allows BuiltIn Position to be used only "
+                    "with the Vertex execution model. "
+                 << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                     referenced_from_inst, execution_model);
+        }
+      }
+    }
+  }
+
   if (function_id_ == 0) {
     // Propagate this rule to all dependant ids in the global scope.
     id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind(
@@ -2075,13 +2161,15 @@
 
 spv_result_t BuiltInsValidator::ValidateVertexIndexAtDefinition(
     const Decoration& decoration, const Instruction& inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     if (spv_result_t error = ValidateI32(
             decoration, inst,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
-                     << "According to the Vulkan spec BuiltIn VertexIndex "
-                        "variable needs to be a 32-bit int scalar. "
+                     << "According to the "
+                     << spvLogStringForEnv(_.context()->target_env)
+                     << " spec BuiltIn VertexIndex variable needs to be a "
+                        "32-bit int scalar. "
                      << message;
             })) {
       return error;
@@ -2143,16 +2231,75 @@
   return SPV_SUCCESS;
 }
 
-spv_result_t BuiltInsValidator::ValidateVertexIndexAtReference(
+spv_result_t BuiltInsValidator::ValidateLocalInvocationIndexAtDefinition(
+    const Decoration& decoration, const Instruction& inst) {
+  if (spvIsWebGPUEnv(_.context()->target_env)) {
+    if (spv_result_t error = ValidateI32(
+            decoration, inst,
+            [this, &inst](const std::string& message) -> spv_result_t {
+              return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << "According to the WebGPU spec BuiltIn "
+                        "LocalInvocationIndex variable needs to be a 32-bit "
+                        "int."
+                     << message;
+            })) {
+      return error;
+    }
+  }
+
+  // Seed at reference checks with this built-in.
+  return ValidateLocalInvocationIndexAtReference(decoration, inst, inst, inst);
+}
+
+spv_result_t BuiltInsValidator::ValidateLocalInvocationIndexAtReference(
     const Decoration& decoration, const Instruction& built_in_inst,
     const Instruction& referenced_inst,
     const Instruction& referenced_from_inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsWebGPUEnv(_.context()->target_env)) {
     const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
     if (storage_class != SpvStorageClassMax &&
         storage_class != SpvStorageClassInput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-             << "Vulkan spec allows BuiltIn VertexIndex to be only used for "
+             << "WebGPU spec allows BuiltIn LocalInvocationIndex to be only "
+                "used for variables with Input storage class. "
+             << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                 referenced_from_inst)
+             << " " << GetStorageClassDesc(referenced_from_inst);
+    }
+
+    for (const SpvExecutionModel execution_model : execution_models_) {
+      if (execution_model != SpvExecutionModelGLCompute) {
+        return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+               << "WebGPU spec allows BuiltIn VertexIndex to be used only "
+                  "with GLCompute execution model. "
+               << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                   referenced_from_inst, execution_model);
+      }
+    }
+  }
+
+  if (function_id_ == 0) {
+    // Propagate this rule to all dependant ids in the global scope.
+    id_to_at_reference_checks_[referenced_from_inst.id()].push_back(
+        std::bind(&BuiltInsValidator::ValidateLocalInvocationIndexAtReference,
+                  this, decoration, built_in_inst, referenced_from_inst,
+                  std::placeholders::_1));
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t BuiltInsValidator::ValidateVertexIndexAtReference(
+    const Decoration& decoration, const Instruction& built_in_inst,
+    const Instruction& referenced_inst,
+    const Instruction& referenced_from_inst) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
+    const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
+    if (storage_class != SpvStorageClassMax &&
+        storage_class != SpvStorageClassInput) {
+      return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << spvLogStringForEnv(_.context()->target_env)
+             << " spec allows BuiltIn VertexIndex to be only used for "
                 "variables with Input storage class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                  referenced_from_inst)
@@ -2162,8 +2309,8 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelVertex) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-               << "Vulkan spec allows BuiltIn VertexIndex to be used only "
-                  "with "
+               << spvLogStringForEnv(_.context()->target_env)
+               << " spec allows BuiltIn VertexIndex to be used only with "
                   "Vertex execution model. "
                << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                    referenced_from_inst, execution_model);
@@ -2299,13 +2446,15 @@
 
 spv_result_t BuiltInsValidator::ValidateComputeShaderI32Vec3InputAtDefinition(
     const Decoration& decoration, const Instruction& inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     if (spv_result_t error = ValidateI32Vec(
             decoration, inst, 3,
             [this, &decoration,
              &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
-                     << "According to the Vulkan spec BuiltIn "
+                     << "According to the "
+                     << spvLogStringForEnv(_.context()->target_env)
+                     << " spec BuiltIn "
                      << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
                                                       decoration.params()[0])
                      << " variable needs to be a 3-component 32-bit int "
@@ -2325,12 +2474,13 @@
     const Decoration& decoration, const Instruction& built_in_inst,
     const Instruction& referenced_inst,
     const Instruction& referenced_from_inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
     if (storage_class != SpvStorageClassMax &&
         storage_class != SpvStorageClassInput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-             << "Vulkan spec allows BuiltIn "
+             << spvLogStringForEnv(_.context()->target_env)
+             << " spec allows BuiltIn "
              << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
                                               decoration.params()[0])
              << " to be only used for variables with Input storage class. "
@@ -2340,11 +2490,15 @@
     }
 
     for (const SpvExecutionModel execution_model : execution_models_) {
-      if (execution_model != SpvExecutionModelGLCompute &&
-          execution_model != SpvExecutionModelTaskNV &&
-          execution_model != SpvExecutionModelMeshNV) {
+      bool has_vulkan_model = execution_model == SpvExecutionModelGLCompute ||
+                              execution_model == SpvExecutionModelTaskNV ||
+                              execution_model == SpvExecutionModelMeshNV;
+      bool has_webgpu_model = execution_model == SpvExecutionModelGLCompute;
+      if ((spvIsVulkanEnv(_.context()->target_env) && !has_vulkan_model) ||
+          (spvIsWebGPUEnv(_.context()->target_env) && !has_webgpu_model)) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-               << "Vulkan spec allows BuiltIn "
+               << spvLogStringForEnv(_.context()->target_env)
+               << " spec allows BuiltIn "
                << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
                                                 decoration.params()[0])
                << " to be used only with GLCompute execution model. "
@@ -2367,8 +2521,9 @@
 
 spv_result_t BuiltInsValidator::ValidateWorkgroupSizeAtDefinition(
     const Decoration& decoration, const Instruction& inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
-    if (!spvOpcodeIsConstant(inst.opcode())) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
+    if (spvIsVulkanEnv(_.context()->target_env) &&
+        !spvOpcodeIsConstant(inst.opcode())) {
       return _.diag(SPV_ERROR_INVALID_DATA, &inst)
              << "Vulkan spec requires BuiltIn WorkgroupSize to be a "
                 "constant. "
@@ -2379,9 +2534,10 @@
             decoration, inst, 3,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
-                     << "According to the Vulkan spec BuiltIn WorkgroupSize "
-                        "variable "
-                        "needs to be a 3-component 32-bit int vector. "
+                     << "According to the "
+                     << spvLogStringForEnv(_.context()->target_env)
+                     << " spec BuiltIn WorkgroupSize variable needs to be a "
+                        "3-component 32-bit int vector. "
                      << message;
             })) {
       return error;
@@ -2396,11 +2552,12 @@
     const Decoration& decoration, const Instruction& built_in_inst,
     const Instruction& referenced_inst,
     const Instruction& referenced_from_inst) {
-  if (spvIsVulkanEnv(_.context()->target_env)) {
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelGLCompute) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-               << "Vulkan spec allows BuiltIn "
+               << spvLogStringForEnv(_.context()->target_env)
+               << " spec allows BuiltIn "
                << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
                                                 decoration.params()[0])
                << " to be used only with GLCompute execution model. "
@@ -2423,6 +2580,15 @@
 spv_result_t BuiltInsValidator::ValidateSingleBuiltInAtDefinition(
     const Decoration& decoration, const Instruction& inst) {
   const SpvBuiltIn label = SpvBuiltIn(decoration.params()[0]);
+
+  if (spvIsWebGPUEnv(_.context()->target_env) &&
+      !IsBuiltInValidForWebGPU(label)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+           << "WebGPU does not allow BuiltIn "
+           << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                            decoration.params()[0]);
+  }
+
   // If you are adding a new BuiltIn enum, please register it here.
   // If the newly added enum has validation rules associated with it
   // consider leaving a TODO and/or creating an issue.
@@ -2502,7 +2668,9 @@
     case SpvBuiltInInstanceId: {
       return ValidateVertexIdOrInstanceIdAtDefinition(decoration, inst);
     }
-    case SpvBuiltInLocalInvocationIndex:
+    case SpvBuiltInLocalInvocationIndex: {
+      return ValidateLocalInvocationIndexAtDefinition(decoration, inst);
+    }
     case SpvBuiltInWorkDim:
     case SpvBuiltInGlobalSize:
     case SpvBuiltInEnqueuedWorkgroupSize:
@@ -2656,14 +2824,15 @@
 
 // Validates correctness of built-in variables.
 spv_result_t ValidateBuiltIns(ValidationState_t& _) {
-  if (!spvIsVulkanEnv(_.context()->target_env)) {
-    // Early return. All currently implemented rules are based on Vulkan spec.
+  if (!spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
+    // Early return. All currently implemented rules are based on Vulkan or
+    // WebGPU spec.
     //
     // TODO: If you are adding validation rules for environments other than
-    // Vulkan (or general rules which are not environment independent), then you
-    // need to modify or remove this condition. Consider also adding early
-    // returns into BuiltIn-specific rules, so that the system doesn't spawn new
-    // rules which don't do anything.
+    // Vulkan or WebGPU (or general rules which are not environment
+    // independent), then you need to modify or remove this condition. Consider
+    // also adding early returns into BuiltIn-specific rules, so that the system
+    // doesn't spawn new rules which don't do anything.
     return SPV_SUCCESS;
   }
 
diff --git a/source/val/validate_cfg.cpp b/source/val/validate_cfg.cpp
index 8fe30a8..6e7acbd 100644
--- a/source/val/validate_cfg.cpp
+++ b/source/val/validate_cfg.cpp
@@ -29,6 +29,7 @@
 
 #include "source/cfa.h"
 #include "source/opcode.h"
+#include "source/spirv_target_env.h"
 #include "source/spirv_validator_options.h"
 #include "source/val/basic_block.h"
 #include "source/val/construct.h"
@@ -112,6 +113,19 @@
   return SPV_SUCCESS;
 }
 
+spv_result_t ValidateBranch(ValidationState_t& _, const Instruction* inst) {
+  // target operands must be OpLabel
+  const auto id = inst->GetOperandAs<uint32_t>(0);
+  const auto target = _.FindDef(id);
+  if (!target || SpvOpLabel != target->opcode()) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "'Target Label' operands for OpBranch must be the ID "
+              "of an OpLabel instruction";
+  }
+
+  return SPV_SUCCESS;
+}
+
 spv_result_t ValidateBranchConditional(ValidationState_t& _,
                                        const Instruction* inst) {
   // num_operands is either 3 or 5 --- if 5, the last two need to be literal
@@ -155,6 +169,26 @@
   return SPV_SUCCESS;
 }
 
+spv_result_t ValidateSwitch(ValidationState_t& _, const Instruction* inst) {
+  const auto num_operands = inst->operands().size();
+  // At least two operands (selector, default), any more than that are
+  // literal/target.
+
+  // target operands must be OpLabel
+  for (size_t i = 2; i < num_operands; i += 2) {
+    // literal, id
+    const auto id = inst->GetOperandAs<uint32_t>(i + 1);
+    const auto target = _.FindDef(id);
+    if (!target || SpvOpLabel != target->opcode()) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "'Target Label' operands for OpSwitch must be IDs of an "
+                "OpLabel instruction";
+    }
+  }
+
+  return SPV_SUCCESS;
+}
+
 spv_result_t ValidateReturnValue(ValidationState_t& _,
                                  const Instruction* inst) {
   const auto value_id = inst->GetOperandAs<uint32_t>(0);
@@ -577,6 +611,120 @@
   return SPV_SUCCESS;
 }
 
+spv_result_t PerformWebGPUCfgChecks(ValidationState_t& _, Function* function) {
+  for (auto& block : function->ordered_blocks()) {
+    if (block->reachable()) continue;
+    if (block->is_type(kBlockTypeMerge)) {
+      // 1. Find the referencing merge and confirm that it is reachable.
+      BasicBlock* merge_header = function->GetMergeHeader(block);
+      assert(merge_header != nullptr);
+      if (!merge_header->reachable()) {
+        return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
+               << "For WebGPU, unreachable merge-blocks must be referenced by "
+                  "a reachable merge instruction.";
+      }
+
+      // 2. Check that the only instructions are OpLabel and OpUnreachable.
+      auto* label_inst = block->label();
+      auto* terminator_inst = block->terminator();
+      assert(label_inst != nullptr);
+      assert(terminator_inst != nullptr);
+
+      if (terminator_inst->opcode() != SpvOpUnreachable) {
+        return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
+               << "For WebGPU, unreachable merge-blocks must terminate with "
+                  "OpUnreachable.";
+      }
+
+      auto label_idx = label_inst - &_.ordered_instructions()[0];
+      auto terminator_idx = terminator_inst - &_.ordered_instructions()[0];
+      if (label_idx + 1 != terminator_idx) {
+        return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
+               << "For WebGPU, unreachable merge-blocks must only contain an "
+                  "OpLabel and OpUnreachable instruction.";
+      }
+
+      // 3. Use label instruction to confirm there is no uses by branches.
+      for (auto use : label_inst->uses()) {
+        const auto* use_inst = use.first;
+        if (spvOpcodeIsBranch(use_inst->opcode())) {
+          return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
+                 << "For WebGPU, unreachable merge-blocks cannot be the target "
+                    "of a branch.";
+        }
+      }
+    } else if (block->is_type(kBlockTypeContinue)) {
+      // 1. Find referencing loop and confirm that it is reachable.
+      std::vector<BasicBlock*> continue_headers =
+          function->GetContinueHeaders(block);
+      if (continue_headers.empty()) {
+        return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
+               << "For WebGPU, unreachable continue-target must be referenced "
+                  "by a loop instruction.";
+      }
+
+      std::vector<BasicBlock*> reachable_headers(continue_headers.size());
+      auto iter =
+          std::copy_if(continue_headers.begin(), continue_headers.end(),
+                       reachable_headers.begin(),
+                       [](BasicBlock* header) { return header->reachable(); });
+      reachable_headers.resize(std::distance(reachable_headers.begin(), iter));
+
+      if (reachable_headers.empty()) {
+        return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
+               << "For WebGPU, unreachable continue-target must be referenced "
+                  "by a reachable loop instruction.";
+      }
+
+      // 2. Check that the only instructions are OpLabel and OpBranch.
+      auto* label_inst = block->label();
+      auto* terminator_inst = block->terminator();
+      assert(label_inst != nullptr);
+      assert(terminator_inst != nullptr);
+
+      if (terminator_inst->opcode() != SpvOpBranch) {
+        return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
+               << "For WebGPU, unreachable continue-target must terminate with "
+                  "OpBranch.";
+      }
+
+      auto label_idx = label_inst - &_.ordered_instructions()[0];
+      auto terminator_idx = terminator_inst - &_.ordered_instructions()[0];
+      if (label_idx + 1 != terminator_idx) {
+        return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
+               << "For WebGPU, unreachable continue-target must only contain "
+                  "an OpLabel and an OpBranch instruction.";
+      }
+
+      // 3. Use label instruction to confirm there is no uses by branches.
+      for (auto use : label_inst->uses()) {
+        const auto* use_inst = use.first;
+        if (spvOpcodeIsBranch(use_inst->opcode())) {
+          return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
+                 << "For WebGPU, unreachable continue-target cannot be the "
+                    "target of a branch.";
+        }
+      }
+
+      // 4. Confirm that continue-target has a back edge to a reachable loop
+      //    header block.
+      auto branch_target = terminator_inst->GetOperandAs<uint32_t>(0);
+      for (auto* continue_header : reachable_headers) {
+        if (branch_target != continue_header->id()) {
+          return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
+                 << "For WebGPU, unreachable continue-target must only have a "
+                    "back edge to a single reachable loop instruction.";
+        }
+      }
+    } else {
+      return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
+             << "For WebGPU, all blocks must be reachable, unless they are "
+             << "degenerate cases of merge-block or continue-target.";
+    }
+  }
+  return SPV_SUCCESS;
+}
+
 spv_result_t PerformCfgChecks(ValidationState_t& _) {
   for (auto& function : _.functions()) {
     // Check all referenced blocks are defined within a function
@@ -656,6 +804,13 @@
                    << _.getIdName(idom->id());
           }
         }
+
+        // For WebGPU check that all unreachable blocks are degenerate cases for
+        // merge-block or continue-target.
+        if (spvIsWebGPUEnv(_.context()->target_env)) {
+          spv_result_t result = PerformWebGPUCfgChecks(_, &function);
+          if (result != SPV_SUCCESS) return result;
+        }
       }
       // If we have structed control flow, check that no block has a control
       // flow nesting depth larger than the limit.
@@ -764,12 +919,18 @@
     case SpvOpPhi:
       if (auto error = ValidatePhi(_, inst)) return error;
       break;
+    case SpvOpBranch:
+      if (auto error = ValidateBranch(_, inst)) return error;
+      break;
     case SpvOpBranchConditional:
       if (auto error = ValidateBranchConditional(_, inst)) return error;
       break;
     case SpvOpReturnValue:
       if (auto error = ValidateReturnValue(_, inst)) return error;
       break;
+    case SpvOpSwitch:
+      if (auto error = ValidateSwitch(_, inst)) return error;
+      break;
     default:
       break;
   }
diff --git a/source/val/validate_composites.cpp b/source/val/validate_composites.cpp
index ccc5587..1c1e77c 100644
--- a/source/val/validate_composites.cpp
+++ b/source/val/validate_composites.cpp
@@ -118,6 +118,10 @@
         *member_type = type_inst->word(component_index + 2);
         break;
       }
+      case SpvOpTypeCooperativeMatrixNV: {
+        *member_type = type_inst->word(2);
+        break;
+      }
       default:
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Reached non-composite type while indexes still remain to "
@@ -315,6 +319,26 @@
 
       break;
     }
+    case SpvOpTypeCooperativeMatrixNV: {
+      const auto result_type_inst = _.FindDef(result_type);
+      assert(result_type_inst);
+      const auto component_type_id =
+          result_type_inst->GetOperandAs<uint32_t>(1);
+
+      if (3 != num_operands) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Expected single constituent";
+      }
+
+      const uint32_t operand_type_id = _.GetOperandTypeId(inst, 2);
+
+      if (operand_type_id != component_type_id) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Expected Constituent type to be equal to the component type";
+      }
+
+      break;
+    }
     default: {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << "Expected Result Type to be a composite type";
@@ -489,6 +513,92 @@
   return SPV_SUCCESS;
 }
 
+// Returns true if |lhs| and |rhs| logically match.
+// 1. Must both be either OpTypeArray or OpTypeStruct
+// 2. If OpTypeArray, then
+//  * Length must be the same
+//  * Element type must match or logically match
+// 3. If OpTypeStruct, then
+//  * Both have same number of elements
+//  * Element N for both structs must match or logically match
+bool LogicallyMatch(ValidationState_t& _, const Instruction* lhs,
+                    const Instruction* rhs) {
+  if (lhs->opcode() != rhs->opcode()) {
+    return false;
+  }
+
+  if (lhs->opcode() == SpvOpTypeArray) {
+    // Size operands must match.
+    if (lhs->GetOperandAs<uint32_t>(2u) != rhs->GetOperandAs<uint32_t>(2u)) {
+      return false;
+    }
+
+    // Elements must match or logically match.
+    const auto lhs_ele_id = lhs->GetOperandAs<uint32_t>(1u);
+    const auto rhs_ele_id = rhs->GetOperandAs<uint32_t>(1u);
+    if (lhs_ele_id == rhs_ele_id) {
+      return true;
+    }
+
+    const auto lhs_ele = _.FindDef(lhs_ele_id);
+    const auto rhs_ele = _.FindDef(rhs_ele_id);
+    if (!lhs_ele || !rhs_ele) {
+      return false;
+    }
+    return LogicallyMatch(_, lhs_ele, rhs_ele);
+  } else if (lhs->opcode() == SpvOpTypeStruct) {
+    // Number of elements must match.
+    if (lhs->operands().size() != rhs->operands().size()) {
+      return false;
+    }
+
+    for (size_t i = 1u; i < lhs->operands().size(); ++i) {
+      const auto lhs_ele_id = lhs->GetOperandAs<uint32_t>(i);
+      const auto rhs_ele_id = rhs->GetOperandAs<uint32_t>(i);
+      // Elements must match or logically match.
+      if (lhs_ele_id == rhs_ele_id) {
+        continue;
+      }
+
+      const auto lhs_ele = _.FindDef(lhs_ele_id);
+      const auto rhs_ele = _.FindDef(rhs_ele_id);
+      if (!lhs_ele || !rhs_ele) {
+        return false;
+      }
+
+      if (!LogicallyMatch(_, lhs_ele, rhs_ele)) {
+        return false;
+      }
+    }
+
+    // All checks passed.
+    return true;
+  }
+
+  // No other opcodes are acceptable at this point. Arrays and structs are
+  // caught above and if they're elements are not arrays or structs they are
+  // required to match exactly.
+  return false;
+}
+
+spv_result_t ValidateCopyLogical(ValidationState_t& _,
+                                 const Instruction* inst) {
+  const auto result_type = _.FindDef(inst->type_id());
+  const auto source = _.FindDef(inst->GetOperandAs<uint32_t>(2u));
+  const auto source_type = _.FindDef(source->type_id());
+  if (!source_type || !result_type || source_type == result_type) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Result Type must not equal the Operand type";
+  }
+
+  if (!LogicallyMatch(_, source_type, result_type)) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Result Type does not logically match the Operand type";
+  }
+
+  return SPV_SUCCESS;
+}
+
 }  // anonymous namespace
 
 // Validates correctness of composite instructions.
@@ -510,6 +620,8 @@
       return ValidateCopyObject(_, inst);
     case SpvOpTranspose:
       return ValidateTranspose(_, inst);
+    case SpvOpCopyLogical:
+      return ValidateCopyLogical(_, inst);
     default:
       break;
   }
diff --git a/source/val/validate_constants.cpp b/source/val/validate_constants.cpp
index 8c02da4..191344e 100644
--- a/source/val/validate_constants.cpp
+++ b/source/val/validate_constants.cpp
@@ -247,6 +247,36 @@
         }
       }
     } break;
+    case SpvOpTypeCooperativeMatrixNV: {
+      if (1 != constituent_count) {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << opcode_name << " Constituent <id> '"
+               << _.getIdName(inst->type_id()) << "' count must be one.";
+      }
+      const auto constituent_id = inst->GetOperandAs<uint32_t>(2);
+      const auto constituent = _.FindDef(constituent_id);
+      if (!constituent || !spvOpcodeIsConstantOrUndef(constituent->opcode())) {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << opcode_name << " Constituent <id> '"
+               << _.getIdName(constituent_id)
+               << "' is not a constant or undef.";
+      }
+      const auto constituent_type = _.FindDef(constituent->type_id());
+      if (!constituent_type) {
+        return _.diag(SPV_ERROR_INVALID_ID, constituent)
+               << "Result type is not defined.";
+      }
+
+      const auto component_type_id = result_type->GetOperandAs<uint32_t>(1);
+      const auto component_type = _.FindDef(component_type_id);
+      if (!component_type || component_type->id() != constituent_type->id()) {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << opcode_name << " Constituent <id> '"
+               << _.getIdName(constituent_id)
+               << "' type does not match the Result Type <id> '"
+               << _.getIdName(result_type->id()) << "'s component type.";
+      }
+    } break;
     default:
       break;
   }
@@ -285,6 +315,7 @@
       return true;
     case SpvOpTypeArray:
     case SpvOpTypeMatrix:
+    case SpvOpTypeCooperativeMatrixNV:
     case SpvOpTypeVector: {
       auto base_type = _.FindDef(instruction[2]);
       return base_type && IsTypeNullable(base_type->words(), _);
@@ -314,6 +345,66 @@
   return SPV_SUCCESS;
 }
 
+spv_result_t ValidateSpecConstantOp(ValidationState_t& _,
+                                    const Instruction* inst) {
+  const auto op = inst->GetOperandAs<SpvOp>(2);
+
+  // The binary parser already ensures that the op is valid for *some*
+  // environment.  Here we check restrictions.
+  switch (op) {
+    case SpvOpQuantizeToF16:
+      if (!_.HasCapability(SpvCapabilityShader)) {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << "Specialization constant operation " << spvOpcodeString(op)
+               << " requires Shader capability";
+      }
+      break;
+
+    case SpvOpUConvert:
+      if (!_.features().uconvert_spec_constant_op &&
+          !_.HasCapability(SpvCapabilityKernel)) {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << "Prior to SPIR-V 1.4, specialization constant operation "
+                  "UConvert requires Kernel capability or extension "
+                  "SPV_AMD_gpu_shader_int16";
+      }
+      break;
+
+    case SpvOpConvertFToS:
+    case SpvOpConvertSToF:
+    case SpvOpConvertFToU:
+    case SpvOpConvertUToF:
+    case SpvOpConvertPtrToU:
+    case SpvOpConvertUToPtr:
+    case SpvOpGenericCastToPtr:
+    case SpvOpPtrCastToGeneric:
+    case SpvOpBitcast:
+    case SpvOpFNegate:
+    case SpvOpFAdd:
+    case SpvOpFSub:
+    case SpvOpFMul:
+    case SpvOpFDiv:
+    case SpvOpFRem:
+    case SpvOpFMod:
+    case SpvOpAccessChain:
+    case SpvOpInBoundsAccessChain:
+    case SpvOpPtrAccessChain:
+    case SpvOpInBoundsPtrAccessChain:
+      if (!_.HasCapability(SpvCapabilityKernel)) {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << "Specialization constant operation " << spvOpcodeString(op)
+               << " requires Kernel capability";
+      }
+      break;
+
+    default:
+      break;
+  }
+
+  // TODO(dneto): Validate result type and arguments to the various operations.
+  return SPV_SUCCESS;
+}
+
 }  // namespace
 
 spv_result_t ConstantPass(ValidationState_t& _, const Instruction* inst) {
@@ -334,6 +425,9 @@
     case SpvOpConstantNull:
       if (auto error = ValidateConstantNull(_, inst)) return error;
       break;
+    case SpvOpSpecConstantOp:
+      if (auto error = ValidateSpecConstantOp(_, inst)) return error;
+      break;
     default:
       break;
   }
diff --git a/source/val/validate_conversion.cpp b/source/val/validate_conversion.cpp
index b51eec9..17af9f4 100644
--- a/source/val/validate_conversion.cpp
+++ b/source/val/validate_conversion.cpp
@@ -32,22 +32,31 @@
   switch (opcode) {
     case SpvOpConvertFToU: {
       if (!_.IsUnsignedIntScalarType(result_type) &&
-          !_.IsUnsignedIntVectorType(result_type))
+          !_.IsUnsignedIntVectorType(result_type) &&
+          !_.IsUnsignedIntCooperativeMatrixType(result_type))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected unsigned int scalar or vector type as Result Type: "
                << spvOpcodeString(opcode);
 
       const uint32_t input_type = _.GetOperandTypeId(inst, 2);
       if (!input_type || (!_.IsFloatScalarType(input_type) &&
-                          !_.IsFloatVectorType(input_type)))
+                          !_.IsFloatVectorType(input_type) &&
+                          !_.IsFloatCooperativeMatrixType(input_type)))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected input to be float scalar or vector: "
                << spvOpcodeString(opcode);
 
-      if (_.GetDimension(result_type) != _.GetDimension(input_type))
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Expected input to have the same dimension as Result Type: "
-               << spvOpcodeString(opcode);
+      if (_.IsCooperativeMatrixType(result_type) ||
+          _.IsCooperativeMatrixType(input_type)) {
+        spv_result_t ret =
+            _.CooperativeMatrixShapesMatch(inst, result_type, input_type);
+        if (ret != SPV_SUCCESS) return ret;
+      } else {
+        if (_.GetDimension(result_type) != _.GetDimension(input_type))
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Expected input to have the same dimension as Result Type: "
+                 << spvOpcodeString(opcode);
+      }
 
       if (!_.features().use_int8_type && (8 == _.GetBitWidth(result_type)))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
@@ -58,22 +67,31 @@
     }
 
     case SpvOpConvertFToS: {
-      if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type))
+      if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type) &&
+          !_.IsIntCooperativeMatrixType(result_type))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected int scalar or vector type as Result Type: "
                << spvOpcodeString(opcode);
 
       const uint32_t input_type = _.GetOperandTypeId(inst, 2);
       if (!input_type || (!_.IsFloatScalarType(input_type) &&
-                          !_.IsFloatVectorType(input_type)))
+                          !_.IsFloatVectorType(input_type) &&
+                          !_.IsFloatCooperativeMatrixType(input_type)))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected input to be float scalar or vector: "
                << spvOpcodeString(opcode);
 
-      if (_.GetDimension(result_type) != _.GetDimension(input_type))
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Expected input to have the same dimension as Result Type: "
-               << spvOpcodeString(opcode);
+      if (_.IsCooperativeMatrixType(result_type) ||
+          _.IsCooperativeMatrixType(input_type)) {
+        spv_result_t ret =
+            _.CooperativeMatrixShapesMatch(inst, result_type, input_type);
+        if (ret != SPV_SUCCESS) return ret;
+      } else {
+        if (_.GetDimension(result_type) != _.GetDimension(input_type))
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Expected input to have the same dimension as Result Type: "
+                 << spvOpcodeString(opcode);
+      }
 
       if (!_.features().use_int8_type && (8 == _.GetBitWidth(result_type)))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
@@ -86,22 +104,31 @@
     case SpvOpConvertSToF:
     case SpvOpConvertUToF: {
       if (!_.IsFloatScalarType(result_type) &&
-          !_.IsFloatVectorType(result_type))
+          !_.IsFloatVectorType(result_type) &&
+          !_.IsFloatCooperativeMatrixType(result_type))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected float scalar or vector type as Result Type: "
                << spvOpcodeString(opcode);
 
       const uint32_t input_type = _.GetOperandTypeId(inst, 2);
       if (!input_type ||
-          (!_.IsIntScalarType(input_type) && !_.IsIntVectorType(input_type)))
+          (!_.IsIntScalarType(input_type) && !_.IsIntVectorType(input_type) &&
+           !_.IsIntCooperativeMatrixType(input_type)))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected input to be int scalar or vector: "
                << spvOpcodeString(opcode);
 
-      if (_.GetDimension(result_type) != _.GetDimension(input_type))
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Expected input to have the same dimension as Result Type: "
-               << spvOpcodeString(opcode);
+      if (_.IsCooperativeMatrixType(result_type) ||
+          _.IsCooperativeMatrixType(input_type)) {
+        spv_result_t ret =
+            _.CooperativeMatrixShapesMatch(inst, result_type, input_type);
+        if (ret != SPV_SUCCESS) return ret;
+      } else {
+        if (_.GetDimension(result_type) != _.GetDimension(input_type))
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Expected input to have the same dimension as Result Type: "
+                 << spvOpcodeString(opcode);
+      }
 
       if (!_.features().use_int8_type && (8 == _.GetBitWidth(input_type)))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
@@ -113,22 +140,31 @@
 
     case SpvOpUConvert: {
       if (!_.IsUnsignedIntScalarType(result_type) &&
-          !_.IsUnsignedIntVectorType(result_type))
+          !_.IsUnsignedIntVectorType(result_type) &&
+          !_.IsUnsignedIntCooperativeMatrixType(result_type))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected unsigned int scalar or vector type as Result Type: "
                << spvOpcodeString(opcode);
 
       const uint32_t input_type = _.GetOperandTypeId(inst, 2);
       if (!input_type ||
-          (!_.IsIntScalarType(input_type) && !_.IsIntVectorType(input_type)))
+          (!_.IsIntScalarType(input_type) && !_.IsIntVectorType(input_type) &&
+           !_.IsIntCooperativeMatrixType(input_type)))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected input to be int scalar or vector: "
                << spvOpcodeString(opcode);
 
-      if (_.GetDimension(result_type) != _.GetDimension(input_type))
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Expected input to have the same dimension as Result Type: "
-               << spvOpcodeString(opcode);
+      if (_.IsCooperativeMatrixType(result_type) ||
+          _.IsCooperativeMatrixType(input_type)) {
+        spv_result_t ret =
+            _.CooperativeMatrixShapesMatch(inst, result_type, input_type);
+        if (ret != SPV_SUCCESS) return ret;
+      } else {
+        if (_.GetDimension(result_type) != _.GetDimension(input_type))
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Expected input to have the same dimension as Result Type: "
+                 << spvOpcodeString(opcode);
+      }
 
       if (_.GetBitWidth(result_type) == _.GetBitWidth(input_type))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
@@ -139,22 +175,31 @@
     }
 
     case SpvOpSConvert: {
-      if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type))
+      if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type) &&
+          !_.IsIntCooperativeMatrixType(result_type))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected int scalar or vector type as Result Type: "
                << spvOpcodeString(opcode);
 
       const uint32_t input_type = _.GetOperandTypeId(inst, 2);
       if (!input_type ||
-          (!_.IsIntScalarType(input_type) && !_.IsIntVectorType(input_type)))
+          (!_.IsIntScalarType(input_type) && !_.IsIntVectorType(input_type) &&
+           !_.IsIntCooperativeMatrixType(input_type)))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected input to be int scalar or vector: "
                << spvOpcodeString(opcode);
 
-      if (_.GetDimension(result_type) != _.GetDimension(input_type))
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Expected input to have the same dimension as Result Type: "
-               << spvOpcodeString(opcode);
+      if (_.IsCooperativeMatrixType(result_type) ||
+          _.IsCooperativeMatrixType(input_type)) {
+        spv_result_t ret =
+            _.CooperativeMatrixShapesMatch(inst, result_type, input_type);
+        if (ret != SPV_SUCCESS) return ret;
+      } else {
+        if (_.GetDimension(result_type) != _.GetDimension(input_type))
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Expected input to have the same dimension as Result Type: "
+                 << spvOpcodeString(opcode);
+      }
 
       if (_.GetBitWidth(result_type) == _.GetBitWidth(input_type))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
@@ -166,22 +211,31 @@
 
     case SpvOpFConvert: {
       if (!_.IsFloatScalarType(result_type) &&
-          !_.IsFloatVectorType(result_type))
+          !_.IsFloatVectorType(result_type) &&
+          !_.IsFloatCooperativeMatrixType(result_type))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected float scalar or vector type as Result Type: "
                << spvOpcodeString(opcode);
 
       const uint32_t input_type = _.GetOperandTypeId(inst, 2);
       if (!input_type || (!_.IsFloatScalarType(input_type) &&
-                          !_.IsFloatVectorType(input_type)))
+                          !_.IsFloatVectorType(input_type) &&
+                          !_.IsFloatCooperativeMatrixType(input_type)))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected input to be float scalar or vector: "
                << spvOpcodeString(opcode);
 
-      if (_.GetDimension(result_type) != _.GetDimension(input_type))
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Expected input to have the same dimension as Result Type: "
-               << spvOpcodeString(opcode);
+      if (_.IsCooperativeMatrixType(result_type) ||
+          _.IsCooperativeMatrixType(input_type)) {
+        spv_result_t ret =
+            _.CooperativeMatrixShapesMatch(inst, result_type, input_type);
+        if (ret != SPV_SUCCESS) return ret;
+      } else {
+        if (_.GetDimension(result_type) != _.GetDimension(input_type))
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Expected input to have the same dimension as Result Type: "
+                 << spvOpcodeString(opcode);
+      }
 
       if (_.GetBitWidth(result_type) == _.GetBitWidth(input_type))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
@@ -217,6 +271,23 @@
       if (!_.IsPointerType(input_type))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected input to be a pointer: " << spvOpcodeString(opcode);
+
+      if (_.addressing_model() == SpvAddressingModelLogical)
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Logical addressing not supported: "
+               << spvOpcodeString(opcode);
+
+      if (_.addressing_model() ==
+          SpvAddressingModelPhysicalStorageBuffer64EXT) {
+        uint32_t input_storage_class = 0;
+        uint32_t input_data_type = 0;
+        _.GetPointerTypeInfo(input_type, &input_data_type,
+                             &input_storage_class);
+        if (input_storage_class != SpvStorageClassPhysicalStorageBufferEXT)
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Pointer storage class must be PhysicalStorageBufferEXT: "
+                 << spvOpcodeString(opcode);
+      }
       break;
     }
 
@@ -251,6 +322,23 @@
       if (!input_type || !_.IsIntScalarType(input_type))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected int scalar as input: " << spvOpcodeString(opcode);
+
+      if (_.addressing_model() == SpvAddressingModelLogical)
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Logical addressing not supported: "
+               << spvOpcodeString(opcode);
+
+      if (_.addressing_model() ==
+          SpvAddressingModelPhysicalStorageBuffer64EXT) {
+        uint32_t result_storage_class = 0;
+        uint32_t result_data_type = 0;
+        _.GetPointerTypeInfo(result_type, &result_data_type,
+                             &result_storage_class);
+        if (result_storage_class != SpvStorageClassPhysicalStorageBufferEXT)
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Pointer storage class must be PhysicalStorageBufferEXT: "
+                 << spvOpcodeString(opcode);
+      }
       break;
     }
 
diff --git a/source/val/validate_datarules.cpp b/source/val/validate_datarules.cpp
index 129b6bb..826eb8d 100644
--- a/source/val/validate_datarules.cpp
+++ b/source/val/validate_datarules.cpp
@@ -214,6 +214,21 @@
   return SPV_SUCCESS;
 }
 
+// Validates that any undefined type of the array is a forward pointer.
+// It is valid to declare a forward pointer, and use its <id> as the element
+// type of the array.
+spv_result_t ValidateArray(ValidationState_t& _, const Instruction* inst) {
+  auto element_type_id = inst->GetOperandAs<const uint32_t>(1);
+  auto element_type_instruction = _.FindDef(element_type_id);
+  if (element_type_instruction == nullptr &&
+      !_.IsForwardPointer(element_type_id)) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Forward reference operands in an OpTypeArray must first be "
+              "declared using OpTypeForwardPointer.";
+  }
+  return SPV_SUCCESS;
+}
+
 }  // namespace
 
 // Validates that Data Rules are followed according to the specifications.
@@ -256,6 +271,10 @@
       if (auto error = ValidateStruct(_, inst)) return error;
       break;
     }
+    case SpvOpTypeArray: {
+      if (auto error = ValidateArray(_, inst)) return error;
+      break;
+    }
     // TODO(ehsan): add more data rules validation here.
     default: { break; }
   }
diff --git a/source/val/validate_decorations.cpp b/source/val/validate_decorations.cpp
index 582d11c..a6a7ce6 100644
--- a/source/val/validate_decorations.cpp
+++ b/source/val/validate_decorations.cpp
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "source/val/validate.h"
-
 #include <algorithm>
 #include <cassert>
 #include <string>
@@ -25,8 +23,10 @@
 
 #include "source/diagnostic.h"
 #include "source/opcode.h"
+#include "source/spirv_constant.h"
 #include "source/spirv_target_env.h"
 #include "source/spirv_validator_options.h"
+#include "source/val/validate_scopes.h"
 #include "source/val/validation_state.h"
 
 namespace spvtools {
@@ -218,6 +218,9 @@
       if (roundUp) baseAlignment = align(baseAlignment, 16u);
       break;
     }
+    case SpvOpTypePointer:
+      baseAlignment = vstate.pointer_size_and_alignment();
+      break;
     default:
       assert(0);
       break;
@@ -254,6 +257,8 @@
       }
       return max_member_alignment;
     } break;
+    case SpvOpTypePointer:
+      return vstate.pointer_size_and_alignment();
     default:
       assert(0);
       break;
@@ -331,6 +336,8 @@
       const auto& constraint = constraints[std::make_pair(lastMember, lastIdx)];
       return offset + getSize(lastMember, constraint, constraints, vstate);
     }
+    case SpvOpTypePointer:
+      return vstate.pointer_size_and_alignment();
     default:
       assert(0);
       return 0;
@@ -372,10 +379,15 @@
 // or row major-ness.
 spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
                          const char* decoration_str, bool blockRules,
+                         uint32_t incoming_offset,
                          MemberConstraints& constraints,
                          ValidationState_t& vstate) {
   if (vstate.options()->skip_block_layout) return SPV_SUCCESS;
 
+  // blockRules are the same as bufferBlock rules if the uniform buffer
+  // standard layout extension is being used.
+  if (vstate.options()->uniform_buffer_standard_layout) blockRules = false;
+
   // Relaxed layout and scalar layout can both be in effect at the same time.
   // For example, relaxed layout is implied by Vulkan 1.1.  But scalar layout
   // is more permissive than relaxed layout.
@@ -422,7 +434,8 @@
         }
       }
     }
-    member_offsets.push_back(MemberOffsetPair{memberIdx, offset});
+    member_offsets.push_back(
+        MemberOffsetPair{memberIdx, incoming_offset + offset});
   }
   std::stable_sort(
       member_offsets.begin(), member_offsets.end(),
@@ -486,9 +499,9 @@
     // Check struct members recursively.
     spv_result_t recursive_status = SPV_SUCCESS;
     if (SpvOpTypeStruct == opcode &&
-        SPV_SUCCESS != (recursive_status =
-                            checkLayout(id, storage_class_str, decoration_str,
-                                        blockRules, constraints, vstate)))
+        SPV_SUCCESS != (recursive_status = checkLayout(
+                            id, storage_class_str, decoration_str, blockRules,
+                            offset, constraints, vstate)))
       return recursive_status;
     // Check matrix stride.
     if (SpvOpTypeMatrix == opcode) {
@@ -500,23 +513,56 @@
                  << " not satisfying alignment to " << alignment;
       }
     }
-    // Check arrays and runtime arrays.
-    if (SpvOpTypeArray == opcode || SpvOpTypeRuntimeArray == opcode) {
-      const auto typeId = inst->word(2);
-      const auto arrayInst = vstate.FindDef(typeId);
-      if (SpvOpTypeStruct == arrayInst->opcode() &&
-          SPV_SUCCESS != (recursive_status = checkLayout(
-                              typeId, storage_class_str, decoration_str,
-                              blockRules, constraints, vstate)))
-        return recursive_status;
+
+    // Check arrays and runtime arrays recursively.
+    auto array_inst = inst;
+    auto array_alignment = alignment;
+    while (array_inst->opcode() == SpvOpTypeArray ||
+           array_inst->opcode() == SpvOpTypeRuntimeArray) {
+      const auto typeId = array_inst->word(2);
+      const auto element_inst = vstate.FindDef(typeId);
       // Check array stride.
-      for (auto& decoration : vstate.id_decorations(id)) {
-        if (SpvDecorationArrayStride == decoration.dec_type() &&
-            !IsAlignedTo(decoration.params()[0], alignment))
-          return fail(memberIdx)
-                 << "is an array with stride " << decoration.params()[0]
-                 << " not satisfying alignment to " << alignment;
+      auto array_stride = 0;
+      for (auto& decoration : vstate.id_decorations(array_inst->id())) {
+        if (SpvDecorationArrayStride == decoration.dec_type()) {
+          array_stride = decoration.params()[0];
+          if (!IsAlignedTo(array_stride, array_alignment))
+            return fail(memberIdx)
+                   << "contains an array with stride " << decoration.params()[0]
+                   << " not satisfying alignment to " << alignment;
+        }
       }
+
+      bool is_int32 = false;
+      bool is_const = false;
+      uint32_t num_elements = 0;
+      if (array_inst->opcode() == SpvOpTypeArray) {
+        std::tie(is_int32, is_const, num_elements) =
+            vstate.EvalInt32IfConst(array_inst->word(3));
+      }
+      num_elements = std::max(1u, num_elements);
+      // Check each element recursively if it is a struct. There is a
+      // limitation to this check if the array size is a spec constant or is a
+      // runtime array then we will only check a single element. This means
+      // some improper straddles might be missed.
+      for (uint32_t i = 0; i < num_elements; ++i) {
+        uint32_t next_offset = i * array_stride + offset;
+        if (SpvOpTypeStruct == element_inst->opcode() &&
+            SPV_SUCCESS != (recursive_status = checkLayout(
+                                typeId, storage_class_str, decoration_str,
+                                blockRules, next_offset, constraints, vstate)))
+          return recursive_status;
+        // If offsets accumulate up to a 16-byte multiple stop checking since
+        // it will just repeat.
+        if (i > 0 && (next_offset % 16 == 0)) break;
+      }
+
+      // Proceed to the element in case it is an array.
+      array_inst = element_inst;
+      array_alignment = scalar_block_layout
+                            ? getScalarAlignment(array_inst->id(), vstate)
+                            : getBaseAlignment(array_inst->id(), blockRules,
+                                               constraint, constraints, vstate);
     }
     nextValidOffset = offset + size;
     if (!scalar_block_layout && blockRules &&
@@ -642,6 +688,7 @@
     int num_builtin_inputs = 0;
     int num_builtin_outputs = 0;
     for (const auto& desc : descs) {
+      std::unordered_set<Instruction*> seen_vars;
       for (auto interface : desc.interfaces) {
         Instruction* var_instr = vstate.FindDef(interface);
         if (!var_instr || SpvOpVariable != var_instr->opcode()) {
@@ -652,14 +699,30 @@
         }
         const SpvStorageClass storage_class =
             var_instr->GetOperandAs<SpvStorageClass>(2);
-        if (storage_class != SpvStorageClassInput &&
-            storage_class != SpvStorageClassOutput) {
-          return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
-                 << "OpEntryPoint interfaces must be OpVariables with "
-                    "Storage Class of Input(1) or Output(3). Found Storage "
-                    "Class "
-                 << storage_class << " for Entry Point id " << entry_point
-                 << ".";
+        if (vstate.version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
+          // Starting in 1.4, OpEntryPoint must list all global variables
+          // it statically uses and those interfaces must be unique.
+          if (storage_class == SpvStorageClassFunction) {
+            return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
+                   << "OpEntryPoint interfaces should only list global "
+                      "variables";
+          }
+
+          if (!seen_vars.insert(var_instr).second) {
+            return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
+                   << "Non-unique OpEntryPoint interface "
+                   << vstate.getIdName(interface) << " is disallowed";
+          }
+        } else {
+          if (storage_class != SpvStorageClassInput &&
+              storage_class != SpvStorageClassOutput) {
+            return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
+                   << "OpEntryPoint interfaces must be OpVariables with "
+                      "Storage Class of Input(1) or Output(3). Found Storage "
+                      "Class "
+                   << storage_class << " for Entry Point id " << entry_point
+                   << ".";
+          }
         }
 
         const uint32_t ptr_id = var_instr->word(1);
@@ -847,11 +910,20 @@
         }
       }
 
-      if (uniform || push_constant || storage_buffer) {
+      const bool phys_storage_buffer =
+          storageClass == SpvStorageClassPhysicalStorageBufferEXT;
+      if (uniform || push_constant || storage_buffer || phys_storage_buffer) {
         const auto ptrInst = vstate.FindDef(words[1]);
         assert(SpvOpTypePointer == ptrInst->opcode());
-        const auto id = ptrInst->words()[3];
-        if (SpvOpTypeStruct != vstate.FindDef(id)->opcode()) continue;
+        auto id = ptrInst->words()[3];
+        auto id_inst = vstate.FindDef(id);
+        // Jump through one level of arraying.
+        if (id_inst->opcode() == SpvOpTypeArray ||
+            id_inst->opcode() == SpvOpTypeRuntimeArray) {
+          id = id_inst->GetOperandAs<uint32_t>(1u);
+          id_inst = vstate.FindDef(id);
+        }
+        if (SpvOpTypeStruct != id_inst->opcode()) continue;
         MemberConstraints constraints;
         ComputeMemberConstraintsForStruct(&constraints, id, LayoutConstraints(),
                                           vstate);
@@ -861,6 +933,13 @@
                     : (push_constant ? "PushConstant" : "StorageBuffer");
 
         if (spvIsVulkanEnv(vstate.context()->target_env)) {
+          if (storage_buffer &&
+              hasDecoration(id, SpvDecorationBufferBlock, vstate)) {
+            return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
+                   << "Storage buffer id '" << var_id
+                   << " In Vulkan, BufferBlock is disallowed on variables in "
+                      "the StorageBuffer storage class";
+          }
           // Vulkan 14.5.1: Check Block decoration for PushConstant variables.
           if (push_constant && !hasDecoration(id, SpvDecorationBlock, vstate)) {
             return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
@@ -899,9 +978,19 @@
           const bool blockDeco = SpvDecorationBlock == dec.dec_type();
           const bool bufferDeco = SpvDecorationBufferBlock == dec.dec_type();
           const bool blockRules = uniform && blockDeco;
-          const bool bufferRules = (uniform && bufferDeco) ||
-                                   (push_constant && blockDeco) ||
-                                   (storage_buffer && blockDeco);
+          const bool bufferRules =
+              (uniform && bufferDeco) || (push_constant && blockDeco) ||
+              ((storage_buffer || phys_storage_buffer) && blockDeco);
+          if (uniform && blockDeco) {
+            vstate.RegisterPointerToUniformBlock(ptrInst->id());
+            vstate.RegisterStructForUniformBlock(id);
+          }
+          if ((uniform && bufferDeco) ||
+              ((storage_buffer || phys_storage_buffer) && blockDeco)) {
+            vstate.RegisterPointerToStorageBuffer(ptrInst->id());
+            vstate.RegisterStructForStorageBuffer(id);
+          }
+
           if (blockRules || bufferRules) {
             const char* deco_str = blockDeco ? "Block" : "BufferBlock";
             spv_result_t recursive_status = SPV_SUCCESS;
@@ -933,12 +1022,12 @@
                         "decorations.";
             } else if (blockRules &&
                        (SPV_SUCCESS != (recursive_status = checkLayout(
-                                            id, sc_str, deco_str, true,
+                                            id, sc_str, deco_str, true, 0,
                                             constraints, vstate)))) {
               return recursive_status;
             } else if (bufferRules &&
                        (SPV_SUCCESS != (recursive_status = checkLayout(
-                                            id, sc_str, deco_str, false,
+                                            id, sc_str, deco_str, false, 0,
                                             constraints, vstate)))) {
               return recursive_status;
             }
@@ -950,42 +1039,69 @@
   return SPV_SUCCESS;
 }
 
+// Returns true if |decoration| cannot be applied to the same id more than once.
+bool AtMostOncePerId(SpvDecoration decoration) {
+  return decoration == SpvDecorationArrayStride;
+}
+
+// Returns true if |decoration| cannot be applied to the same member more than
+// once.
+bool AtMostOncePerMember(SpvDecoration decoration) {
+  switch (decoration) {
+    case SpvDecorationOffset:
+    case SpvDecorationMatrixStride:
+    case SpvDecorationRowMajor:
+    case SpvDecorationColMajor:
+      return true;
+    default:
+      return false;
+  }
+}
+
+// Returns the string name for |decoration|.
+const char* GetDecorationName(SpvDecoration decoration) {
+  switch (decoration) {
+    case SpvDecorationAliased:
+      return "Aliased";
+    case SpvDecorationRestrict:
+      return "Restrict";
+    case SpvDecorationArrayStride:
+      return "ArrayStride";
+    case SpvDecorationOffset:
+      return "Offset";
+    case SpvDecorationMatrixStride:
+      return "MatrixStride";
+    case SpvDecorationRowMajor:
+      return "RowMajor";
+    case SpvDecorationColMajor:
+      return "ColMajor";
+    case SpvDecorationBlock:
+      return "Block";
+    case SpvDecorationBufferBlock:
+      return "BufferBlock";
+    default:
+      return "";
+  }
+}
+
 spv_result_t CheckDecorationsCompatibility(ValidationState_t& vstate) {
-  using AtMostOnceSet = std::unordered_set<SpvDecoration, SpvDecorationHash>;
-  using MutuallyExclusiveSets =
-      std::vector<std::unordered_set<SpvDecoration, SpvDecorationHash>>;
   using PerIDKey = std::tuple<SpvDecoration, uint32_t>;
   using PerMemberKey = std::tuple<SpvDecoration, uint32_t, uint32_t>;
-  using DecorationNameTable =
-      std::unordered_map<SpvDecoration, std::string, SpvDecorationHash>;
 
-  static const auto* const at_most_once_per_id = new AtMostOnceSet{
-      SpvDecorationArrayStride,
-  };
-  static const auto* const at_most_once_per_member = new AtMostOnceSet{
-      SpvDecorationOffset,
-      SpvDecorationMatrixStride,
-      SpvDecorationRowMajor,
-      SpvDecorationColMajor,
-  };
-  static const auto* const mutually_exclusive_per_id =
-      new MutuallyExclusiveSets{
-          {SpvDecorationBlock, SpvDecorationBufferBlock},
-      };
-  static const auto* const mutually_exclusive_per_member =
-      new MutuallyExclusiveSets{
-          {SpvDecorationRowMajor, SpvDecorationColMajor},
-      };
-  // For printing the decoration name.
-  static const auto* const decoration_name = new DecorationNameTable{
-      {SpvDecorationArrayStride, "ArrayStride"},
-      {SpvDecorationOffset, "Offset"},
-      {SpvDecorationMatrixStride, "MatrixStride"},
-      {SpvDecorationRowMajor, "RowMajor"},
-      {SpvDecorationColMajor, "ColMajor"},
-      {SpvDecorationBlock, "Block"},
-      {SpvDecorationBufferBlock, "BufferBlock"},
-  };
+  // An Array of pairs where the decorations in the pair cannot both be applied
+  // to the same id.
+  static const SpvDecoration mutually_exclusive_per_id[][2] = {
+      {SpvDecorationBlock, SpvDecorationBufferBlock},
+      {SpvDecorationRestrict, SpvDecorationAliased}};
+  static const auto num_mutually_exclusive_per_id_pairs =
+      sizeof(mutually_exclusive_per_id) / (2 * sizeof(SpvDecoration));
+
+  // An Array of pairs where the decorations in the pair cannot both be applied
+  // to the same member.
+  static const SpvDecoration mutually_exclusive_per_member[][2] = {
+      {SpvDecorationRowMajor, SpvDecorationColMajor}};
+  static const auto num_mutually_exclusive_per_mem_pairs =
+      sizeof(mutually_exclusive_per_member) / (2 * sizeof(SpvDecoration));
 
   std::set<PerIDKey> seen_per_id;
   std::set<PerMemberKey> seen_per_member;
@@ -997,26 +1113,31 @@
       const auto dec_type = static_cast<SpvDecoration>(words[2]);
       const auto k = PerIDKey(dec_type, id);
       const auto already_used = !seen_per_id.insert(k).second;
-      if (already_used &&
-          at_most_once_per_id->find(dec_type) != at_most_once_per_id->end()) {
+      if (already_used && AtMostOncePerId(dec_type)) {
         return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
                << "ID '" << id << "' decorated with "
-               << decoration_name->at(dec_type)
+               << GetDecorationName(dec_type)
                << " multiple times is not allowed.";
       }
       // Verify certain mutually exclusive decorations are not both applied on
       // an ID.
-      for (const auto& s : *mutually_exclusive_per_id) {
-        if (s.find(dec_type) == s.end()) continue;
-        for (auto excl_dec_type : s) {
-          if (excl_dec_type == dec_type) continue;
-          const auto excl_k = PerIDKey(excl_dec_type, id);
-          if (seen_per_id.find(excl_k) != seen_per_id.end()) {
-            return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
-                   << "ID '" << id << "' decorated with both "
-                   << decoration_name->at(dec_type) << " and "
-                   << decoration_name->at(excl_dec_type) << " is not allowed.";
-          }
+      for (uint32_t pair_idx = 0;
+           pair_idx < num_mutually_exclusive_per_id_pairs; ++pair_idx) {
+        SpvDecoration excl_dec_type = SpvDecorationMax;
+        if (mutually_exclusive_per_id[pair_idx][0] == dec_type) {
+          excl_dec_type = mutually_exclusive_per_id[pair_idx][1];
+        } else if (mutually_exclusive_per_id[pair_idx][1] == dec_type) {
+          excl_dec_type = mutually_exclusive_per_id[pair_idx][0];
+        } else {
+          continue;
+        }
+
+        const auto excl_k = PerIDKey(excl_dec_type, id);
+        if (seen_per_id.find(excl_k) != seen_per_id.end()) {
+          return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
+                 << "ID '" << id << "' decorated with both "
+                 << GetDecorationName(dec_type) << " and "
+                 << GetDecorationName(excl_dec_type) << " is not allowed.";
         }
       }
     } else if (SpvOpMemberDecorate == inst.opcode()) {
@@ -1025,27 +1146,32 @@
       const auto dec_type = static_cast<SpvDecoration>(words[3]);
       const auto k = PerMemberKey(dec_type, id, member_id);
       const auto already_used = !seen_per_member.insert(k).second;
-      if (already_used && at_most_once_per_member->find(dec_type) !=
-                              at_most_once_per_member->end()) {
+      if (already_used && AtMostOncePerMember(dec_type)) {
         return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
                << "ID '" << id << "', member '" << member_id
-               << "' decorated with " << decoration_name->at(dec_type)
+               << "' decorated with " << GetDecorationName(dec_type)
                << " multiple times is not allowed.";
       }
       // Verify certain mutually exclusive decorations are not both applied on
       // a (ID, member) tuple.
-      for (const auto& s : *mutually_exclusive_per_member) {
-        if (s.find(dec_type) == s.end()) continue;
-        for (auto excl_dec_type : s) {
-          if (excl_dec_type == dec_type) continue;
-          const auto excl_k = PerMemberKey(excl_dec_type, id, member_id);
-          if (seen_per_member.find(excl_k) != seen_per_member.end()) {
-            return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
-                   << "ID '" << id << "', member '" << member_id
-                   << "' decorated with both " << decoration_name->at(dec_type)
-                   << " and " << decoration_name->at(excl_dec_type)
-                   << " is not allowed.";
-          }
+      for (uint32_t pair_idx = 0;
+           pair_idx < num_mutually_exclusive_per_mem_pairs; ++pair_idx) {
+        SpvDecoration excl_dec_type = SpvDecorationMax;
+        if (mutually_exclusive_per_member[pair_idx][0] == dec_type) {
+          excl_dec_type = mutually_exclusive_per_member[pair_idx][1];
+        } else if (mutually_exclusive_per_member[pair_idx][1] == dec_type) {
+          excl_dec_type = mutually_exclusive_per_member[pair_idx][0];
+        } else {
+          continue;
+        }
+
+        const auto excl_k = PerMemberKey(excl_dec_type, id, member_id);
+        if (seen_per_member.find(excl_k) != seen_per_member.end()) {
+          return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
+                 << "ID '" << id << "', member '" << member_id
+                 << "' decorated with both " << GetDecorationName(dec_type)
+                 << " and " << GetDecorationName(excl_dec_type)
+                 << " is not allowed.";
         }
       }
     }
@@ -1097,6 +1223,9 @@
   // Validates Object operand of an OpStore
   for (const auto& use : inst.uses()) {
     const auto store = use.first;
+    if (store->opcode() == SpvOpFConvert) continue;
+    if (spvOpcodeIsDebug(store->opcode())) continue;
+    if (spvOpcodeIsDecoration(store->opcode())) continue;
     if (store->opcode() != SpvOpStore) {
       return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
              << "FPRoundingMode decoration can be applied only to the "
@@ -1127,25 +1256,75 @@
     if (storage != SpvStorageClassStorageBuffer &&
         storage != SpvStorageClassUniform &&
         storage != SpvStorageClassPushConstant &&
-        storage != SpvStorageClassInput && storage != SpvStorageClassOutput) {
+        storage != SpvStorageClassInput && storage != SpvStorageClassOutput &&
+        storage != SpvStorageClassPhysicalStorageBufferEXT) {
       return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
              << "FPRoundingMode decoration can be applied only to the "
                 "Object operand of an OpStore in the StorageBuffer, "
-                "Uniform, PushConstant, Input, or Output Storage "
-                "Classes.";
+                "PhysicalStorageBufferEXT, Uniform, PushConstant, Input, or "
+                "Output Storage Classes.";
     }
   }
   return SPV_SUCCESS;
 }
 
-// Returns SPV_SUCCESS if validation rules are satisfied for Uniform
-// decorations. Otherwise emits a diagnostic and returns something other than
-// SPV_SUCCESS. Assumes each decoration on a group has been propagated down to
-// the group members.
+// Returns SPV_SUCCESS if validation rules are satisfied for the NonWritable
+// decoration.  Otherwise emits a diagnostic and returns something other than
+// SPV_SUCCESS.  The |inst| parameter is the object being decorated.  This must
+// be called after TypePass and AnnotateCheckDecorationsOfBuffers are called.
+spv_result_t CheckNonWritableDecoration(ValidationState_t& vstate,
+                                        const Instruction& inst,
+                                        const Decoration& decoration) {
+  assert(inst.id() && "Parser ensures the target of the decoration has an ID");
+
+  if (decoration.struct_member_index() == Decoration::kInvalidMember) {
+    // The target must be a memory object declaration.
+    // First, it must be a variable or function parameter.
+    const auto opcode = inst.opcode();
+    const auto type_id = inst.type_id();
+    if (opcode != SpvOpVariable && opcode != SpvOpFunctionParameter) {
+      return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+             << "Target of NonWritable decoration must be a memory object "
+                "declaration (a variable or a function parameter)";
+    }
+    const auto var_storage_class = opcode == SpvOpVariable
+                                       ? inst.GetOperandAs<SpvStorageClass>(2)
+                                       : SpvStorageClassMax;
+    if ((var_storage_class == SpvStorageClassFunction ||
+         var_storage_class == SpvStorageClassPrivate) &&
+        vstate.features().nonwritable_var_in_function_or_private) {
+      // New permitted feature in SPIR-V 1.4.
+    } else if (
+        // It may point to a UBO, SSBO, or storage image.
+        vstate.IsPointerToUniformBlock(type_id) ||
+        vstate.IsPointerToStorageBuffer(type_id) ||
+        vstate.IsPointerToStorageImage(type_id)) {
+    } else {
+      return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+             << "Target of NonWritable decoration is invalid: must point to a "
+                "storage image, uniform block, "
+             << (vstate.features().nonwritable_var_in_function_or_private
+                     ? "storage buffer, or variable in Private or Function "
+                       "storage class"
+                     : "or storage buffer");
+    }
+  }
+
+  return SPV_SUCCESS;
+}
+
+// Returns SPV_SUCCESS if validation rules are satisfied for Uniform or
+// UniformId decorations. Otherwise emits a diagnostic and returns something
+// other than SPV_SUCCESS. Assumes each decoration on a group has been
+// propagated down to the group members.  The |inst| parameter is the object
+// being decorated.
 spv_result_t CheckUniformDecoration(ValidationState_t& vstate,
                                     const Instruction& inst,
-                                    const Decoration&) {
-  // Uniform must decorate an "object"
+                                    const Decoration& decoration) {
+  const char* const dec_name =
+      decoration.dec_type() == SpvDecorationUniform ? "Uniform" : "UniformId";
+
+  // Uniform or UniformId must decorate an "object"
   //  - has a result ID
   //  - is an instantiation of a non-void type.  So it has a type ID, and that
   //  type is not void.
@@ -1154,22 +1333,66 @@
 
   if (inst.type_id() == 0) {
     return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
-           << "Uniform decoration applied to a non-object";
+           << dec_name << " decoration applied to a non-object";
   }
   if (Instruction* type_inst = vstate.FindDef(inst.type_id())) {
     if (type_inst->opcode() == SpvOpTypeVoid) {
       return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
-             << "Uniform decoration applied to a value with void type";
+             << dec_name << " decoration applied to a value with void type";
     }
   } else {
     // We might never get here because this would have been rejected earlier in
     // the flow.
     return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
-           << "Uniform decoration applied to an object with invalid type";
+           << dec_name << " decoration applied to an object with invalid type";
   }
+
+  // Use of Uniform with OpDecorate is checked elsewhere.
+  // Use of UniformId with OpDecorateId is checked elsewhere.
+
+  if (decoration.dec_type() == SpvDecorationUniformId) {
+    assert(decoration.params().size() == 1 &&
+           "Grammar ensures UniformId has one parameter");
+
+    // The scope id is an execution scope.
+    if (auto error =
+            ValidateExecutionScope(vstate, &inst, decoration.params()[0]))
+      return error;
+  }
+
   return SPV_SUCCESS;
 }
 
+// Returns SPV_SUCCESS if validation rules are satisfied for NoSignedWrap or
+// NoUnsignedWrap decorations. Otherwise emits a diagnostic and returns
+// something other than SPV_SUCCESS. Assumes each decoration on a group has been
+// propagated down to the group members.
+spv_result_t CheckIntegerWrapDecoration(ValidationState_t& vstate,
+                                        const Instruction& inst,
+                                        const Decoration& decoration) {
+  switch (inst.opcode()) {
+    case SpvOpIAdd:
+    case SpvOpISub:
+    case SpvOpIMul:
+    case SpvOpShiftLeftLogical:
+    case SpvOpSNegate:
+      return SPV_SUCCESS;
+    case SpvOpExtInst:
+      // TODO(dneto): Only certain extended instructions allow these
+      // decorations.  For now allow anything.
+      return SPV_SUCCESS;
+    default:
+      break;
+  }
+
+  return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+         << (decoration.dec_type() == SpvDecorationNoSignedWrap
+                 ? "NoSignedWrap"
+                 : "NoUnsignedWrap")
+         << " decoration may not be applied to "
+         << spvOpcodeString(inst.opcode());
+}
+
 #define PASS_OR_BAIL_AT_LINE(X, LINE)           \
   {                                             \
     spv_result_t e##LINE = (X);                 \
@@ -1196,16 +1419,23 @@
     // been propagated down to the group members.
     if (inst->opcode() == SpvOpDecorationGroup) continue;
 
-    // Validates FPRoundingMode decoration
     for (const auto& decoration : decorations) {
       switch (decoration.dec_type()) {
         case SpvDecorationFPRoundingMode:
           if (is_shader)
             PASS_OR_BAIL(CheckFPRoundingModeForShaders(vstate, *inst));
           break;
+        case SpvDecorationNonWritable:
+          PASS_OR_BAIL(CheckNonWritableDecoration(vstate, *inst, decoration));
+          break;
         case SpvDecorationUniform:
+        case SpvDecorationUniformId:
           PASS_OR_BAIL(CheckUniformDecoration(vstate, *inst, decoration));
           break;
+        case SpvDecorationNoSignedWrap:
+        case SpvDecorationNoUnsignedWrap:
+          PASS_OR_BAIL(CheckIntegerWrapDecoration(vstate, *inst, decoration));
+          break;
         default:
           break;
       }
diff --git a/source/val/validate_function.cpp b/source/val/validate_function.cpp
index 39f00fe..5609008 100644
--- a/source/val/validate_function.cpp
+++ b/source/val/validate_function.cpp
@@ -24,6 +24,17 @@
 namespace val {
 namespace {
 
+// Returns true if |a| and |b| are instruction defining pointers that point to
+// the same type.
+bool ArePointersToSameType(val::Instruction* a, val::Instruction* b) {
+  if (a->opcode() != SpvOpTypePointer || b->opcode() != SpvOpTypePointer) {
+    return false;
+  }
+
+  uint32_t a_type = a->GetOperandAs<uint32_t>(2);
+  return a_type && (a_type == b->GetOperandAs<uint32_t>(2));
+}
+
 spv_result_t ValidateFunction(ValidationState_t& _, const Instruction* inst) {
   const auto function_type_id = inst->GetOperandAs<uint32_t>(3);
   const auto function_type = _.FindDef(function_type_id);
@@ -41,18 +52,22 @@
            << _.getIdName(return_id) << "'.";
   }
 
+  const std::vector<SpvOp> acceptable = {
+      SpvOpDecorate,
+      SpvOpEnqueueKernel,
+      SpvOpEntryPoint,
+      SpvOpExecutionMode,
+      SpvOpExecutionModeId,
+      SpvOpFunctionCall,
+      SpvOpGetKernelNDrangeSubGroupCount,
+      SpvOpGetKernelNDrangeMaxSubGroupSize,
+      SpvOpGetKernelWorkGroupSize,
+      SpvOpGetKernelPreferredWorkGroupSizeMultiple,
+      SpvOpGetKernelLocalSizeForSubgroupCount,
+      SpvOpGetKernelMaxNumSubgroups,
+      SpvOpName};
   for (auto& pair : inst->uses()) {
     const auto* use = pair.first;
-    const std::vector<SpvOp> acceptable = {
-        SpvOpFunctionCall,
-        SpvOpEntryPoint,
-        SpvOpEnqueueKernel,
-        SpvOpGetKernelNDrangeSubGroupCount,
-        SpvOpGetKernelNDrangeMaxSubGroupSize,
-        SpvOpGetKernelWorkGroupSize,
-        SpvOpGetKernelPreferredWorkGroupSizeMultiple,
-        SpvOpGetKernelLocalSizeForSubgroupCount,
-        SpvOpGetKernelMaxNumSubgroups};
     if (std::find(acceptable.begin(), acceptable.end(), use->opcode()) ==
         acceptable.end()) {
       return _.diag(SPV_ERROR_INVALID_ID, use)
@@ -111,6 +126,79 @@
            << "' does not match the OpTypeFunction parameter "
               "type of the same index.";
   }
+
+  // Validate that PhysicalStorageBufferEXT have one of Restrict, Aliased,
+  // RestrictPointerEXT, or AliasedPointerEXT.
+  auto param_nonarray_type_id = param_type->id();
+  while (_.GetIdOpcode(param_nonarray_type_id) == SpvOpTypeArray) {
+    param_nonarray_type_id =
+        _.FindDef(param_nonarray_type_id)->GetOperandAs<uint32_t>(1u);
+  }
+  if (_.GetIdOpcode(param_nonarray_type_id) == SpvOpTypePointer) {
+    auto param_nonarray_type = _.FindDef(param_nonarray_type_id);
+    if (param_nonarray_type->GetOperandAs<uint32_t>(1u) ==
+        SpvStorageClassPhysicalStorageBufferEXT) {
+      // check for Aliased or Restrict
+      const auto& decorations = _.id_decorations(inst->id());
+
+      bool foundAliased = std::any_of(
+          decorations.begin(), decorations.end(), [](const Decoration& d) {
+            return SpvDecorationAliased == d.dec_type();
+          });
+
+      bool foundRestrict = std::any_of(
+          decorations.begin(), decorations.end(), [](const Decoration& d) {
+            return SpvDecorationRestrict == d.dec_type();
+          });
+
+      if (!foundAliased && !foundRestrict) {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << "OpFunctionParameter " << inst->id()
+               << ": expected Aliased or Restrict for PhysicalStorageBufferEXT "
+                  "pointer.";
+      }
+      if (foundAliased && foundRestrict) {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << "OpFunctionParameter " << inst->id()
+               << ": can't specify both Aliased and Restrict for "
+                  "PhysicalStorageBufferEXT pointer.";
+      }
+    } else {
+      const auto pointee_type_id =
+          param_nonarray_type->GetOperandAs<uint32_t>(2);
+      const auto pointee_type = _.FindDef(pointee_type_id);
+      if (SpvOpTypePointer == pointee_type->opcode() &&
+          pointee_type->GetOperandAs<uint32_t>(1u) ==
+              SpvStorageClassPhysicalStorageBufferEXT) {
+        // check for AliasedPointerEXT/RestrictPointerEXT
+        const auto& decorations = _.id_decorations(inst->id());
+
+        bool foundAliased = std::any_of(
+            decorations.begin(), decorations.end(), [](const Decoration& d) {
+              return SpvDecorationAliasedPointerEXT == d.dec_type();
+            });
+
+        bool foundRestrict = std::any_of(
+            decorations.begin(), decorations.end(), [](const Decoration& d) {
+              return SpvDecorationRestrictPointerEXT == d.dec_type();
+            });
+
+        if (!foundAliased && !foundRestrict) {
+          return _.diag(SPV_ERROR_INVALID_ID, inst)
+                 << "OpFunctionParameter " << inst->id()
+                 << ": expected AliasedPointerEXT or RestrictPointerEXT for "
+                    "PhysicalStorageBufferEXT pointer.";
+        }
+        if (foundAliased && foundRestrict) {
+          return _.diag(SPV_ERROR_INVALID_ID, inst)
+                 << "OpFunctionParameter " << inst->id()
+                 << ": can't specify both AliasedPointerEXT and "
+                    "RestrictPointerEXT for PhysicalStorageBufferEXT pointer.";
+        }
+      }
+    }
+  }
+
   return SPV_SUCCESS;
 }
 
@@ -168,12 +256,60 @@
     const auto parameter_type_id =
         function_type->GetOperandAs<uint32_t>(param_index);
     const auto parameter_type = _.FindDef(parameter_type_id);
-    if (!parameter_type || argument_type->id() != parameter_type->id()) {
+    if (!parameter_type ||
+        (argument_type->id() != parameter_type->id() &&
+         !(_.options()->relax_logical_pointer &&
+           ArePointersToSameType(argument_type, parameter_type)))) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
              << "OpFunctionCall Argument <id> '" << _.getIdName(argument_id)
              << "'s type does not match Function <id> '"
              << _.getIdName(parameter_type_id) << "'s parameter type.";
     }
+
+    if (_.addressing_model() == SpvAddressingModelLogical) {
+      if (parameter_type->opcode() == SpvOpTypePointer &&
+          !_.options()->relax_logical_pointer) {
+        SpvStorageClass sc = parameter_type->GetOperandAs<SpvStorageClass>(1u);
+        // Validate which storage classes can be pointer operands.
+        switch (sc) {
+          case SpvStorageClassUniformConstant:
+          case SpvStorageClassFunction:
+          case SpvStorageClassPrivate:
+          case SpvStorageClassWorkgroup:
+          case SpvStorageClassAtomicCounter:
+            // These are always allowed.
+            break;
+          case SpvStorageClassStorageBuffer:
+            if (!_.features().variable_pointers_storage_buffer) {
+              return _.diag(SPV_ERROR_INVALID_ID, inst)
+                     << "StorageBuffer pointer operand "
+                     << _.getIdName(argument_id)
+                     << " requires a variable pointers capability";
+            }
+            break;
+          default:
+            return _.diag(SPV_ERROR_INVALID_ID, inst)
+                   << "Invalid storage class for pointer operand "
+                   << _.getIdName(argument_id);
+        }
+
+        // Validate memory object declaration requirements.
+        if (argument->opcode() != SpvOpVariable &&
+            argument->opcode() != SpvOpFunctionParameter) {
+          const bool ssbo_vptr =
+              _.features().variable_pointers_storage_buffer &&
+              sc == SpvStorageClassStorageBuffer;
+          const bool wg_vptr =
+              _.features().variable_pointers && sc == SpvStorageClassWorkgroup;
+          const bool uc_ptr = sc == SpvStorageClassUniformConstant;
+          if (!ssbo_vptr && !wg_vptr && !uc_ptr) {
+            return _.diag(SPV_ERROR_INVALID_ID, inst)
+                   << "Pointer operand " << _.getIdName(argument_id)
+                   << " must be a memory object declaration";
+          }
+        }
+      }
+    }
   }
   return SPV_SUCCESS;
 }
diff --git a/source/val/validate_id.cpp b/source/val/validate_id.cpp
index fcba711..cb18e13 100644
--- a/source/val/validate_id.cpp
+++ b/source/val/validate_id.cpp
@@ -167,10 +167,26 @@
           const auto opcode = inst->opcode();
           if (spvOpcodeGeneratesType(def->opcode()) &&
               !spvOpcodeGeneratesType(opcode) && !spvOpcodeIsDebug(opcode) &&
-              !spvOpcodeIsDecoration(opcode) && opcode != SpvOpFunction) {
+              !spvOpcodeIsDecoration(opcode) && opcode != SpvOpFunction &&
+              opcode != SpvOpCooperativeMatrixLengthNV &&
+              !(opcode == SpvOpSpecConstantOp &&
+                inst->word(3) == SpvOpCooperativeMatrixLengthNV)) {
             return _.diag(SPV_ERROR_INVALID_ID, inst)
                    << "Operand " << _.getIdName(operand_word)
                    << " cannot be a type";
+          } else if (def->type_id() == 0 && !spvOpcodeGeneratesType(opcode) &&
+                     !spvOpcodeIsDebug(opcode) &&
+                     !spvOpcodeIsDecoration(opcode) &&
+                     !spvOpcodeIsBranch(opcode) && opcode != SpvOpPhi &&
+                     opcode != SpvOpExtInst && opcode != SpvOpExtInstImport &&
+                     opcode != SpvOpSelectionMerge &&
+                     opcode != SpvOpLoopMerge && opcode != SpvOpFunction &&
+                     opcode != SpvOpCooperativeMatrixLengthNV &&
+                     !(opcode == SpvOpSpecConstantOp &&
+                       inst->word(3) == SpvOpCooperativeMatrixLengthNV)) {
+            return _.diag(SPV_ERROR_INVALID_ID, inst)
+                   << "Operand " << _.getIdName(operand_word)
+                   << " requires a type";
           } else {
             ret = SPV_SUCCESS;
           }
diff --git a/source/val/validate_image.cpp b/source/val/validate_image.cpp
index 8a357ff..ebf9ae0 100644
--- a/source/val/validate_image.cpp
+++ b/source/val/validate_image.cpp
@@ -63,6 +63,8 @@
     case SpvImageOperandsMakeTexelVisibleKHRMask:
     case SpvImageOperandsNonPrivateTexelKHRMask:
     case SpvImageOperandsVolatileTexelKHRMask:
+    case SpvImageOperandsSignExtendMask:
+    case SpvImageOperandsZeroExtendMask:
       return true;
   }
   return false;
@@ -218,10 +220,12 @@
   const SpvOp opcode = inst->opcode();
   const size_t num_words = inst->words().size();
 
-  // NonPrivate and Volatile take no operand words.
+  // NonPrivate, Volatile, SignExtend, ZeroExtend take no operand words.
   const uint32_t mask_bits_having_operands =
       mask & ~uint32_t(SpvImageOperandsNonPrivateTexelKHRMask |
-                       SpvImageOperandsVolatileTexelKHRMask);
+                       SpvImageOperandsVolatileTexelKHRMask |
+                       SpvImageOperandsSignExtendMask |
+                       SpvImageOperandsZeroExtendMask);
   size_t expected_num_image_operand_words =
       spvtools::utils::CountSetBits(mask_bits_having_operands);
   if (mask & SpvImageOperandsGradMask) {
@@ -541,6 +545,32 @@
     if (auto error = ValidateMemoryScope(_, inst, visible_scope)) return error;
   }
 
+  if (mask & SpvImageOperandsSignExtendMask) {
+    // Checked elsewhere: SPIR-V 1.4 version or later.
+
+    // "The texel value is converted to the target value via sign extension.
+    // Only valid when the texel type is a scalar or vector of integer type."
+    //
+    // We don't have enough information to know what the texel type is.
+    // In OpenCL, knowledge is deferred until runtime: the image SampledType is
+    // void, and the Format is Unknown.
+    // In Vulkan, the texel type is only known in all cases by the pipeline
+    // setup.
+  }
+
+  if (mask & SpvImageOperandsZeroExtendMask) {
+    // Checked elsewhere: SPIR-V 1.4 version or later.
+
+    // "The texel value is converted to the target value via zero extension.
+    // Only valid when the texel type is a scalar or vector of integer type."
+    //
+    // We don't have enough information to know what the texel type is.
+    // In OpenCL, knowledge is deferred until runtime: the image SampledType is
+    // void, and the Format is Unknown.
+    // In Vulkan, the texel type is only known in all cases by the pipeline
+    // setup.
+  }
+
   return SPV_SUCCESS;
 }
 
@@ -748,6 +778,33 @@
   return SPV_SUCCESS;
 }
 
+bool IsAllowedSampledImageOperand(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpSampledImage:
+    case SpvOpImageSampleImplicitLod:
+    case SpvOpImageSampleExplicitLod:
+    case SpvOpImageSampleDrefImplicitLod:
+    case SpvOpImageSampleDrefExplicitLod:
+    case SpvOpImageSampleProjImplicitLod:
+    case SpvOpImageSampleProjExplicitLod:
+    case SpvOpImageSampleProjDrefImplicitLod:
+    case SpvOpImageSampleProjDrefExplicitLod:
+    case SpvOpImageGather:
+    case SpvOpImageDrefGather:
+    case SpvOpImage:
+    case SpvOpImageQueryLod:
+    case SpvOpImageSparseSampleImplicitLod:
+    case SpvOpImageSparseSampleExplicitLod:
+    case SpvOpImageSparseSampleDrefImplicitLod:
+    case SpvOpImageSparseSampleDrefExplicitLod:
+    case SpvOpImageSparseGather:
+    case SpvOpImageSparseDrefGather:
+      return true;
+    default:
+      return false;
+  }
+}
+
 spv_result_t ValidateSampledImage(ValidationState_t& _,
                                   const Instruction* inst) {
   if (_.GetIdOpcode(inst->type_id()) != SpvOpTypeSampledImage) {
@@ -800,10 +857,9 @@
   // to OpPhi instructions or OpSelect instructions, or any instructions other
   // than the image lookup and query instructions specified to take an operand
   // whose type is OpTypeSampledImage.
-  std::vector<uint32_t> consumers = _.getSampledImageConsumers(inst->id());
+  std::vector<Instruction*> consumers = _.getSampledImageConsumers(inst->id());
   if (!consumers.empty()) {
-    for (auto consumer_id : consumers) {
-      const auto consumer_instr = _.FindDef(consumer_id);
+    for (auto consumer_instr : consumers) {
       const auto consumer_opcode = consumer_instr->opcode();
       if (consumer_instr->block() != inst->block()) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
@@ -814,13 +870,9 @@
                << _.getIdName(inst->id())
                << "' has a consumer in a different basic "
                   "block. The consumer instruction <id> is '"
-               << _.getIdName(consumer_id) << "'.";
+               << _.getIdName(consumer_instr->id()) << "'.";
       }
-      // TODO: The following check is incomplete. We should also check that the
-      // Sampled Image is not used by instructions that should not take
-      // SampledImage as an argument. We could find the list of valid
-      // instructions by scanning for "Sampled Image" in the operand description
-      // field in the grammar file.
+
       if (consumer_opcode == SpvOpPhi || consumer_opcode == SpvOpSelect) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
                << "Result <id> from OpSampledImage instruction must not appear "
@@ -828,8 +880,20 @@
                   "operands of Op"
                << spvOpcodeString(static_cast<SpvOp>(consumer_opcode)) << "."
                << " Found result <id> '" << _.getIdName(inst->id())
-               << "' as an operand of <id> '" << _.getIdName(consumer_id)
-               << "'.";
+               << "' as an operand of <id> '"
+               << _.getIdName(consumer_instr->id()) << "'.";
+      }
+
+      if (!IsAllowedSampledImageOperand(consumer_opcode)) {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << "Result <id> from OpSampledImage instruction must not appear "
+                  "as operand for Op"
+               << spvOpcodeString(static_cast<SpvOp>(consumer_opcode))
+               << ", since it is not specificed as taking an "
+               << "OpTypeSampledImage."
+               << " Found result <id> '" << _.getIdName(inst->id())
+               << "' as an operand of <id> '"
+               << _.getIdName(consumer_instr->id()) << "'.";
       }
     }
   }
@@ -1328,11 +1392,13 @@
            << " components, but given only " << actual_coord_size;
   }
 
-  if (info.format == SpvImageFormatUnknown && info.dim != SpvDimSubpassData &&
-      !_.HasCapability(SpvCapabilityStorageImageReadWithoutFormat)) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "Capability StorageImageReadWithoutFormat is required to "
-           << "read storage image";
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    if (info.format == SpvImageFormatUnknown && info.dim != SpvDimSubpassData &&
+        !_.HasCapability(SpvCapabilityStorageImageReadWithoutFormat)) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Capability StorageImageReadWithoutFormat is required to "
+             << "read storage image";
+    }
   }
 
   if (inst->words().size() <= 5) return SPV_SUCCESS;
@@ -1405,12 +1471,14 @@
     }
   }
 
-  if (info.format == SpvImageFormatUnknown && info.dim != SpvDimSubpassData &&
-      !_.HasCapability(SpvCapabilityStorageImageWriteWithoutFormat)) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "Capability StorageImageWriteWithoutFormat is required to "
-              "write "
-           << "to storage image";
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    if (info.format == SpvImageFormatUnknown && info.dim != SpvDimSubpassData &&
+        !_.HasCapability(SpvCapabilityStorageImageWriteWithoutFormat)) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Capability StorageImageWriteWithoutFormat is required to "
+                "write "
+             << "to storage image";
+    }
   }
 
   if (inst->words().size() <= 4) return SPV_SUCCESS;
diff --git a/source/val/validate_instruction.cpp b/source/val/validate_instruction.cpp
index 17949d2..655cb02 100644
--- a/source/val/validate_instruction.cpp
+++ b/source/val/validate_instruction.cpp
@@ -14,10 +14,9 @@
 
 // Performs validation on instructions that appear inside of a SPIR-V block.
 
-#include "source/val/validate.h"
-
 #include <algorithm>
 #include <cassert>
+#include <iomanip>
 #include <sstream>
 #include <string>
 #include <vector>
@@ -35,6 +34,7 @@
 #include "source/spirv_validator_options.h"
 #include "source/util/string_utils.h"
 #include "source/val/function.h"
+#include "source/val/validate.h"
 #include "source/val/validation_state.h"
 
 namespace spvtools {
@@ -55,6 +55,18 @@
   return ss.str();
 }
 
+bool IsValidWebGPUStorageClass(SpvStorageClass storage_class) {
+  return storage_class == SpvStorageClassUniformConstant ||
+         storage_class == SpvStorageClassUniform ||
+         storage_class == SpvStorageClassStorageBuffer ||
+         storage_class == SpvStorageClassInput ||
+         storage_class == SpvStorageClassOutput ||
+         storage_class == SpvStorageClassImage ||
+         storage_class == SpvStorageClassWorkgroup ||
+         storage_class == SpvStorageClassPrivate ||
+         storage_class == SpvStorageClassFunction;
+}
+
 // Returns capabilities that enable an opcode.  An empty result is interpreted
 // as no prohibition of use of the opcode.  If the result is non-empty, then
 // the opcode may only be used if at least one of the capabilities is specified
@@ -86,21 +98,59 @@
   return CapabilitySet();
 }
 
+// Returns SPV_SUCCESS if, for the given operand, the target environment
+// satsifies minimum version requirements, or if the module declares an
+// enabling extension for the operand.  Otherwise emit a diagnostic and
+// return an error code.
+spv_result_t OperandVersionExtensionCheck(
+    ValidationState_t& _, const Instruction* inst, size_t which_operand,
+    const spv_operand_desc_t& operand_desc, uint32_t word) {
+  const uint32_t module_version = _.version();
+  const uint32_t operand_min_version = operand_desc.minVersion;
+  const bool reserved = operand_min_version == 0xffffffffu;
+  const bool version_satisfied =
+      !reserved && (operand_min_version <= module_version);
+
+  if (version_satisfied) {
+    return SPV_SUCCESS;
+  }
+  if (!reserved && operand_desc.numExtensions == 0) {
+    return _.diag(SPV_ERROR_WRONG_VERSION, inst)
+           << spvtools::utils::CardinalToOrdinal(which_operand)
+           << " operand of " << spvOpcodeString(inst->opcode()) << ": operand "
+           << operand_desc.name << "(" << word << ") requires SPIR-V version "
+           << SPV_SPIRV_VERSION_MAJOR_PART(operand_min_version) << "."
+           << SPV_SPIRV_VERSION_MINOR_PART(operand_min_version) << " or later";
+  } else {
+    ExtensionSet required_extensions(operand_desc.numExtensions,
+                                     operand_desc.extensions);
+    if (!_.HasAnyOfExtensions(required_extensions)) {
+      return _.diag(SPV_ERROR_MISSING_EXTENSION, inst)
+             << spvtools::utils::CardinalToOrdinal(which_operand)
+             << " operand of " << spvOpcodeString(inst->opcode())
+             << ": operand " << operand_desc.name << "(" << word
+             << ") requires one of these extensions: "
+             << ExtensionSetToString(required_extensions);
+    }
+  }
+  return SPV_SUCCESS;
+}
+
 // Returns SPV_SUCCESS if the given operand is enabled by capabilities declared
 // in the module.  Otherwise issues an error message and returns
 // SPV_ERROR_INVALID_CAPABILITY.
 spv_result_t CheckRequiredCapabilities(ValidationState_t& state,
                                        const Instruction* inst,
                                        size_t which_operand,
-                                       spv_operand_type_t type,
-                                       uint32_t operand) {
+                                       const spv_parsed_operand_t& operand,
+                                       uint32_t word) {
   // Mere mention of PointSize, ClipDistance, or CullDistance in a Builtin
   // decoration does not require the associated capability.  The use of such
   // a variable value should trigger the capability requirement, but that's
   // not implemented yet.  This rule is independent of target environment.
   // See https://github.com/KhronosGroup/SPIRV-Tools/issues/365
-  if (type == SPV_OPERAND_TYPE_BUILT_IN) {
-    switch (operand) {
+  if (operand.type == SPV_OPERAND_TYPE_BUILT_IN) {
+    switch (word) {
       case SpvBuiltInPointSize:
       case SpvBuiltInClipDistance:
       case SpvBuiltInCullDistance:
@@ -108,14 +158,14 @@
       default:
         break;
     }
-  } else if (type == SPV_OPERAND_TYPE_FP_ROUNDING_MODE) {
+  } else if (operand.type == SPV_OPERAND_TYPE_FP_ROUNDING_MODE) {
     // Allow all FP rounding modes if requested
     if (state.features().free_fp_rounding_mode) {
       return SPV_SUCCESS;
     }
-  } else if (type == SPV_OPERAND_TYPE_GROUP_OPERATION &&
+  } else if (operand.type == SPV_OPERAND_TYPE_GROUP_OPERATION &&
              state.features().group_ops_reduce_and_scans &&
-             (operand <= uint32_t(SpvGroupOperationExclusiveScan))) {
+             (word <= uint32_t(SpvGroupOperationExclusiveScan))) {
     // Allow certain group operations if requested.
     return SPV_SUCCESS;
   }
@@ -123,10 +173,10 @@
   CapabilitySet enabling_capabilities;
   spv_operand_desc operand_desc = nullptr;
   const auto lookup_result =
-      state.grammar().lookupOperand(type, operand, &operand_desc);
+      state.grammar().lookupOperand(operand.type, word, &operand_desc);
   if (lookup_result == SPV_SUCCESS) {
     // Allow FPRoundingMode decoration if requested.
-    if (type == SPV_OPERAND_TYPE_DECORATION &&
+    if (operand.type == SPV_OPERAND_TYPE_DECORATION &&
         operand_desc->value == SpvDecorationFPRoundingMode) {
       if (state.features().free_fp_rounding_mode) return SPV_SUCCESS;
 
@@ -149,29 +199,13 @@
              << " requires one of these capabilities: "
              << ToString(enabling_capabilities, state.grammar());
     }
+    return OperandVersionExtensionCheck(state, inst, which_operand,
+                                        *operand_desc, word);
   }
 
   return SPV_SUCCESS;
 }
 
-// Returns operand's required extensions.
-ExtensionSet RequiredExtensions(const ValidationState_t& state,
-                                spv_operand_type_t type, uint32_t operand) {
-  spv_operand_desc operand_desc;
-  if (state.grammar().lookupOperand(type, operand, &operand_desc) ==
-      SPV_SUCCESS) {
-    assert(operand_desc);
-    // If this operand is incorporated into core SPIR-V before or in the current
-    // target environment, we don't require extensions anymore.
-    if (spvVersionForTargetEnv(state.grammar().target_env()) >=
-        operand_desc->minVersion)
-      return {};
-    return {operand_desc->numExtensions, operand_desc->extensions};
-  }
-
-  return {};
-}
-
 // Returns SPV_ERROR_INVALID_BINARY and emits a diagnostic if the instruction
 // is explicitly reserved in the SPIR-V core spec.  Otherwise return
 // SPV_SUCCESS.
@@ -232,7 +266,7 @@
       for (uint32_t mask_bit = 0x80000000; mask_bit; mask_bit >>= 1) {
         if (word & mask_bit) {
           spv_result_t status =
-              CheckRequiredCapabilities(_, inst, i + 1, operand.type, mask_bit);
+              CheckRequiredCapabilities(_, inst, i + 1, operand, mask_bit);
           if (status != SPV_SUCCESS) return status;
         }
       }
@@ -243,34 +277,13 @@
     } else {
       // Check the operand word as a whole.
       spv_result_t status =
-          CheckRequiredCapabilities(_, inst, i + 1, operand.type, word);
+          CheckRequiredCapabilities(_, inst, i + 1, operand, word);
       if (status != SPV_SUCCESS) return status;
     }
   }
   return SPV_SUCCESS;
 }
 
-// Checks that all extensions required by the given instruction's operands were
-// declared in the module.
-spv_result_t ExtensionCheck(ValidationState_t& _, const Instruction* inst) {
-  const SpvOp opcode = inst->opcode();
-  for (size_t operand_index = 0; operand_index < inst->operands().size();
-       ++operand_index) {
-    const auto& operand = inst->operand(operand_index);
-    const uint32_t word = inst->word(operand.offset);
-    const ExtensionSet required_extensions =
-        RequiredExtensions(_, operand.type, word);
-    if (!_.HasAnyOfExtensions(required_extensions)) {
-      return _.diag(SPV_ERROR_MISSING_EXTENSION, inst)
-             << spvtools::utils::CardinalToOrdinal(operand_index + 1)
-             << " operand of " << spvOpcodeString(opcode) << ": operand "
-             << word << " requires one of these extensions: "
-             << ExtensionSetToString(required_extensions);
-    }
-  }
-  return SPV_SUCCESS;
-}
-
 // Checks that the instruction can be used in this target environment's base
 // version. Assumes that CapabilityCheck has checked direct capability
 // dependencies for the opcode.
@@ -289,24 +302,27 @@
     return SPV_SUCCESS;
   }
 
+  const auto module_version = _.version();
+
   ExtensionSet exts(inst_desc->numExtensions, inst_desc->extensions);
   if (exts.IsEmpty()) {
-    // If no extensions can enable this instruction, then emit error messages
-    // only concerning core SPIR-V versions if errors happen.
+    // If no extensions can enable this instruction, then emit error
+    // messages only concerning core SPIR-V versions if errors happen.
     if (min_version == ~0u) {
       return _.diag(SPV_ERROR_WRONG_VERSION, inst)
              << spvOpcodeString(opcode) << " is reserved for future use.";
     }
 
-    if (spvVersionForTargetEnv(_.grammar().target_env()) < min_version) {
+    if (module_version < min_version) {
       return _.diag(SPV_ERROR_WRONG_VERSION, inst)
              << spvOpcodeString(opcode) << " requires "
              << spvTargetEnvDescription(
                     static_cast<spv_target_env>(min_version))
              << " at minimum.";
     }
-  // Otherwise, we only error out when no enabling extensions are registered.
   } else if (!_.HasAnyOfExtensions(exts)) {
+    // Otherwise, we only error out when no enabling extensions are
+    // registered.
     if (min_version == ~0u) {
       return _.diag(SPV_ERROR_MISSING_EXTENSION, inst)
              << spvOpcodeString(opcode)
@@ -314,11 +330,11 @@
              << ExtensionSetToString(exts);
     }
 
-    if (static_cast<uint32_t>(_.grammar().target_env()) < min_version) {
+    if (module_version < min_version) {
       return _.diag(SPV_ERROR_WRONG_VERSION, inst)
-             << spvOpcodeString(opcode) << " requires "
-             << spvTargetEnvDescription(
-                    static_cast<spv_target_env>(min_version))
+             << spvOpcodeString(opcode) << " requires SPIR-V version "
+             << SPV_SPIRV_VERSION_MAJOR_PART(min_version) << "."
+             << SPV_SPIRV_VERSION_MINOR_PART(min_version)
              << " at minimum or one of the following extensions: "
              << ExtensionSetToString(exts);
     }
@@ -355,11 +371,10 @@
 
   // Section 2.17 of SPIRV Spec specifies that the "Structure Nesting Depth"
   // must be less than or equal to 255.
-  // This is interpreted as structures including other structures as members.
-  // The code does not follow pointers or look into arrays to see if we reach a
-  // structure downstream.
-  // The nesting depth of a struct is 1+(largest depth of any member).
-  // Scalars are at depth 0.
+  // This is interpreted as structures including other structures as
+  // members. The code does not follow pointers or look into arrays to see
+  // if we reach a structure downstream. The nesting depth of a struct is
+  // 1+(largest depth of any member). Scalars are at depth 0.
   uint32_t max_member_depth = 0;
   // Struct members start at word 2 of OpTypeStruct instruction.
   for (size_t word_i = 2; word_i < inst->words().size(); ++word_i) {
@@ -382,8 +397,8 @@
   return SPV_SUCCESS;
 }
 
-// Checks that the number of (literal, label) pairs in OpSwitch is within the
-// limit.
+// Checks that the number of (literal, label) pairs in OpSwitch is within
+// the limit.
 spv_result_t LimitCheckSwitch(ValidationState_t& _, const Instruction* inst) {
   if (SpvOpSwitch == inst->opcode()) {
     // The instruction syntax is as follows:
@@ -402,7 +417,8 @@
   return SPV_SUCCESS;
 }
 
-// Ensure the number of variables of the given class does not exceed the limit.
+// Ensure the number of variables of the given class does not exceed the
+// limit.
 spv_result_t LimitCheckNumVars(ValidationState_t& _, const uint32_t var_id,
                                const SpvStorageClass storage_class) {
   if (SpvStorageClassFunction == storage_class) {
@@ -460,7 +476,8 @@
     if (_.memory_model() != SpvMemoryModelVulkanKHR &&
         _.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "VulkanMemoryModelKHR capability must only be specified if the "
+             << "VulkanMemoryModelKHR capability must only be specified if "
+                "the "
                 "VulkanKHR memory model is used.";
     }
 
@@ -483,6 +500,15 @@
     if (auto error = LimitCheckNumVars(_, inst->id(), storage_class)) {
       return error;
     }
+
+    if (spvIsWebGPUEnv(_.context()->target_env) &&
+        !IsValidWebGPUStorageClass(storage_class)) {
+      return _.diag(SPV_ERROR_INVALID_BINARY, inst)
+             << "For WebGPU, OpVariable storage class must be one of "
+                "UniformConstant, Uniform, StorageBuffer, Input, Output, "
+                "Image, Workgroup, Private, Function for WebGPU";
+    }
+
     if (storage_class == SpvStorageClassGeneric)
       return _.diag(SPV_ERROR_INVALID_BINARY, inst)
              << "OpVariable storage class cannot be Generic";
@@ -506,6 +532,15 @@
                   "outside of a function";
       }
     }
+  } else if (opcode == SpvOpTypePointer) {
+    const auto storage_class = inst->GetOperandAs<SpvStorageClass>(1);
+    if (spvIsWebGPUEnv(_.context()->target_env) &&
+        !IsValidWebGPUStorageClass(storage_class)) {
+      return _.diag(SPV_ERROR_INVALID_BINARY, inst)
+             << "For WebGPU, OpTypePointer storage class must be one of "
+                "UniformConstant, Uniform, StorageBuffer, Input, Output, "
+                "Image, Workgroup, Private, Function";
+    }
   }
 
   // SPIR-V Spec 2.16.3: Validation Rules for Kernel Capabilities: The
@@ -518,7 +553,6 @@
               "capability is used.";
   }
 
-  if (auto error = ExtensionCheck(_, inst)) return error;
   if (auto error = ReservedCheck(_, inst)) return error;
   if (auto error = EnvironmentCheck(_, inst)) return error;
   if (auto error = CapabilityCheck(_, inst)) return error;
diff --git a/source/val/validate_interfaces.cpp b/source/val/validate_interfaces.cpp
index fffc6da..c85b673 100644
--- a/source/val/validate_interfaces.cpp
+++ b/source/val/validate_interfaces.cpp
@@ -12,14 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "source/val/validate.h"
-
 #include <algorithm>
 #include <vector>
 
 #include "source/diagnostic.h"
+#include "source/spirv_constant.h"
+#include "source/spirv_target_env.h"
 #include "source/val/function.h"
 #include "source/val/instruction.h"
+#include "source/val/validate.h"
 #include "source/val/validation_state.h"
 
 namespace spvtools {
@@ -27,10 +28,16 @@
 namespace {
 
 // Returns true if \c inst is an input or output variable.
-bool is_interface_variable(const Instruction* inst) {
-  return inst->opcode() == SpvOpVariable &&
-         (inst->word(3u) == SpvStorageClassInput ||
-          inst->word(3u) == SpvStorageClassOutput);
+bool is_interface_variable(const Instruction* inst, bool is_spv_1_4) {
+  if (is_spv_1_4) {
+    // Starting in SPIR-V 1.4, all global variables are interface variables.
+    return inst->opcode() == SpvOpVariable &&
+           inst->word(3u) != SpvStorageClassFunction;
+  } else {
+    return inst->opcode() == SpvOpVariable &&
+           (inst->word(3u) == SpvStorageClassInput ||
+            inst->word(3u) == SpvStorageClassOutput);
+  }
 }
 
 // Checks that \c var is listed as an interface in all the entry points that use
@@ -85,9 +92,8 @@
       }
       if (!found) {
         return _.diag(SPV_ERROR_INVALID_ID, var)
-               << (var->word(3u) == SpvStorageClassInput ? "Input" : "Output")
-               << " variable id <" << var->id() << "> is used by entry point '"
-               << desc.name << "' id <" << id
+               << "Interface variable id <" << var->id()
+               << "> is used by entry point '" << desc.name << "' id <" << id
                << ">, but is not listed as an interface";
       }
     }
@@ -99,8 +105,9 @@
 }  // namespace
 
 spv_result_t ValidateInterfaces(ValidationState_t& _) {
+  bool is_spv_1_4 = _.version() >= SPV_SPIRV_VERSION_WORD(1, 4);
   for (auto& inst : _.ordered_instructions()) {
-    if (is_interface_variable(&inst)) {
+    if (is_interface_variable(&inst, is_spv_1_4)) {
       if (auto error = check_interface_variable(_, &inst)) {
         return error;
       }
diff --git a/source/val/validate_logicals.cpp b/source/val/validate_logicals.cpp
index 9c637c4..5886dbf 100644
--- a/source/val/validate_logicals.cpp
+++ b/source/val/validate_logicals.cpp
@@ -151,10 +151,19 @@
         const Instruction* type_inst = _.FindDef(result_type);
         assert(type_inst);
 
+        const auto composites = _.features().select_between_composites;
+        auto fail = [&_, composites, inst, opcode]() -> spv_result_t {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Expected scalar or "
+                 << (composites ? "composite" : "vector")
+                 << " type as Result Type: " << spvOpcodeString(opcode);
+        };
+
         const SpvOp type_opcode = type_inst->opcode();
         switch (type_opcode) {
           case SpvOpTypePointer: {
-            if (!_.features().variable_pointers &&
+            if (_.addressing_model() == SpvAddressingModelLogical &&
+                !_.features().variable_pointers &&
                 !_.features().variable_pointers_storage_buffer)
               return _.diag(SPV_ERROR_INVALID_DATA, inst)
                      << "Using pointers with OpSelect requires capability "
@@ -173,35 +182,48 @@
             break;
           }
 
-          default: {
+          // Not RuntimeArray because of other rules.
+          case SpvOpTypeArray:
+          case SpvOpTypeMatrix:
+          case SpvOpTypeStruct: {
+            if (!composites) return fail();
+            break;
+          };
+
+          default:
+            return fail();
+        }
+
+        const uint32_t condition_type = _.GetOperandTypeId(inst, 2);
+        const uint32_t left_type = _.GetOperandTypeId(inst, 3);
+        const uint32_t right_type = _.GetOperandTypeId(inst, 4);
+
+        if (!condition_type || (!_.IsBoolScalarType(condition_type) &&
+                                !_.IsBoolVectorType(condition_type)))
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Expected bool scalar or vector type as condition: "
+                 << spvOpcodeString(opcode);
+
+        if (_.GetDimension(condition_type) != dimension) {
+          // If the condition is a vector type, then the result must also be a
+          // vector with matching dimensions. In SPIR-V 1.4, a scalar condition
+          // can be used to select between vector types. |composites| is a
+          // proxy for SPIR-V 1.4 functionality.
+          if (!composites || _.IsBoolVectorType(condition_type)) {
             return _.diag(SPV_ERROR_INVALID_DATA, inst)
-                   << "Expected scalar or vector type as Result Type: "
+                   << "Expected vector sizes of Result Type and the condition "
+                      "to be equal: "
                    << spvOpcodeString(opcode);
           }
         }
+
+        if (result_type != left_type || result_type != right_type)
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Expected both objects to be of Result Type: "
+                 << spvOpcodeString(opcode);
+
+        break;
       }
-
-      const uint32_t condition_type = _.GetOperandTypeId(inst, 2);
-      const uint32_t left_type = _.GetOperandTypeId(inst, 3);
-      const uint32_t right_type = _.GetOperandTypeId(inst, 4);
-
-      if (!condition_type || (!_.IsBoolScalarType(condition_type) &&
-                              !_.IsBoolVectorType(condition_type)))
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Expected bool scalar or vector type as condition: "
-               << spvOpcodeString(opcode);
-
-      if (_.GetDimension(condition_type) != dimension)
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Expected vector sizes of Result Type and the condition to be"
-               << " equal: " << spvOpcodeString(opcode);
-
-      if (result_type != left_type || result_type != right_type)
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Expected both objects to be of Result Type: "
-               << spvOpcodeString(opcode);
-
-      break;
     }
 
     case SpvOpIEqual:
diff --git a/source/val/validate_memory.cpp b/source/val/validate_memory.cpp
index 6e7c9e8..e141309 100644
--- a/source/val/validate_memory.cpp
+++ b/source/val/validate_memory.cpp
@@ -197,17 +197,49 @@
   return false;
 }
 
+bool ContainsCooperativeMatrix(ValidationState_t& _,
+                               const Instruction* storage) {
+  const size_t elem_type_index = 1;
+  uint32_t elem_type_id;
+  Instruction* elem_type;
+
+  switch (storage->opcode()) {
+    case SpvOpTypeCooperativeMatrixNV:
+      return true;
+    case SpvOpTypeArray:
+    case SpvOpTypeRuntimeArray:
+      elem_type_id = storage->GetOperandAs<uint32_t>(elem_type_index);
+      elem_type = _.FindDef(elem_type_id);
+      return ContainsCooperativeMatrix(_, elem_type);
+    case SpvOpTypeStruct:
+      for (size_t member_type_index = 1;
+           member_type_index < storage->operands().size();
+           ++member_type_index) {
+        auto member_type_id =
+            storage->GetOperandAs<uint32_t>(member_type_index);
+        auto member_type = _.FindDef(member_type_id);
+        if (ContainsCooperativeMatrix(_, member_type)) return true;
+      }
+      break;
+    default:
+      break;
+  }
+  return false;
+}
+
 std::pair<SpvStorageClass, SpvStorageClass> GetStorageClass(
     ValidationState_t& _, const Instruction* inst) {
   SpvStorageClass dst_sc = SpvStorageClassMax;
   SpvStorageClass src_sc = SpvStorageClassMax;
   switch (inst->opcode()) {
+    case SpvOpCooperativeMatrixLoadNV:
     case SpvOpLoad: {
       auto load_pointer = _.FindDef(inst->GetOperandAs<uint32_t>(2));
       auto load_pointer_type = _.FindDef(load_pointer->type_id());
       dst_sc = load_pointer_type->GetOperandAs<SpvStorageClass>(1);
       break;
     }
+    case SpvOpCooperativeMatrixStoreNV:
     case SpvOpStore: {
       auto store_pointer = _.FindDef(inst->GetOperandAs<uint32_t>(0));
       auto store_pointer_type = _.FindDef(store_pointer->type_id());
@@ -231,55 +263,70 @@
   return std::make_pair(dst_sc, src_sc);
 }
 
-// This function is only called for OpLoad, OpStore, OpCopyMemory and
-// OpCopyMemorySized.
-uint32_t GetMakeAvailableScope(const Instruction* inst, uint32_t mask) {
-  uint32_t offset = 1;
-  if (mask & SpvMemoryAccessAlignedMask) ++offset;
-
-  uint32_t scope_id = 0;
-  switch (inst->opcode()) {
-    case SpvOpLoad:
-    case SpvOpCopyMemorySized:
-      return inst->GetOperandAs<uint32_t>(3 + offset);
-    case SpvOpStore:
-    case SpvOpCopyMemory:
-      return inst->GetOperandAs<uint32_t>(2 + offset);
-    default:
-      assert(false && "unexpected opcode");
-      break;
-  }
-
-  return scope_id;
+// Returns the number of instruction words taken up by a memory access
+// argument and its implied operands.
+int MemoryAccessNumWords(uint32_t mask) {
+  int result = 1;  // Count the mask
+  if (mask & SpvMemoryAccessAlignedMask) ++result;
+  if (mask & SpvMemoryAccessMakePointerAvailableKHRMask) ++result;
+  if (mask & SpvMemoryAccessMakePointerVisibleKHRMask) ++result;
+  return result;
 }
 
+// Returns the scope ID operand for MakeAvailable memory access with mask
+// at the given operand index.
 // This function is only called for OpLoad, OpStore, OpCopyMemory and
-// OpCopyMemorySized.
-uint32_t GetMakeVisibleScope(const Instruction* inst, uint32_t mask) {
-  uint32_t offset = 1;
-  if (mask & SpvMemoryAccessAlignedMask) ++offset;
-  if (mask & SpvMemoryAccessMakePointerAvailableKHRMask) ++offset;
+// OpCopyMemorySized, OpCooperativeMatrixLoadNV, and
+// OpCooperativeMatrixStoreNV.
+uint32_t GetMakeAvailableScope(const Instruction* inst, uint32_t mask,
+                               uint32_t mask_index) {
+  assert(mask & SpvMemoryAccessMakePointerAvailableKHRMask);
+  uint32_t this_bit = uint32_t(SpvMemoryAccessMakePointerAvailableKHRMask);
+  uint32_t index =
+      mask_index - 1 + MemoryAccessNumWords(mask & (this_bit | (this_bit - 1)));
+  return inst->GetOperandAs<uint32_t>(index);
+}
 
-  uint32_t scope_id = 0;
-  switch (inst->opcode()) {
-    case SpvOpLoad:
-    case SpvOpCopyMemorySized:
-      return inst->GetOperandAs<uint32_t>(3 + offset);
-    case SpvOpStore:
-    case SpvOpCopyMemory:
-      return inst->GetOperandAs<uint32_t>(2 + offset);
-    default:
-      assert(false && "unexpected opcode");
-      break;
+// This function is only called for OpLoad, OpStore, OpCopyMemory,
+// OpCopyMemorySized, OpCooperativeMatrixLoadNV, and
+// OpCooperativeMatrixStoreNV.
+uint32_t GetMakeVisibleScope(const Instruction* inst, uint32_t mask,
+                             uint32_t mask_index) {
+  assert(mask & SpvMemoryAccessMakePointerVisibleKHRMask);
+  uint32_t this_bit = uint32_t(SpvMemoryAccessMakePointerVisibleKHRMask);
+  uint32_t index =
+      mask_index - 1 + MemoryAccessNumWords(mask & (this_bit | (this_bit - 1)));
+  return inst->GetOperandAs<uint32_t>(index);
+}
+
+bool DoesStructContainRTA(const ValidationState_t& _, const Instruction* inst) {
+  for (size_t member_index = 1; member_index < inst->operands().size();
+       ++member_index) {
+    const auto member_id = inst->GetOperandAs<uint32_t>(member_index);
+    const auto member_type = _.FindDef(member_id);
+    if (member_type->opcode() == SpvOpTypeRuntimeArray) return true;
   }
-
-  return scope_id;
+  return false;
 }
 
 spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst,
-                               uint32_t mask) {
+                               uint32_t index) {
+  SpvStorageClass dst_sc, src_sc;
+  std::tie(dst_sc, src_sc) = GetStorageClass(_, inst);
+  if (inst->operands().size() <= index) {
+    if (src_sc == SpvStorageClassPhysicalStorageBufferEXT ||
+        dst_sc == SpvStorageClassPhysicalStorageBufferEXT) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "Memory accesses with PhysicalStorageBufferEXT must use "
+                "Aligned.";
+    }
+    return SPV_SUCCESS;
+  }
+
+  const uint32_t mask = inst->GetOperandAs<uint32_t>(index);
   if (mask & SpvMemoryAccessMakePointerAvailableKHRMask) {
-    if (inst->opcode() == SpvOpLoad) {
+    if (inst->opcode() == SpvOpLoad ||
+        inst->opcode() == SpvOpCooperativeMatrixLoadNV) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
              << "MakePointerAvailableKHR cannot be used with OpLoad.";
     }
@@ -291,13 +338,14 @@
     }
 
     // Check the associated scope for MakeAvailableKHR.
-    const auto available_scope = GetMakeAvailableScope(inst, mask);
+    const auto available_scope = GetMakeAvailableScope(inst, mask, index);
     if (auto error = ValidateMemoryScope(_, inst, available_scope))
       return error;
   }
 
   if (mask & SpvMemoryAccessMakePointerVisibleKHRMask) {
-    if (inst->opcode() == SpvOpStore) {
+    if (inst->opcode() == SpvOpStore ||
+        inst->opcode() == SpvOpCooperativeMatrixStoreNV) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
              << "MakePointerVisibleKHR cannot be used with OpStore.";
     }
@@ -305,36 +353,45 @@
     if (!(mask & SpvMemoryAccessNonPrivatePointerKHRMask)) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
              << "NonPrivatePointerKHR must be specified if "
-                "MakePointerVisibleKHR is specified.";
+             << "MakePointerVisibleKHR is specified.";
     }
 
     // Check the associated scope for MakeVisibleKHR.
-    const auto visible_scope = GetMakeVisibleScope(inst, mask);
+    const auto visible_scope = GetMakeVisibleScope(inst, mask, index);
     if (auto error = ValidateMemoryScope(_, inst, visible_scope)) return error;
   }
 
   if (mask & SpvMemoryAccessNonPrivatePointerKHRMask) {
-    SpvStorageClass dst_sc, src_sc;
-    std::tie(dst_sc, src_sc) = GetStorageClass(_, inst);
     if (dst_sc != SpvStorageClassUniform &&
         dst_sc != SpvStorageClassWorkgroup &&
         dst_sc != SpvStorageClassCrossWorkgroup &&
         dst_sc != SpvStorageClassGeneric && dst_sc != SpvStorageClassImage &&
-        dst_sc != SpvStorageClassStorageBuffer) {
+        dst_sc != SpvStorageClassStorageBuffer &&
+        dst_sc != SpvStorageClassPhysicalStorageBufferEXT) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
              << "NonPrivatePointerKHR requires a pointer in Uniform, "
-                "Workgroup, CrossWorkgroup, Generic, Image or StorageBuffer "
-                "storage classes.";
+             << "Workgroup, CrossWorkgroup, Generic, Image or StorageBuffer "
+             << "storage classes.";
     }
     if (src_sc != SpvStorageClassMax && src_sc != SpvStorageClassUniform &&
         src_sc != SpvStorageClassWorkgroup &&
         src_sc != SpvStorageClassCrossWorkgroup &&
         src_sc != SpvStorageClassGeneric && src_sc != SpvStorageClassImage &&
-        src_sc != SpvStorageClassStorageBuffer) {
+        src_sc != SpvStorageClassStorageBuffer &&
+        src_sc != SpvStorageClassPhysicalStorageBufferEXT) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
              << "NonPrivatePointerKHR requires a pointer in Uniform, "
-                "Workgroup, CrossWorkgroup, Generic, Image or StorageBuffer "
-                "storage classes.";
+             << "Workgroup, CrossWorkgroup, Generic, Image or StorageBuffer "
+             << "storage classes.";
+    }
+  }
+
+  if (!(mask & SpvMemoryAccessAlignedMask)) {
+    if (src_sc == SpvStorageClassPhysicalStorageBufferEXT ||
+        dst_sc == SpvStorageClassPhysicalStorageBufferEXT) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "Memory accesses with PhysicalStorageBufferEXT must use "
+                "Aligned.";
     }
   }
 
@@ -372,7 +429,12 @@
   if (storage_class != SpvStorageClassWorkgroup &&
       storage_class != SpvStorageClassCrossWorkgroup &&
       storage_class != SpvStorageClassPrivate &&
-      storage_class != SpvStorageClassFunction) {
+      storage_class != SpvStorageClassFunction &&
+      storage_class != SpvStorageClassRayPayloadNV &&
+      storage_class != SpvStorageClassIncomingRayPayloadNV &&
+      storage_class != SpvStorageClassHitAttributeNV &&
+      storage_class != SpvStorageClassCallableDataNV &&
+      storage_class != SpvStorageClassIncomingCallableDataNV) {
     const auto storage_index = 2;
     const auto storage_id = result_type->GetOperandAs<uint32_t>(storage_index);
     const auto storage = _.FindDef(storage_id);
@@ -409,21 +471,21 @@
   }
 
   // Variable pointer related restrictions.
-  auto pointee = _.FindDef(result_type->word(3));
+  const auto pointee = _.FindDef(result_type->word(3));
   if (_.addressing_model() == SpvAddressingModelLogical &&
       !_.options()->relax_logical_pointer) {
     // VariablePointersStorageBuffer is implied by VariablePointers.
     if (pointee->opcode() == SpvOpTypePointer) {
       if (!_.HasCapability(SpvCapabilityVariablePointersStorageBuffer)) {
-        return _.diag(SPV_ERROR_INVALID_ID, inst) << "In Logical addressing, "
-                                                     "variables may not "
-                                                     "allocate a pointer type";
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << "In Logical addressing, variables may not allocate a pointer "
+               << "type";
       } else if (storage_class != SpvStorageClassFunction &&
                  storage_class != SpvStorageClassPrivate) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
                << "In Logical addressing with variable pointers, variables "
-                  "that allocate pointers must be in Function or Private "
-                  "storage classes";
+               << "that allocate pointers must be in Function or Private "
+               << "storage classes";
       }
     }
   }
@@ -466,10 +528,9 @@
                << "' has illegal type.\n"
                << "From Vulkan spec, section 14.5.2:\n"
                << "Variables identified with the Uniform storage class are "
-                  "used "
-               << "to access transparent buffer backed resources. Such "
-                  "variables "
-               << "must be typed as OpTypeStruct, or an array of this type";
+               << "used to access transparent buffer backed resources. Such "
+               << "variables must be typed as OpTypeStruct, or an array of "
+               << "this type";
       }
     }
   }
@@ -479,27 +540,172 @@
   if (inst->operands().size() > 3 && storage_class != SpvStorageClassOutput &&
       storage_class != SpvStorageClassPrivate &&
       storage_class != SpvStorageClassFunction) {
-    if (spvIsVulkanEnv(_.context()->target_env)) {
+    if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
              << "OpVariable, <id> '" << _.getIdName(inst->id())
              << "', has a disallowed initializer & storage class "
              << "combination.\n"
-             << "From Vulkan spec, Appendix A:\n"
+             << "From " << spvLogStringForEnv(_.context()->target_env)
+             << " spec:\n"
              << "Variable declarations that include initializers must have "
              << "one of the following storage classes: Output, Private, or "
              << "Function";
     }
+  }
+
+  // WebGPU: All variables with storage class Output, Private, or Function MUST
+  // have an initializer.
+  if (spvIsWebGPUEnv(_.context()->target_env) && inst->operands().size() <= 3 &&
+      (storage_class == SpvStorageClassOutput ||
+       storage_class == SpvStorageClassPrivate ||
+       storage_class == SpvStorageClassFunction)) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "OpVariable, <id> '" << _.getIdName(inst->id())
+           << "', must have an initializer.\n"
+           << "From WebGPU execution environment spec:\n"
+           << "All variables in the following storage classes must have an "
+           << "initializer: Output, Private, or Function";
+  }
+
+  if (storage_class == SpvStorageClassPhysicalStorageBufferEXT) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "PhysicalStorageBufferEXT must not be used with OpVariable.";
+  }
+
+  auto pointee_base = pointee;
+  while (pointee_base->opcode() == SpvOpTypeArray) {
+    pointee_base = _.FindDef(pointee_base->GetOperandAs<uint32_t>(1u));
+  }
+  if (pointee_base->opcode() == SpvOpTypePointer) {
+    if (pointee_base->GetOperandAs<uint32_t>(1u) ==
+        SpvStorageClassPhysicalStorageBufferEXT) {
+      // check for AliasedPointerEXT/RestrictPointerEXT
+      bool foundAliased =
+          _.HasDecoration(inst->id(), SpvDecorationAliasedPointerEXT);
+      bool foundRestrict =
+          _.HasDecoration(inst->id(), SpvDecorationRestrictPointerEXT);
+      if (!foundAliased && !foundRestrict) {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << "OpVariable " << inst->id()
+               << ": expected AliasedPointerEXT or RestrictPointerEXT for "
+               << "PhysicalStorageBufferEXT pointer.";
+      }
+      if (foundAliased && foundRestrict) {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << "OpVariable " << inst->id()
+               << ": can't specify both AliasedPointerEXT and "
+               << "RestrictPointerEXT for PhysicalStorageBufferEXT pointer.";
+      }
+    }
+  }
+
+  // Vulkan specific validation rules for OpTypeRuntimeArray
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    const auto type_index = 2;
+    const auto value_id = result_type->GetOperandAs<uint32_t>(type_index);
+    auto value_type = _.FindDef(value_id);
+    // OpTypeRuntimeArray should only ever be in a container like OpTypeStruct,
+    // so should never appear as a bare variable.
+    // Unless the module has the RuntimeDescriptorArrayEXT capability.
+    if (value_type && value_type->opcode() == SpvOpTypeRuntimeArray) {
+      if (!_.HasCapability(SpvCapabilityRuntimeDescriptorArrayEXT)) {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << "OpVariable, <id> '" << _.getIdName(inst->id())
+               << "', is attempting to create memory for an illegal type, "
+               << "OpTypeRuntimeArray.\nFor Vulkan OpTypeRuntimeArray can only "
+               << "appear as the final member of an OpTypeStruct, thus cannot "
+               << "be instantiated via OpVariable";
+      } else {
+        // A bare variable OpTypeRuntimeArray is allowed in this context, but
+        // still need to check the storage class.
+        if (storage_class != SpvStorageClassStorageBuffer &&
+            storage_class != SpvStorageClassUniform &&
+            storage_class != SpvStorageClassUniformConstant) {
+          return _.diag(SPV_ERROR_INVALID_ID, inst)
+                 << "For Vulkan with RuntimeDescriptorArrayEXT, a variable "
+                 << "containing OpTypeRuntimeArray must have storage class of "
+                 << "StorageBuffer, Uniform, or UniformConstant.";
+        }
+      }
+    }
 
-    if (spvIsWebGPUEnv(_.context()->target_env)) {
+    // If an OpStruct has an OpTypeRuntimeArray somewhere within it, then it
+    // must either have the storage class StorageBuffer and be decorated
+    // with Block, or it must be in the Uniform storage class and be decorated
+    // as BufferBlock.
+    if (value_type && value_type->opcode() == SpvOpTypeStruct) {
+      if (DoesStructContainRTA(_, value_type)) {
+        if (storage_class == SpvStorageClassStorageBuffer) {
+          if (!_.HasDecoration(value_id, SpvDecorationBlock)) {
+            return _.diag(SPV_ERROR_INVALID_ID, inst)
+                   << "For Vulkan, an OpTypeStruct variable containing an "
+                   << "OpTypeRuntimeArray must be decorated with Block if it "
+                   << "has storage class StorageBuffer.";
+          }
+        } else if (storage_class == SpvStorageClassUniform) {
+          if (!_.HasDecoration(value_id, SpvDecorationBufferBlock)) {
+            return _.diag(SPV_ERROR_INVALID_ID, inst)
+                   << "For Vulkan, an OpTypeStruct variable containing an "
+                   << "OpTypeRuntimeArray must be decorated with BufferBlock "
+                   << "if it has storage class Uniform.";
+          }
+        } else {
+          return _.diag(SPV_ERROR_INVALID_ID, inst)
+                 << "For Vulkan, OpTypeStruct variables containing "
+                 << "OpTypeRuntimeArray must have storage class of "
+                 << "StorageBuffer or Uniform.";
+        }
+      }
+    }
+  }
+
+  // WebGPU specific validation rules for OpTypeRuntimeArray
+  if (spvIsWebGPUEnv(_.context()->target_env)) {
+    const auto type_index = 2;
+    const auto value_id = result_type->GetOperandAs<uint32_t>(type_index);
+    auto value_type = _.FindDef(value_id);
+    // OpTypeRuntimeArray should only ever be in an OpTypeStruct,
+    // so should never appear as a bare variable.
+    if (value_type && value_type->opcode() == SpvOpTypeRuntimeArray) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
              << "OpVariable, <id> '" << _.getIdName(inst->id())
-             << "', has a disallowed initializer & storage class "
-             << "combination.\n"
-             << "From WebGPU execution environment spec:\n"
-             << "Variable declarations that include initializers must have "
-             << "one of the following storage classes: Output, Private, or "
-             << "Function";
+             << "', is attempting to create memory for an illegal type, "
+             << "OpTypeRuntimeArray.\nFor WebGPU OpTypeRuntimeArray can only "
+             << "appear as the final member of an OpTypeStruct, thus cannot "
+             << "be instantiated via OpVariable";
     }
+
+    // If an OpStruct has an OpTypeRuntimeArray somewhere within it, then it
+    // must have the storage class StorageBuffer and be decorated
+    // with Block.
+    if (value_type && value_type->opcode() == SpvOpTypeStruct) {
+      if (DoesStructContainRTA(_, value_type)) {
+        if (storage_class == SpvStorageClassStorageBuffer) {
+          if (!_.HasDecoration(value_id, SpvDecorationBlock)) {
+            return _.diag(SPV_ERROR_INVALID_ID, inst)
+                   << "For WebGPU, an OpTypeStruct variable containing an "
+                   << "OpTypeRuntimeArray must be decorated with Block if it "
+                   << "has storage class StorageBuffer.";
+          }
+        } else {
+          return _.diag(SPV_ERROR_INVALID_ID, inst)
+                 << "For WebGPU, OpTypeStruct variables containing "
+                 << "OpTypeRuntimeArray must have storage class of "
+                 << "StorageBuffer";
+        }
+      }
+    }
+  }
+
+  // Cooperative matrix types can only be allocated in Function or Private
+  if ((storage_class != SpvStorageClassFunction &&
+       storage_class != SpvStorageClassPrivate) &&
+      ContainsCooperativeMatrix(_, pointee)) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Cooperative matrix types (or types containing them) can only be "
+              "allocated "
+           << "in Function or Private storage classes or as function "
+              "parameters";
   }
 
   return SPV_SUCCESS;
@@ -545,11 +751,7 @@
            << "'s type.";
   }
 
-  if (inst->operands().size() > 3) {
-    if (auto error =
-            CheckMemoryAccess(_, inst, inst->GetOperandAs<uint32_t>(3)))
-      return error;
-  }
+  if (auto error = CheckMemoryAccess(_, inst, 3)) return error;
 
   return SPV_SUCCESS;
 }
@@ -637,15 +839,56 @@
     }
   }
 
-  if (inst->operands().size() > 2) {
-    if (auto error =
-            CheckMemoryAccess(_, inst, inst->GetOperandAs<uint32_t>(2)))
-      return error;
-  }
+  if (auto error = CheckMemoryAccess(_, inst, 2)) return error;
 
   return SPV_SUCCESS;
 }
 
+spv_result_t ValidateCopyMemoryMemoryAccess(ValidationState_t& _,
+                                            const Instruction* inst) {
+  assert(inst->opcode() == SpvOpCopyMemory ||
+         inst->opcode() == SpvOpCopyMemorySized);
+  const uint32_t first_access_index = inst->opcode() == SpvOpCopyMemory ? 2 : 3;
+  if (inst->operands().size() > first_access_index) {
+    if (auto error = CheckMemoryAccess(_, inst, first_access_index))
+      return error;
+
+    const auto first_access = inst->GetOperandAs<uint32_t>(first_access_index);
+    const uint32_t second_access_index =
+        first_access_index + MemoryAccessNumWords(first_access);
+    if (inst->operands().size() > second_access_index) {
+      if (_.features().copy_memory_permits_two_memory_accesses) {
+        if (auto error = CheckMemoryAccess(_, inst, second_access_index))
+          return error;
+
+        // In the two-access form in SPIR-V 1.4 and later:
+        //  - the first is the target (write) access and it can't have
+        //  make-visible.
+        //  - the second is the source (read) access and it can't have
+        //  make-available.
+        if (first_access & SpvMemoryAccessMakePointerVisibleKHRMask) {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Target memory access must not include "
+                    "MakePointerVisibleKHR";
+        }
+        const auto second_access =
+            inst->GetOperandAs<uint32_t>(second_access_index);
+        if (second_access & SpvMemoryAccessMakePointerAvailableKHRMask) {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Source memory access must not include "
+                    "MakePointerAvailableKHR";
+        }
+      } else {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << spvOpcodeString(static_cast<SpvOp>(inst->opcode()))
+               << " with two memory access operands requires SPIR-V 1.4 or "
+                  "later";
+      }
+    }
+  }
+  return SPV_SUCCESS;
+}
+
 spv_result_t ValidateCopyMemory(ValidationState_t& _, const Instruction* inst) {
   const auto target_index = 0;
   const auto target_id = inst->GetOperandAs<uint32_t>(target_index);
@@ -705,11 +948,7 @@
              << _.getIdName(source_type->id()) << "'s type.";
     }
 
-    if (inst->operands().size() > 2) {
-      if (auto error =
-              CheckMemoryAccess(_, inst, inst->GetOperandAs<uint32_t>(2)))
-        return error;
-    }
+    if (auto error = CheckMemoryAccess(_, inst, 2)) return error;
   } else {
     const auto size_id = inst->GetOperandAs<uint32_t>(2);
     const auto size = _.FindDef(size_id);
@@ -753,13 +992,9 @@
         break;
     }
 
-    if (inst->operands().size() > 3) {
-      if (auto error =
-              CheckMemoryAccess(_, inst, inst->GetOperandAs<uint32_t>(3)))
-        return error;
-    }
+    if (auto error = CheckMemoryAccess(_, inst, 3)) return error;
   }
-  return SPV_SUCCESS;
+  return ValidateCopyMemoryMemoryAccess(_, inst);
 }
 
 spv_result_t ValidateAccessChain(ValidationState_t& _,
@@ -849,10 +1084,11 @@
     switch (type_pointee->opcode()) {
       case SpvOpTypeMatrix:
       case SpvOpTypeVector:
+      case SpvOpTypeCooperativeMatrixNV:
       case SpvOpTypeArray:
       case SpvOpTypeRuntimeArray: {
-        // In OpTypeMatrix, OpTypeVector, OpTypeArray, and OpTypeRuntimeArray,
-        // word 2 is the Element Type.
+        // In OpTypeMatrix, OpTypeVector, SpvOpTypeCooperativeMatrixNV,
+        // OpTypeArray, and OpTypeRuntimeArray, word 2 is the Element Type.
         type_pointee = _.FindDef(type_pointee->word(2));
         break;
       }
@@ -982,6 +1218,191 @@
   return SPV_SUCCESS;
 }
 
+spv_result_t ValidateCooperativeMatrixLengthNV(ValidationState_t& state,
+                                               const Instruction* inst) {
+  std::string instr_name =
+      "Op" + std::string(spvOpcodeString(static_cast<SpvOp>(inst->opcode())));
+
+  // Result type must be a 32-bit unsigned int.
+  auto result_type = state.FindDef(inst->type_id());
+  if (result_type->opcode() != SpvOpTypeInt ||
+      result_type->GetOperandAs<uint32_t>(1) != 32 ||
+      result_type->GetOperandAs<uint32_t>(2) != 0) {
+    return state.diag(SPV_ERROR_INVALID_ID, inst)
+           << "The Result Type of " << instr_name << " <id> '"
+           << state.getIdName(inst->id())
+           << "' must be OpTypeInt with width 32 and signedness 0.";
+  }
+
+  auto type_id = inst->GetOperandAs<uint32_t>(2);
+  auto type = state.FindDef(type_id);
+  if (type->opcode() != SpvOpTypeCooperativeMatrixNV) {
+    return state.diag(SPV_ERROR_INVALID_ID, inst)
+           << "The type in " << instr_name << " <id> '"
+           << state.getIdName(type_id)
+           << "' must be OpTypeCooperativeMatrixNV.";
+  }
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateCooperativeMatrixLoadStoreNV(ValidationState_t& _,
+                                                  const Instruction* inst) {
+  uint32_t type_id;
+  const char* opname;
+  if (inst->opcode() == SpvOpCooperativeMatrixLoadNV) {
+    type_id = inst->type_id();
+    opname = "SpvOpCooperativeMatrixLoadNV";
+  } else {
+    // get Object operand's type
+    type_id = _.FindDef(inst->GetOperandAs<uint32_t>(1))->type_id();
+    opname = "SpvOpCooperativeMatrixStoreNV";
+  }
+
+  auto matrix_type = _.FindDef(type_id);
+
+  if (matrix_type->opcode() != SpvOpTypeCooperativeMatrixNV) {
+    if (inst->opcode() == SpvOpCooperativeMatrixLoadNV) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "SpvOpCooperativeMatrixLoadNV Result Type <id> '"
+             << _.getIdName(type_id) << "' is not a cooperative matrix type.";
+    } else {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "SpvOpCooperativeMatrixStoreNV Object type <id> '"
+             << _.getIdName(type_id) << "' is not a cooperative matrix type.";
+    }
+  }
+
+  const bool uses_variable_pointers =
+      _.features().variable_pointers ||
+      _.features().variable_pointers_storage_buffer;
+  const auto pointer_index =
+      (inst->opcode() == SpvOpCooperativeMatrixLoadNV) ? 2u : 0u;
+  const auto pointer_id = inst->GetOperandAs<uint32_t>(pointer_index);
+  const auto pointer = _.FindDef(pointer_id);
+  if (!pointer ||
+      ((_.addressing_model() == SpvAddressingModelLogical) &&
+       ((!uses_variable_pointers &&
+         !spvOpcodeReturnsLogicalPointer(pointer->opcode())) ||
+        (uses_variable_pointers &&
+         !spvOpcodeReturnsLogicalVariablePointer(pointer->opcode()))))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << opname << " Pointer <id> '" << _.getIdName(pointer_id)
+           << "' is not a logical pointer.";
+  }
+
+  const auto pointer_type_id = pointer->type_id();
+  const auto pointer_type = _.FindDef(pointer_type_id);
+  if (!pointer_type || pointer_type->opcode() != SpvOpTypePointer) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << opname << " type for pointer <id> '" << _.getIdName(pointer_id)
+           << "' is not a pointer type.";
+  }
+
+  const auto storage_class_index = 1u;
+  const auto storage_class =
+      pointer_type->GetOperandAs<uint32_t>(storage_class_index);
+
+  if (storage_class != SpvStorageClassWorkgroup &&
+      storage_class != SpvStorageClassStorageBuffer &&
+      storage_class != SpvStorageClassPhysicalStorageBufferEXT) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << opname << " storage class for pointer type <id> '"
+           << _.getIdName(pointer_type_id)
+           << "' is not Workgroup or StorageBuffer.";
+  }
+
+  const auto pointee_id = pointer_type->GetOperandAs<uint32_t>(2);
+  const auto pointee_type = _.FindDef(pointee_id);
+  if (!pointee_type || !(_.IsIntScalarOrVectorType(pointee_id) ||
+                         _.IsFloatScalarOrVectorType(pointee_id))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << opname << " Pointer <id> '" << _.getIdName(pointer->id())
+           << "'s Type must be a scalar or vector type.";
+  }
+
+  const auto stride_index =
+      (inst->opcode() == SpvOpCooperativeMatrixLoadNV) ? 3u : 2u;
+  const auto stride_id = inst->GetOperandAs<uint32_t>(stride_index);
+  const auto stride = _.FindDef(stride_id);
+  if (!stride || !_.IsIntScalarType(stride->type_id())) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Stride operand <id> '" << _.getIdName(stride_id)
+           << "' must be a scalar integer type.";
+  }
+
+  const auto colmajor_index =
+      (inst->opcode() == SpvOpCooperativeMatrixLoadNV) ? 4u : 3u;
+  const auto colmajor_id = inst->GetOperandAs<uint32_t>(colmajor_index);
+  const auto colmajor = _.FindDef(colmajor_id);
+  if (!colmajor || !_.IsBoolScalarType(colmajor->type_id()) ||
+      !(spvOpcodeIsConstant(colmajor->opcode()) ||
+        spvOpcodeIsSpecConstant(colmajor->opcode()))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Column Major operand <id> '" << _.getIdName(colmajor_id)
+           << "' must be a boolean constant instruction.";
+  }
+
+  const auto memory_access_index =
+      (inst->opcode() == SpvOpCooperativeMatrixLoadNV) ? 5u : 4u;
+  if (inst->operands().size() > memory_access_index) {
+    if (auto error = CheckMemoryAccess(_, inst, memory_access_index))
+      return error;
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidatePtrComparison(ValidationState_t& _,
+                                   const Instruction* inst) {
+  if (_.addressing_model() == SpvAddressingModelLogical &&
+      !_.features().variable_pointers_storage_buffer) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Instruction cannot be used without a variable pointers "
+              "capability";
+  }
+
+  const auto result_type = _.FindDef(inst->type_id());
+  if (inst->opcode() == SpvOpPtrDiff) {
+    if (!result_type || result_type->opcode() != SpvOpTypeInt) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "Result Type must be an integer scalar";
+    }
+  } else {
+    if (!result_type || result_type->opcode() != SpvOpTypeBool) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "Result Type must be OpTypeBool";
+    }
+  }
+
+  const auto op1 = _.FindDef(inst->GetOperandAs<uint32_t>(2u));
+  const auto op2 = _.FindDef(inst->GetOperandAs<uint32_t>(3u));
+  if (!op1 || !op2 || op1->type_id() != op2->type_id()) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "The types of Operand 1 and Operand 2 must match";
+  }
+  const auto op1_type = _.FindDef(op1->type_id());
+  if (!op1_type || op1_type->opcode() != SpvOpTypePointer) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Operand type must be a pointer";
+  }
+
+  if (_.addressing_model() == SpvAddressingModelLogical) {
+    SpvStorageClass sc = op1_type->GetOperandAs<SpvStorageClass>(1u);
+    if (sc != SpvStorageClassWorkgroup && sc != SpvStorageClassStorageBuffer) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "Invalid pointer storage class";
+    }
+
+    if (sc == SpvStorageClassWorkgroup && !_.features().variable_pointers) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "Workgroup storage class pointer requires VariablePointers "
+                "capability to be specified";
+    }
+  }
+
+  return SPV_SUCCESS;
+}
+
 }  // namespace
 
 spv_result_t MemoryPass(ValidationState_t& _, const Instruction* inst) {
@@ -1010,6 +1431,19 @@
     case SpvOpArrayLength:
       if (auto error = ValidateArrayLength(_, inst)) return error;
       break;
+    case SpvOpCooperativeMatrixLoadNV:
+    case SpvOpCooperativeMatrixStoreNV:
+      if (auto error = ValidateCooperativeMatrixLoadStoreNV(_, inst))
+        return error;
+      break;
+    case SpvOpCooperativeMatrixLengthNV:
+      if (auto error = ValidateCooperativeMatrixLengthNV(_, inst)) return error;
+      break;
+    case SpvOpPtrEqual:
+    case SpvOpPtrNotEqual:
+    case SpvOpPtrDiff:
+      if (auto error = ValidatePtrComparison(_, inst)) return error;
+      break;
     case SpvOpImageTexelPointer:
     case SpvOpGenericPtrMemSemantics:
     default:
diff --git a/source/val/validate_memory_semantics.cpp b/source/val/validate_memory_semantics.cpp
index ec6df38..31154f5 100644
--- a/source/val/validate_memory_semantics.cpp
+++ b/source/val/validate_memory_semantics.cpp
@@ -48,21 +48,36 @@
   }
 
   if (spvIsWebGPUEnv(_.context()->target_env)) {
-    uint32_t valid_bits = SpvMemorySemanticsAcquireMask |
-                          SpvMemorySemanticsReleaseMask |
-                          SpvMemorySemanticsAcquireReleaseMask |
-                          SpvMemorySemanticsUniformMemoryMask |
+    uint32_t valid_bits = SpvMemorySemanticsUniformMemoryMask |
                           SpvMemorySemanticsWorkgroupMemoryMask |
                           SpvMemorySemanticsImageMemoryMask |
                           SpvMemorySemanticsOutputMemoryKHRMask |
                           SpvMemorySemanticsMakeAvailableKHRMask |
                           SpvMemorySemanticsMakeVisibleKHRMask;
+    if (!spvOpcodeIsAtomicOp(inst->opcode())) {
+      valid_bits |= SpvMemorySemanticsAcquireReleaseMask;
+    }
+
     if (value & ~valid_bits) {
+      if (spvOpcodeIsAtomicOp(inst->opcode())) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "WebGPU spec disallows, for OpAtomic*, any bit masks in "
+                  "Memory Semantics that are not UniformMemory, "
+                  "WorkgroupMemory, ImageMemory, or OutputMemoryKHR";
+      } else {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "WebGPU spec disallows any bit masks in Memory Semantics "
+                  "that are not AcquireRelease, UniformMemory, "
+                  "WorkgroupMemory, ImageMemory, OutputMemoryKHR, "
+                  "MakeAvailableKHR, or MakeVisibleKHR";
+      }
+    }
+
+    if (!spvOpcodeIsAtomicOp(inst->opcode()) &&
+        !(value & SpvMemorySemanticsAcquireReleaseMask)) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "WebGPU spec disallows any bit masks in Memory Semantics that "
-                "are not Acquire, Release, AcquireRelease, UniformMemory, "
-                "WorkgroupMemory, ImageMemory, OutputMemoryKHR, "
-                "MakeAvailableKHR, or MakeVisibleKHR";
+             << "WebGPU spec requires AcquireRelease to set in Memory "
+                "Semantics.";
     }
   }
 
@@ -119,15 +134,8 @@
            << ": Memory Semantics UniformMemory requires capability Shader";
   }
 
-  // Disabling this check until
-  // https://github.com/KhronosGroup/glslang/issues/1618 is resolved.
-  //   if (value & SpvMemorySemanticsAtomicCounterMemoryMask &&
-  //      !_.HasCapability(SpvCapabilityAtomicStorage)) {
-  //    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-  //           << spvOpcodeString(opcode)
-  //           << ": Memory Semantics AtomicCounterMemory requires capability "
-  //              "AtomicStorage";
-  //  }
+  // Checking for SpvCapabilityAtomicStorage is intentionally not done here. See
+  // https://github.com/KhronosGroup/glslang/issues/1618 for the reasoning why.
 
   if (value & (SpvMemorySemanticsMakeAvailableKHRMask |
                SpvMemorySemanticsMakeVisibleKHRMask)) {
diff --git a/source/val/validate_mode_setting.cpp b/source/val/validate_mode_setting.cpp
index c1bfc27..943629d 100644
--- a/source/val/validate_mode_setting.cpp
+++ b/source/val/validate_mode_setting.cpp
@@ -33,12 +33,11 @@
            << "OpEntryPoint Entry Point <id> '" << _.getIdName(entry_point_id)
            << "' is not a function.";
   }
-  // don't check kernel function signatures
+
+  // Only check the shader execution models
   const SpvExecutionModel execution_model =
       inst->GetOperandAs<SpvExecutionModel>(0);
   if (execution_model != SpvExecutionModelKernel) {
-    // TODO: Check the entry point signature is void main(void), may be subject
-    // to change
     const auto entry_point_type_id = entry_point->GetOperandAs<uint32_t>(3);
     const auto entry_point_type = _.FindDef(entry_point_type_id);
     if (!entry_point_type || 3 != entry_point_type->words().size()) {
@@ -236,6 +235,36 @@
   }
 
   const auto mode = inst->GetOperandAs<SpvExecutionMode>(1);
+  if (inst->opcode() == SpvOpExecutionModeId) {
+    size_t operand_count = inst->operands().size();
+    for (size_t i = 2; i < operand_count; ++i) {
+      const auto operand_id = inst->GetOperandAs<uint32_t>(2);
+      const auto* operand_inst = _.FindDef(operand_id);
+      if (mode == SpvExecutionModeSubgroupsPerWorkgroupId ||
+          mode == SpvExecutionModeLocalSizeHintId ||
+          mode == SpvExecutionModeLocalSizeId) {
+        if (!spvOpcodeIsConstant(operand_inst->opcode())) {
+          return _.diag(SPV_ERROR_INVALID_ID, inst)
+                 << "For OpExecutionModeId all Extra Operand ids must be "
+                    "constant "
+                    "instructions.";
+        }
+      } else {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << "OpExecutionModeId is only valid when the Mode operand is an "
+                  "execution mode that takes Extra Operands that are id "
+                  "operands.";
+      }
+    }
+  } else if (mode == SpvExecutionModeSubgroupsPerWorkgroupId ||
+             mode == SpvExecutionModeLocalSizeHintId ||
+             mode == SpvExecutionModeLocalSizeId) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "OpExecutionMode is only valid when the Mode operand is an "
+              "execution mode that takes no Extra Operands, or takes Extra "
+              "Operands that are not id operands.";
+  }
+
   const auto* models = _.GetExecutionModels(entry_point_id);
   switch (mode) {
     case SpvExecutionModeInvocations:
@@ -344,6 +373,7 @@
     case SpvExecutionModeOriginLowerLeft:
     case SpvExecutionModeEarlyFragmentTests:
     case SpvExecutionModeDepthReplacing:
+    case SpvExecutionModeDepthGreater:
     case SpvExecutionModeDepthLess:
     case SpvExecutionModeDepthUnchanged:
       if (!std::all_of(models->begin(), models->end(),
@@ -411,6 +441,21 @@
     }
   }
 
+  if (spvIsWebGPUEnv(_.context()->target_env)) {
+    if (mode != SpvExecutionModeOriginUpperLeft &&
+        mode != SpvExecutionModeDepthReplacing &&
+        mode != SpvExecutionModeDepthGreater &&
+        mode != SpvExecutionModeDepthLess &&
+        mode != SpvExecutionModeDepthUnchanged &&
+        mode != SpvExecutionModeLocalSize &&
+        mode != SpvExecutionModeLocalSizeHint) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Execution mode must be one of OriginUpperLeft, "
+                "DepthReplacing, DepthGreater, DepthLess, DepthUnchanged, "
+                "LocalSize, or LocalSizeHint for WebGPU environment.";
+    }
+  }
+
   return SPV_SUCCESS;
 }
 
diff --git a/source/val/validate_scopes.cpp b/source/val/validate_scopes.cpp
index 3ba8f3b..7e396c0 100644
--- a/source/val/validate_scopes.cpp
+++ b/source/val/validate_scopes.cpp
@@ -22,6 +22,23 @@
 namespace spvtools {
 namespace val {
 
+bool IsValidScope(uint32_t scope) {
+  // Deliberately avoid a default case so we have to update the list when the
+  // scopes list changes.
+  switch (static_cast<SpvScope>(scope)) {
+    case SpvScopeCrossDevice:
+    case SpvScopeDevice:
+    case SpvScopeWorkgroup:
+    case SpvScopeSubgroup:
+    case SpvScopeInvocation:
+    case SpvScopeQueueFamilyKHR:
+      return true;
+    case SpvScopeMax:
+      break;
+  }
+  return false;
+}
+
 spv_result_t ValidateExecutionScope(ValidationState_t& _,
                                     const Instruction* inst, uint32_t scope) {
   SpvOp opcode = inst->opcode();
@@ -36,14 +53,27 @@
   }
 
   if (!is_const_int32) {
-    if (_.HasCapability(SpvCapabilityShader)) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Scope ids must be "
-                                                     "OpConstant when Shader "
-                                                     "capability is present";
+    if (_.HasCapability(SpvCapabilityShader) &&
+        !_.HasCapability(SpvCapabilityCooperativeMatrixNV)) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Scope ids must be OpConstant when Shader capability is "
+             << "present";
+    }
+    if (_.HasCapability(SpvCapabilityShader) &&
+        _.HasCapability(SpvCapabilityCooperativeMatrixNV) &&
+        !spvOpcodeIsConstant(_.GetIdOpcode(scope))) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Scope ids must be constant or specialization constant when "
+             << "CooperativeMatrixNV capability is present";
     }
     return SPV_SUCCESS;
   }
 
+  if (is_const_int32 && !IsValidScope(value)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Invalid scope value:\n " << _.Disassemble(*_.FindDef(scope));
+  }
+
   // Vulkan specific rules
   if (spvIsVulkanEnv(_.context()->target_env)) {
     // Vulkan 1.1 specific rules
@@ -54,7 +84,7 @@
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << spvOpcodeString(opcode)
                << ": in Vulkan environment Execution scope is limited to "
-                  "Subgroup";
+               << "Subgroup";
       }
     }
 
@@ -86,7 +116,7 @@
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << spvOpcodeString(opcode)
              << ": in Vulkan environment Execution Scope is limited to "
-                "Workgroup and Subgroup";
+             << "Workgroup and Subgroup";
     }
   }
 
@@ -97,7 +127,7 @@
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << spvOpcodeString(opcode)
              << ": in WebGPU environment Execution Scope is limited to "
-                "Workgroup and Subgroup";
+             << "Workgroup and Subgroup";
     }
   }
 
@@ -130,14 +160,27 @@
   }
 
   if (!is_const_int32) {
-    if (_.HasCapability(SpvCapabilityShader)) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Scope ids must be "
-                                                     "OpConstant when Shader "
-                                                     "capability is present";
+    if (_.HasCapability(SpvCapabilityShader) &&
+        !_.HasCapability(SpvCapabilityCooperativeMatrixNV)) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Scope ids must be OpConstant when Shader capability is "
+             << "present";
+    }
+    if (_.HasCapability(SpvCapabilityShader) &&
+        _.HasCapability(SpvCapabilityCooperativeMatrixNV) &&
+        !spvOpcodeIsConstant(_.GetIdOpcode(scope))) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Scope ids must be constant or specialization constant when "
+             << "CooperativeMatrixNV capability is present";
     }
     return SPV_SUCCESS;
   }
 
+  if (is_const_int32 && !IsValidScope(value)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Invalid scope value:\n " << _.Disassemble(*_.FindDef(scope));
+  }
+
   if (value == SpvScopeQueueFamilyKHR) {
     if (_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
       return SPV_SUCCESS;
@@ -145,7 +188,7 @@
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << spvOpcodeString(opcode)
              << ": Memory Scope QueueFamilyKHR requires capability "
-                "VulkanMemoryModelKHR";
+             << "VulkanMemoryModelKHR";
     }
   }
 
@@ -154,7 +197,7 @@
       !_.HasCapability(SpvCapabilityVulkanMemoryModelDeviceScopeKHR)) {
     return _.diag(SPV_ERROR_INVALID_DATA, inst)
            << "Use of device scope with VulkanKHR memory model requires the "
-              "VulkanMemoryModelDeviceScopeKHR capability";
+           << "VulkanMemoryModelDeviceScopeKHR capability";
   }
 
   // Vulkan Specific rules
@@ -171,8 +214,7 @@
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << spvOpcodeString(opcode)
              << ": in Vulkan 1.0 environment Memory Scope is limited to "
-                "Device, "
-                "Workgroup and Invocation";
+             << "Device, Workgroup and Invocation";
     }
     // Vulkan 1.1 specifc rules
     if (_.context()->target_env == SPV_ENV_VULKAN_1_1 &&
@@ -181,8 +223,18 @@
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << spvOpcodeString(opcode)
              << ": in Vulkan 1.1 environment Memory Scope is limited to "
-                "Device, "
-                "Workgroup and Invocation";
+             << "Device, Workgroup and Invocation";
+    }
+  }
+
+  // WebGPU specific rules
+  if (spvIsWebGPUEnv(_.context()->target_env)) {
+    if (value != SpvScopeWorkgroup && value != SpvScopeSubgroup &&
+        value != SpvScopeQueueFamilyKHR) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << spvOpcodeString(opcode)
+             << ": in WebGPU environment Memory Scope is limited to "
+             << "Workgroup, Subgroup and QueuFamilyKHR";
     }
   }
 
diff --git a/source/val/validate_type.cpp b/source/val/validate_type.cpp
index e0f2786..e3c7662 100644
--- a/source/val/validate_type.cpp
+++ b/source/val/validate_type.cpp
@@ -14,10 +14,10 @@
 
 // Ensures type declarations are unique unless allowed by the specification.
 
-#include "source/val/validate.h"
-
 #include "source/opcode.h"
+#include "source/spirv_target_env.h"
 #include "source/val/instruction.h"
+#include "source/val/validate.h"
 #include "source/val/validation_state.h"
 
 namespace spvtools {
@@ -66,6 +66,16 @@
   return SPV_SUCCESS;
 }
 
+spv_result_t ValidateTypeInt(ValidationState_t& _, const Instruction* inst) {
+  const auto signedness_index = 2;
+  const auto signedness = inst->GetOperandAs<uint32_t>(signedness_index);
+  if (signedness != 0 && signedness != 1) {
+    return _.diag(SPV_ERROR_INVALID_VALUE, inst)
+           << "OpTypeInt has invalid signedness:";
+  }
+  return SPV_SUCCESS;
+}
+
 spv_result_t ValidateTypeVector(ValidationState_t& _, const Instruction* inst) {
   const auto component_index = 1;
   const auto component_id = inst->GetOperandAs<uint32_t>(component_index);
@@ -106,6 +116,14 @@
            << "' is a void type.";
   }
 
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env) &&
+      element_type->opcode() == SpvOpTypeRuntimeArray) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "OpTypeArray Element Type <id> '" << _.getIdName(element_type_id)
+           << "' is not valid in "
+           << spvLogStringForEnv(_.context()->target_env) << " environments.";
+  }
+
   const auto length_index = 2;
   const auto length_id = inst->GetOperandAs<uint32_t>(length_index);
   const auto length = _.FindDef(length_id);
@@ -161,6 +179,14 @@
            << _.getIdName(element_id) << "' is a void type.";
   }
 
+  if (spvIsVulkanOrWebGPUEnv(_.context()->target_env) &&
+      element_type->opcode() == SpvOpTypeRuntimeArray) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "OpTypeRuntimeArray Element Type <id> '"
+           << _.getIdName(element_id) << "' is not valid in "
+           << spvLogStringForEnv(_.context()->target_env) << " environments.";
+  }
+
   return SPV_SUCCESS;
 }
 
@@ -184,11 +210,11 @@
       return _.diag(SPV_ERROR_INVALID_ID, inst)
              << "Structure <id> " << _.getIdName(member_type_id)
              << " contains members with BuiltIn decoration. Therefore this "
-                "structure may not be contained as a member of another "
-                "structure "
-                "type. Structure <id> "
-             << _.getIdName(struct_id) << " contains structure <id> "
-             << _.getIdName(member_type_id) << ".";
+             << "structure may not be contained as a member of another "
+             << "structure "
+             << "type. Structure <id> " << _.getIdName(struct_id)
+             << " contains structure <id> " << _.getIdName(member_type_id)
+             << ".";
     }
     if (_.IsForwardPointer(member_type_id)) {
       // If we're dealing with a forward pointer:
@@ -206,7 +232,43 @@
                << ".";
       }
     }
+
+    if (spvIsVulkanOrWebGPUEnv(_.context()->target_env) &&
+        member_type->opcode() == SpvOpTypeRuntimeArray) {
+      const bool is_last_member =
+          member_type_index == inst->operands().size() - 1;
+      if (!is_last_member) {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << "In " << spvLogStringForEnv(_.context()->target_env)
+               << ", OpTypeRuntimeArray must only be used for the last member "
+                  "of an OpTypeStruct";
+      }
+    }
   }
+
+  bool has_nested_blockOrBufferBlock_struct = false;
+  // Struct members start at word 2 of OpTypeStruct instruction.
+  for (size_t word_i = 2; word_i < inst->words().size(); ++word_i) {
+    auto member = inst->word(word_i);
+    auto memberTypeInstr = _.FindDef(member);
+    if (memberTypeInstr && SpvOpTypeStruct == memberTypeInstr->opcode()) {
+      if (_.HasDecoration(memberTypeInstr->id(), SpvDecorationBlock) ||
+          _.HasDecoration(memberTypeInstr->id(), SpvDecorationBufferBlock) ||
+          _.GetHasNestedBlockOrBufferBlockStruct(memberTypeInstr->id()))
+        has_nested_blockOrBufferBlock_struct = true;
+    }
+  }
+
+  _.SetHasNestedBlockOrBufferBlockStruct(inst->id(),
+                                         has_nested_blockOrBufferBlock_struct);
+  if (_.GetHasNestedBlockOrBufferBlockStruct(inst->id()) &&
+      (_.HasDecoration(inst->id(), SpvDecorationBufferBlock) ||
+       _.HasDecoration(inst->id(), SpvDecorationBlock))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "rules: A Block or BufferBlock cannot be nested within another "
+              "Block or BufferBlock. ";
+  }
+
   std::unordered_set<uint32_t> built_in_members;
   for (auto decoration : _.id_decorations(struct_id)) {
     if (decoration.dec_type() == SpvDecorationBuiltIn &&
@@ -219,9 +281,9 @@
   if (num_builtin_members > 0 && num_builtin_members != num_struct_members) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
            << "When BuiltIn decoration is applied to a structure-type member, "
-              "all members of that structure type must also be decorated with "
-              "BuiltIn (No allowed mixing of built-in variables and "
-              "non-built-in variables within a single structure). Structure id "
+           << "all members of that structure type must also be decorated with "
+           << "BuiltIn (No allowed mixing of built-in variables and "
+           << "non-built-in variables within a single structure). Structure id "
            << struct_id << " does not meet this requirement.";
   }
   if (num_builtin_members > 0) {
@@ -232,15 +294,32 @@
 
 spv_result_t ValidateTypePointer(ValidationState_t& _,
                                  const Instruction* inst) {
-  const auto type_id = inst->GetOperandAs<uint32_t>(2);
-  const auto type = _.FindDef(type_id);
+  auto type_id = inst->GetOperandAs<uint32_t>(2);
+  auto type = _.FindDef(type_id);
   if (!type || !spvOpcodeGeneratesType(type->opcode())) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
            << "OpTypePointer Type <id> '" << _.getIdName(type_id)
            << "' is not a type.";
   }
+  // See if this points to a storage image.
+  const auto storage_class = inst->GetOperandAs<SpvStorageClass>(1);
+  if (storage_class == SpvStorageClassUniformConstant) {
+    // Unpack an optional level of arraying.
+    if (type->opcode() == SpvOpTypeArray ||
+        type->opcode() == SpvOpTypeRuntimeArray) {
+      type_id = type->GetOperandAs<uint32_t>(1);
+      type = _.FindDef(type_id);
+    }
+    if (type->opcode() == SpvOpTypeImage) {
+      const auto sampled = type->GetOperandAs<uint32_t>(6);
+      // 2 indicates this image is known to be be used without a sampler, i.e.
+      // a storage image.
+      if (sampled == 2) _.RegisterPointerToStorageImage(inst->id());
+    }
+  }
   return SPV_SUCCESS;
 }
+}  // namespace
 
 spv_result_t ValidateTypeFunction(ValidationState_t& _,
                                   const Instruction* inst) {
@@ -278,10 +357,12 @@
            << num_args << " arguments.";
   }
 
-  // The only valid uses of OpTypeFunction are in an OpFunction instruction.
+  // The only valid uses of OpTypeFunction are in an OpFunction, debugging, or
+  // decoration instruction.
   for (auto& pair : inst->uses()) {
     const auto* use = pair.first;
-    if (use->opcode() != SpvOpFunction) {
+    if (use->opcode() != SpvOpFunction && !spvOpcodeIsDebug(use->opcode()) &&
+        !spvOpcodeIsDecoration(use->opcode())) {
       return _.diag(SPV_ERROR_INVALID_ID, use)
              << "Invalid use of function type result id "
              << _.getIdName(inst->id()) << ".";
@@ -304,13 +385,58 @@
       pointer_type_inst->GetOperandAs<uint32_t>(1)) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
            << "Storage class in OpTypeForwardPointer does not match the "
-              "pointer definition.";
+           << "pointer definition.";
   }
 
   return SPV_SUCCESS;
 }
 
-}  // namespace
+spv_result_t ValidateTypeCooperativeMatrixNV(ValidationState_t& _,
+                                             const Instruction* inst) {
+  const auto component_type_index = 1;
+  const auto component_type_id =
+      inst->GetOperandAs<uint32_t>(component_type_index);
+  const auto component_type = _.FindDef(component_type_id);
+  if (!component_type || (SpvOpTypeFloat != component_type->opcode() &&
+                          SpvOpTypeInt != component_type->opcode())) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "OpTypeCooperativeMatrixNV Component Type <id> '"
+           << _.getIdName(component_type_id)
+           << "' is not a scalar numerical type.";
+  }
+
+  const auto scope_index = 2;
+  const auto scope_id = inst->GetOperandAs<uint32_t>(scope_index);
+  const auto scope = _.FindDef(scope_id);
+  if (!scope || !_.IsIntScalarType(scope->type_id()) ||
+      !spvOpcodeIsConstant(scope->opcode())) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "OpTypeCooperativeMatrixNV Scope <id> '" << _.getIdName(scope_id)
+           << "' is not a constant instruction with scalar integer type.";
+  }
+
+  const auto rows_index = 3;
+  const auto rows_id = inst->GetOperandAs<uint32_t>(rows_index);
+  const auto rows = _.FindDef(rows_id);
+  if (!rows || !_.IsIntScalarType(rows->type_id()) ||
+      !spvOpcodeIsConstant(rows->opcode())) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "OpTypeCooperativeMatrixNV Rows <id> '" << _.getIdName(rows_id)
+           << "' is not a constant instruction with scalar integer type.";
+  }
+
+  const auto cols_index = 4;
+  const auto cols_id = inst->GetOperandAs<uint32_t>(cols_index);
+  const auto cols = _.FindDef(cols_id);
+  if (!cols || !_.IsIntScalarType(cols->type_id()) ||
+      !spvOpcodeIsConstant(cols->opcode())) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "OpTypeCooperativeMatrixNV Cols <id> '" << _.getIdName(rows_id)
+           << "' is not a constant instruction with scalar integer type.";
+  }
+
+  return SPV_SUCCESS;
+}
 
 spv_result_t TypePass(ValidationState_t& _, const Instruction* inst) {
   if (!spvOpcodeGeneratesType(inst->opcode()) &&
@@ -321,6 +447,9 @@
   if (auto error = ValidateUniqueness(_, inst)) return error;
 
   switch (inst->opcode()) {
+    case SpvOpTypeInt:
+      if (auto error = ValidateTypeInt(_, inst)) return error;
+      break;
     case SpvOpTypeVector:
       if (auto error = ValidateTypeVector(_, inst)) return error;
       break;
@@ -345,6 +474,9 @@
     case SpvOpTypeForwardPointer:
       if (auto error = ValidateTypeForwardPointer(_, inst)) return error;
       break;
+    case SpvOpTypeCooperativeMatrixNV:
+      if (auto error = ValidateTypeCooperativeMatrixNV(_, inst)) return error;
+      break;
     default:
       break;
   }
diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp
index a10186f..325a3e8 100644
--- a/source/val/validation_state.cpp
+++ b/source/val/validation_state.cpp
@@ -19,6 +19,7 @@
 #include <utility>
 
 #include "source/opcode.h"
+#include "source/spirv_constant.h"
 #include "source/spirv_target_env.h"
 #include "source/val/basic_block.h"
 #include "source/val/construct.h"
@@ -146,6 +147,30 @@
   return SPV_SUCCESS;
 }
 
+spv_result_t setHeader(void* user_data, spv_endianness_t, uint32_t,
+                       uint32_t version, uint32_t generator, uint32_t id_bound,
+                       uint32_t) {
+  ValidationState_t& vstate =
+      *(reinterpret_cast<ValidationState_t*>(user_data));
+  vstate.setIdBound(id_bound);
+  vstate.setGenerator(generator);
+  vstate.setVersion(version);
+
+  return SPV_SUCCESS;
+}
+
+// Add features based on SPIR-V core version number.
+void UpdateFeaturesBasedOnSpirvVersion(ValidationState_t::Feature* features,
+                                       uint32_t version) {
+  assert(features);
+  if (version >= SPV_SPIRV_VERSION_WORD(1, 4)) {
+    features->select_between_composites = true;
+    features->copy_memory_permits_two_memory_accesses = true;
+    features->uconvert_spec_constant_op = true;
+    features->nonwritable_var_in_function_or_private = true;
+  }
+}
+
 }  // namespace
 
 ValidationState_t::ValidationState_t(const spv_const_context ctx,
@@ -168,9 +193,11 @@
       global_vars_(),
       local_vars_(),
       struct_nesting_depth_(),
+      struct_has_nested_blockorbufferblock_struct_(),
       grammar_(ctx),
       addressing_model_(SpvAddressingModelMax),
       memory_model_(SpvMemoryModelMax),
+      pointer_size_and_alignment_(0),
       in_function_(false),
       num_of_warnings_(0),
       max_num_of_warnings_(max_warnings) {
@@ -203,11 +230,12 @@
     spv_context_t hijacked_context = *ctx;
     hijacked_context.consumer = [](spv_message_level_t, const char*,
                                    const spv_position_t&, const char*) {};
-    spvBinaryParse(&hijacked_context, this, words, num_words,
-                   /* parsed_header = */ nullptr, CountInstructions,
+    spvBinaryParse(&hijacked_context, this, words, num_words, setHeader,
+                   CountInstructions,
                    /* diagnostic = */ nullptr);
     preallocateStorage();
   }
+  UpdateFeaturesBasedOnSpirvVersion(&features_, version_);
 
   friendly_mapper_ = spvtools::MakeUnique<spvtools::FriendlyNameMapper>(
       context_, words_, num_words_);
@@ -411,6 +439,11 @@
       // https://github.com/KhronosGroup/SPIRV-Tools/issues/1375
       features_.declare_float16_type = true;
       break;
+    case kSPV_AMD_gpu_shader_int16:
+      // This is not yet in the extension, but it's recommended for it.
+      // See https://github.com/KhronosGroup/glslang/issues/848
+      features_.uconvert_spec_constant_op = true;
+      break;
     case kSPV_AMD_shader_ballot:
       // The grammar doesn't encode the fact that SPV_AMD_shader_ballot
       // enables the use of group operations Reduce, InclusiveScan,
@@ -435,6 +468,17 @@
 
 void ValidationState_t::set_addressing_model(SpvAddressingModel am) {
   addressing_model_ = am;
+  switch (am) {
+    case SpvAddressingModelPhysical32:
+      pointer_size_and_alignment_ = 4;
+      break;
+    default:
+    // fall through
+    case SpvAddressingModelPhysical64:
+    case SpvAddressingModelPhysicalStorageBuffer64EXT:
+      pointer_size_and_alignment_ = 8;
+      break;
+  }
 }
 
 SpvAddressingModel ValidationState_t::addressing_model() const {
@@ -522,15 +566,15 @@
       const uint32_t operand_word = inst->word(operand.offset);
       Instruction* operand_inst = FindDef(operand_word);
       if (operand_inst && SpvOpSampledImage == operand_inst->opcode()) {
-        RegisterSampledImageConsumer(operand_word, inst->id());
+        RegisterSampledImageConsumer(operand_word, inst);
       }
     }
   }
 }
 
-std::vector<uint32_t> ValidationState_t::getSampledImageConsumers(
+std::vector<Instruction*> ValidationState_t::getSampledImageConsumers(
     uint32_t sampled_image_id) const {
-  std::vector<uint32_t> result;
+  std::vector<Instruction*> result;
   auto iter = sampled_image_consumers_.find(sampled_image_id);
   if (iter != sampled_image_consumers_.end()) {
     result = iter->second;
@@ -539,8 +583,8 @@
 }
 
 void ValidationState_t::RegisterSampledImageConsumer(uint32_t sampled_image_id,
-                                                     uint32_t consumer_id) {
-  sampled_image_consumers_[sampled_image_id].push_back(consumer_id);
+                                                     Instruction* consumer) {
+  sampled_image_consumers_[sampled_image_id].push_back(consumer);
 }
 
 uint32_t ValidationState_t::getIdBound() const { return id_bound_; }
@@ -592,6 +636,9 @@
     case SpvOpTypeMatrix:
       return GetComponentType(inst->word(2));
 
+    case SpvOpTypeCooperativeMatrixNV:
+      return inst->word(2);
+
     default:
       break;
   }
@@ -616,6 +663,10 @@
     case SpvOpTypeMatrix:
       return inst->word(3);
 
+    case SpvOpTypeCooperativeMatrixNV:
+      // Actual dimension isn't known, return 0
+      return 0;
+
     default:
       break;
   }
@@ -844,6 +895,86 @@
   return true;
 }
 
+bool ValidationState_t::IsCooperativeMatrixType(uint32_t id) const {
+  const Instruction* inst = FindDef(id);
+  assert(inst);
+  return inst->opcode() == SpvOpTypeCooperativeMatrixNV;
+}
+
+bool ValidationState_t::IsFloatCooperativeMatrixType(uint32_t id) const {
+  if (!IsCooperativeMatrixType(id)) return false;
+  return IsFloatScalarType(FindDef(id)->word(2));
+}
+
+bool ValidationState_t::IsIntCooperativeMatrixType(uint32_t id) const {
+  if (!IsCooperativeMatrixType(id)) return false;
+  return IsIntScalarType(FindDef(id)->word(2));
+}
+
+bool ValidationState_t::IsUnsignedIntCooperativeMatrixType(uint32_t id) const {
+  if (!IsCooperativeMatrixType(id)) return false;
+  return IsUnsignedIntScalarType(FindDef(id)->word(2));
+}
+
+spv_result_t ValidationState_t::CooperativeMatrixShapesMatch(
+    const Instruction* inst, uint32_t m1, uint32_t m2) {
+  const auto m1_type = FindDef(m1);
+  const auto m2_type = FindDef(m2);
+
+  if (m1_type->opcode() != SpvOpTypeCooperativeMatrixNV ||
+      m2_type->opcode() != SpvOpTypeCooperativeMatrixNV) {
+    return diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Expected cooperative matrix types";
+  }
+
+  uint32_t m1_scope_id = m1_type->GetOperandAs<uint32_t>(2);
+  uint32_t m1_rows_id = m1_type->GetOperandAs<uint32_t>(3);
+  uint32_t m1_cols_id = m1_type->GetOperandAs<uint32_t>(4);
+
+  uint32_t m2_scope_id = m2_type->GetOperandAs<uint32_t>(2);
+  uint32_t m2_rows_id = m2_type->GetOperandAs<uint32_t>(3);
+  uint32_t m2_cols_id = m2_type->GetOperandAs<uint32_t>(4);
+
+  bool m1_is_int32 = false, m1_is_const_int32 = false, m2_is_int32 = false,
+       m2_is_const_int32 = false;
+  uint32_t m1_value = 0, m2_value = 0;
+
+  std::tie(m1_is_int32, m1_is_const_int32, m1_value) =
+      EvalInt32IfConst(m1_scope_id);
+  std::tie(m2_is_int32, m2_is_const_int32, m2_value) =
+      EvalInt32IfConst(m2_scope_id);
+
+  if (m1_is_const_int32 && m2_is_const_int32 && m1_value != m2_value) {
+    return diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Expected scopes of Matrix and Result Type to be "
+           << "identical";
+  }
+
+  std::tie(m1_is_int32, m1_is_const_int32, m1_value) =
+      EvalInt32IfConst(m1_rows_id);
+  std::tie(m2_is_int32, m2_is_const_int32, m2_value) =
+      EvalInt32IfConst(m2_rows_id);
+
+  if (m1_is_const_int32 && m2_is_const_int32 && m1_value != m2_value) {
+    return diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Expected rows of Matrix type and Result Type to be "
+           << "identical";
+  }
+
+  std::tie(m1_is_int32, m1_is_const_int32, m1_value) =
+      EvalInt32IfConst(m1_cols_id);
+  std::tie(m2_is_int32, m2_is_const_int32, m2_value) =
+      EvalInt32IfConst(m2_cols_id);
+
+  if (m1_is_const_int32 && m2_is_const_int32 && m1_value != m2_value) {
+    return diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Expected columns of Matrix type and Result Type to be "
+           << "identical";
+  }
+
+  return SPV_SUCCESS;
+}
+
 uint32_t ValidationState_t::GetOperandTypeId(const Instruction* inst,
                                              size_t operand_index) const {
   return GetTypeId(inst->GetOperandAs<uint32_t>(operand_index));
@@ -872,7 +1003,7 @@
 }
 
 std::tuple<bool, bool, uint32_t> ValidationState_t::EvalInt32IfConst(
-    uint32_t id) {
+    uint32_t id) const {
   const Instruction* const inst = FindDef(id);
   assert(inst);
   const uint32_t type = inst->type_id();
diff --git a/source/val/validation_state.h b/source/val/validation_state.h
index 85229f2..5e84e24 100644
--- a/source/val/validation_state.h
+++ b/source/val/validation_state.h
@@ -15,6 +15,7 @@
 #ifndef SOURCE_VAL_VALIDATION_STATE_H_
 #define SOURCE_VAL_VALIDATION_STATE_H_
 
+#include <algorithm>
 #include <map>
 #include <set>
 #include <string>
@@ -104,6 +105,21 @@
     // - ArrayStride and MatrixStride are multiples of scalar alignment
     // Members need not be listed in offset order
     bool scalar_block_layout = false;
+
+    // SPIR-V 1.4 allows us to select between any two composite values
+    // of the same type.
+    bool select_between_composites = false;
+
+    // SPIR-V 1.4 allows two memory access operands for OpCopyMemory and
+    // OpCopyMemorySized.
+    bool copy_memory_permits_two_memory_accesses = false;
+
+    // SPIR-V 1.4 allows UConvert as a spec constant op in any environment.
+    // The Kernel capability already enables it, separately from this flag.
+    bool uconvert_spec_constant_op = false;
+
+    // SPIR-V 1.4 allows Function and Private variables to be NonWritable
+    bool nonwritable_var_in_function_or_private = false;
   };
 
   ValidationState_t(const spv_const_context context,
@@ -339,6 +355,11 @@
   /// Returns the addressing model of this module, or Logical if uninitialized.
   SpvAddressingModel addressing_model() const;
 
+  /// Returns the addressing model of this module, or Logical if uninitialized.
+  uint32_t pointer_size_and_alignment() const {
+    return pointer_size_and_alignment_;
+  }
+
   /// Sets the memory model of this module.
   void set_memory_model(SpvMemoryModel mm);
 
@@ -387,17 +408,23 @@
   std::vector<Decoration>& id_decorations(uint32_t id) {
     return id_decorations_[id];
   }
-  const std::vector<Decoration>& id_decorations(uint32_t id) const {
-    // TODO: This would throw or generate SIGABRT if id has no
-    // decorations. Remove/refactor this function.
-    return id_decorations_.at(id);
-  }
 
   // Returns const pointer to the internal decoration container.
   const std::map<uint32_t, std::vector<Decoration>>& id_decorations() const {
     return id_decorations_;
   }
 
+  /// Returns true if the given id <id> has the given decoration <dec>,
+  /// otherwise returns false.
+  bool HasDecoration(uint32_t id, SpvDecoration dec) {
+    const auto& decorations = id_decorations_.find(id);
+    if (decorations == id_decorations_.end()) return false;
+
+    return std::any_of(
+        decorations->second.begin(), decorations->second.end(),
+        [dec](const Decoration& d) { return dec == d.dec_type(); });
+  }
+
   /// Finds id's def, if it exists.  If found, returns the definition otherwise
   /// nullptr
   const Instruction* FindDef(uint32_t id) const;
@@ -416,13 +443,13 @@
     return all_definitions_;
   }
 
-  /// Returns a vector containing the Ids of instructions that consume the given
+  /// Returns a vector containing the instructions that consume the given
   /// SampledImage id.
-  std::vector<uint32_t> getSampledImageConsumers(uint32_t id) const;
+  std::vector<Instruction*> getSampledImageConsumers(uint32_t id) const;
 
   /// Records cons_id as a consumer of sampled_image_id.
   void RegisterSampledImageConsumer(uint32_t sampled_image_id,
-                                    uint32_t cons_id);
+                                    Instruction* consumer);
 
   /// Returns the set of Global Variables.
   std::unordered_set<uint32_t>& global_vars() { return global_vars_; }
@@ -458,6 +485,18 @@
     return struct_nesting_depth_[id];
   }
 
+  /// Records the has a nested block/bufferblock decorated struct for a given
+  /// struct ID
+  void SetHasNestedBlockOrBufferBlockStruct(uint32_t id, bool has) {
+    struct_has_nested_blockorbufferblock_struct_[id] = has;
+  }
+
+  /// For a given struct ID returns true if it has a nested block/bufferblock
+  /// decorated struct
+  bool GetHasNestedBlockOrBufferBlockStruct(uint32_t id) {
+    return struct_has_nested_blockorbufferblock_struct_[id];
+  }
+
   /// Records that the structure type has a member decorated with a built-in.
   void RegisterStructTypeWithBuiltInMember(uint32_t id) {
     builtin_structs_.insert(id);
@@ -524,6 +563,10 @@
   bool IsBoolVectorType(uint32_t id) const;
   bool IsBoolScalarOrVectorType(uint32_t id) const;
   bool IsPointerType(uint32_t id) const;
+  bool IsCooperativeMatrixType(uint32_t id) const;
+  bool IsFloatCooperativeMatrixType(uint32_t id) const;
+  bool IsIntCooperativeMatrixType(uint32_t id) const;
+  bool IsUnsignedIntCooperativeMatrixType(uint32_t id) const;
 
   // Gets value from OpConstant and OpSpecConstant as uint64.
   // Returns false on failure (no instruction, wrong instruction, not int).
@@ -546,11 +589,68 @@
   bool GetPointerTypeInfo(uint32_t id, uint32_t* data_type,
                           uint32_t* storage_class) const;
 
+  // Is the ID the type of a pointer to a uniform block: Block-decorated struct
+  // in uniform storage class? The result is only valid after internal method
+  // CheckDecorationsOfBuffers has been called.
+  bool IsPointerToUniformBlock(uint32_t type_id) const {
+    return pointer_to_uniform_block_.find(type_id) !=
+           pointer_to_uniform_block_.cend();
+  }
+  // Save the ID of a pointer to uniform block.
+  void RegisterPointerToUniformBlock(uint32_t type_id) {
+    pointer_to_uniform_block_.insert(type_id);
+  }
+  // Is the ID the type of a struct used as a uniform block?
+  // The result is only valid after internal method CheckDecorationsOfBuffers
+  // has been called.
+  bool IsStructForUniformBlock(uint32_t type_id) const {
+    return struct_for_uniform_block_.find(type_id) !=
+           struct_for_uniform_block_.cend();
+  }
+  // Save the ID of a struct of a uniform block.
+  void RegisterStructForUniformBlock(uint32_t type_id) {
+    struct_for_uniform_block_.insert(type_id);
+  }
+  // Is the ID the type of a pointer to a storage buffer: BufferBlock-decorated
+  // struct in uniform storage class, or Block-decorated struct in StorageBuffer
+  // storage class? The result is only valid after internal method
+  // CheckDecorationsOfBuffers has been called.
+  bool IsPointerToStorageBuffer(uint32_t type_id) const {
+    return pointer_to_storage_buffer_.find(type_id) !=
+           pointer_to_storage_buffer_.cend();
+  }
+  // Save the ID of a pointer to a storage buffer.
+  void RegisterPointerToStorageBuffer(uint32_t type_id) {
+    pointer_to_storage_buffer_.insert(type_id);
+  }
+  // Is the ID the type of a struct for storage buffer?
+  // The result is only valid after internal method CheckDecorationsOfBuffers
+  // has been called.
+  bool IsStructForStorageBuffer(uint32_t type_id) const {
+    return struct_for_storage_buffer_.find(type_id) !=
+           struct_for_storage_buffer_.cend();
+  }
+  // Save the ID of a struct of a storage buffer.
+  void RegisterStructForStorageBuffer(uint32_t type_id) {
+    struct_for_storage_buffer_.insert(type_id);
+  }
+
+  // Is the ID the type of a pointer to a storage image?  That is, the pointee
+  // type is an image type which is known to not use a sampler.
+  bool IsPointerToStorageImage(uint32_t type_id) const {
+    return pointer_to_storage_image_.find(type_id) !=
+           pointer_to_storage_image_.cend();
+  }
+  // Save the ID of a pointer to a storage image.
+  void RegisterPointerToStorageImage(uint32_t type_id) {
+    pointer_to_storage_image_.insert(type_id);
+  }
+
   // Tries to evaluate a 32-bit signed or unsigned scalar integer constant.
   // Returns tuple <is_int32, is_const_int32, value>.
   // OpSpecConstant* return |is_const_int32| as false since their values cannot
   // be relied upon during validation.
-  std::tuple<bool, bool, uint32_t> EvalInt32IfConst(uint32_t id);
+  std::tuple<bool, bool, uint32_t> EvalInt32IfConst(uint32_t id) const;
 
   // Returns the disassembly string for the given instruction.
   std::string Disassemble(const Instruction& inst) const;
@@ -558,6 +658,12 @@
   // Returns the disassembly string for the given instruction.
   std::string Disassemble(const uint32_t* words, uint16_t num_words) const;
 
+  // Returns whether type m1 and type m2 are cooperative matrices with
+  // the same "shape" (matching scope, rows, cols). If any are specialization
+  // constants, we assume they can match because we can't prove they don't.
+  spv_result_t CooperativeMatrixShapesMatch(const Instruction* inst,
+                                            uint32_t m1, uint32_t m2);
+
  private:
   ValidationState_t(const ValidationState_t&);
 
@@ -589,7 +695,8 @@
 
   /// Stores a vector of instructions that use the result of a given
   /// OpSampledImage instruction.
-  std::unordered_map<uint32_t, std::vector<uint32_t>> sampled_image_consumers_;
+  std::unordered_map<uint32_t, std::vector<Instruction*>>
+      sampled_image_consumers_;
 
   /// A map of operand IDs and their names defined by the OpName instruction
   std::unordered_map<uint32_t, std::string> operand_names_;
@@ -643,6 +750,10 @@
   /// Structure Nesting Depth
   std::unordered_map<uint32_t, uint32_t> struct_nesting_depth_;
 
+  /// Structure has nested blockorbufferblock struct
+  std::unordered_map<uint32_t, bool>
+      struct_has_nested_blockorbufferblock_struct_;
+
   /// Stores the list of decorations for a given <id>
   std::map<uint32_t, std::vector<Decoration>> id_decorations_;
 
@@ -656,6 +767,9 @@
 
   SpvAddressingModel addressing_model_;
   SpvMemoryModel memory_model_;
+  // pointer size derived from addressing model. Assumes all storage classes
+  // have the same pointer size (for physical pointer types).
+  uint32_t pointer_size_and_alignment_;
 
   /// NOTE: See correspoding getter functions
   bool in_function_;
@@ -682,6 +796,23 @@
   std::unordered_map<uint32_t, std::vector<uint32_t>> function_to_entry_points_;
   const std::vector<uint32_t> empty_ids_;
 
+  // The IDs of types of pointers to Block-decorated structs in Uniform storage
+  // class. This is populated at the start of ValidateDecorations.
+  std::unordered_set<uint32_t> pointer_to_uniform_block_;
+  // The IDs of struct types for uniform blocks.
+  // This is populated at the start of ValidateDecorations.
+  std::unordered_set<uint32_t> struct_for_uniform_block_;
+  // The IDs of types of pointers to BufferBlock-decorated structs in Uniform
+  // storage class, or Block-decorated structs in StorageBuffer storage class.
+  // This is populated at the start of ValidateDecorations.
+  std::unordered_set<uint32_t> pointer_to_storage_buffer_;
+  // The IDs of struct types for storage buffers.
+  // This is populated at the start of ValidateDecorations.
+  std::unordered_set<uint32_t> struct_for_storage_buffer_;
+  // The IDs of types of pointers to storage images.  This is populated in the
+  // TypePass.
+  std::unordered_set<uint32_t> pointer_to_storage_image_;
+
   /// Maps ids to friendly names.
   std::unique_ptr<spvtools::FriendlyNameMapper> friendly_mapper_;
   spvtools::NameMapper name_mapper_;
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 9226ea7..bf0792c 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -129,6 +129,7 @@
   text_start_new_inst_test.cpp
   text_to_binary.annotation_test.cpp
   text_to_binary.barrier_test.cpp
+  text_to_binary.composite_test.cpp
   text_to_binary.constant_test.cpp
   text_to_binary.control_flow_test.cpp
   text_to_binary_test.cpp
diff --git a/test/assembly_context_test.cpp b/test/assembly_context_test.cpp
index b6d60b9..ee0bb24 100644
--- a/test/assembly_context_test.cpp
+++ b/test/assembly_context_test.cpp
@@ -46,7 +46,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     BinaryEncodeString, EncodeStringTest,
     ::testing::ValuesIn(std::vector<EncodeStringCase>{
       // Use cases that exercise at least one to two words,
@@ -70,7 +70,7 @@
       // A very long string, encoded after an initial word.
       // SPIR-V limits strings to 65535 characters.
       {std::string(65535, 'a'), {1}},
-    }),);
+    }));
 // clang-format on
 
 }  // namespace
diff --git a/test/binary_header_get_test.cpp b/test/binary_header_get_test.cpp
index e771f1a..dcaf992 100644
--- a/test/binary_header_get_test.cpp
+++ b/test/binary_header_get_test.cpp
@@ -51,7 +51,7 @@
   ASSERT_EQ(SPV_SUCCESS, spvBinaryHeaderGet(&const_bin, endian, &header));
 
   ASSERT_EQ(static_cast<uint32_t>(SpvMagicNumber), header.magic);
-  ASSERT_EQ(0x00010300u, header.version);
+  ASSERT_EQ(0x00010400u, header.version);
   ASSERT_EQ(static_cast<uint32_t>(SPV_GENERATOR_CODEPLAY), header.generator);
   ASSERT_EQ(1u, header.bound);
   ASSERT_EQ(0u, header.schema);
diff --git a/test/binary_parse_test.cpp b/test/binary_parse_test.cpp
index d1c38d6..b966102 100644
--- a/test/binary_parse_test.cpp
+++ b/test/binary_parse_test.cpp
@@ -197,6 +197,8 @@
 
 class BinaryParseTest : public spvtest::TextToBinaryTestBase<::testing::Test> {
  protected:
+  ~BinaryParseTest() { spvDiagnosticDestroy(diagnostic_); }
+
   void Parse(const SpirvVector& words, spv_result_t expected_result,
              bool flip_words = false) {
     SpirvVector flipped_words(words);
@@ -308,7 +310,7 @@
         EXPECT_STREQ("input", source);
         EXPECT_EQ(0u, position.line);
         EXPECT_EQ(0u, position.column);
-        EXPECT_EQ(5u, position.index);
+        EXPECT_EQ(1u, position.index);
         EXPECT_STREQ("Invalid opcode: 65535", message);
       });
 
@@ -559,7 +561,7 @@
   EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     BinaryParseDiagnostic, BinaryParseWordsAndCountDiagnosticTest,
     ::testing::ValuesIn(std::vector<WordsAndCountDiagnosticCase>{
         {nullptr, 0, "Missing module."},
@@ -573,7 +575,7 @@
          "Module has incomplete header: only 3 words instead of 5"},
         {kHeaderForBound1, 4,
          "Module has incomplete header: only 4 words instead of 5"},
-    }), );
+    }));
 
 // A binary parser diagnostic test case where a vector of words is
 // provided.  We'll use this to express cases that can't be created
@@ -596,7 +598,7 @@
   EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     BinaryParseDiagnostic, BinaryParseWordVectorDiagnosticTest,
     ::testing::ValuesIn(std::vector<WordVectorDiagnosticCase>{
         {Concatenate({ExpectedHeaderForBound(1), {spvOpcodeMake(0, SpvOpNop)}}),
@@ -814,7 +816,7 @@
              MakeInstruction(SpvOpConstant, {1, 2, 42}),
          }),
          "Type Id 1 is not a scalar numeric type"},
-    }), );
+    }));
 
 // A binary parser diagnostic case generated from an assembly text input.
 struct AssemblyDiagnosticCase {
@@ -834,7 +836,7 @@
   EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     BinaryParseDiagnostic, BinaryParseAssemblyDiagnosticTest,
     ::testing::ValuesIn(std::vector<AssemblyDiagnosticCase>{
         {"%1 = OpConstant !0 42", "Error: Type Id is 0"},
@@ -884,7 +886,7 @@
          "Invalid image operand: 32770 has invalid mask component 32768"},
         {"OpSelectionMerge %1 !7",
          "Invalid selection control operand: 7 has invalid mask component 4"},
-    }), );
+    }));
 
 }  // namespace
 }  // namespace spvtools
diff --git a/test/binary_to_text.literal_test.cpp b/test/binary_to_text.literal_test.cpp
index bcfb0f0..02daac7 100644
--- a/test/binary_to_text.literal_test.cpp
+++ b/test/binary_to_text.literal_test.cpp
@@ -32,7 +32,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     StringLiterals, RoundTripLiteralsTest,
     ::testing::ValuesIn(std::vector<std::string>{
         "OpName %1 \"\"\n",           // empty
@@ -49,7 +49,7 @@
         "OpName %1 \"\\\"foo\nbar\\\"\"\n",       // escaped quote
         "OpName %1 \"\\\\foo\nbar\\\\\"\n",       // escaped backslash
         "OpName %1 \"\xE4\xBA\xB2\"\n",             // UTF-8
-    }),);
+    }));
 // clang-format on
 
 using RoundTripSpecialCaseLiteralsTest = spvtest::TextToBinaryTestBase<
@@ -63,13 +63,13 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     StringLiterals, RoundTripSpecialCaseLiteralsTest,
     ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
       {"OpName %1 \"\\foo\"\n", "OpName %1 \"foo\"\n"}, // Escape f
       {"OpName %1 \"\\\nfoo\"\n", "OpName %1 \"\nfoo\"\n"}, // Escape newline
       {"OpName %1 \"\\\xE4\xBA\xB2\"\n", "OpName %1 \"\xE4\xBA\xB2\"\n"}, // Escape utf-8
-    }),);
+    }));
 // clang-format on
 
 }  // namespace
diff --git a/test/binary_to_text_test.cpp b/test/binary_to_text_test.cpp
index 016041f..e8a02fd 100644
--- a/test/binary_to_text_test.cpp
+++ b/test/binary_to_text_test.cpp
@@ -34,8 +34,12 @@
 
 class BinaryToText : public ::testing::Test {
  public:
-  BinaryToText() : context(spvContextCreate(SPV_ENV_UNIVERSAL_1_0)) {}
-  ~BinaryToText() { spvContextDestroy(context); }
+  BinaryToText()
+      : context(spvContextCreate(SPV_ENV_UNIVERSAL_1_0)), binary(nullptr) {}
+  ~BinaryToText() {
+    spvBinaryDestroy(binary);
+    spvContextDestroy(context);
+  }
 
   virtual void SetUp() {
     const char* textStr = R"(
@@ -63,17 +67,20 @@
     spv_diagnostic diagnostic = nullptr;
     spv_result_t error =
         spvTextToBinary(context, text.str, text.length, &binary, &diagnostic);
-    if (error) {
-      spvDiagnosticPrint(diagnostic);
-      spvDiagnosticDestroy(diagnostic);
-      ASSERT_EQ(SPV_SUCCESS, error);
-    }
+    spvDiagnosticPrint(diagnostic);
+    spvDiagnosticDestroy(diagnostic);
+    ASSERT_EQ(SPV_SUCCESS, error);
   }
 
-  virtual void TearDown() { spvBinaryDestroy(binary); }
+  virtual void TearDown() {
+    spvBinaryDestroy(binary);
+    binary = nullptr;
+  }
 
   // Compiles the given assembly text, and saves it into 'binary'.
   void CompileSuccessfully(std::string text) {
+    spvBinaryDestroy(binary);
+    binary = nullptr;
     spv_diagnostic diagnostic = nullptr;
     EXPECT_EQ(SPV_SUCCESS, spvTextToBinary(context, text.c_str(), text.size(),
                                            &binary, &diagnostic));
@@ -164,7 +171,7 @@
               Eq(GetParam().expected_error_message));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     InvalidIds, BinaryToTextFail,
     ::testing::ValuesIn(std::vector<FailedDecodeCase>{
         {"", spvtest::MakeInstruction(SpvOpTypeVoid, {0}),
@@ -189,9 +196,9 @@
          "%2 = OpTypeVector %1 4",
          spvtest::MakeInstruction(SpvOpConstant, {2, 3, 999}),
          "Type Id 2 is not a scalar numeric type"},
-    }), );
+    }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     InvalidIdsCheckedDuringLiteralCaseParsing, BinaryToTextFail,
     ::testing::ValuesIn(std::vector<FailedDecodeCase>{
         {"", spvtest::MakeInstruction(SpvOpSwitch, {1, 2, 3, 4}),
@@ -205,7 +212,7 @@
         {"%1 = OpTypeFloat 32\n%2 = OpConstant %1 1.5",
          spvtest::MakeInstruction(SpvOpSwitch, {2, 3, 4, 5}),
          "Invalid OpSwitch: selector id 2 is not a scalar integer"},
-    }), );
+    }));
 
 TEST_F(TextToBinaryTest, OneInstruction) {
   const std::string input = "OpSource OpenCL_C 12\n";
@@ -236,7 +243,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     NumericLiterals, RoundTripInstructionsTest,
     // This test is independent of environment, so just test the one.
     Combine(::testing::Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
@@ -268,10 +275,10 @@
                 "%1 = OpTypeFloat 64\n%2 = OpConstant %1 -0x1.0002p+1024\n", // NaN
                 "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0x1p+1024\n", // Inf
                 "%1 = OpTypeFloat 64\n%2 = OpConstant %1 -0x1p+1024\n", // -Inf
-            })), );
+            })));
 // clang-format on
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     MemoryAccessMasks, RoundTripInstructionsTest,
     Combine(::testing::Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
                               SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_3),
@@ -285,9 +292,9 @@
                 "OpStore %1 %2 Volatile|Aligned 16\n",
                 "OpStore %1 %2 Volatile|Nontemporal\n",
                 "OpStore %1 %2 Volatile|Aligned|Nontemporal 32\n",
-            })), );
+            })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     FPFastMathModeMasks, RoundTripInstructionsTest,
     Combine(
         ::testing::Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
@@ -303,9 +310,9 @@
             "OpDecorate %1 FPFastMathMode NotNaN|NotInf\n",
             "OpDecorate %1 FPFastMathMode NSZ|AllowRecip\n",
             "OpDecorate %1 FPFastMathMode NotNaN|NotInf|NSZ|AllowRecip|Fast\n",
-        })), );
+        })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LoopControlMasks, RoundTripInstructionsTest,
     Combine(::testing::Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
                               SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_2),
@@ -314,18 +321,18 @@
                 "OpLoopMerge %1 %2 Unroll\n",
                 "OpLoopMerge %1 %2 DontUnroll\n",
                 "OpLoopMerge %1 %2 Unroll|DontUnroll\n",
-            })), );
+            })));
 
-INSTANTIATE_TEST_CASE_P(LoopControlMasksV11, RoundTripInstructionsTest,
-                        Combine(::testing::Values(SPV_ENV_UNIVERSAL_1_1,
-                                                  SPV_ENV_UNIVERSAL_1_2,
-                                                  SPV_ENV_UNIVERSAL_1_3),
-                                ::testing::ValuesIn(std::vector<std::string>{
-                                    "OpLoopMerge %1 %2 DependencyInfinite\n",
-                                    "OpLoopMerge %1 %2 DependencyLength 8\n",
-                                })), );
+INSTANTIATE_TEST_SUITE_P(LoopControlMasksV11, RoundTripInstructionsTest,
+                         Combine(::testing::Values(SPV_ENV_UNIVERSAL_1_1,
+                                                   SPV_ENV_UNIVERSAL_1_2,
+                                                   SPV_ENV_UNIVERSAL_1_3),
+                                 ::testing::ValuesIn(std::vector<std::string>{
+                                     "OpLoopMerge %1 %2 DependencyInfinite\n",
+                                     "OpLoopMerge %1 %2 DependencyLength 8\n",
+                                 })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SelectionControlMasks, RoundTripInstructionsTest,
     Combine(::testing::Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
                               SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_2),
@@ -334,9 +341,9 @@
                 "OpSelectionMerge %1 Flatten\n",
                 "OpSelectionMerge %1 DontFlatten\n",
                 "OpSelectionMerge %1 Flatten|DontFlatten\n",
-            })), );
+            })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     FunctionControlMasks, RoundTripInstructionsTest,
     Combine(::testing::Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
                               SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_3),
@@ -348,9 +355,9 @@
                 "%2 = OpFunction %1 Const %3\n",
                 "%2 = OpFunction %1 Inline|Pure|Const %3\n",
                 "%2 = OpFunction %1 DontInline|Const %3\n",
-            })), );
+            })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ImageMasks, RoundTripInstructionsTest,
     Combine(::testing::Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
                               SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_3),
@@ -371,9 +378,9 @@
                 "%2 = OpImageFetch %1 %3 %4 Sample|MinLod %5 %6\n",
                 "%2 = OpImageFetch %1 %3 %4"
                 " Bias|Lod|Grad|ConstOffset|Offset|ConstOffsets|Sample|MinLod"
-                " %5 %6 %7 %8 %9 %10 %11 %12 %13\n"})), );
+                " %5 %6 %7 %8 %9 %10 %11 %12 %13\n"})));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     NewInstructionsInSPIRV1_2, RoundTripInstructionsTest,
     Combine(::testing::Values(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_3),
             ::testing::ValuesIn(std::vector<std::string>{
@@ -382,7 +389,7 @@
                 "OpExecutionModeId %1 LocalSizeHintId %2\n",
                 "OpDecorateId %1 AlignmentId %2\n",
                 "OpDecorateId %1 MaxByteOffsetId %2\n",
-            })), );
+            })));
 
 using MaskSorting = TextToBinaryTest;
 
@@ -530,23 +537,23 @@
   spvTextDestroy(decoded_text);
 }
 
-INSTANTIATE_TEST_CASE_P(GeneratorStrings, GeneratorStringTest,
-                        ::testing::ValuesIn(std::vector<GeneratorStringCase>{
-                            {SPV_GENERATOR_KHRONOS, 12, "Khronos; 12"},
-                            {SPV_GENERATOR_LUNARG, 99, "LunarG; 99"},
-                            {SPV_GENERATOR_VALVE, 1, "Valve; 1"},
-                            {SPV_GENERATOR_CODEPLAY, 65535, "Codeplay; 65535"},
-                            {SPV_GENERATOR_NVIDIA, 19, "NVIDIA; 19"},
-                            {SPV_GENERATOR_ARM, 1000, "ARM; 1000"},
-                            {SPV_GENERATOR_KHRONOS_LLVM_TRANSLATOR, 38,
-                             "Khronos LLVM/SPIR-V Translator; 38"},
-                            {SPV_GENERATOR_KHRONOS_ASSEMBLER, 2,
-                             "Khronos SPIR-V Tools Assembler; 2"},
-                            {SPV_GENERATOR_KHRONOS_GLSLANG, 1,
-                             "Khronos Glslang Reference Front End; 1"},
-                            {1000, 18, "Unknown(1000); 18"},
-                            {65535, 32767, "Unknown(65535); 32767"},
-                        }), );
+INSTANTIATE_TEST_SUITE_P(GeneratorStrings, GeneratorStringTest,
+                         ::testing::ValuesIn(std::vector<GeneratorStringCase>{
+                             {SPV_GENERATOR_KHRONOS, 12, "Khronos; 12"},
+                             {SPV_GENERATOR_LUNARG, 99, "LunarG; 99"},
+                             {SPV_GENERATOR_VALVE, 1, "Valve; 1"},
+                             {SPV_GENERATOR_CODEPLAY, 65535, "Codeplay; 65535"},
+                             {SPV_GENERATOR_NVIDIA, 19, "NVIDIA; 19"},
+                             {SPV_GENERATOR_ARM, 1000, "ARM; 1000"},
+                             {SPV_GENERATOR_KHRONOS_LLVM_TRANSLATOR, 38,
+                              "Khronos LLVM/SPIR-V Translator; 38"},
+                             {SPV_GENERATOR_KHRONOS_ASSEMBLER, 2,
+                              "Khronos SPIR-V Tools Assembler; 2"},
+                             {SPV_GENERATOR_KHRONOS_GLSLANG, 1,
+                              "Khronos Glslang Reference Front End; 1"},
+                             {1000, 18, "Unknown(1000); 18"},
+                             {65535, 32767, "Unknown(65535); 32767"},
+                         }));
 
 // TODO(dneto): Test new instructions and enums in SPIR-V 1.3
 
diff --git a/test/c_interface_test.cpp b/test/c_interface_test.cpp
index 1b735be..c644fb9 100644
--- a/test/c_interface_test.cpp
+++ b/test/c_interface_test.cpp
@@ -150,7 +150,7 @@
         EXPECT_STREQ("input", source);
         EXPECT_EQ(0u, position.line);
         EXPECT_EQ(0u, position.column);
-        EXPECT_EQ(5u, position.index);
+        EXPECT_EQ(1u, position.index);
         EXPECT_STREQ("Invalid opcode: 65535", message);
       });
 
diff --git a/test/comp/markv_codec_test.cpp b/test/comp/markv_codec_test.cpp
index 283fcd3..a7af617 100644
--- a/test/comp/markv_codec_test.cpp
+++ b/test/comp/markv_codec_test.cpp
@@ -817,12 +817,12 @@
 )");
 }
 
-INSTANTIATE_TEST_CASE_P(AllMarkvModels, MarkvTest,
-                        ::testing::ValuesIn(std::vector<MarkvModelType>{
-                            kMarkvModelShaderLite,
-                            kMarkvModelShaderMid,
-                            kMarkvModelShaderMax,
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllMarkvModels, MarkvTest,
+                         ::testing::ValuesIn(std::vector<MarkvModelType>{
+                             kMarkvModelShaderLite,
+                             kMarkvModelShaderMid,
+                             kMarkvModelShaderMax,
+                         }));
 
 }  // namespace
 }  // namespace comp
diff --git a/test/enum_set_test.cpp b/test/enum_set_test.cpp
index ddacd42..047d642 100644
--- a/test/enum_set_test.cpp
+++ b/test/enum_set_test.cpp
@@ -267,24 +267,24 @@
   EXPECT_THAT(ElementsIn(assigned), Eq(GetParam().expected));
 }
 
-INSTANTIATE_TEST_CASE_P(Samples, CapabilitySetForEachTest,
-                        ValuesIn(std::vector<ForEachCase>{
-                            {{}, {}},
-                            {{SpvCapabilityMatrix}, {SpvCapabilityMatrix}},
-                            {{SpvCapabilityKernel, SpvCapabilityShader},
-                             {SpvCapabilityShader, SpvCapabilityKernel}},
-                            {{static_cast<SpvCapability>(999)},
-                             {static_cast<SpvCapability>(999)}},
-                            {{static_cast<SpvCapability>(0x7fffffff)},
-                             {static_cast<SpvCapability>(0x7fffffff)}},
-                            // Mixture and out of order
-                            {{static_cast<SpvCapability>(0x7fffffff),
-                              static_cast<SpvCapability>(100),
-                              SpvCapabilityShader, SpvCapabilityMatrix},
-                             {SpvCapabilityMatrix, SpvCapabilityShader,
-                              static_cast<SpvCapability>(100),
-                              static_cast<SpvCapability>(0x7fffffff)}},
-                        }), );
+INSTANTIATE_TEST_SUITE_P(Samples, CapabilitySetForEachTest,
+                         ValuesIn(std::vector<ForEachCase>{
+                             {{}, {}},
+                             {{SpvCapabilityMatrix}, {SpvCapabilityMatrix}},
+                             {{SpvCapabilityKernel, SpvCapabilityShader},
+                              {SpvCapabilityShader, SpvCapabilityKernel}},
+                             {{static_cast<SpvCapability>(999)},
+                              {static_cast<SpvCapability>(999)}},
+                             {{static_cast<SpvCapability>(0x7fffffff)},
+                              {static_cast<SpvCapability>(0x7fffffff)}},
+                             // Mixture and out of order
+                             {{static_cast<SpvCapability>(0x7fffffff),
+                               static_cast<SpvCapability>(100),
+                               SpvCapabilityShader, SpvCapabilityMatrix},
+                              {SpvCapabilityMatrix, SpvCapabilityShader,
+                               static_cast<SpvCapability>(100),
+                               static_cast<SpvCapability>(0x7fffffff)}},
+                         }));
 
 }  // namespace
 }  // namespace spvtools
diff --git a/test/enum_string_mapping_test.cpp b/test/enum_string_mapping_test.cpp
index b525d60..a0379c1 100644
--- a/test/enum_string_mapping_test.cpp
+++ b/test/enum_string_mapping_test.cpp
@@ -64,7 +64,7 @@
   EXPECT_EQ(capability_str, result_str);
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     AllExtensions, ExtensionTest,
     ValuesIn(std::vector<std::pair<Extension, std::string>>({
         {Extension::kSPV_KHR_16bit_storage, "SPV_KHR_16bit_storage"},
@@ -89,13 +89,13 @@
         {Extension::kSPV_KHR_8bit_storage, "SPV_KHR_8bit_storage"},
     })));
 
-INSTANTIATE_TEST_CASE_P(UnknownExtensions, UnknownExtensionTest,
-                        Values("", "SPV_KHR_", "SPV_KHR_device_group_ERROR",
-                               /*alphabetically before all extensions*/ "A",
-                               /*alphabetically after all extensions*/ "Z",
-                               "SPV_ERROR_random_string_hfsdklhlktherh"));
+INSTANTIATE_TEST_SUITE_P(UnknownExtensions, UnknownExtensionTest,
+                         Values("", "SPV_KHR_", "SPV_KHR_device_group_ERROR",
+                                /*alphabetically before all extensions*/ "A",
+                                /*alphabetically after all extensions*/ "Z",
+                                "SPV_ERROR_random_string_hfsdklhlktherh"));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     AllCapabilities, CapabilityTest,
     ValuesIn(std::vector<std::pair<SpvCapability, std::string>>(
         {{SpvCapabilityMatrix, "Matrix"},
@@ -189,7 +189,7 @@
           "ShaderViewportIndexLayerEXT"},
          {SpvCapabilityShaderViewportMaskNV, "ShaderViewportMaskNV"},
          {SpvCapabilityShaderStereoViewNV, "ShaderStereoViewNV"},
-         {SpvCapabilityPerViewAttributesNV, "PerViewAttributesNV"}})), );
+         {SpvCapabilityPerViewAttributesNV, "PerViewAttributesNV"}})));
 
 }  // namespace
 }  // namespace spvtools
diff --git a/test/ext_inst.debuginfo_test.cpp b/test/ext_inst.debuginfo_test.cpp
index 15fa8f7..ec012e0 100644
--- a/test/ext_inst.debuginfo_test.cpp
+++ b/test/ext_inst.debuginfo_test.cpp
@@ -368,30 +368,30 @@
   }
 
 // DebugInfo 4.1 Absent Debugging Information
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugInfoNone, ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_0(InfoNone),  // enum value 0
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugInfoNone, ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_0(InfoNone),  // enum value 0
+                         })));
 
 // DebugInfo 4.2 Compilation Unit
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugCompilationUnit,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_ILL(CompilationUnit, 100, 42),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugCompilationUnit,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_ILL(CompilationUnit, 100, 42),
+                         })));
 
 // DebugInfo 4.3 Type instructions
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeBasic, ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_IIE(TypeBasic, Unspecified),
-                            CASE_IIE(TypeBasic, Address),
-                            CASE_IIE(TypeBasic, Boolean),
-                            CASE_IIE(TypeBasic, Float),
-                            CASE_IIE(TypeBasic, Signed),
-                            CASE_IIE(TypeBasic, SignedChar),
-                            CASE_IIE(TypeBasic, Unsigned),
-                            CASE_IIE(TypeBasic, UnsignedChar),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugTypeBasic, ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_IIE(TypeBasic, Unspecified),
+                             CASE_IIE(TypeBasic, Address),
+                             CASE_IIE(TypeBasic, Boolean),
+                             CASE_IIE(TypeBasic, Float),
+                             CASE_IIE(TypeBasic, Signed),
+                             CASE_IIE(TypeBasic, SignedChar),
+                             CASE_IIE(TypeBasic, Unsigned),
+                             CASE_IIE(TypeBasic, UnsignedChar),
+                         })));
 
 // The FlagIsPublic is value is (1 << 0) | (1 << 2) which is the same
 // as the bitwise-OR of FlagIsProtected and FlagIsPrivate.
@@ -417,7 +417,7 @@
   EXPECT_THAT(EncodeAndDecodeSuccessfully(input), Eq(expected)) << input;
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DebugInfoDebugTypePointer, ExtInstDebugInfoRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
 
@@ -463,49 +463,50 @@
                 uint32_t(DebugInfoFlagIndirectVariable) |
                 uint32_t(DebugInfoFlagIsOptimized)),
 
-    })), );
+    })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeQualifier,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_IE(TypeQualifier, ConstType),
-                            CASE_IE(TypeQualifier, VolatileType),
-                            CASE_IE(TypeQualifier, RestrictType),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugTypeQualifier,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_IE(TypeQualifier, ConstType),
+                             CASE_IE(TypeQualifier, VolatileType),
+                             CASE_IE(TypeQualifier, RestrictType),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeArray, ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_II(TypeArray),
-                            CASE_III(TypeArray),
-                            CASE_IIII(TypeArray),
-                            CASE_IIIII(TypeArray),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugTypeArray, ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_II(TypeArray),
+                             CASE_III(TypeArray),
+                             CASE_IIII(TypeArray),
+                             CASE_IIIII(TypeArray),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeVector, ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_IL(TypeVector, 2),
-                            CASE_IL(TypeVector, 3),
-                            CASE_IL(TypeVector, 4),
-                            CASE_IL(TypeVector, 16),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugTypeVector,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_IL(TypeVector, 2),
+                             CASE_IL(TypeVector, 3),
+                             CASE_IL(TypeVector, 4),
+                             CASE_IL(TypeVector, 16),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypedef, ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_IIILLI(Typedef, 12, 13),
-                            CASE_IIILLI(Typedef, 14, 99),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugTypedef, ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_IIILLI(Typedef, 12, 13),
+                             CASE_IIILLI(Typedef, 14, 99),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeFunction,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_I(TypeFunction),
-                            CASE_II(TypeFunction),
-                            CASE_III(TypeFunction),
-                            CASE_IIII(TypeFunction),
-                            CASE_IIIII(TypeFunction),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugTypeFunction,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_I(TypeFunction),
+                             CASE_II(TypeFunction),
+                             CASE_III(TypeFunction),
+                             CASE_IIII(TypeFunction),
+                             CASE_IIIII(TypeFunction),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DebugInfoDebugTypeEnum, ExtInstDebugInfoRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         CASE_IIILLIIFII(
@@ -518,9 +519,9 @@
                           uint32_t(DebugInfoFlagStaticMember)),
         CASE_IIILLIIFIIIIII(TypeEnum, 99, 1, "FlagStaticMember",
                             uint32_t(DebugInfoFlagStaticMember)),
-    })), );
+    })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DebugInfoDebugTypeComposite, ExtInstDebugInfoRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         CASE_IEILLIIF(
@@ -545,22 +546,22 @@
                          uint32_t(DebugInfoFlagIsPrivate)),
         CASE_IEILLIIFIIII(TypeComposite, Class, 9, 10, "FlagIsPrivate",
                           uint32_t(DebugInfoFlagIsPrivate)),
-    })), );
+    })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeMember, ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_IIILLIIIF(TypeMember, 12, 13, "FlagIsPrivate",
-                                           uint32_t(DebugInfoFlagIsPrivate)),
-                            CASE_IIILLIIIF(TypeMember, 99, 100,
-                                           "FlagIsPrivate|FlagFwdDecl",
-                                           uint32_t(DebugInfoFlagIsPrivate) |
-                                               uint32_t(DebugInfoFlagFwdDecl)),
-                            // Add the optional Id argument.
-                            CASE_IIILLIIIFI(TypeMember, 12, 13, "FlagIsPrivate",
-                                            uint32_t(DebugInfoFlagIsPrivate)),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(
+    DebugInfoDebugTypeMember, ExtInstDebugInfoRoundTripTest,
+    ::testing::ValuesIn(std::vector<InstructionCase>({
+        CASE_IIILLIIIF(TypeMember, 12, 13, "FlagIsPrivate",
+                       uint32_t(DebugInfoFlagIsPrivate)),
+        CASE_IIILLIIIF(TypeMember, 99, 100, "FlagIsPrivate|FlagFwdDecl",
+                       uint32_t(DebugInfoFlagIsPrivate) |
+                           uint32_t(DebugInfoFlagFwdDecl)),
+        // Add the optional Id argument.
+        CASE_IIILLIIIFI(TypeMember, 12, 13, "FlagIsPrivate",
+                        uint32_t(DebugInfoFlagIsPrivate)),
+    })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DebugInfoDebugTypeInheritance, ExtInstDebugInfoRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         CASE_IIIIF(TypeInheritance, "FlagIsPrivate",
@@ -568,53 +569,53 @@
         CASE_IIIIF(TypeInheritance, "FlagIsPrivate|FlagFwdDecl",
                    uint32_t(DebugInfoFlagIsPrivate) |
                        uint32_t(DebugInfoFlagFwdDecl)),
-    })), );
+    })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypePtrToMember,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_II(TypePtrToMember),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugTypePtrToMember,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_II(TypePtrToMember),
+                         })));
 
 // DebugInfo 4.4 Templates
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeTemplate,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_II(TypeTemplate),
-                            CASE_III(TypeTemplate),
-                            CASE_IIII(TypeTemplate),
-                            CASE_IIIII(TypeTemplate),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugTypeTemplate,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_II(TypeTemplate),
+                             CASE_III(TypeTemplate),
+                             CASE_IIII(TypeTemplate),
+                             CASE_IIIII(TypeTemplate),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeTemplateParameter,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_IIIILL(TypeTemplateParameter, 1, 2),
-                            CASE_IIIILL(TypeTemplateParameter, 99, 102),
-                            CASE_IIIILL(TypeTemplateParameter, 10, 7),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugTypeTemplateParameter,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_IIIILL(TypeTemplateParameter, 1, 2),
+                             CASE_IIIILL(TypeTemplateParameter, 99, 102),
+                             CASE_IIIILL(TypeTemplateParameter, 10, 7),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeTemplateTemplateParameter,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_IIILL(TypeTemplateTemplateParameter, 1, 2),
-                            CASE_IIILL(TypeTemplateTemplateParameter, 99, 102),
-                            CASE_IIILL(TypeTemplateTemplateParameter, 10, 7),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugTypeTemplateTemplateParameter,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_IIILL(TypeTemplateTemplateParameter, 1, 2),
+                             CASE_IIILL(TypeTemplateTemplateParameter, 99, 102),
+                             CASE_IIILL(TypeTemplateTemplateParameter, 10, 7),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeTemplateParameterPack,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_IILLI(TypeTemplateParameterPack, 1, 2),
-                            CASE_IILLII(TypeTemplateParameterPack, 99, 102),
-                            CASE_IILLIII(TypeTemplateParameterPack, 10, 7),
-                            CASE_IILLIIII(TypeTemplateParameterPack, 10, 7),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugTypeTemplateParameterPack,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_IILLI(TypeTemplateParameterPack, 1, 2),
+                             CASE_IILLII(TypeTemplateParameterPack, 99, 102),
+                             CASE_IILLIII(TypeTemplateParameterPack, 10, 7),
+                             CASE_IILLIIII(TypeTemplateParameterPack, 10, 7),
+                         })));
 
 // DebugInfo 4.5 Global Variables
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DebugInfoDebugGlobalVariable, ExtInstDebugInfoRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         CASE_IIILLIIIF(GlobalVariable, 1, 2, "FlagIsOptimized",
@@ -625,20 +626,20 @@
                         uint32_t(DebugInfoFlagIsOptimized)),
         CASE_IIILLIIIFI(GlobalVariable, 42, 43, "FlagIsOptimized",
                         uint32_t(DebugInfoFlagIsOptimized)),
-    })), );
+    })));
 
 // DebugInfo 4.6 Functions
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DebugInfoDebugFunctionDeclaration, ExtInstDebugInfoRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         CASE_IIILLIIF(FunctionDeclaration, 1, 2, "FlagIsOptimized",
                       uint32_t(DebugInfoFlagIsOptimized)),
         CASE_IIILLIIF(FunctionDeclaration, 42, 43, "FlagFwdDecl",
                       uint32_t(DebugInfoFlagFwdDecl)),
-    })), );
+    })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DebugInfoDebugFunction, ExtInstDebugInfoRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         CASE_IIILLIIFLI(Function, 1, 2, "FlagIsOptimized",
@@ -650,65 +651,65 @@
                          uint32_t(DebugInfoFlagIsOptimized), 3),
         CASE_IIILLIIFLII(Function, 42, 43, "FlagFwdDecl",
                          uint32_t(DebugInfoFlagFwdDecl), 44),
-    })), );
+    })));
 
 // DebugInfo 4.7 Local Information
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugLexicalBlock,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_ILLII(LexicalBlock, 1, 2),
-                            CASE_ILLII(LexicalBlock, 42, 43),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugLexicalBlock,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_ILLII(LexicalBlock, 1, 2),
+                             CASE_ILLII(LexicalBlock, 42, 43),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugLexicalBlockDiscriminator,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_ILI(LexicalBlockDiscriminator, 1),
-                            CASE_ILI(LexicalBlockDiscriminator, 42),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugLexicalBlockDiscriminator,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_ILI(LexicalBlockDiscriminator, 1),
+                             CASE_ILI(LexicalBlockDiscriminator, 42),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugScope, ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_I(Scope),
-                            CASE_II(Scope),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugScope, ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_I(Scope),
+                             CASE_II(Scope),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugNoScope, ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_0(NoScope),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugNoScope, ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_0(NoScope),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugInlinedAt, ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_LII(InlinedAt, 1),
-                            CASE_LII(InlinedAt, 42),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugInlinedAt, ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_LII(InlinedAt, 1),
+                             CASE_LII(InlinedAt, 42),
+                         })));
 
 // DebugInfo 4.8 Local Variables
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugLocalVariable,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_IIILLI(LocalVariable, 1, 2),
-                            CASE_IIILLI(LocalVariable, 42, 43),
-                            CASE_IIILLIL(LocalVariable, 1, 2, 3),
-                            CASE_IIILLIL(LocalVariable, 42, 43, 44),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugLocalVariable,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_IIILLI(LocalVariable, 1, 2),
+                             CASE_IIILLI(LocalVariable, 42, 43),
+                             CASE_IIILLIL(LocalVariable, 1, 2, 3),
+                             CASE_IIILLIL(LocalVariable, 42, 43, 44),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugInlinedVariable,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_II(InlinedVariable),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugInlinedVariable,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_II(InlinedVariable),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugDebugDeclare,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_III(Declare),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugDebugDeclare,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_III(Declare),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DebugInfoDebugDebugValue, ExtInstDebugInfoRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         CASE_III(Value),
@@ -717,53 +718,54 @@
         CASE_IIIIII(Value),
         // Test up to 4 id parameters. We can always try more.
         CASE_IIIIIII(Value),
-    })), );
+    })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugDebugOperation,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_E(Operation, Deref),
-                            CASE_E(Operation, Plus),
-                            CASE_E(Operation, Minus),
-                            CASE_EL(Operation, PlusUconst, 1),
-                            CASE_EL(Operation, PlusUconst, 42),
-                            CASE_ELL(Operation, BitPiece, 1, 2),
-                            CASE_ELL(Operation, BitPiece, 4, 5),
-                            CASE_E(Operation, Swap),
-                            CASE_E(Operation, Xderef),
-                            CASE_E(Operation, StackValue),
-                            CASE_EL(Operation, Constu, 1),
-                            CASE_EL(Operation, Constu, 42),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugDebugOperation,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_E(Operation, Deref),
+                             CASE_E(Operation, Plus),
+                             CASE_E(Operation, Minus),
+                             CASE_EL(Operation, PlusUconst, 1),
+                             CASE_EL(Operation, PlusUconst, 42),
+                             CASE_ELL(Operation, BitPiece, 1, 2),
+                             CASE_ELL(Operation, BitPiece, 4, 5),
+                             CASE_E(Operation, Swap),
+                             CASE_E(Operation, Xderef),
+                             CASE_E(Operation, StackValue),
+                             CASE_EL(Operation, Constu, 1),
+                             CASE_EL(Operation, Constu, 42),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugDebugExpression,
-                        ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_0(Expression),
-                            CASE_I(Expression),
-                            CASE_II(Expression),
-                            CASE_III(Expression),
-                            CASE_IIII(Expression),
-                            CASE_IIIII(Expression),
-                            CASE_IIIIII(Expression),
-                            CASE_IIIIIII(Expression),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugDebugExpression,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_0(Expression),
+                             CASE_I(Expression),
+                             CASE_II(Expression),
+                             CASE_III(Expression),
+                             CASE_IIII(Expression),
+                             CASE_IIIII(Expression),
+                             CASE_IIIIII(Expression),
+                             CASE_IIIIIII(Expression),
+                         })));
 
 // DebugInfo 4.9 Macros
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugMacroDef, ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_ILI(MacroDef, 1),
-                            CASE_ILI(MacroDef, 42),
-                            CASE_ILII(MacroDef, 1),
-                            CASE_ILII(MacroDef, 42),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugMacroDef, ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_ILI(MacroDef, 1),
+                             CASE_ILI(MacroDef, 42),
+                             CASE_ILII(MacroDef, 1),
+                             CASE_ILII(MacroDef, 42),
+                         })));
 
-INSTANTIATE_TEST_CASE_P(DebugInfoDebugMacroUndef, ExtInstDebugInfoRoundTripTest,
-                        ::testing::ValuesIn(std::vector<InstructionCase>({
-                            CASE_ILI(MacroUndef, 1),
-                            CASE_ILI(MacroUndef, 42),
-                        })), );
+INSTANTIATE_TEST_SUITE_P(DebugInfoDebugMacroUndef,
+                         ExtInstDebugInfoRoundTripTest,
+                         ::testing::ValuesIn(std::vector<InstructionCase>({
+                             CASE_ILI(MacroUndef, 1),
+                             CASE_ILI(MacroUndef, 42),
+                         })));
 
 #undef CASE_0
 #undef CASE_ILL
diff --git a/test/ext_inst.glsl_test.cpp b/test/ext_inst.glsl_test.cpp
index 991c487..41d222f 100644
--- a/test/ext_inst.glsl_test.cpp
+++ b/test/ext_inst.glsl_test.cpp
@@ -61,7 +61,7 @@
 ; Generator: Khronos SPIR-V Tools Assembler; 0
 ; Bound: 9
 ; Schema: 0)";
-  spv_binary binary;
+  spv_binary binary = nullptr;
   spv_diagnostic diagnostic;
   spv_result_t error = spvTextToBinary(context, spirv.c_str(), spirv.size(),
                                        &binary, &diagnostic);
@@ -102,10 +102,11 @@
   }
   EXPECT_EQ(spirv_header + spirv, output_text->str);
   spvTextDestroy(output_text);
+  spvBinaryDestroy(binary);
   spvContextDestroy(context);
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ExtInstParameters, ExtInstGLSLstd450RoundTripTest,
     ::testing::ValuesIn(std::vector<ExtInstContext>({
         // We are only testing the correctness of encoding and decoding here.
@@ -197,7 +198,7 @@
         {"NMin", "%5 %5", 79, 7, {5, 5}},
         {"NMax", "%5 %5", 80, 7, {5, 5}},
         {"NClamp", "%5 %5 %5", 81, 8, {5, 5, 5}},
-    })), );
+    })));
 
 }  // namespace
 }  // namespace spvtools
diff --git a/test/ext_inst.opencl_test.cpp b/test/ext_inst.opencl_test.cpp
index 06bc5e8..7dd903e 100644
--- a/test/ext_inst.opencl_test.cpp
+++ b/test/ext_inst.opencl_test.cpp
@@ -90,7 +90,7 @@
 
 // clang-format off
 // OpenCL.std: 2.1 Math extended instructions
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpenCLMath, ExtInstOpenCLStdRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         // We are only testing the correctness of encoding and decoding here.
@@ -190,10 +190,10 @@
         CASE1(Native_sin, native_sin),
         CASE1(Native_sqrt, native_sqrt),
         CASE1(Native_tan, native_tan), // enum value 94
-    })),);
+    })));
 
 // OpenCL.std: 2.1 Integer instructions
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpenCLInteger, ExtInstOpenCLStdRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         CASE1(SAbs, s_abs), // enum value 141
@@ -230,10 +230,10 @@
         CASE2(UAbs_diff, u_abs_diff),
         CASE2(UMul_hi, u_mul_hi),
         CASE3(UMad_hi, u_mad_hi), // enum value 204
-    })),);
+    })));
 
 // OpenCL.std: 2.3 Common instrucitons
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpenCLCommon, ExtInstOpenCLStdRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         CASE3(FClamp, fclamp), // enum value 95
@@ -245,10 +245,10 @@
         CASE2(Step, step),
         CASE3(Smoothstep, smoothstep),
         CASE1(Sign, sign), // enum value 103
-    })),);
+    })));
 
 // OpenCL.std: 2.4 Geometric instructions
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpenCLGeometric, ExtInstOpenCLStdRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         CASE2(Cross, cross), // enum value 104
@@ -258,18 +258,18 @@
         CASE2(Fast_distance, fast_distance),
         CASE1(Fast_length, fast_length),
         CASE1(Fast_normalize, fast_normalize), // enum value 110
-    })),);
+    })));
 
 // OpenCL.std: 2.5 Relational instructions
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpenCLRelational, ExtInstOpenCLStdRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         CASE3(Bitselect, bitselect), // enum value 186
         CASE3(Select, select), // enum value 187
-    })),);
+    })));
 
 // OpenCL.std: 2.6 Vector data load and store instructions
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpenCLVectorLoadStore, ExtInstOpenCLStdRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         // The last argument to Vloadn must be one of 2, 3, 4, 8, 16.
@@ -306,20 +306,20 @@
         CASE3Round(Vstorea_halfn_r, vstorea_halfn_r, RTZ),
         CASE3Round(Vstorea_halfn_r, vstorea_halfn_r, RTP),
         CASE3Round(Vstorea_halfn_r, vstorea_halfn_r, RTN),
-    })),);
+    })));
 
 // OpenCL.std: 2.7 Miscellaneous vector instructions
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpenCLMiscellaneousVector, ExtInstOpenCLStdRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         CASE2(Shuffle, shuffle),
         CASE3(Shuffle2, shuffle2),
-    })),);
+    })));
 
 // OpenCL.std: 2.8 Miscellaneous instructions
 
 #define PREFIX uint32_t(OpenCLLIB::Entrypoints::Printf), "printf"
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpenCLMiscPrintf, ExtInstOpenCLStdRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
       // Printf is interesting because it takes a variable number of arguments.
@@ -338,14 +338,14 @@
         {4, 5, 6, 7, 8, 9, 10, 11, 12, 13}},
       {PREFIX, "%4 %5 %6 %7 %8 %9 %10 %11 %12 %13 %14",
         {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}},
-    })),);
+    })));
 #undef PREFIX
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpenCLMiscPrefetch, ExtInstOpenCLStdRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         CASE2(Prefetch, prefetch),
-    })),);
+    })));
 
 // OpenCL.std: 2.9.1 Image encoding
 // No new instructions defined in this section.
diff --git a/test/generator_magic_number_test.cpp b/test/generator_magic_number_test.cpp
index bc5fdf5..7131ac4 100644
--- a/test/generator_magic_number_test.cpp
+++ b/test/generator_magic_number_test.cpp
@@ -34,7 +34,7 @@
               GetParam().name());
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Registered, GeneratorMagicNumberTest,
     ::testing::ValuesIn(std::vector<EnumCase<spv_generator_t>>{
         {SPV_GENERATOR_KHRONOS, "Khronos"},
@@ -47,16 +47,16 @@
          "Khronos LLVM/SPIR-V Translator"},
         {SPV_GENERATOR_KHRONOS_ASSEMBLER, "Khronos SPIR-V Tools Assembler"},
         {SPV_GENERATOR_KHRONOS_GLSLANG, "Khronos Glslang Reference Front End"},
-    }), );
+    }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Unregistered, GeneratorMagicNumberTest,
     ::testing::ValuesIn(std::vector<EnumCase<spv_generator_t>>{
         // We read registered entries from the SPIR-V XML Registry file
         // which can change over time.
         {spv_generator_t(1000), "Unknown"},
         {spv_generator_t(9999), "Unknown"},
-    }), );
+    }));
 
 }  // namespace
 }  // namespace spvtools
diff --git a/test/hex_float_test.cpp b/test/hex_float_test.cpp
index 8745060..c422f75 100644
--- a/test/hex_float_test.cpp
+++ b/test/hex_float_test.cpp
@@ -81,7 +81,7 @@
   EXPECT_THAT(Decode<double>(GetParam().second), Eq(GetParam().first));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float32Tests, HexFloatTest,
     ::testing::ValuesIn(std::vector<std::pair<FloatProxy<float>, std::string>>({
         {0.f, "0x0p+0"},
@@ -131,9 +131,9 @@
         {float(ldexp(1.0, -127) / 2.0 + (ldexp(1.0, -127) / 4.0f)),
          "0x1.8p-128"},
 
-    })), );
+    })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float32NanTests, HexFloatTest,
     ::testing::ValuesIn(std::vector<std::pair<FloatProxy<float>, std::string>>({
         // Various NAN and INF cases
@@ -149,9 +149,9 @@
         {uint32_t(0x7f800c00), "0x1.0018p+128"},     // +nan
         {uint32_t(0x7F80F000), "0x1.01ep+128"},      // +nan
         {uint32_t(0x7FFFFFFF), "0x1.fffffep+128"},   // +nan
-    })), );
+    })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float64Tests, HexDoubleTest,
     ::testing::ValuesIn(
         std::vector<std::pair<FloatProxy<double>, std::string>>({
@@ -222,9 +222,9 @@
             {ldexp(1.0, -1023) / 2.0 + (ldexp(1.0, -1023) / 4.0),
              "0x1.8p-1024"},
 
-        })), );
+        })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float64NanTests, HexDoubleTest,
     ::testing::ValuesIn(std::vector<
                         std::pair<FloatProxy<double>, std::string>>({
@@ -241,7 +241,7 @@
         {uint64_t(0x7FF0000000000001LL), "0x1.0000000000001p+1024"},   // -nan
         {uint64_t(0x7FF0000300000000LL), "0x1.00003p+1024"},           // -nan
         {uint64_t(0x7FFFFFFFFFFFFFFFLL), "0x1.fffffffffffffp+1024"},   // -nan
-    })), );
+    })));
 
 // Tests that encoding a value and decoding it again restores
 // the same value.
@@ -263,14 +263,14 @@
   EXPECT_THAT(GetParam(), Eq(res.getAsFloat()));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float32StoreTests, RoundTripFloatTest,
     ::testing::ValuesIn(std::vector<float>(
         {// Value requiring more than 6 digits of precision to be
          // represented accurately.
          3.0000002f})));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float64StoreTests, RoundTripDoubleTest,
     ::testing::ValuesIn(std::vector<double>(
         {// Value requiring more than 15 digits of precision to be
@@ -300,7 +300,7 @@
   EXPECT_THAT(Decode<double>(GetParam().first), Eq(GetParam().second));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float32DecodeTests, DecodeHexFloatTest,
     ::testing::ValuesIn(std::vector<std::pair<std::string, FloatProxy<float>>>({
         {"0x0p+000", 0.f},
@@ -320,9 +320,9 @@
         {"0xFFp+0", 255.f},
         {"0x0.8p+0", 0.5f},
         {"0x0.4p+0", 0.25f},
-    })), );
+    })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float32DecodeInfTests, DecodeHexFloatTest,
     ::testing::ValuesIn(std::vector<std::pair<std::string, FloatProxy<float>>>({
         // inf cases
@@ -330,9 +330,9 @@
         {"0x32p+127", uint32_t(0x7F800000)},   // inf
         {"0x32p+500", uint32_t(0x7F800000)},   // inf
         {"-0x32p+127", uint32_t(0xFF800000)},  // -inf
-    })), );
+    })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float64DecodeTests, DecodeHexDoubleTest,
     ::testing::ValuesIn(
         std::vector<std::pair<std::string, FloatProxy<double>>>({
@@ -353,9 +353,9 @@
             {"0xFFp+0", 255.},
             {"0x0.8p+0", 0.5},
             {"0x0.4p+0", 0.25},
-        })), );
+        })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float64DecodeInfTests, DecodeHexDoubleTest,
     ::testing::ValuesIn(
         std::vector<std::pair<std::string, FloatProxy<double>>>({
@@ -364,7 +364,7 @@
             {"0x32p+1023", uint64_t(0x7FF0000000000000)},   // inf
             {"0x32p+5000", uint64_t(0x7FF0000000000000)},   // inf
             {"-0x32p+1023", uint64_t(0xFFF0000000000000)},  // -inf
-        })), );
+        })));
 
 TEST(FloatProxy, ValidConversion) {
   EXPECT_THAT(FloatProxy<float>(1.f).getAsFloat(), Eq(1.0f));
@@ -503,7 +503,7 @@
       Eq(GetParam().second));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float32Tests, FloatProxyFloatTest,
     ::testing::ValuesIn(std::vector<std::pair<FloatProxy<float>, std::string>>({
         // Zero
@@ -533,9 +533,9 @@
 
         {std::numeric_limits<float>::infinity(), "0x1p+128"},
         {-std::numeric_limits<float>::infinity(), "-0x1p+128"},
-    })), );
+    })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float64Tests, FloatProxyDoubleTest,
     ::testing::ValuesIn(
         std::vector<std::pair<FloatProxy<double>, std::string>>({
@@ -570,7 +570,7 @@
             {std::numeric_limits<double>::infinity(), "0x1p+1024"},
             {-std::numeric_limits<double>::infinity(), "-0x1p+1024"},
 
-        })), );
+        })));
 
 // double is used so that unbiased_exponent can be used with the output
 // of ldexp directly.
@@ -792,7 +792,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(F32ToF16, HexFloatRoundTest,
+INSTANTIATE_TEST_SUITE_P(F32ToF16, HexFloatRoundTest,
   ::testing::ValuesIn(std::vector<RoundSignificandCase>(
   {
     {float_fractions({0}), std::make_pair(half_bits_set({}), false), RD::kToZero},
@@ -838,7 +838,7 @@
     {static_cast<float>(ldexp(float_fractions({0, 1, 11, 13}), -129)), std::make_pair(half_bits_set({0, 9}), false), RD::kToPositiveInfinity},
     {static_cast<float>(ldexp(float_fractions({0, 1, 11, 13}), -131)), std::make_pair(half_bits_set({0}), false), RD::kToNegativeInfinity},
     {static_cast<float>(ldexp(float_fractions({0, 1, 11, 13}), -130)), std::make_pair(half_bits_set({0, 9}), false), RD::kToNearestEven},
-  })),);
+  })));
 // clang-format on
 
 struct UpCastSignificandCase {
@@ -872,7 +872,7 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     F16toF32, HexFloatRoundUpSignificandTest,
     // 0xFC00 of the source 16-bit hex value cover the sign and the exponent.
     // They are ignored for this test.
@@ -881,7 +881,7 @@
         {0x0F00, 0x600000},
         {0x0F01, 0x602000},
         {0x0FFF, 0x7FE000},
-    })), );
+    })));
 
 struct DownCastTest {
   float source_float;
@@ -923,7 +923,7 @@
 const uint16_t positive_infinity = 0x7C00;
 const uint16_t negative_infinity = 0xFC00;
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     F32ToF16, HexFloatFP32To16Tests,
     ::testing::ValuesIn(std::vector<DownCastTest>({
         // Exactly representable as half.
@@ -1014,7 +1014,7 @@
           RD::kToNearestEven}},
 
         // Nans are below because we cannot test for equality.
-    })), );
+    })));
 
 struct UpCastCase {
   uint16_t source_half;
@@ -1043,7 +1043,7 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     F16ToF32, HexFloatFP16To32Tests,
     ::testing::ValuesIn(std::vector<UpCastCase>({
         {0x0000, 0.f},
@@ -1064,7 +1064,7 @@
         // inf
         {0x7C00, std::numeric_limits<float>::infinity()},
         {0xFC00, -std::numeric_limits<float>::infinity()},
-    })), );
+    })));
 
 TEST(HexFloatOperationTests, NanTests) {
   using HF = HexFloat<FloatProxy<float>>;
@@ -1137,7 +1137,7 @@
   return FloatParseCase<T>{literal, negate_value, true, proxy_expected_value};
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     FloatParse, ParseNormalFloatTest,
     ::testing::ValuesIn(std::vector<FloatParseCase<float>>{
         // Failing cases due to trivially incorrect syntax.
@@ -1169,7 +1169,7 @@
         // We can't have -1e40 and negate_value == true since
         // that represents an original case of "--1e40" which
         // is invalid.
-    }), );
+    }));
 
 using ParseNormalFloat16Test =
     ::testing::TestWithParam<FloatParseCase<Float16>>;
@@ -1188,7 +1188,7 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float16Parse, ParseNormalFloat16Test,
     ::testing::ValuesIn(std::vector<FloatParseCase<Float16>>{
         // Failing cases due to trivially incorrect syntax.
@@ -1212,7 +1212,7 @@
         BadFloatParseCase<Float16>("-2.0", true, uint16_t{0}),
         BadFloatParseCase<Float16>("+0.0", true, uint16_t{0}),
         BadFloatParseCase<Float16>("+2.0", true, uint16_t{0}),
-    }), );
+    }));
 
 // A test case for detecting infinities.
 template <typename T>
@@ -1235,7 +1235,7 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     FloatOverflow, FloatProxyParseOverflowFloatTest,
     ::testing::ValuesIn(std::vector<OverflowParseCase<float>>({
         {"0", true, 0.0f},
@@ -1247,7 +1247,7 @@
         {"-1e40", false, -FLT_MAX},
         {"1e400", false, FLT_MAX},
         {"-1e400", false, -FLT_MAX},
-    })), );
+    })));
 
 using FloatProxyParseOverflowDoubleTest =
     ::testing::TestWithParam<OverflowParseCase<double>>;
@@ -1262,7 +1262,7 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DoubleOverflow, FloatProxyParseOverflowDoubleTest,
     ::testing::ValuesIn(std::vector<OverflowParseCase<double>>({
         {"0", true, 0.0},
@@ -1274,7 +1274,7 @@
         {"-1e40", true, -1e40},
         {"1e400", false, DBL_MAX},
         {"-1e400", false, -DBL_MAX},
-    })), );
+    })));
 
 using FloatProxyParseOverflowFloat16Test =
     ::testing::TestWithParam<OverflowParseCase<uint16_t>>;
@@ -1291,7 +1291,7 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Float16Overflow, FloatProxyParseOverflowFloat16Test,
     ::testing::ValuesIn(std::vector<OverflowParseCase<uint16_t>>({
         {"0", true, uint16_t{0}},
@@ -1305,7 +1305,7 @@
         {"-1e38", false, uint16_t{0xfbff}},
         {"-1e40", false, uint16_t{0xfbff}},
         {"-1e400", false, uint16_t{0xfbff}},
-    })), );
+    })));
 
 TEST(FloatProxy, Max) {
   EXPECT_THAT(FloatProxy<Float16>::max().getAsFloat().get_value(),
diff --git a/test/name_mapper_test.cpp b/test/name_mapper_test.cpp
index 9a9ee8a..00fbeed 100644
--- a/test/name_mapper_test.cpp
+++ b/test/name_mapper_test.cpp
@@ -54,31 +54,31 @@
       << " for id " << GetParam().id;
 }
 
-INSTANTIATE_TEST_CASE_P(ScalarType, FriendlyNameTest,
-                        ::testing::ValuesIn(std::vector<NameIdCase>{
-                            {"%1 = OpTypeVoid", 1, "void"},
-                            {"%1 = OpTypeBool", 1, "bool"},
-                            {"%1 = OpTypeInt 8 0", 1, "uchar"},
-                            {"%1 = OpTypeInt 8 1", 1, "char"},
-                            {"%1 = OpTypeInt 16 0", 1, "ushort"},
-                            {"%1 = OpTypeInt 16 1", 1, "short"},
-                            {"%1 = OpTypeInt 32 0", 1, "uint"},
-                            {"%1 = OpTypeInt 32 1", 1, "int"},
-                            {"%1 = OpTypeInt 64 0", 1, "ulong"},
-                            {"%1 = OpTypeInt 64 1", 1, "long"},
-                            {"%1 = OpTypeInt 1 0", 1, "u1"},
-                            {"%1 = OpTypeInt 1 1", 1, "i1"},
-                            {"%1 = OpTypeInt 33 0", 1, "u33"},
-                            {"%1 = OpTypeInt 33 1", 1, "i33"},
+INSTANTIATE_TEST_SUITE_P(ScalarType, FriendlyNameTest,
+                         ::testing::ValuesIn(std::vector<NameIdCase>{
+                             {"%1 = OpTypeVoid", 1, "void"},
+                             {"%1 = OpTypeBool", 1, "bool"},
+                             {"%1 = OpTypeInt 8 0", 1, "uchar"},
+                             {"%1 = OpTypeInt 8 1", 1, "char"},
+                             {"%1 = OpTypeInt 16 0", 1, "ushort"},
+                             {"%1 = OpTypeInt 16 1", 1, "short"},
+                             {"%1 = OpTypeInt 32 0", 1, "uint"},
+                             {"%1 = OpTypeInt 32 1", 1, "int"},
+                             {"%1 = OpTypeInt 64 0", 1, "ulong"},
+                             {"%1 = OpTypeInt 64 1", 1, "long"},
+                             {"%1 = OpTypeInt 1 0", 1, "u1"},
+                             {"%1 = OpTypeInt 1 1", 1, "i1"},
+                             {"%1 = OpTypeInt 33 0", 1, "u33"},
+                             {"%1 = OpTypeInt 33 1", 1, "i33"},
 
-                            {"%1 = OpTypeFloat 16", 1, "half"},
-                            {"%1 = OpTypeFloat 32", 1, "float"},
-                            {"%1 = OpTypeFloat 64", 1, "double"},
-                            {"%1 = OpTypeFloat 10", 1, "fp10"},
-                            {"%1 = OpTypeFloat 55", 1, "fp55"},
-                        }), );
+                             {"%1 = OpTypeFloat 16", 1, "half"},
+                             {"%1 = OpTypeFloat 32", 1, "float"},
+                             {"%1 = OpTypeFloat 64", 1, "double"},
+                             {"%1 = OpTypeFloat 10", 1, "fp10"},
+                             {"%1 = OpTypeFloat 55", 1, "fp55"},
+                         }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     VectorType, FriendlyNameTest,
     ::testing::ValuesIn(std::vector<NameIdCase>{
         {"%1 = OpTypeBool %2 = OpTypeVector %1 1", 2, "v1bool"},
@@ -97,9 +97,9 @@
         // OpName overrides the element name.
         {"OpName %1 \"time\" %1 = OpTypeFloat 32 %2 = OpTypeVector %1 2", 2,
          "v2time"},
-    }), );
+    }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     MatrixType, FriendlyNameTest,
     ::testing::ValuesIn(std::vector<NameIdCase>{
         {"%1 = OpTypeBool %2 = OpTypeVector %1 2 %3 = OpTypeMatrix %2 2", 3,
@@ -114,9 +114,9 @@
         {"OpName %2 \"lat_long\" %1 = OpTypeFloat 32 %2 = OpTypeVector %1 2 %3 "
          "= OpTypeMatrix %2 4",
          3, "mat4lat_long"},
-    }), );
+    }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpName, FriendlyNameTest,
     ::testing::ValuesIn(std::vector<NameIdCase>{
         {"OpName %1 \"abcdefg\"", 1, "abcdefg"},
@@ -146,38 +146,38 @@
         // OpName can override other inferences.  We assume valid instruction
         // ordering, where OpName precedes type definitions.
         {"OpName %1 \"myfloat\" %1 = OpTypeFloat 32", 1, "myfloat"},
-    }), );
+    }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     UniquenessHeuristic, FriendlyNameTest,
     ::testing::ValuesIn(std::vector<NameIdCase>{
         {"%1 = OpTypeVoid %2 = OpTypeVoid %3 = OpTypeVoid", 1, "void"},
         {"%1 = OpTypeVoid %2 = OpTypeVoid %3 = OpTypeVoid", 2, "void_0"},
         {"%1 = OpTypeVoid %2 = OpTypeVoid %3 = OpTypeVoid", 3, "void_1"},
-    }), );
+    }));
 
-INSTANTIATE_TEST_CASE_P(Arrays, FriendlyNameTest,
-                        ::testing::ValuesIn(std::vector<NameIdCase>{
-                            {"OpName %2 \"FortyTwo\" %1 = OpTypeFloat 32 "
-                             "%2 = OpConstant %1 42 %3 = OpTypeArray %1 %2",
-                             3, "_arr_float_FortyTwo"},
-                            {"%1 = OpTypeInt 32 0 "
-                             "%2 = OpTypeRuntimeArray %1",
-                             2, "_runtimearr_uint"},
-                        }), );
+INSTANTIATE_TEST_SUITE_P(Arrays, FriendlyNameTest,
+                         ::testing::ValuesIn(std::vector<NameIdCase>{
+                             {"OpName %2 \"FortyTwo\" %1 = OpTypeFloat 32 "
+                              "%2 = OpConstant %1 42 %3 = OpTypeArray %1 %2",
+                              3, "_arr_float_FortyTwo"},
+                             {"%1 = OpTypeInt 32 0 "
+                              "%2 = OpTypeRuntimeArray %1",
+                              2, "_runtimearr_uint"},
+                         }));
 
-INSTANTIATE_TEST_CASE_P(Structs, FriendlyNameTest,
-                        ::testing::ValuesIn(std::vector<NameIdCase>{
-                            {"%1 = OpTypeBool "
-                             "%2 = OpTypeStruct %1 %1 %1",
-                             2, "_struct_2"},
-                            {"%1 = OpTypeBool "
-                             "%2 = OpTypeStruct %1 %1 %1 "
-                             "%3 = OpTypeStruct %2 %2",
-                             3, "_struct_3"},
-                        }), );
+INSTANTIATE_TEST_SUITE_P(Structs, FriendlyNameTest,
+                         ::testing::ValuesIn(std::vector<NameIdCase>{
+                             {"%1 = OpTypeBool "
+                              "%2 = OpTypeStruct %1 %1 %1",
+                              2, "_struct_2"},
+                             {"%1 = OpTypeBool "
+                              "%2 = OpTypeStruct %1 %1 %1 "
+                              "%3 = OpTypeStruct %2 %2",
+                              3, "_struct_3"},
+                         }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Pointer, FriendlyNameTest,
     ::testing::ValuesIn(std::vector<NameIdCase>{
         {"%1 = OpTypeFloat 32 %2 = OpTypePointer Workgroup %1", 2,
@@ -189,22 +189,22 @@
         {"%1 = OpTypeBool OpTypeForwardPointer %2 Private %2 = OpTypePointer "
          "Private %1",
          2, "_ptr_Private_bool"},
-    }), );
+    }));
 
-INSTANTIATE_TEST_CASE_P(ExoticTypes, FriendlyNameTest,
-                        ::testing::ValuesIn(std::vector<NameIdCase>{
-                            {"%1 = OpTypeEvent", 1, "Event"},
-                            {"%1 = OpTypeDeviceEvent", 1, "DeviceEvent"},
-                            {"%1 = OpTypeReserveId", 1, "ReserveId"},
-                            {"%1 = OpTypeQueue", 1, "Queue"},
-                            {"%1 = OpTypeOpaque \"hello world!\"", 1,
-                             "Opaque_hello_world_"},
-                            {"%1 = OpTypePipe ReadOnly", 1, "PipeReadOnly"},
-                            {"%1 = OpTypePipe WriteOnly", 1, "PipeWriteOnly"},
-                            {"%1 = OpTypePipe ReadWrite", 1, "PipeReadWrite"},
-                            {"%1 = OpTypePipeStorage", 1, "PipeStorage"},
-                            {"%1 = OpTypeNamedBarrier", 1, "NamedBarrier"},
-                        }), );
+INSTANTIATE_TEST_SUITE_P(ExoticTypes, FriendlyNameTest,
+                         ::testing::ValuesIn(std::vector<NameIdCase>{
+                             {"%1 = OpTypeEvent", 1, "Event"},
+                             {"%1 = OpTypeDeviceEvent", 1, "DeviceEvent"},
+                             {"%1 = OpTypeReserveId", 1, "ReserveId"},
+                             {"%1 = OpTypeQueue", 1, "Queue"},
+                             {"%1 = OpTypeOpaque \"hello world!\"", 1,
+                              "Opaque_hello_world_"},
+                             {"%1 = OpTypePipe ReadOnly", 1, "PipeReadOnly"},
+                             {"%1 = OpTypePipe WriteOnly", 1, "PipeWriteOnly"},
+                             {"%1 = OpTypePipe ReadWrite", 1, "PipeReadWrite"},
+                             {"%1 = OpTypePipeStorage", 1, "PipeStorage"},
+                             {"%1 = OpTypeNamedBarrier", 1, "NamedBarrier"},
+                         }));
 
 // Makes a test case for a BuiltIn variable declaration.
 NameIdCase BuiltInCase(std::string assembly_name, std::string expected) {
@@ -226,7 +226,7 @@
   return BuiltInCase(assembly_name, std::string("gl_") + assembly_name);
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     BuiltIns, FriendlyNameTest,
     ::testing::ValuesIn(std::vector<NameIdCase>{
         BuiltInGLCase("Position"),
@@ -275,15 +275,15 @@
         BuiltInCase("SubgroupGtMaskKHR"),
         BuiltInCase("SubgroupLeMaskKHR"),
         BuiltInCase("SubgroupLtMaskKHR"),
-    }), );
+    }));
 
-INSTANTIATE_TEST_CASE_P(DebugNameOverridesBuiltin, FriendlyNameTest,
-                        ::testing::ValuesIn(std::vector<NameIdCase>{
-                            {"OpName %1 \"foo\" OpDecorate %1 BuiltIn WorkDim "
-                             "%1 = OpVariable %2 Input",
-                             1, "foo"}}), );
+INSTANTIATE_TEST_SUITE_P(DebugNameOverridesBuiltin, FriendlyNameTest,
+                         ::testing::ValuesIn(std::vector<NameIdCase>{
+                             {"OpName %1 \"foo\" OpDecorate %1 BuiltIn WorkDim "
+                              "%1 = OpVariable %2 Input",
+                              1, "foo"}}));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SimpleIntegralConstants, FriendlyNameTest,
     ::testing::ValuesIn(std::vector<NameIdCase>{
         {"%1 = OpTypeInt 32 0 %2 = OpConstant %1 0", 2, "uint_0"},
@@ -301,9 +301,9 @@
         {"%1 = OpTypeInt 33 0 %2 = OpConstant %1 0", 2, "u33_0"},
         {"%1 = OpTypeInt 33 1 %2 = OpConstant %1 10", 2, "i33_10"},
         {"%1 = OpTypeInt 33 1 %2 = OpConstant %1 -19", 2, "i33_n19"},
-    }), );
+    }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SimpleFloatConstants, FriendlyNameTest,
     ::testing::ValuesIn(std::vector<NameIdCase>{
         {"%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1.ff4p+16", 2,
@@ -334,14 +334,14 @@
          "double_0x1p_1024"},  // Inf
         {"%1 = OpTypeFloat 64\n%2 = OpConstant %1 -0x1p+1024", 2,
          "double_n0x1p_1024"},  // -Inf
-    }), );
+    }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     BooleanConstants, FriendlyNameTest,
     ::testing::ValuesIn(std::vector<NameIdCase>{
         {"%1 = OpTypeBool\n%2 = OpConstantTrue %1", 2, "true"},
         {"%1 = OpTypeBool\n%2 = OpConstantFalse %1", 2, "false"},
-    }), );
+    }));
 
 }  // namespace
 }  // namespace spvtools
diff --git a/test/named_id_test.cpp b/test/named_id_test.cpp
index 4ba54ad..01f09be 100644
--- a/test/named_id_test.cpp
+++ b/test/named_id_test.cpp
@@ -65,7 +65,7 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidAndInvalidIds, IdValidityTest,
     ::testing::ValuesIn(std::vector<IdCheckCase>(
         {{"%1", true},          {"%2abc", true},   {"%3Def", true},
@@ -81,7 +81,7 @@
          {"%foo_@_bar", false}, {"%", false},
 
          {"5", false},          {"32", false},     {"foo", false},
-         {"a%bar", false}})), );
+         {"a%bar", false}})));
 
 }  // namespace
 }  // namespace spvtools
diff --git a/test/opcode_require_capabilities_test.cpp b/test/opcode_require_capabilities_test.cpp
index 32bf1dc..07e86f8 100644
--- a/test/opcode_require_capabilities_test.cpp
+++ b/test/opcode_require_capabilities_test.cpp
@@ -42,7 +42,7 @@
       ElementsIn(CapabilitySet(entry->numCapabilities, entry->capabilities)));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TableRowTest, OpcodeTableCapabilitiesTest,
     // Spot-check a few opcodes.
     ::testing::Values(
@@ -72,7 +72,7 @@
                                    CapabilitySet{SpvCapabilityNamedBarrier}},
         ExpectedOpCodeCapabilities{
             SpvOpGetKernelMaxNumSubgroups,
-            CapabilitySet{SpvCapabilitySubgroupDispatch}}), );
+            CapabilitySet{SpvCapabilitySubgroupDispatch}}));
 
 }  // namespace
 }  // namespace spvtools
diff --git a/test/opcode_table_get_test.cpp b/test/opcode_table_get_test.cpp
index 6f80ad7..5ebd6c1 100644
--- a/test/opcode_table_get_test.cpp
+++ b/test/opcode_table_get_test.cpp
@@ -32,8 +32,8 @@
   ASSERT_EQ(SPV_ERROR_INVALID_POINTER, spvOpcodeTableGet(nullptr, GetParam()));
 }
 
-INSTANTIATE_TEST_CASE_P(OpcodeTableGet, GetTargetOpcodeTableGetTest,
-                        ValuesIn(spvtest::AllTargetEnvironments()));
+INSTANTIATE_TEST_SUITE_P(OpcodeTableGet, GetTargetOpcodeTableGetTest,
+                         ValuesIn(spvtest::AllTargetEnvironments()));
 
 }  // namespace
 }  // namespace spvtools
diff --git a/test/operand_capabilities_test.cpp b/test/operand_capabilities_test.cpp
index e58bc9d..5b06527 100644
--- a/test/operand_capabilities_test.cpp
+++ b/test/operand_capabilities_test.cpp
@@ -91,7 +91,7 @@
   }
 
 // See SPIR-V Section 3.3 Execution Model
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ExecutionModel, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -104,30 +104,30 @@
                 CASE1(EXECUTION_MODEL, ExecutionModelFragment, Shader),
                 CASE1(EXECUTION_MODEL, ExecutionModelGLCompute, Shader),
                 CASE1(EXECUTION_MODEL, ExecutionModelKernel, Kernel),
-            })), );
+            })));
 
 // See SPIR-V Section 3.4 Addressing Model
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     AddressingModel, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
                 CASE0(ADDRESSING_MODEL, AddressingModelLogical),
                 CASE1(ADDRESSING_MODEL, AddressingModelPhysical32, Addresses),
                 CASE1(ADDRESSING_MODEL, AddressingModelPhysical64, Addresses),
-            })), );
+            })));
 
 // See SPIR-V Section 3.5 Memory Model
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     MemoryModel, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
                 CASE1(MEMORY_MODEL, MemoryModelSimple, Shader),
                 CASE1(MEMORY_MODEL, MemoryModelGLSL450, Shader),
                 CASE1(MEMORY_MODEL, MemoryModelOpenCL, Kernel),
-            })), );
+            })));
 
 // See SPIR-V Section 3.6 Execution Mode
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ExecutionMode, EnumCapabilityTest,
     Combine(
         Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
@@ -169,9 +169,9 @@
             CASE1(EXECUTION_MODE, ExecutionModeOutputTriangleStrip, Geometry),
             CASE1(EXECUTION_MODE, ExecutionModeVecTypeHint, Kernel),
             CASE1(EXECUTION_MODE, ExecutionModeContractionOff, Kernel),
-        })), );
+        })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ExecutionModeV11, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -180,10 +180,10 @@
                 CASE1(EXECUTION_MODE, ExecutionModeSubgroupSize,
                       SubgroupDispatch),
                 CASE1(EXECUTION_MODE, ExecutionModeSubgroupsPerWorkgroup,
-                      SubgroupDispatch)})), );
+                      SubgroupDispatch)})));
 
 // See SPIR-V Section 3.7 Storage Class
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     StorageClass, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -199,10 +199,10 @@
                 CASE1(STORAGE_CLASS, StorageClassPushConstant, Shader),
                 CASE1(STORAGE_CLASS, StorageClassAtomicCounter, AtomicStorage),
                 CASE0(STORAGE_CLASS, StorageClassImage),
-            })), );
+            })));
 
 // See SPIR-V Section 3.8 Dim
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Dim, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -213,10 +213,10 @@
                 CASE2(DIMENSIONALITY, DimRect, SampledRect, ImageRect),
                 CASE2(DIMENSIONALITY, DimBuffer, SampledBuffer, ImageBuffer),
                 CASE1(DIMENSIONALITY, DimSubpassData, InputAttachment),
-            })), );
+            })));
 
 // See SPIR-V Section 3.9 Sampler Addressing Mode
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SamplerAddressingMode, EnumCapabilityTest,
     Combine(
         Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
@@ -228,19 +228,19 @@
             CASE1(SAMPLER_ADDRESSING_MODE, SamplerAddressingModeRepeat, Kernel),
             CASE1(SAMPLER_ADDRESSING_MODE, SamplerAddressingModeRepeatMirrored,
                   Kernel),
-        })), );
+        })));
 
 // See SPIR-V Section 3.10 Sampler Filter Mode
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SamplerFilterMode, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
                 CASE1(SAMPLER_FILTER_MODE, SamplerFilterModeNearest, Kernel),
                 CASE1(SAMPLER_FILTER_MODE, SamplerFilterModeLinear, Kernel),
-            })), );
+            })));
 
 // See SPIR-V Section 3.11 Image Format
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ImageFormat, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -286,10 +286,10 @@
         CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatR16ui, StorageImageExtendedFormats),
         CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatR8ui, StorageImageExtendedFormats),
                 // clang-format on
-            })), );
+            })));
 
 // See SPIR-V Section 3.12 Image Channel Order
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ImageChannelOrder, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -314,10 +314,10 @@
                 CASE1(IMAGE_CHANNEL_ORDER, ImageChannelOrdersRGBA, Kernel),
                 CASE1(IMAGE_CHANNEL_ORDER, ImageChannelOrdersBGRA, Kernel),
                 CASE1(IMAGE_CHANNEL_ORDER, ImageChannelOrderABGR, Kernel),
-            })), );
+            })));
 
 // See SPIR-V Section 3.13 Image Channel Data Type
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ImageChannelDataType, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -340,10 +340,10 @@
                 CASE1(IMAGE_CHANNEL_DATA_TYPE, ImageChannelDataTypeUnormInt24, Kernel),
                 CASE1(IMAGE_CHANNEL_DATA_TYPE, ImageChannelDataTypeUnormInt101010_2, Kernel),
                 // clang-format on
-            })), );
+            })));
 
 // See SPIR-V Section 3.14 Image Operands
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ImageOperands, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -358,10 +358,10 @@
                 CASE0(OPTIONAL_IMAGE, ImageOperandsSampleMask),
                 CASE1(OPTIONAL_IMAGE, ImageOperandsMinLodMask, MinLod),
                 // clang-format on
-            })), );
+            })));
 
 // See SPIR-V Section 3.15 FP Fast Math Mode
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     FPFastMathMode, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -371,29 +371,29 @@
                 CASE1(FP_FAST_MATH_MODE, FPFastMathModeNSZMask, Kernel),
                 CASE1(FP_FAST_MATH_MODE, FPFastMathModeAllowRecipMask, Kernel),
                 CASE1(FP_FAST_MATH_MODE, FPFastMathModeFastMask, Kernel),
-            })), );
+            })));
 
 // See SPIR-V Section 3.17 Linkage Type
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LinkageType, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
                 CASE1(LINKAGE_TYPE, LinkageTypeExport, Linkage),
                 CASE1(LINKAGE_TYPE, LinkageTypeImport, Linkage),
-            })), );
+            })));
 
 // See SPIR-V Section 3.18 Access Qualifier
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     AccessQualifier, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
                 CASE1(ACCESS_QUALIFIER, AccessQualifierReadOnly, Kernel),
                 CASE1(ACCESS_QUALIFIER, AccessQualifierWriteOnly, Kernel),
                 CASE1(ACCESS_QUALIFIER, AccessQualifierReadWrite, Kernel),
-            })), );
+            })));
 
 // See SPIR-V Section 3.19 Function Parameter Attribute
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     FunctionParameterAttribute, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -407,10 +407,10 @@
                 CASE1(FUNCTION_PARAMETER_ATTRIBUTE, FunctionParameterAttributeNoWrite, Kernel),
                 CASE1(FUNCTION_PARAMETER_ATTRIBUTE, FunctionParameterAttributeNoReadWrite, Kernel),
                 // clang-format on
-            })), );
+            })));
 
 // See SPIR-V Section 3.20 Decoration
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Decoration, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -460,25 +460,25 @@
                 CASE1(DECORATION, DecorationInputAttachmentIndex,
                       InputAttachment),
                 CASE1(DECORATION, DecorationAlignment, Kernel),
-            })), );
+            })));
 
 #if 0
 // SpecId has different requirements in v1.0 and v1.1:
-INSTANTIATE_TEST_CASE_P(DecorationSpecIdV10, EnumCapabilityTest,
+INSTANTIATE_TEST_SUITE_P(DecorationSpecIdV10, EnumCapabilityTest,
                         Combine(Values(SPV_ENV_UNIVERSAL_1_0),
                                 ValuesIn(std::vector<EnumCapabilityCase>{CASE1(
-                                    DECORATION, DecorationSpecId, Shader)})), );
+                                    DECORATION, DecorationSpecId, Shader)})));
 #endif
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DecorationV11, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
                 CASE2(DECORATION, DecorationSpecId, Shader, Kernel),
-                CASE1(DECORATION, DecorationMaxByteOffset, Addresses)})), );
+                CASE1(DECORATION, DecorationMaxByteOffset, Addresses)})));
 
 // See SPIR-V Section 3.21 BuiltIn
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     BuiltIn, EnumCapabilityTest,
     Combine(
         Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
@@ -530,38 +530,38 @@
             CASE1(BUILT_IN, BuiltInVertexIndex, Shader),
             CASE1(BUILT_IN, BuiltInInstanceIndex, Shader),
             // clang-format on
-        })), );
+        })));
 
 // See SPIR-V Section 3.22 Selection Control
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SelectionControl, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
                 CASE0(SELECTION_CONTROL, SelectionControlMaskNone),
                 CASE0(SELECTION_CONTROL, SelectionControlFlattenMask),
                 CASE0(SELECTION_CONTROL, SelectionControlDontFlattenMask),
-            })), );
+            })));
 
 // See SPIR-V Section 3.23 Loop Control
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LoopControl, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
                 CASE0(LOOP_CONTROL, LoopControlMaskNone),
                 CASE0(LOOP_CONTROL, LoopControlUnrollMask),
                 CASE0(LOOP_CONTROL, LoopControlDontUnrollMask),
-            })), );
+            })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LoopControlV11, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
                 CASE0(LOOP_CONTROL, LoopControlDependencyInfiniteMask),
                 CASE0(LOOP_CONTROL, LoopControlDependencyLengthMask),
-            })), );
+            })));
 
 // See SPIR-V Section 3.24 Function Control
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     FunctionControl, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -570,10 +570,10 @@
                 CASE0(FUNCTION_CONTROL, FunctionControlDontInlineMask),
                 CASE0(FUNCTION_CONTROL, FunctionControlPureMask),
                 CASE0(FUNCTION_CONTROL, FunctionControlConstMask),
-            })), );
+            })));
 
 // See SPIR-V Section 3.25 Memory Semantics <id>
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     MemorySemantics, EnumCapabilityTest,
     Combine(
         Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
@@ -592,10 +592,10 @@
             CASE1(MEMORY_SEMANTICS_ID, MemorySemanticsAtomicCounterMemoryMask,
                   AtomicStorage),  // Bug 15234
             CASE0(MEMORY_SEMANTICS_ID, MemorySemanticsImageMemoryMask),
-        })), );
+        })));
 
 // See SPIR-V Section 3.26 Memory Access
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     MemoryAccess, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -603,10 +603,10 @@
                 CASE0(OPTIONAL_MEMORY_ACCESS, MemoryAccessVolatileMask),
                 CASE0(OPTIONAL_MEMORY_ACCESS, MemoryAccessAlignedMask),
                 CASE0(OPTIONAL_MEMORY_ACCESS, MemoryAccessNontemporalMask),
-            })), );
+            })));
 
 // See SPIR-V Section 3.27 Scope <id>
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Scope, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
                    SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_3),
@@ -617,10 +617,10 @@
                 CASE0(SCOPE_ID, ScopeSubgroup),
                 CASE0(SCOPE_ID, ScopeInvocation),
                 CASE1(SCOPE_ID, ScopeQueueFamilyKHR, VulkanMemoryModelKHR),
-            })), );
+            })));
 
 // See SPIR-V Section 3.28 Group Operation
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     GroupOperation, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -630,10 +630,10 @@
                       GroupNonUniformArithmetic, GroupNonUniformBallot),
                 CASE3(GROUP_OPERATION, GroupOperationExclusiveScan, Kernel,
                       GroupNonUniformArithmetic, GroupNonUniformBallot),
-            })), );
+            })));
 
 // See SPIR-V Section 3.29 Kernel Enqueue Flags
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     KernelEnqueueFlags, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
@@ -641,20 +641,20 @@
                 CASE1(KERNEL_ENQ_FLAGS, KernelEnqueueFlagsWaitKernel, Kernel),
                 CASE1(KERNEL_ENQ_FLAGS, KernelEnqueueFlagsWaitWorkGroup,
                       Kernel),
-            })), );
+            })));
 
 // See SPIR-V Section 3.30 Kernel Profiling Info
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     KernelProfilingInfo, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
                 CASE0(KERNEL_PROFILING_INFO, KernelProfilingInfoMaskNone),
                 CASE1(KERNEL_PROFILING_INFO, KernelProfilingInfoCmdExecTimeMask,
                       Kernel),
-            })), );
+            })));
 
 // See SPIR-V Section 3.31 Capability
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     CapabilityDependsOn, EnumCapabilityTest,
     Combine(
         Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
@@ -717,16 +717,16 @@
             CASE1(CAPABILITY, CapabilityStorageImageWriteWithoutFormat, Shader),
             CASE1(CAPABILITY, CapabilityMultiViewport, Geometry),
             // clang-format on
-        })), );
+        })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     CapabilityDependsOnV11, EnumCapabilityTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCapabilityCase>{
                 CASE1(CAPABILITY, CapabilitySubgroupDispatch, DeviceEnqueue),
                 CASE1(CAPABILITY, CapabilityNamedBarrier, Kernel),
                 CASE1(CAPABILITY, CapabilityPipeStorage, Pipes),
-            })), );
+            })));
 
 #undef CASE0
 #undef CASE1
diff --git a/test/operand_pattern_test.cpp b/test/operand_pattern_test.cpp
index d2d92a0..1caf008 100644
--- a/test/operand_pattern_test.cpp
+++ b/test/operand_pattern_test.cpp
@@ -84,7 +84,7 @@
 #define PREFIX1                                                         \
   SPV_OPERAND_TYPE_STORAGE_CLASS, SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE, \
       SPV_OPERAND_TYPE_ID
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OperandPattern, MaskExpansionTest,
     ::testing::ValuesIn(std::vector<MaskExpansionCase>{
         // No bits means no change.
@@ -111,7 +111,7 @@
          SpvMemoryAccessVolatileMask | SpvMemoryAccessAlignedMask,
          {PREFIX1},
          {PREFIX1, SPV_OPERAND_TYPE_LITERAL_INTEGER}},
-    }), );
+    }));
 #undef PREFIX0
 #undef PREFIX1
 
@@ -137,9 +137,9 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(MatchableOperandExpansion,
-                        MatchableOperandExpansionTest,
-                        ::testing::ValuesIn(allOperandTypes()), );
+INSTANTIATE_TEST_SUITE_P(MatchableOperandExpansion,
+                         MatchableOperandExpansionTest,
+                         ::testing::ValuesIn(allOperandTypes()));
 
 using VariableOperandExpansionTest =
     ::testing::TestWithParam<spv_operand_type_t>;
@@ -157,9 +157,9 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(NonMatchableOperandExpansion,
-                        VariableOperandExpansionTest,
-                        ::testing::ValuesIn(allOperandTypes()), );
+INSTANTIATE_TEST_SUITE_P(NonMatchableOperandExpansion,
+                         VariableOperandExpansionTest,
+                         ::testing::ValuesIn(allOperandTypes()));
 
 TEST(AlternatePatternFollowingImmediate, Empty) {
   EXPECT_THAT(spvAlternatePatternFollowingImmediate({}),
diff --git a/test/operand_test.cpp b/test/operand_test.cpp
index 08522c3..4e2c321 100644
--- a/test/operand_test.cpp
+++ b/test/operand_test.cpp
@@ -33,10 +33,10 @@
   ASSERT_EQ(SPV_ERROR_INVALID_POINTER, spvOperandTableGet(nullptr, GetParam()));
 }
 
-INSTANTIATE_TEST_CASE_P(OperandTableGet, GetTargetTest,
-                        ValuesIn(std::vector<spv_target_env>{
-                            SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
-                            SPV_ENV_VULKAN_1_0}), );
+INSTANTIATE_TEST_SUITE_P(OperandTableGet, GetTargetTest,
+                         ValuesIn(std::vector<spv_target_env>{
+                             SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
+                             SPV_ENV_VULKAN_1_0}));
 
 TEST(OperandString, AllAreDefinedExceptVariable) {
   // None has no string, so don't test it.
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
index 1e32df3..e10fe6b 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -21,6 +21,8 @@
        block_merge_test.cpp
        ccp_test.cpp
        cfg_cleanup_test.cpp
+       cfg_test.cpp
+       code_sink_test.cpp
        combine_access_chains_test.cpp
        common_uniform_elim_test.cpp
        compact_ids_test.cpp
@@ -29,16 +31,20 @@
        dead_branch_elim_test.cpp
        dead_insert_elim_test.cpp
        dead_variable_elim_test.cpp
+       decompose_initialized_variables_test.cpp
        decoration_manager_test.cpp
        def_use_test.cpp
        eliminate_dead_const_test.cpp
        eliminate_dead_functions_test.cpp
+       eliminate_dead_member_test.cpp
        feature_manager_test.cpp
+       fix_storage_class_test.cpp
        flatten_decoration_test.cpp
        fold_spec_const_op_composite_test.cpp
        fold_test.cpp
        freeze_spec_const_test.cpp
        function_test.cpp
+       generate_webgpu_initializers_test.cpp
        if_conversion_test.cpp
        inline_opaque_test.cpp
        inline_test.cpp
@@ -50,6 +56,7 @@
        ir_context_test.cpp
        ir_loader_test.cpp
        iterator_test.cpp
+       legalize_vector_shuffle_test.cpp
        line_debug_info_test.cpp
        local_access_chain_convert_test.cpp
        local_redundancy_elimination_test.cpp
@@ -75,12 +82,14 @@
        set_spec_const_default_value_test.cpp
        simplification_test.cpp
        strength_reduction_test.cpp
+       strip_atomic_counter_memory_test.cpp
        strip_debug_info_test.cpp
        strip_reflect_info_test.cpp
        struct_cfg_analysis_test.cpp
        type_manager_test.cpp
        types_test.cpp
        unify_const_test.cpp
+       upgrade_memory_model_test.cpp
        utils_test.cpp pass_utils.cpp
        value_table_test.cpp
        vector_dce_test.cpp
@@ -88,9 +97,3 @@
   LIBS SPIRV-Tools-opt
   PCH_FILE pch_test_opt
 )
-
-add_spvtools_unittest(TARGET upgrade_memory_model
-  SRCS upgrade_memory_model_test.cpp pass_utils.cpp
-  LIBS SPIRV-Tools-opt
-  PCH_FILE pch_test_opt
-)
diff --git a/test/opt/aggressive_dead_code_elim_test.cpp b/test/opt/aggressive_dead_code_elim_test.cpp
index e58a76f..ae98b93 100644
--- a/test/opt/aggressive_dead_code_elim_test.cpp
+++ b/test/opt/aggressive_dead_code_elim_test.cpp
@@ -4254,7 +4254,7 @@
   SinglePassRunAndMatch<AggressiveDCEPass>(assembly_with_dead_const, false);
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ScalarTypeConstants, AggressiveEliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<AggressiveEliminateDeadConstantTestCase>({
         // clang-format off
@@ -4346,7 +4346,7 @@
         // clang-format on
     })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     VectorTypeConstants, AggressiveEliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<AggressiveEliminateDeadConstantTestCase>({
         // clang-format off
@@ -4473,7 +4473,7 @@
         // clang-format on
     })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     StructTypeConstants, AggressiveEliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<AggressiveEliminateDeadConstantTestCase>({
         // clang-format off
@@ -4644,7 +4644,7 @@
         // clang-format on
     })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ScalarTypeSpecConstants, AggressiveEliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<AggressiveEliminateDeadConstantTestCase>({
         // clang-format off
@@ -4697,7 +4697,7 @@
         // clang-format on
     })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     VectorTypeSpecConstants, AggressiveEliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<AggressiveEliminateDeadConstantTestCase>({
         // clang-format off
@@ -4819,7 +4819,7 @@
         // clang-format on
     })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SpecConstantOp, AggressiveEliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<AggressiveEliminateDeadConstantTestCase>({
         // clang-format off
@@ -5001,7 +5001,7 @@
         // clang-format on
     })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LongDefUseChain, AggressiveEliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<AggressiveEliminateDeadConstantTestCase>({
         // clang-format off
@@ -5151,7 +5151,6 @@
 TEST_F(AggressiveDCETest, ParitallyDeadDecorationGroup) {
   const std::string text = R"(
 ; CHECK: OpDecorate [[grp:%\w+]] Restrict
-; CHECK: OpDecorate [[grp]] Aliased
 ; CHECK: [[grp]] = OpDecorationGroup
 ; CHECK: OpGroupDecorate [[grp]] [[output:%\w+]]
 ; CHECK: [[output]] = OpVariable {{%\w+}} Output
@@ -5161,7 +5160,6 @@
 OpEntryPoint Fragment %main "main" %output
 OpExecutionMode %main OriginUpperLeft
 OpDecorate %1 Restrict
-OpDecorate %1 Aliased
 %1 = OpDecorationGroup
 OpGroupDecorate %1 %var %output
 %void = OpTypeVoid
@@ -5185,7 +5183,6 @@
 TEST_F(AggressiveDCETest, ParitallyDeadDecorationGroupDifferentGroupDecorate) {
   const std::string text = R"(
 ; CHECK: OpDecorate [[grp:%\w+]] Restrict
-; CHECK: OpDecorate [[grp]] Aliased
 ; CHECK: [[grp]] = OpDecorationGroup
 ; CHECK: OpGroupDecorate [[grp]] [[output:%\w+]]
 ; CHECK-NOT: OpGroupDecorate
@@ -5196,7 +5193,6 @@
 OpEntryPoint Fragment %main "main" %output
 OpExecutionMode %main OriginUpperLeft
 OpDecorate %1 Restrict
-OpDecorate %1 Aliased
 %1 = OpDecorationGroup
 OpGroupDecorate %1 %output
 OpGroupDecorate %1 %var
@@ -6214,6 +6210,292 @@
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
   SinglePassRunAndMatch<AggressiveDCEPass>(test, true);
 }
+
+TEST_F(AggressiveDCETest, DeadInfiniteLoop) {
+  const std::string test = R"(
+; CHECK: OpSwitch {{%\w+}} {{%\w+}} {{\w+}} {{%\w+}} {{\w+}} [[block:%\w+]]
+; CHECK: [[block]] = OpLabel
+; CHECK-NEXT: OpBranch [[block:%\w+]]
+; CHECK: [[block]] = OpLabel
+; CHECK-NEXT: OpBranch [[block:%\w+]]
+; CHECK: [[block]] = OpLabel
+; CHECK-NEXT: OpReturn
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+          %6 = OpTypeVoid
+          %7 = OpTypeFunction %6
+          %8 = OpTypeFloat 32
+          %9 = OpTypeVector %8 3
+         %10 = OpTypeFunction %9
+         %11 = OpConstant %8 1
+         %12 = OpConstantComposite %9 %11 %11 %11
+         %13 = OpTypeInt 32 1
+         %32 = OpUndef %13
+          %2 = OpFunction %6 None %7
+         %33 = OpLabel
+               OpBranch %34
+         %34 = OpLabel
+               OpLoopMerge %35 %36 None
+               OpBranch %37
+         %37 = OpLabel
+         %38 = OpFunctionCall %9 %39
+               OpSelectionMerge %40 None
+               OpSwitch %32 %40 14 %41 58 %42
+         %42 = OpLabel
+               OpBranch %43
+         %43 = OpLabel
+               OpLoopMerge %44 %45 None
+               OpBranch %45
+         %45 = OpLabel
+               OpBranch %43
+         %44 = OpLabel
+               OpUnreachable
+         %41 = OpLabel
+               OpBranch %36
+         %40 = OpLabel
+               OpBranch %36
+         %36 = OpLabel
+               OpBranch %34
+         %35 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %39 = OpFunction %9 None %10
+         %46 = OpLabel
+               OpReturnValue %12
+               OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<AggressiveDCEPass>(test, true);
+}
+
+TEST_F(AggressiveDCETest, DeadInfiniteLoopReturnValue) {
+  const std::string test = R"(
+; CHECK: [[vec3:%\w+]] = OpTypeVector
+; CHECK: [[undef:%\w+]] = OpUndef [[vec3]]
+; CHECK: OpSwitch {{%\w+}} {{%\w+}} {{\w+}} {{%\w+}} {{\w+}} [[block:%\w+]]
+; CHECK: [[block]] = OpLabel
+; CHECK-NEXT: OpBranch [[block:%\w+]]
+; CHECK: [[block]] = OpLabel
+; CHECK-NEXT: OpBranch [[block:%\w+]]
+; CHECK: [[block]] = OpLabel
+; CHECK-NEXT: OpReturnValue [[undef]]
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+          %6 = OpTypeVoid
+          %7 = OpTypeFunction %6
+          %8 = OpTypeFloat 32
+          %9 = OpTypeVector %8 3
+         %10 = OpTypeFunction %9
+         %11 = OpConstant %8 1
+         %12 = OpConstantComposite %9 %11 %11 %11
+         %13 = OpTypeInt 32 1
+         %32 = OpUndef %13
+          %2 = OpFunction %6 None %7
+      %entry = OpLabel
+       %call = OpFunctionCall %9 %func
+               OpReturn
+               OpFunctionEnd
+       %func = OpFunction %9 None %10
+         %33 = OpLabel
+               OpBranch %34
+         %34 = OpLabel
+               OpLoopMerge %35 %36 None
+               OpBranch %37
+         %37 = OpLabel
+         %38 = OpFunctionCall %9 %39
+               OpSelectionMerge %40 None
+               OpSwitch %32 %40 14 %41 58 %42
+         %42 = OpLabel
+               OpBranch %43
+         %43 = OpLabel
+               OpLoopMerge %44 %45 None
+               OpBranch %45
+         %45 = OpLabel
+               OpBranch %43
+         %44 = OpLabel
+               OpUnreachable
+         %41 = OpLabel
+               OpBranch %36
+         %40 = OpLabel
+               OpBranch %36
+         %36 = OpLabel
+               OpBranch %34
+         %35 = OpLabel
+               OpReturnValue %12
+               OpFunctionEnd
+         %39 = OpFunction %9 None %10
+         %46 = OpLabel
+               OpReturnValue %12
+               OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<AggressiveDCEPass>(test, true);
+}
+
+TEST_F(AggressiveDCETest, TestVariablePointer) {
+  const std::string before =
+      R"(OpCapability Shader
+OpCapability VariablePointers
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %2 "main"
+OpExecutionMode %2 LocalSize 1 1 1
+OpSource GLSL 450
+OpMemberDecorate %_struct_3 0 Offset 0
+OpDecorate %_struct_3 Block
+OpDecorate %4 DescriptorSet 0
+OpDecorate %4 Binding 0
+OpDecorate %_ptr_StorageBuffer_int ArrayStride 4
+OpDecorate %_arr_int_int_128 ArrayStride 4
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%int_128 = OpConstant %int 128
+%_arr_int_int_128 = OpTypeArray %int %int_128
+%_struct_3 = OpTypeStruct %_arr_int_int_128
+%_ptr_StorageBuffer__struct_3 = OpTypePointer StorageBuffer %_struct_3
+%4 = OpVariable %_ptr_StorageBuffer__struct_3 StorageBuffer
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%_ptr_StorageBuffer_int = OpTypePointer StorageBuffer %int
+%2 = OpFunction %void None %8
+%16 = OpLabel
+%17 = OpAccessChain %_ptr_StorageBuffer_int %4 %int_0 %int_0
+OpBranch %18
+%18 = OpLabel
+%19 = OpPhi %_ptr_StorageBuffer_int %17 %16 %20 %21
+OpLoopMerge %22 %21 None
+OpBranchConditional %true %23 %22
+%23 = OpLabel
+OpStore %19 %int_0
+OpBranch %21
+%21 = OpLabel
+%20 = OpPtrAccessChain %_ptr_StorageBuffer_int %19 %int_1
+OpBranch %18
+%22 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<AggressiveDCEPass>(before, before, true, true);
+}
+
+TEST_F(AggressiveDCETest, DeadInputInterfaceV13) {
+  const std::string spirv = R"(
+; CHECK: OpEntryPoint GLCompute %main "main" [[var:%\w+]]
+; CHECK: [[var]] = OpVariable
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %dead
+OpExecutionMode %main LocalSize 1 1 1
+OpName %main "main"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_input_int = OpTypePointer Input %int
+%dead = OpVariable %ptr_input_int Input
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_3);
+  SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
+}
+
+TEST_F(AggressiveDCETest, DeadInputInterfaceV14) {
+  const std::string spirv = R"(
+; CHECK: OpEntryPoint GLCompute %main "main" [[var:%\w+]]
+; CHECK: [[var]] = OpVariable
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %dead
+OpExecutionMode %main LocalSize 1 1 1
+OpName %main "main"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_input_int = OpTypePointer Input %int
+%dead = OpVariable %ptr_input_int Input
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
+}
+
+TEST_F(AggressiveDCETest, DeadInterfaceV14) {
+  const std::string spirv = R"(
+; CHECK-NOT: OpEntryPoint GLCompute %main "main" %
+; CHECK: OpEntryPoint GLCompute %main "main"
+; CHECK-NOT: OpVariable
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %dead
+OpExecutionMode %main LocalSize 1 1 1
+OpName %main "main"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_private_int = OpTypePointer Private %int
+%dead = OpVariable %ptr_private_int Private
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
+}
+
+TEST_F(AggressiveDCETest, DeadInterfacesV14) {
+  const std::string spirv = R"(
+; CHECK: OpEntryPoint GLCompute %main "main" %live1 %live2
+; CHECK-NOT: %dead
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %live1 %dead1 %dead2 %live2
+OpExecutionMode %main LocalSize 1 1 1
+OpName %main "main"
+OpName %live1 "live1"
+OpName %live2 "live2"
+OpName %dead1 "dead1"
+OpName %dead2 "dead2"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int0 = OpConstant %int 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%live1 = OpVariable %ptr_ssbo_int StorageBuffer
+%live2 = OpVariable %ptr_ssbo_int StorageBuffer
+%dead1 = OpVariable %ptr_ssbo_int StorageBuffer
+%dead2 = OpVariable %ptr_ssbo_int StorageBuffer
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpStore %live1 %int0
+OpStore %live2 %int0
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    Check that logical addressing required
diff --git a/test/opt/block_merge_test.cpp b/test/opt/block_merge_test.cpp
index 654e880..f87fd80 100644
--- a/test/opt/block_merge_test.cpp
+++ b/test/opt/block_merge_test.cpp
@@ -483,6 +483,8 @@
 ; CHECK-NOT: OpLoopMerge
 ; CHECK: OpReturn
 ; CHECK: [[continue:%\w+]] = OpLabel
+; CHECK-NEXT: OpBranch [[block:%\w+]]
+; CHECK: [[block]] = OpLabel
 ; CHECK-NEXT: OpBranch [[header]]
 OpCapability Shader
 %1 = OpExtInstImport "GLSL.std.450"
@@ -588,7 +590,7 @@
 OpFunctionEnd
 )";
 
-  SinglePassRunAndMatch<BlockMergePass>(text, true);
+  SinglePassRunAndMatch<BlockMergePass>(text, false);
 }
 
 TEST_F(BlockMergeTest, DontMergeReturn) {
@@ -741,6 +743,186 @@
   SinglePassRunAndMatch<BlockMergePass>(text, true);
 }
 
+TEST_F(BlockMergeTest, OpPhiInSuccessor) {
+  // Checks that when merging blocks A and B, the OpPhi at the start of B is
+  // removed and uses of its definition are replaced appropriately.
+  const std::string prefix =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource ESSL 310
+OpName %main "main"
+OpName %x "x"
+OpName %y "y"
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_1 = OpConstant %int 1
+%main = OpFunction %void None %6
+%10 = OpLabel
+%x = OpVariable %_ptr_Function_int Function
+%y = OpVariable %_ptr_Function_int Function
+OpStore %x %int_1
+%11 = OpLoad %int %x
+)";
+
+  const std::string suffix_before =
+      R"(OpBranch %12
+%12 = OpLabel
+%13 = OpPhi %int %11 %10
+OpStore %y %13
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string suffix_after =
+      R"(OpStore %y %11
+OpReturn
+OpFunctionEnd
+)";
+  SinglePassRunAndCheck<BlockMergePass>(prefix + suffix_before,
+                                        prefix + suffix_after, true, true);
+}
+
+TEST_F(BlockMergeTest, MultipleOpPhisInSuccessor) {
+  // Checks that when merging blocks A and B, the OpPhis at the start of B are
+  // removed and uses of their definitions are replaced appropriately.
+  const std::string prefix =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource ESSL 310
+OpName %main "main"
+OpName %S "S"
+OpMemberName %S 0 "x"
+OpMemberName %S 1 "f"
+OpName %s "s"
+OpName %g "g"
+OpName %y "y"
+OpName %t "t"
+OpName %z "z"
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%float = OpTypeFloat 32
+%S = OpTypeStruct %int %float
+%_ptr_Function_S = OpTypePointer Function %S
+%int_1 = OpConstant %int 1
+%float_2 = OpConstant %float 2
+%16 = OpConstantComposite %S %int_1 %float_2
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Function_int = OpTypePointer Function %int
+%int_3 = OpConstant %int 3
+%int_0 = OpConstant %int 0
+%main = OpFunction %void None %10
+%21 = OpLabel
+%s = OpVariable %_ptr_Function_S Function
+%g = OpVariable %_ptr_Function_float Function
+%y = OpVariable %_ptr_Function_int Function
+%t = OpVariable %_ptr_Function_S Function
+%z = OpVariable %_ptr_Function_float Function
+OpStore %s %16
+OpStore %g %float_2
+OpStore %y %int_3
+%22 = OpLoad %S %s
+OpStore %t %22
+%23 = OpAccessChain %_ptr_Function_float %s %int_1
+%24 = OpLoad %float %23
+%25 = OpLoad %float %g
+)";
+
+  const std::string suffix_before =
+      R"(OpBranch %26
+%26 = OpLabel
+%27 = OpPhi %float %24 %21
+%28 = OpPhi %float %25 %21
+%29 = OpFAdd %float %27 %28
+%30 = OpAccessChain %_ptr_Function_int %s %int_0
+%31 = OpLoad %int %30
+OpBranch %32
+%32 = OpLabel
+%33 = OpPhi %float %29 %26
+%34 = OpPhi %int %31 %26
+%35 = OpConvertSToF %float %34
+OpBranch %36
+%36 = OpLabel
+%37 = OpPhi %float %35 %32
+%38 = OpFSub %float %33 %37
+%39 = OpLoad %int %y
+OpBranch %40
+%40 = OpLabel
+%41 = OpPhi %float %38 %36
+%42 = OpPhi %int %39 %36
+%43 = OpConvertSToF %float %42
+%44 = OpFAdd %float %41 %43
+OpStore %z %44
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string suffix_after =
+      R"(%29 = OpFAdd %float %24 %25
+%30 = OpAccessChain %_ptr_Function_int %s %int_0
+%31 = OpLoad %int %30
+%35 = OpConvertSToF %float %31
+%38 = OpFSub %float %29 %35
+%39 = OpLoad %int %y
+%43 = OpConvertSToF %float %39
+%44 = OpFAdd %float %38 %43
+OpStore %z %44
+OpReturn
+OpFunctionEnd
+)";
+  SinglePassRunAndCheck<BlockMergePass>(prefix + suffix_before,
+                                        prefix + suffix_after, true, true);
+}
+
+TEST_F(BlockMergeTest, UnreachableLoop) {
+  const std::string spirv = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource ESSL 310
+OpName %main "main"
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%bool = OpTypeBool
+%false = OpConstantFalse %bool
+%main = OpFunction %void None %4
+%9 = OpLabel
+OpBranch %10
+%11 = OpLabel
+OpLoopMerge %12 %13 None
+OpBranchConditional %false %13 %14
+%13 = OpLabel
+OpSelectionMerge %15 None
+OpBranchConditional %false %16 %17
+%16 = OpLabel
+OpBranch %15
+%17 = OpLabel
+OpBranch %15
+%15 = OpLabel
+OpBranch %11
+%14 = OpLabel
+OpReturn
+%12 = OpLabel
+OpBranch %10
+%10 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<BlockMergePass>(spirv, spirv, true, true);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    More complex control flow
diff --git a/test/opt/ccp_test.cpp b/test/opt/ccp_test.cpp
index 20d883b..2ee7cce 100644
--- a/test/opt/ccp_test.cpp
+++ b/test/opt/ccp_test.cpp
@@ -896,6 +896,35 @@
   SinglePassRunAndMatch<CCPPass>(text, true);
 }
 
+TEST_F(CCPTest, FoldWithDecoration) {
+  const std::string text = R"(
+; CHECK: OpCapability
+; CHECK-NOT: OpDecorate
+; CHECK: OpFunctionEnd
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %3 RelaxedPrecision
+       %void = OpTypeVoid
+          %5 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v3float = OpTypeVector %float 3
+    %float_0 = OpConstant %float 0
+    %v4float = OpTypeVector %float 4
+         %10 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+          %2 = OpFunction %void None %5
+         %11 = OpLabel
+          %3 = OpVectorShuffle %v3float %10 %10 0 1 2
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<CCPPass>(text, true);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/cfg_test.cpp b/test/opt/cfg_test.cpp
new file mode 100644
index 0000000..2cfc9f3
--- /dev/null
+++ b/test/opt/cfg_test.cpp
@@ -0,0 +1,205 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "source/opt/ir_context.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::ContainerEq;
+
+using CFGTest = PassTest<::testing::Test>;
+
+TEST_F(CFGTest, ForEachBlockInPostOrderIf) {
+  const std::string test = R"(
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpName %main "main"
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%5 = OpConstant %uint 5
+%main = OpFunction %void None %4
+%8 = OpLabel
+OpSelectionMerge %10 None
+OpBranchConditional %true %9 %10
+%9 = OpLabel
+OpBranch %10
+%10 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, test,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ASSERT_NE(nullptr, context);
+
+  CFG* cfg = context->cfg();
+  Module* module = context->module();
+  Function* function = &*module->begin();
+  std::vector<uint32_t> order;
+  cfg->ForEachBlockInPostOrder(&*function->begin(), [&order](BasicBlock* bb) {
+    order.push_back(bb->id());
+  });
+
+  std::vector<uint32_t> expected_result = {10, 9, 8};
+  EXPECT_THAT(order, ContainerEq(expected_result));
+}
+
+TEST_F(CFGTest, ForEachBlockInPostOrderLoop) {
+  const std::string test = R"(
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpName %main "main"
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%5 = OpConstant %uint 5
+%main = OpFunction %void None %4
+%8 = OpLabel
+OpBranch %9
+%9 = OpLabel
+OpLoopMerge %11 %10 None
+OpBranchConditional %true %11 %10
+%10 = OpLabel
+OpBranch %9
+%11 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, test,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ASSERT_NE(nullptr, context);
+
+  CFG* cfg = context->cfg();
+  Module* module = context->module();
+  Function* function = &*module->begin();
+  std::vector<uint32_t> order;
+  cfg->ForEachBlockInPostOrder(&*function->begin(), [&order](BasicBlock* bb) {
+    order.push_back(bb->id());
+  });
+
+  std::vector<uint32_t> expected_result1 = {10, 11, 9, 8};
+  std::vector<uint32_t> expected_result2 = {11, 10, 9, 8};
+  EXPECT_THAT(order, AnyOf(ContainerEq(expected_result1),
+                           ContainerEq(expected_result2)));
+}
+
+TEST_F(CFGTest, ForEachBlockInReversePostOrderIf) {
+  const std::string test = R"(
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpName %main "main"
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%5 = OpConstant %uint 5
+%main = OpFunction %void None %4
+%8 = OpLabel
+OpSelectionMerge %10 None
+OpBranchConditional %true %9 %10
+%9 = OpLabel
+OpBranch %10
+%10 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, test,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ASSERT_NE(nullptr, context);
+
+  CFG* cfg = context->cfg();
+  Module* module = context->module();
+  Function* function = &*module->begin();
+  std::vector<uint32_t> order;
+  cfg->ForEachBlockInReversePostOrder(
+      &*function->begin(),
+      [&order](BasicBlock* bb) { order.push_back(bb->id()); });
+
+  std::vector<uint32_t> expected_result = {8, 9, 10};
+  EXPECT_THAT(order, ContainerEq(expected_result));
+}
+
+TEST_F(CFGTest, ForEachBlockInReversePostOrderLoop) {
+  const std::string test = R"(
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpName %main "main"
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%5 = OpConstant %uint 5
+%main = OpFunction %void None %4
+%8 = OpLabel
+OpBranch %9
+%9 = OpLabel
+OpLoopMerge %11 %10 None
+OpBranchConditional %true %11 %10
+%10 = OpLabel
+OpBranch %9
+%11 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, test,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ASSERT_NE(nullptr, context);
+
+  CFG* cfg = context->cfg();
+  Module* module = context->module();
+  Function* function = &*module->begin();
+  std::vector<uint32_t> order;
+  cfg->ForEachBlockInReversePostOrder(
+      &*function->begin(),
+      [&order](BasicBlock* bb) { order.push_back(bb->id()); });
+
+  std::vector<uint32_t> expected_result1 = {8, 9, 10, 11};
+  std::vector<uint32_t> expected_result2 = {8, 9, 11, 10};
+  EXPECT_THAT(order, AnyOf(ContainerEq(expected_result1),
+                           ContainerEq(expected_result2)));
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/test/opt/code_sink_test.cpp b/test/opt/code_sink_test.cpp
new file mode 100644
index 0000000..f1bd127
--- /dev/null
+++ b/test/opt/code_sink_test.cpp
@@ -0,0 +1,557 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string>
+
+#include "gmock/gmock.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using CodeSinkTest = PassTest<::testing::Test>;
+
+TEST_F(CodeSinkTest, MoveToNextBlock) {
+  const std::string text = R"(
+;CHECK: OpFunction
+;CHECK: OpLabel
+;CHECK: OpLabel
+;CHECK: [[ac:%\w+]] = OpAccessChain
+;CHECK: [[ld:%\w+]] = OpLoad %uint [[ac]]
+;CHECK: OpCopyObject %uint [[ld]]
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+          %9 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %10 = OpTypeFunction %void
+          %1 = OpFunction %void None %10
+         %11 = OpLabel
+         %12 = OpAccessChain %_ptr_Uniform_uint %9 %uint_0
+         %13 = OpLoad %uint %12
+               OpBranch %14
+         %14 = OpLabel
+         %15 = OpCopyObject %uint %13
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<CodeSinkingPass>(text, true);
+}
+
+TEST_F(CodeSinkTest, MovePastSelection) {
+  const std::string text = R"(
+;CHECK: OpFunction
+;CHECK: OpLabel
+;CHECK: OpSelectionMerge [[merge_bb:%\w+]]
+;CHECK: [[merge_bb]] = OpLabel
+;CHECK: [[ac:%\w+]] = OpAccessChain
+;CHECK: [[ld:%\w+]] = OpLoad %uint [[ac]]
+;CHECK: OpCopyObject %uint [[ld]]
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %16
+         %17 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<CodeSinkingPass>(text, true);
+}
+
+TEST_F(CodeSinkTest, MoveIntoSelection) {
+  const std::string text = R"(
+;CHECK: OpFunction
+;CHECK: OpLabel
+;CHECK: OpSelectionMerge [[merge_bb:%\w+]]
+;CHECK-NEXT: OpBranchConditional %true [[bb:%\w+]] [[merge_bb]]
+;CHECK: [[bb]] = OpLabel
+;CHECK-NEXT: [[ac:%\w+]] = OpAccessChain
+;CHECK-NEXT: [[ld:%\w+]] = OpLoad %uint [[ac]]
+;CHECK-NEXT: OpCopyObject %uint [[ld]]
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %16
+         %17 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<CodeSinkingPass>(text, true);
+}
+
+TEST_F(CodeSinkTest, LeaveBeforeSelection) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %20
+         %20 = OpLabel
+               OpBranch %16
+         %17 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpBranch %16
+         %16 = OpLabel
+         %19 = OpCopyObject %uint %15
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, LeaveAloneUseInSameBlock) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+       %cond = OpIEqual %bool %15 %uint_0
+               OpSelectionMerge %16 None
+               OpBranchConditional %cond %17 %16
+         %17 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+         %19 = OpCopyObject %uint %15
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, DontMoveIntoLoop) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpBranch %17
+         %17 = OpLabel
+               OpLoopMerge %merge %cont None
+               OpBranch %cont
+       %cont = OpLabel
+       %cond = OpIEqual %bool %15 %uint_0
+               OpBranchConditional %cond %merge %17
+      %merge = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, DontMoveIntoLoop2) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %16
+         %17 = OpLabel
+               OpLoopMerge %merge %cont None
+               OpBranch %cont
+       %cont = OpLabel
+       %cond = OpIEqual %bool %15 %uint_0
+               OpBranchConditional %cond %merge %17
+      %merge = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, DontMoveSelectionUsedInBothSides) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %20
+         %20 = OpLabel
+         %19 = OpCopyObject %uint %15
+               OpBranch %16
+         %17 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, DontMoveBecauseOfStore) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpStore %14 %15
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %20
+         %20 = OpLabel
+               OpBranch %16
+         %17 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, MoveReadOnlyLoadWithSync) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%mem_semantics = OpConstant %uint 0x42 ; Uniform memeory arquire
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpMemoryBarrier %uint_4 %mem_semantics
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %20
+         %20 = OpLabel
+               OpBranch %16
+         %17 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, DontMoveBecauseOfSync) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpDecorate %_arr_uint_uint_4 BufferBlock
+               OpMemberDecorate %_arr_uint_uint_4 0 Offset 0
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%mem_semantics = OpConstant %uint 0x42 ; Uniform memeory arquire
+%_arr_uint_uint_4 = OpTypeStruct %uint
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+               OpMemoryBarrier %uint_4 %mem_semantics
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %20
+         %20 = OpLabel
+               OpBranch %16
+         %17 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, DontMoveBecauseOfAtomicWithSync) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpDecorate %_arr_uint_uint_4 BufferBlock
+               OpMemberDecorate %_arr_uint_uint_4 0 Offset 0
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%mem_semantics = OpConstant %uint 0x42 ; Uniform memeory arquire
+%_arr_uint_uint_4 = OpTypeStruct %uint
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+         %al = OpAtomicLoad %uint %14 %uint_4 %mem_semantics
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %20
+         %20 = OpLabel
+               OpBranch %16
+         %17 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, MoveWithAtomicWithoutSync) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpDecorate %_arr_uint_uint_4 BufferBlock
+               OpMemberDecorate %_arr_uint_uint_4 0 Offset 0
+       %void = OpTypeVoid
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeStruct %uint
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+         %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+         %12 = OpTypeFunction %void
+          %1 = OpFunction %void None %12
+         %13 = OpLabel
+         %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+         %15 = OpLoad %uint %14
+         %al = OpAtomicLoad %uint %14 %uint_4 %uint_0
+               OpSelectionMerge %16 None
+               OpBranchConditional %true %17 %20
+         %20 = OpLabel
+               OpBranch %16
+         %17 = OpLabel
+         %18 = OpCopyObject %uint %15
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, DecorationOnLoad) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main" %2
+               OpDecorate %3 RelaxedPrecision
+       %void = OpTypeVoid
+          %5 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+%_ptr_Input_float = OpTypePointer Input %float
+          %2 = OpVariable %_ptr_Input_float Input
+          %1 = OpFunction %void None %5
+          %8 = OpLabel
+          %3 = OpLoad %float %2
+               OpReturn
+               OpFunctionEnd
+)";
+
+  // We just want to make sure the code does not crash.
+  auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/test/opt/common_uniform_elim_test.cpp b/test/opt/common_uniform_elim_test.cpp
index 76ab344..923f786 100644
--- a/test/opt/common_uniform_elim_test.cpp
+++ b/test/opt/common_uniform_elim_test.cpp
@@ -1329,6 +1329,145 @@
   SinglePassRunAndMatch<CommonUniformElimPass>(text, true);
 }
 
+TEST_F(CommonUniformElimTest, LoadPlacedAfterPhi) {
+  const std::string text = R"(
+; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Uniform
+; CHECK: OpSelectionMerge [[merge:%\w+]]
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: OpPhi
+; CHECK-NEXT: OpLoad {{%\w+}} [[var]]
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpMemberDecorate %_struct_3 0 Offset 0
+               OpDecorate %_struct_3 Block
+               OpDecorate %4 DescriptorSet 0
+               OpDecorate %4 Binding 0
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void
+       %bool = OpTypeBool
+      %false = OpConstantFalse %bool
+       %uint = OpTypeInt 32 0
+     %v2uint = OpTypeVector %uint 2
+  %_struct_3 = OpTypeStruct %v2uint
+%_ptr_Uniform__struct_3 = OpTypePointer Uniform %_struct_3
+          %4 = OpVariable %_ptr_Uniform__struct_3 Uniform
+     %uint_0 = OpConstant %uint 0
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+     %uint_2 = OpConstant %uint 2
+          %2 = OpFunction %void None %6
+         %15 = OpLabel
+               OpSelectionMerge %16 None
+               OpBranchConditional %false %17 %16
+         %17 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+         %18 = OpPhi %bool %false %15 %false %17
+               OpSelectionMerge %19 None
+               OpBranchConditional %false %20 %21
+         %20 = OpLabel
+         %22 = OpAccessChain %_ptr_Uniform_uint %4 %uint_0 %uint_0
+         %23 = OpLoad %uint %22
+               OpBranch %19
+         %21 = OpLabel
+               OpBranch %19
+         %19 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<CommonUniformElimPass>(text, true);
+}
+
+TEST_F(CommonUniformElimTest, TestVariablePointer) {
+  // Same test a basic1 except the variable pointers capability has been added.
+  // This should stop the transformation from running.
+  const std::string test =
+      R"(OpCapability Shader
+OpCapability VariablePointers
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %fi %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %fi "fi"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_F"
+OpMemberName %U_t 1 "g_F2"
+OpName %_ ""
+OpName %f2 "f2"
+OpName %gl_FragColor "gl_FragColor"
+OpMemberDecorate %U_t 0 Offset 0
+OpMemberDecorate %U_t 1 Offset 4
+OpDecorate %U_t Block
+OpDecorate %_ DescriptorSet 0
+%void = OpTypeVoid
+%11 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Input_float = OpTypePointer Input %float
+%fi = OpVariable %_ptr_Input_float Input
+%float_0 = OpConstant %float 0
+%bool = OpTypeBool
+%U_t = OpTypeStruct %float %float
+%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
+%_ = OpVariable %_ptr_Uniform_U_t Uniform
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%_ptr_Function_float = OpTypePointer Function %float
+%int_1 = OpConstant %int 1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %11
+%26 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%f2 = OpVariable %_ptr_Function_float Function
+%27 = OpLoad %v4float %BaseColor
+OpStore %v %27
+%28 = OpLoad %float %fi
+%29 = OpFOrdGreaterThan %bool %28 %float_0
+OpSelectionMerge %30 None
+OpBranchConditional %29 %31 %32
+%31 = OpLabel
+%33 = OpLoad %v4float %v
+%34 = OpAccessChain %_ptr_Uniform_float %_ %int_0
+%35 = OpLoad %float %34
+%36 = OpVectorTimesScalar %v4float %33 %35
+OpStore %v %36
+OpBranch %30
+%32 = OpLabel
+%37 = OpAccessChain %_ptr_Uniform_float %_ %int_1
+%38 = OpLoad %float %37
+%39 = OpAccessChain %_ptr_Uniform_float %_ %int_0
+%40 = OpLoad %float %39
+%41 = OpFSub %float %38 %40
+OpStore %f2 %41
+%42 = OpLoad %v4float %v
+%43 = OpLoad %float %f2
+%44 = OpVectorTimesScalar %v4float %42 %43
+OpStore %v %44
+OpBranch %30
+%30 = OpLabel
+%45 = OpLoad %v4float %v
+OpStore %gl_FragColor %45
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<CommonUniformElimPass>(test, test, true, true);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    Disqualifying cases: extensions, decorations, non-logical addressing,
diff --git a/test/opt/dead_branch_elim_test.cpp b/test/opt/dead_branch_elim_test.cpp
index 66e3bdd..eec11f2 100644
--- a/test/opt/dead_branch_elim_test.cpp
+++ b/test/opt/dead_branch_elim_test.cpp
@@ -2868,6 +2868,131 @@
   SinglePassRunAndMatch<DeadBranchElimPass>(body, true);
 }
 
+TEST_F(DeadBranchElimTest, DontFoldBackedge) {
+  const std::string body =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%bool = OpTypeBool
+%false = OpConstantFalse %bool
+%2 = OpFunction %void None %4
+%7 = OpLabel
+OpBranch %8
+%8 = OpLabel
+OpLoopMerge %9 %10 None
+OpBranch %11
+%11 = OpLabel
+%12 = OpUndef %bool
+OpSelectionMerge %10 None
+OpBranchConditional %12 %13 %10
+%13 = OpLabel
+OpBranch %9
+%10 = OpLabel
+OpBranch %14
+%14 = OpLabel
+OpBranchConditional %false %8 %9
+%9 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<DeadBranchElimPass>(body, body, true);
+}
+
+TEST_F(DeadBranchElimTest, FoldBackedgeToHeader) {
+  const std::string body =
+      R"(
+; CHECK: OpLabel
+; CHECK: [[header:%\w+]] = OpLabel
+; CHECK-NEXT: OpLoopMerge {{%\w+}} [[cont:%\w+]]
+; CHECK: [[cont]] = OpLabel
+; This branch may not be in the continue block, but must come after it.
+; CHECK: OpBranch [[header]]
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%2 = OpFunction %void None %4
+%7 = OpLabel
+OpBranch %8
+%8 = OpLabel
+OpLoopMerge %9 %10 None
+OpBranch %11
+%11 = OpLabel
+%12 = OpUndef %bool
+OpSelectionMerge %10 None
+OpBranchConditional %12 %13 %10
+%13 = OpLabel
+OpBranch %9
+%10 = OpLabel
+OpBranch %14
+%14 = OpLabel
+OpBranchConditional %true %8 %9
+%9 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<DeadBranchElimPass>(body, true);
+}
+
+TEST_F(DeadBranchElimTest, UnreachableMergeAndContinueSameBlock) {
+  const std::string spirv = R"(
+; CHECK: OpLabel
+; CHECK: [[outer:%\w+]] = OpLabel
+; CHECK-NEXT: OpLoopMerge [[outer_merge:%\w+]] [[outer_cont:%\w+]] None
+; CHECK-NEXT: OpBranch [[inner:%\w+]]
+; CHECK: [[inner]] = OpLabel
+; CHECK: OpLoopMerge [[outer_cont]] [[inner_cont:%\w+]] None
+; CHECK: [[inner_cont]] = OpLabel
+; CHECK-NEXT: OpBranch [[inner]]
+; CHECK: [[outer_cont]] = OpLabel
+; CHECK-NEXT: OpBranch [[outer]]
+; CHECK: [[outer_merge]] = OpLabel
+; CHECK-NEXT: OpUnreachable
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpBranch %outer_loop
+%outer_loop = OpLabel
+OpLoopMerge %outer_merge %outer_continue None
+OpBranch %inner_loop
+%inner_loop = OpLabel
+OpLoopMerge %outer_continue %inner_continue None
+OpBranch %inner_body
+%inner_body = OpLabel
+OpSelectionMerge %inner_continue None
+OpBranchConditional %true %ret %inner_continue
+%ret = OpLabel
+OpReturn
+%inner_continue = OpLabel
+OpBranchConditional %true %outer_continue %inner_loop
+%outer_continue = OpLabel
+OpBranchConditional %true %outer_merge %outer_loop
+%outer_merge = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<DeadBranchElimPass>(spirv, true);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    More complex control flow
diff --git a/test/opt/decompose_initialized_variables_test.cpp b/test/opt/decompose_initialized_variables_test.cpp
new file mode 100644
index 0000000..cdebb3f
--- /dev/null
+++ b/test/opt/decompose_initialized_variables_test.cpp
@@ -0,0 +1,252 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <vector>
+
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using DecomposeInitializedVariablesTest = PassTest<::testing::Test>;
+
+std::string single_entry_header = R"(OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Vertex %1 "shader"
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%4 = OpConstantNull %uint
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+)";
+
+std::string GetFunctionTest(std::string body) {
+  auto result = single_entry_header;
+  result += "%_ptr_Function_uint = OpTypePointer Function %uint\n";
+  result += "%1 = OpFunction %void None %6\n";
+  result += "%8 = OpLabel\n";
+  result += body + "\n";
+  result += "OpReturn\n";
+  result += "OpFunctionEnd\n";
+  return result;
+}
+
+TEST_F(DecomposeInitializedVariablesTest, FunctionChanged) {
+  std::string input = "%9 = OpVariable %_ptr_Function_uint Function %uint_1";
+  std::string expected = R"(%9 = OpVariable %_ptr_Function_uint Function
+OpStore %9 %uint_1)";
+
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      GetFunctionTest(input), GetFunctionTest(expected),
+      /* skip_nop = */ false);
+}
+
+TEST_F(DecomposeInitializedVariablesTest, FunctionUnchanged) {
+  std::string input = "%9 = OpVariable %_ptr_Function_uint Function";
+
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      GetFunctionTest(input), GetFunctionTest(input), /* skip_nop = */ false);
+}
+
+TEST_F(DecomposeInitializedVariablesTest, FunctionMultipleVariables) {
+  std::string input = R"(%9 = OpVariable %_ptr_Function_uint Function %uint_1
+%10 = OpVariable %_ptr_Function_uint Function %4)";
+  std::string expected = R"(%9 = OpVariable %_ptr_Function_uint Function
+%10 = OpVariable %_ptr_Function_uint Function
+OpStore %9 %uint_1
+OpStore %10 %4)";
+
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      GetFunctionTest(input), GetFunctionTest(expected),
+      /* skip_nop = */ false);
+}
+
+std::string GetGlobalTest(std::string storage_class, bool initialized,
+                          bool decomposed) {
+  auto result = single_entry_header;
+
+  result += "%_ptr_" + storage_class + "_uint = OpTypePointer " +
+            storage_class + " %uint\n";
+  if (initialized) {
+    result += "%8 = OpVariable %_ptr_" + storage_class + "_uint " +
+              storage_class + " %4\n";
+  } else {
+    result += "%8 = OpVariable %_ptr_" + storage_class + "_uint " +
+              storage_class + "\n";
+  }
+  result += R"(%1 = OpFunction %void None %9
+%9 = OpLabel
+)";
+  if (decomposed) result += "OpStore %8 %4\n";
+  result += R"(OpReturn
+OpFunctionEnd
+)";
+  return result;
+}
+
+TEST_F(DecomposeInitializedVariablesTest, PrivateChanged) {
+  std::string input = GetGlobalTest("Private", true, false);
+  std::string expected = GetGlobalTest("Private", false, true);
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      input, expected, /* skip_nop = */ false);
+}
+
+TEST_F(DecomposeInitializedVariablesTest, PrivateUnchanged) {
+  std::string input = GetGlobalTest("Private", false, false);
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      input, input, /* skip_nop = */ false);
+}
+
+TEST_F(DecomposeInitializedVariablesTest, OutputChanged) {
+  std::string input = GetGlobalTest("Output", true, false);
+  std::string expected = GetGlobalTest("Output", false, true);
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      input, expected, /* skip_nop = */ false);
+}
+
+TEST_F(DecomposeInitializedVariablesTest, OutputUnchanged) {
+  std::string input = GetGlobalTest("Output", false, false);
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      input, input, /* skip_nop = */ false);
+}
+
+std::string multiple_entry_header = R"(OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Vertex %1 "vertex"
+OpEntryPoint Fragment %2 "fragment"
+%uint = OpTypeInt 32 0
+%4 = OpConstantNull %uint
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+)";
+
+std::string GetGlobalMultipleEntryTest(std::string storage_class,
+                                       bool initialized, bool decomposed) {
+  auto result = multiple_entry_header;
+  result += "%_ptr_" + storage_class + "_uint = OpTypePointer " +
+            storage_class + " %uint\n";
+  if (initialized) {
+    result += "%8 = OpVariable %_ptr_" + storage_class + "_uint " +
+              storage_class + " %4\n";
+  } else {
+    result += "%8 = OpVariable %_ptr_" + storage_class + "_uint " +
+              storage_class + "\n";
+  }
+  result += R"(%1 = OpFunction %void None %9
+%9 = OpLabel
+)";
+  if (decomposed) result += "OpStore %8 %4\n";
+  result += R"(OpReturn
+OpFunctionEnd
+%2 = OpFunction %void None %10
+%10 = OpLabel
+)";
+  if (decomposed) result += "OpStore %8 %4\n";
+  result += R"(OpReturn
+OpFunctionEnd
+)";
+
+  return result;
+}
+
+TEST_F(DecomposeInitializedVariablesTest, PrivateMultipleEntryChanged) {
+  std::string input = GetGlobalMultipleEntryTest("Private", true, false);
+  std::string expected = GetGlobalMultipleEntryTest("Private", false, true);
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      input, expected, /* skip_nop = */ false);
+}
+
+TEST_F(DecomposeInitializedVariablesTest, PrivateMultipleEntryUnchanged) {
+  std::string input = GetGlobalMultipleEntryTest("Private", false, false);
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      input, input, /* skip_nop = */ false);
+}
+
+TEST_F(DecomposeInitializedVariablesTest, OutputMultipleEntryChanged) {
+  std::string input = GetGlobalMultipleEntryTest("Output", true, false);
+  std::string expected = GetGlobalMultipleEntryTest("Output", false, true);
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      input, expected, /* skip_nop = */ false);
+}
+
+TEST_F(DecomposeInitializedVariablesTest, OutputMultipleEntryUnchanged) {
+  std::string input = GetGlobalMultipleEntryTest("Output", false, false);
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      input, input, /* skip_nop = */ false);
+}
+
+std::string GetGlobalWithNonEntryPointTest(std::string storage_class,
+                                           bool initialized, bool decomposed) {
+  auto result = single_entry_header;
+  result += "%_ptr_" + storage_class + "_uint = OpTypePointer " +
+            storage_class + " %uint\n";
+  if (initialized) {
+    result += "%8 = OpVariable %_ptr_" + storage_class + "_uint " +
+              storage_class + " %4\n";
+  } else {
+    result += "%8 = OpVariable %_ptr_" + storage_class + "_uint " +
+              storage_class + "\n";
+  }
+  result += R"(%1 = OpFunction %void None %9
+%9 = OpLabel
+)";
+  if (decomposed) result += "OpStore %8 %4\n";
+  result += R"(OpReturn
+OpFunctionEnd
+%10 = OpFunction %void None %11
+%11 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  return result;
+}
+
+TEST_F(DecomposeInitializedVariablesTest, PrivateWithNonEntryPointChanged) {
+  std::string input = GetGlobalWithNonEntryPointTest("Private", true, false);
+  std::string expected = GetGlobalWithNonEntryPointTest("Private", false, true);
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      input, expected, /* skip_nop = */ false);
+}
+
+TEST_F(DecomposeInitializedVariablesTest, PrivateWithNonEntryPointUnchanged) {
+  std::string input = GetGlobalWithNonEntryPointTest("Private", false, false);
+  //  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      input, input, /* skip_nop = */ false);
+}
+
+TEST_F(DecomposeInitializedVariablesTest, OutputWithNonEntryPointChanged) {
+  std::string input = GetGlobalWithNonEntryPointTest("Output", true, false);
+  std::string expected = GetGlobalWithNonEntryPointTest("Output", false, true);
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      input, expected, /* skip_nop = */ false);
+}
+
+TEST_F(DecomposeInitializedVariablesTest, OutputWithNonEntryPointUnchanged) {
+  std::string input = GetGlobalWithNonEntryPointTest("Output", false, false);
+  //  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<DecomposeInitializedVariablesPass>(
+      input, input, /* skip_nop = */ false);
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/test/opt/decoration_manager_test.cpp b/test/opt/decoration_manager_test.cpp
index f85ff6a..3ae6458 100644
--- a/test/opt/decoration_manager_test.cpp
+++ b/test/opt/decoration_manager_test.cpp
@@ -709,8 +709,8 @@
   EXPECT_THAT(GetErrorMessage(), "");
 
   std::string expected_decorations =
-      R"(OpDecorateStringGOOGLE %5 HlslSemanticGOOGLE "blah"
-OpDecorateId %5 HlslCounterBufferGOOGLE %2
+      R"(OpDecorateString %5 UserSemantic "blah"
+OpDecorateId %5 CounterBuffer %2
 OpDecorate %5 Aliased
 )";
   EXPECT_THAT(ToText(decorations), expected_decorations);
@@ -720,11 +720,11 @@
 OpExtension "SPV_GOOGLE_hlsl_functionality1"
 OpExtension "SPV_GOOGLE_decorate_string"
 OpMemoryModel Logical GLSL450
-OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "blah"
-OpDecorateId %1 HlslCounterBufferGOOGLE %2
+OpDecorateString %1 UserSemantic "blah"
+OpDecorateId %1 CounterBuffer %2
 OpDecorate %1 Aliased
-OpDecorateStringGOOGLE %5 HlslSemanticGOOGLE "blah"
-OpDecorateId %5 HlslCounterBufferGOOGLE %2
+OpDecorateString %5 UserSemantic "blah"
+OpDecorateId %5 CounterBuffer %2
 OpDecorate %5 Aliased
 %3 = OpTypeInt 32 0
 %4 = OpTypePointer Uniform %3
diff --git a/test/opt/def_use_test.cpp b/test/opt/def_use_test.cpp
index 3b856ce..cfdad74 100644
--- a/test/opt/def_use_test.cpp
+++ b/test/opt/def_use_test.cpp
@@ -206,7 +206,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TestCase, ParseDefUseTest,
     ::testing::ValuesIn(std::vector<ParseDefUseCase>{
         {"", {{}, {}}},                              // no instruction
@@ -629,7 +629,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TestCase, ReplaceUseTest,
     ::testing::ValuesIn(std::vector<ReplaceUseCase>{
       { // no use, no replace request
@@ -981,7 +981,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TestCase, KillDefTest,
     ::testing::ValuesIn(std::vector<KillDefCase>{
       { // no def, no use, no kill
@@ -1343,7 +1343,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TestCase, AnalyzeInstDefUseTest,
     ::testing::ValuesIn(std::vector<AnalyzeInstDefUseTestCase>{
       { // A type declaring instruction.
@@ -1464,7 +1464,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TestCase, KillInstTest,
     ::testing::ValuesIn(std::vector<KillInstTestCase>{
       // Kill id defining instructions.
@@ -1588,7 +1588,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TestCase, GetAnnotationsTest,
     ::testing::ValuesIn(std::vector<GetAnnotationsTestCase>{
       // empty
diff --git a/test/opt/eliminate_dead_const_test.cpp b/test/opt/eliminate_dead_const_test.cpp
index 7fac866..59f06f9 100644
--- a/test/opt/eliminate_dead_const_test.cpp
+++ b/test/opt/eliminate_dead_const_test.cpp
@@ -197,7 +197,7 @@
       assembly_with_dead_const, expected, /*  skip_nop = */ true);
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ScalarTypeConstants, EliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
         // clang-format off
@@ -265,7 +265,7 @@
         // clang-format on
     })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     VectorTypeConstants, EliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
         // clang-format off
@@ -358,7 +358,7 @@
         // clang-format on
     })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     StructTypeConstants, EliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
         // clang-format off
@@ -485,7 +485,7 @@
         // clang-format on
     })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ScalarTypeSpecConstants, EliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
         // clang-format off
@@ -522,7 +522,7 @@
         // clang-format on
     })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     VectorTypeSpecConstants, EliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
         // clang-format off
@@ -617,7 +617,7 @@
         // clang-format on
     })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SpecConstantOp, EliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
         // clang-format off
@@ -768,7 +768,7 @@
         // clang-format on
     })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LongDefUseChain, EliminateDeadConstantTest,
     ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
         // clang-format off
diff --git a/test/opt/eliminate_dead_member_test.cpp b/test/opt/eliminate_dead_member_test.cpp
new file mode 100644
index 0000000..7d5db7d
--- /dev/null
+++ b/test/opt/eliminate_dead_member_test.cpp
@@ -0,0 +1,1087 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "assembly_builder.h"
+#include "gmock/gmock.h"
+#include "pass_fixture.h"
+#include "pass_utils.h"
+
+namespace {
+
+using namespace spvtools;
+
+using EliminateDeadMemberTest = opt::PassTest<::testing::Test>;
+
+TEST_F(EliminateDeadMemberTest, RemoveMember1) {
+  // Test that the member "y" is removed.
+  // Update OpMemberName for |y| and |z|.
+  // Update OpMemberDecorate for |y| and |z|.
+  // Update OpAccessChain for access to |z|.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "x"
+; CHECK-NEXT: OpMemberName %type__Globals 1 "z"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 0
+; CHECK: OpMemberDecorate %type__Globals 1 Offset 8
+; CHECK: %type__Globals = OpTypeStruct %float %float
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %int_0
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %uint_1
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %in_var_Position %gl_Position
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %in_var_Position "in.var.Position"
+               OpName %main "main"
+               OpDecorate %gl_Position BuiltIn Position
+               OpDecorate %in_var_Position Location 0
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 8
+               OpDecorate %type__Globals Block
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %float = OpTypeFloat 32
+      %int_2 = OpConstant %int 2
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %15 = OpTypeFunction %void
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+%in_var_Position = OpVariable %_ptr_Input_v4float Input
+%gl_Position = OpVariable %_ptr_Output_v4float Output
+       %main = OpFunction %void None %15
+         %17 = OpLabel
+         %18 = OpLoad %v4float %in_var_Position
+         %19 = OpAccessChain %_ptr_Uniform_float %_Globals %int_0
+         %20 = OpLoad %float %19
+         %21 = OpCompositeExtract %float %18 0
+         %22 = OpFAdd %float %21 %20
+         %23 = OpCompositeInsert %v4float %22 %18 0
+         %24 = OpCompositeExtract %float %18 1
+         %25 = OpCompositeInsert %v4float %24 %23 1
+         %26 = OpAccessChain %_ptr_Uniform_float %_Globals %int_2
+         %27 = OpLoad %float %26
+         %28 = OpCompositeExtract %float %18 2
+         %29 = OpFAdd %float %28 %27
+         %30 = OpCompositeInsert %v4float %29 %25 2
+               OpStore %gl_Position %30
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMemberWithGroupDecorations) {
+  // Test that the member "y" is removed.
+  // Update OpGroupMemberDecorate for %type__Globals member 1 and 2.
+  // Update OpAccessChain for access to %type__Globals member 2.
+  const std::string text = R"(
+; CHECK: OpDecorate [[gr1:%\w+]] Offset 0
+; CHECK: OpDecorate [[gr2:%\w+]] Offset 4
+; CHECK: OpDecorate [[gr3:%\w+]] Offset 8
+; CHECK: [[gr1]] = OpDecorationGroup
+; CHECK: [[gr2]] = OpDecorationGroup
+; CHECK: [[gr3]] = OpDecorationGroup
+; CHECK: OpGroupMemberDecorate [[gr1]] %type__Globals 0
+; CHECK-NOT: OpGroupMemberDecorate [[gr2]]
+; CHECK: OpGroupMemberDecorate [[gr3]] %type__Globals 1
+; CHECK: %type__Globals = OpTypeStruct %float %float
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %int_0
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %uint_1
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %in_var_Position %gl_Position
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpName %_Globals "$Globals"
+               OpDecorate %gl_Position BuiltIn Position
+               OpDecorate %in_var_Position Location 0
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpDecorate %gr1 Offset 0
+               OpDecorate %gr2 Offset 4
+               OpDecorate %gr3 Offset 8
+               OpDecorate %type__Globals Block
+        %gr1 = OpDecorationGroup
+        %gr2 = OpDecorationGroup
+        %gr3 = OpDecorationGroup
+               OpGroupMemberDecorate %gr1 %type__Globals 0
+               OpGroupMemberDecorate %gr2 %type__Globals 1
+               OpGroupMemberDecorate %gr3 %type__Globals 2
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %float = OpTypeFloat 32
+      %int_2 = OpConstant %int 2
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %15 = OpTypeFunction %void
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+%in_var_Position = OpVariable %_ptr_Input_v4float Input
+%gl_Position = OpVariable %_ptr_Output_v4float Output
+       %main = OpFunction %void None %15
+         %17 = OpLabel
+         %18 = OpLoad %v4float %in_var_Position
+         %19 = OpAccessChain %_ptr_Uniform_float %_Globals %int_0
+         %20 = OpLoad %float %19
+         %21 = OpCompositeExtract %float %18 0
+         %22 = OpFAdd %float %21 %20
+         %23 = OpCompositeInsert %v4float %22 %18 0
+         %24 = OpCompositeExtract %float %18 1
+         %25 = OpCompositeInsert %v4float %24 %23 1
+         %26 = OpAccessChain %_ptr_Uniform_float %_Globals %int_2
+         %27 = OpLoad %float %26
+         %28 = OpCompositeExtract %float %18 2
+         %29 = OpFAdd %float %28 %27
+         %30 = OpCompositeInsert %v4float %29 %25 2
+               OpStore %gl_Position %30
+               OpReturn
+               OpFunctionEnd
+)";
+
+  // Skipping validation because of a bug in the validator.  See issue #2376.
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, false);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMemberUpdateConstant) {
+  // Test that the member "x" is removed.
+  // Update the OpConstantComposite instruction.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "y"
+; CHECK-NEXT: OpMemberName %type__Globals 1 "z"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 4
+; CHECK: OpMemberDecorate %type__Globals 1 Offset 8
+; CHECK: %type__Globals = OpTypeStruct %float %float
+; CHECK: OpConstantComposite %type__Globals %float_1 %float_2
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %uint_0
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %uint_1
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %in_var_Position %gl_Position
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %in_var_Position "in.var.Position"
+               OpName %main "main"
+               OpDecorate %gl_Position BuiltIn Position
+               OpDecorate %in_var_Position Location 0
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 8
+               OpDecorate %type__Globals Block
+        %int = OpTypeInt 32 1
+      %int_1 = OpConstant %int 1
+      %float = OpTypeFloat 32
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+    %float_2 = OpConstant %float 2
+      %int_2 = OpConstant %int 2
+%type__Globals = OpTypeStruct %float %float %float
+         %13 = OpConstantComposite %type__Globals %float_0 %float_1 %float_2
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %19 = OpTypeFunction %void
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+%in_var_Position = OpVariable %_ptr_Input_v4float Input
+%gl_Position = OpVariable %_ptr_Output_v4float Output
+       %main = OpFunction %void None %19
+         %21 = OpLabel
+         %22 = OpLoad %v4float %in_var_Position
+         %23 = OpAccessChain %_ptr_Uniform_float %_Globals %int_1
+         %24 = OpLoad %float %23
+         %25 = OpCompositeExtract %float %22 0
+         %26 = OpFAdd %float %25 %24
+         %27 = OpCompositeInsert %v4float %26 %22 0
+         %28 = OpCompositeExtract %float %22 1
+         %29 = OpCompositeInsert %v4float %28 %27 1
+         %30 = OpAccessChain %_ptr_Uniform_float %_Globals %int_2
+         %31 = OpLoad %float %30
+         %32 = OpCompositeExtract %float %22 2
+         %33 = OpFAdd %float %32 %31
+         %34 = OpCompositeInsert %v4float %33 %29 2
+               OpStore %gl_Position %34
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMemberUpdateCompositeConstruct) {
+  // Test that the member "x" is removed.
+  // Update the OpConstantComposite instruction.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "y"
+; CHECK-NEXT: OpMemberName %type__Globals 1 "z"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 4
+; CHECK: OpMemberDecorate %type__Globals 1 Offset 8
+; CHECK: %type__Globals = OpTypeStruct %float %float
+; CHECK: OpCompositeConstruct %type__Globals %float_1 %float_2
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %uint_0
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals %uint_1
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %in_var_Position %gl_Position
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %in_var_Position "in.var.Position"
+               OpName %main "main"
+               OpDecorate %gl_Position BuiltIn Position
+               OpDecorate %in_var_Position Location 0
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 8
+               OpDecorate %type__Globals Block
+        %int = OpTypeInt 32 1
+      %int_1 = OpConstant %int 1
+      %float = OpTypeFloat 32
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+    %float_2 = OpConstant %float 2
+      %int_2 = OpConstant %int 2
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %19 = OpTypeFunction %void
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+%in_var_Position = OpVariable %_ptr_Input_v4float Input
+%gl_Position = OpVariable %_ptr_Output_v4float Output
+       %main = OpFunction %void None %19
+         %21 = OpLabel
+         %13 = OpCompositeConstruct %type__Globals %float_0 %float_1 %float_2
+         %22 = OpLoad %v4float %in_var_Position
+         %23 = OpAccessChain %_ptr_Uniform_float %_Globals %int_1
+         %24 = OpLoad %float %23
+         %25 = OpCompositeExtract %float %22 0
+         %26 = OpFAdd %float %25 %24
+         %27 = OpCompositeInsert %v4float %26 %22 0
+         %28 = OpCompositeExtract %float %22 1
+         %29 = OpCompositeInsert %v4float %28 %27 1
+         %30 = OpAccessChain %_ptr_Uniform_float %_Globals %int_2
+         %31 = OpLoad %float %30
+         %32 = OpCompositeExtract %float %22 2
+         %33 = OpFAdd %float %32 %31
+         %34 = OpCompositeInsert %v4float %33 %29 2
+               OpStore %gl_Position %34
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMembersUpdateInserExtract1) {
+  // Test that the members "x" and "z" are removed.
+  // Update the OpCompositeExtract instruction.
+  // Remove the OpCompositeInsert instruction since the member being inserted is
+  // dead.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "y"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 4
+; CHECK-NOT: OpMemberDecorate %type__Globals 1 Offset
+; CHECK: %type__Globals = OpTypeStruct %float
+; CHECK: [[ld:%\w+]] = OpLoad %type__Globals %_Globals
+; CHECK: OpCompositeExtract %float [[ld]] 0
+; CHECK-NOT: OpCompositeInsert
+; CHECK: OpReturn
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 8
+               OpDecorate %type__Globals Block
+      %float = OpTypeFloat 32
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %7 = OpTypeFunction %void
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %void None %7
+          %8 = OpLabel
+          %9 = OpLoad %type__Globals %_Globals
+         %10 = OpCompositeExtract %float %9 1
+         %11 = OpCompositeInsert %type__Globals %10 %9 2
+               OpReturn
+               OpFunctionEnd
+
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMembersUpdateInserExtract2) {
+  // Test that the members "x" and "z" are removed.
+  // Update the OpCompositeExtract instruction.
+  // Update the OpCompositeInsert instruction.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "y"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 4
+; CHECK-NOT: OpMemberDecorate %type__Globals 1 Offset
+; CHECK: %type__Globals = OpTypeStruct %float
+; CHECK: [[ld:%\w+]] = OpLoad %type__Globals %_Globals
+; CHECK: [[ex:%\w+]] = OpCompositeExtract %float [[ld]] 0
+; CHECK: OpCompositeInsert %type__Globals [[ex]] [[ld]] 0
+; CHECK: OpReturn
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 8
+               OpDecorate %type__Globals Block
+      %float = OpTypeFloat 32
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %7 = OpTypeFunction %void
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %void None %7
+          %8 = OpLabel
+          %9 = OpLoad %type__Globals %_Globals
+         %10 = OpCompositeExtract %float %9 1
+         %11 = OpCompositeInsert %type__Globals %10 %9 1
+               OpReturn
+               OpFunctionEnd
+
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMembersUpdateInserExtract3) {
+  // Test that the members "x" and "z" are removed, and one member from the
+  // substruct. Update the OpCompositeExtract instruction. Update the
+  // OpCompositeInsert instruction.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "y"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 16
+; CHECK-NOT: OpMemberDecorate %type__Globals 1 Offset
+; CHECK: OpMemberDecorate [[struct:%\w+]] 0 Offset 4
+; CHECK: [[struct:%\w+]] = OpTypeStruct %float
+; CHECK: %type__Globals = OpTypeStruct [[struct]]
+; CHECK: [[ld:%\w+]] = OpLoad %type__Globals %_Globals
+; CHECK: [[ex:%\w+]] = OpCompositeExtract %float [[ld]] 0 0
+; CHECK: OpCompositeInsert %type__Globals [[ex]] [[ld]] 0 0
+; CHECK: OpReturn
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 16
+               OpMemberDecorate %type__Globals 2 Offset 24
+               OpMemberDecorate %_struct_6 0 Offset 0
+               OpMemberDecorate %_struct_6 1 Offset 4
+               OpDecorate %type__Globals Block
+      %float = OpTypeFloat 32
+  %_struct_6 = OpTypeStruct %float %float
+%type__Globals = OpTypeStruct %float %_struct_6 %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %7 = OpTypeFunction %void
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %void None %7
+          %8 = OpLabel
+          %9 = OpLoad %type__Globals %_Globals
+         %10 = OpCompositeExtract %float %9 1 1
+         %11 = OpCompositeInsert %type__Globals %10 %9 1 1
+               OpReturn
+               OpFunctionEnd
+
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMembersUpdateInserExtract4) {
+  // Test that the members "x" and "z" are removed, and one member from the
+  // substruct. Update the OpCompositeExtract instruction. Update the
+  // OpCompositeInsert instruction.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "y"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 16
+; CHECK-NOT: OpMemberDecorate %type__Globals 1 Offset
+; CHECK: OpMemberDecorate [[struct:%\w+]] 0 Offset 4
+; CHECK: [[struct:%\w+]] = OpTypeStruct %float
+; CHECK: [[array:%\w+]] = OpTypeArray [[struct]]
+; CHECK: %type__Globals = OpTypeStruct [[array]]
+; CHECK: [[ld:%\w+]] = OpLoad %type__Globals %_Globals
+; CHECK: [[ex:%\w+]] = OpCompositeExtract %float [[ld]] 0 1 0
+; CHECK: OpCompositeInsert %type__Globals [[ex]] [[ld]] 0 1 0
+; CHECK: OpReturn
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 16
+               OpMemberDecorate %type__Globals 2 Offset 80
+               OpMemberDecorate %_struct_6 0 Offset 0
+               OpMemberDecorate %_struct_6 1 Offset 4
+               OpDecorate %array ArrayStride 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0                         ; 32-bit int, sign-less
+     %uint_4 = OpConstant %uint 4
+      %float = OpTypeFloat 32
+  %_struct_6 = OpTypeStruct %float %float
+  %array = OpTypeArray %_struct_6 %uint_4
+%type__Globals = OpTypeStruct %float %array %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %7 = OpTypeFunction %void
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %void None %7
+          %8 = OpLabel
+          %9 = OpLoad %type__Globals %_Globals
+         %10 = OpCompositeExtract %float %9 1 1 1
+         %11 = OpCompositeInsert %type__Globals %10 %9 1 1 1
+               OpReturn
+               OpFunctionEnd
+
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMembersUpdateArrayLength) {
+  // Test that the members "x" and "y" are removed.
+  // Member "z" is live because of the OpArrayLength instruction.
+  // Update the OpArrayLength instruction.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "z"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 16
+; CHECK-NOT: OpMemberDecorate %type__Globals 1 Offset
+; CHECK: %type__Globals = OpTypeStruct %_runtimearr_float
+; CHECK: OpArrayLength %uint %_Globals 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0
+      %float = OpTypeFloat 32
+%_runtimearr_float = OpTypeRuntimeArray %float
+%type__Globals = OpTypeStruct %float %float %_runtimearr_float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %9 = OpTypeFunction %void
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %void None %9
+         %10 = OpLabel
+         %11 = OpLoad %type__Globals %_Globals
+         %12 = OpArrayLength %uint %_Globals 2
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, KeepMembersOpStore) {
+  // Test that all members are kept because of an OpStore.
+  // No change expected.
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %_Globals "$Globals2"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0
+      %float = OpTypeFloat 32
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %9 = OpTypeFunction %void
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+   %_Globals2 = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %void None %9
+         %10 = OpLabel
+         %11 = OpLoad %type__Globals %_Globals
+               OpStore %_Globals2 %11
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<opt::EliminateDeadMembersPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(EliminateDeadMemberTest, KeepMembersOpCopyMemory) {
+  // Test that all members are kept because of an OpCopyMemory.
+  // No change expected.
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %_Globals "$Globals2"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0
+      %float = OpTypeFloat 32
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %9 = OpTypeFunction %void
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+   %_Globals2 = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %void None %9
+         %10 = OpLabel
+               OpCopyMemory %_Globals2 %_Globals
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<opt::EliminateDeadMembersPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(EliminateDeadMemberTest, KeepMembersOpCopyMemorySized) {
+  // Test that all members are kept because of an OpCopyMemorySized.
+  // No change expected.
+  const std::string text = R"(
+               OpCapability Shader
+               OpCapability Addresses
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %_Globals "$Globals2"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0
+    %uint_20 = OpConstant %uint 20
+      %float = OpTypeFloat 32
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %9 = OpTypeFunction %void
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+   %_Globals2 = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %void None %9
+         %10 = OpLabel
+               OpCopyMemorySized %_Globals2 %_Globals %uint_20
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<opt::EliminateDeadMembersPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(EliminateDeadMemberTest, KeepMembersOpReturnValue) {
+  // Test that all members are kept because of an OpCopyMemorySized.
+  // No change expected.
+  const std::string text = R"(
+               OpCapability Shader
+               OpCapability Linkage
+               OpMemoryModel Logical GLSL450
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %_Globals "$Globals2"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0
+    %uint_20 = OpConstant %uint 20
+      %float = OpTypeFloat 32
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+       %void = OpTypeVoid
+          %9 = OpTypeFunction %type__Globals
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+   %_Globals2 = OpVariable %_ptr_Uniform_type__Globals Uniform
+       %main = OpFunction %type__Globals None %9
+         %10 = OpLabel
+         %11 = OpLoad %type__Globals %_Globals
+               OpReturnValue %11
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<opt::EliminateDeadMembersPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMemberAccessChainWithArrays) {
+  // Leave only 1 member in each of the structs.
+  // Update OpMemberName, OpMemberDecorate, and OpAccessChain.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "y"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 16
+; CHECK: OpMemberDecorate [[struct:%\w+]] 0 Offset 4
+; CHECK: [[struct]] = OpTypeStruct %float
+; CHECK: [[array:%\w+]] = OpTypeArray [[struct]]
+; CHECK: %type__Globals = OpTypeStruct [[array]]
+; CHECK: [[undef:%\w+]] = OpUndef %uint
+; CHECK: OpAccessChain %_ptr_Uniform_float %_Globals [[undef]] %uint_0 [[undef]] %uint_0
+               OpCapability Shader
+               OpCapability VariablePointersStorageBuffer
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 16
+               OpMemberDecorate %type__Globals 2 Offset 48
+               OpMemberDecorate %_struct_4 0 Offset 0
+               OpMemberDecorate %_struct_4 1 Offset 4
+               OpDecorate %_arr__struct_4_uint_2 ArrayStride 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
+     %uint_2 = OpConstant %uint 2
+     %uint_3 = OpConstant %uint 3
+      %float = OpTypeFloat 32
+  %_struct_4 = OpTypeStruct %float %float
+%_arr__struct_4_uint_2 = OpTypeArray %_struct_4 %uint_2
+%type__Globals = OpTypeStruct %float %_arr__struct_4_uint_2 %float
+%_arr_type__Globals_uint_3 = OpTypeArray %type__Globals %uint_3
+%_ptr_Uniform__arr_type__Globals_uint_3 = OpTypePointer Uniform %_arr_type__Globals_uint_3
+       %void = OpTypeVoid
+         %15 = OpTypeFunction %void
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+   %_Globals = OpVariable %_ptr_Uniform__arr_type__Globals_uint_3 Uniform
+       %main = OpFunction %void None %15
+         %17 = OpLabel
+         %18 = OpUndef %uint
+         %19 = OpAccessChain %_ptr_Uniform_float %_Globals %18 %uint_1 %18 %uint_1
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMemberInboundsAccessChain) {
+  // Test that the member "y" is removed.
+  // Update OpMemberName for |y| and |z|.
+  // Update OpMemberDecorate for |y| and |z|.
+  // Update OpInboundsAccessChain for access to |z|.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "x"
+; CHECK-NEXT: OpMemberName %type__Globals 1 "z"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 0
+; CHECK: OpMemberDecorate %type__Globals 1 Offset 8
+; CHECK: %type__Globals = OpTypeStruct %float %float
+; CHECK: OpInBoundsAccessChain %_ptr_Uniform_float %_Globals %int_0
+; CHECK: OpInBoundsAccessChain %_ptr_Uniform_float %_Globals %uint_1
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %in_var_Position %gl_Position
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %in_var_Position "in.var.Position"
+               OpName %main "main"
+               OpDecorate %gl_Position BuiltIn Position
+               OpDecorate %in_var_Position Location 0
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 8
+               OpDecorate %type__Globals Block
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %float = OpTypeFloat 32
+      %int_2 = OpConstant %int 2
+%type__Globals = OpTypeStruct %float %float %float
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %15 = OpTypeFunction %void
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+   %_Globals = OpVariable %_ptr_Uniform_type__Globals Uniform
+%in_var_Position = OpVariable %_ptr_Input_v4float Input
+%gl_Position = OpVariable %_ptr_Output_v4float Output
+       %main = OpFunction %void None %15
+         %17 = OpLabel
+         %18 = OpLoad %v4float %in_var_Position
+         %19 = OpInBoundsAccessChain %_ptr_Uniform_float %_Globals %int_0
+         %20 = OpLoad %float %19
+         %21 = OpCompositeExtract %float %18 0
+         %22 = OpFAdd %float %21 %20
+         %23 = OpCompositeInsert %v4float %22 %18 0
+         %24 = OpCompositeExtract %float %18 1
+         %25 = OpCompositeInsert %v4float %24 %23 1
+         %26 = OpInBoundsAccessChain %_ptr_Uniform_float %_Globals %int_2
+         %27 = OpLoad %float %26
+         %28 = OpCompositeExtract %float %18 2
+         %29 = OpFAdd %float %28 %27
+         %30 = OpCompositeInsert %v4float %29 %25 2
+               OpStore %gl_Position %30
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMemberPtrAccessChain) {
+  // Test that the member "y" is removed.
+  // Update OpMemberName for |y| and |z|.
+  // Update OpMemberDecorate for |y| and |z|.
+  // Update OpInboundsAccessChain for access to |z|.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "x"
+; CHECK-NEXT: OpMemberName %type__Globals 1 "z"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 0
+; CHECK: OpMemberDecorate %type__Globals 1 Offset 16
+; CHECK: %type__Globals = OpTypeStruct %float %float
+; CHECK: [[ac:%\w+]] = OpAccessChain %_ptr_Uniform_type__Globals %_Globals %uint_0
+; CHECK: OpPtrAccessChain %_ptr_Uniform_float [[ac]] %uint_1 %uint_0
+; CHECK: OpPtrAccessChain %_ptr_Uniform_float [[ac]] %uint_0 %uint_1
+               OpCapability Shader
+               OpCapability VariablePointersStorageBuffer
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
+     %uint_2 = OpConstant %uint 2
+     %uint_3 = OpConstant %uint 3
+      %float = OpTypeFloat 32
+%type__Globals = OpTypeStruct %float %float %float
+%_arr_type__Globals_uint_3 = OpTypeArray %type__Globals %uint_3
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+%_ptr_Uniform__arr_type__Globals_uint_3 = OpTypePointer Uniform %_arr_type__Globals_uint_3
+       %void = OpTypeVoid
+         %14 = OpTypeFunction %void
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+   %_Globals = OpVariable %_ptr_Uniform__arr_type__Globals_uint_3 Uniform
+       %main = OpFunction %void None %14
+         %16 = OpLabel
+         %17 = OpAccessChain %_ptr_Uniform_type__Globals %_Globals %uint_0
+         %18 = OpPtrAccessChain %_ptr_Uniform_float %17 %uint_1 %uint_0
+         %19 = OpPtrAccessChain %_ptr_Uniform_float %17 %uint_0 %uint_2
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, RemoveMemberInBoundsPtrAccessChain) {
+  // Test that the member "y" is removed.
+  // Update OpMemberName for |y| and |z|.
+  // Update OpMemberDecorate for |y| and |z|.
+  // Update OpInboundsAccessChain for access to |z|.
+  const std::string text = R"(
+; CHECK: OpName
+; CHECK-NEXT: OpMemberName %type__Globals 0 "x"
+; CHECK-NEXT: OpMemberName %type__Globals 1 "z"
+; CHECK-NOT: OpMemberName
+; CHECK: OpMemberDecorate %type__Globals 0 Offset 0
+; CHECK: OpMemberDecorate %type__Globals 1 Offset 16
+; CHECK: %type__Globals = OpTypeStruct %float %float
+; CHECK: [[ac:%\w+]] = OpAccessChain %_ptr_Uniform_type__Globals %_Globals %uint_0
+; CHECK: OpInBoundsPtrAccessChain %_ptr_Uniform_float [[ac]] %uint_1 %uint_0
+; CHECK: OpInBoundsPtrAccessChain %_ptr_Uniform_float [[ac]] %uint_0 %uint_1
+               OpCapability Shader
+               OpCapability Addresses
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource HLSL 600
+               OpName %type__Globals "type.$Globals"
+               OpMemberName %type__Globals 0 "x"
+               OpMemberName %type__Globals 1 "y"
+               OpMemberName %type__Globals 2 "z"
+               OpName %_Globals "$Globals"
+               OpName %main "main"
+               OpDecorate %_Globals DescriptorSet 0
+               OpDecorate %_Globals Binding 0
+               OpMemberDecorate %type__Globals 0 Offset 0
+               OpMemberDecorate %type__Globals 1 Offset 4
+               OpMemberDecorate %type__Globals 2 Offset 16
+               OpDecorate %type__Globals Block
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
+     %uint_2 = OpConstant %uint 2
+     %uint_3 = OpConstant %uint 3
+      %float = OpTypeFloat 32
+%type__Globals = OpTypeStruct %float %float %float
+%_arr_type__Globals_uint_3 = OpTypeArray %type__Globals %uint_3
+%_ptr_Uniform_type__Globals = OpTypePointer Uniform %type__Globals
+%_ptr_Uniform__arr_type__Globals_uint_3 = OpTypePointer Uniform %_arr_type__Globals_uint_3
+       %void = OpTypeVoid
+         %14 = OpTypeFunction %void
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+   %_Globals = OpVariable %_ptr_Uniform__arr_type__Globals_uint_3 Uniform
+       %main = OpFunction %void None %14
+         %16 = OpLabel
+         %17 = OpAccessChain %_ptr_Uniform_type__Globals %_Globals %uint_0
+         %18 = OpInBoundsPtrAccessChain %_ptr_Uniform_float %17 %uint_1 %uint_0
+         %19 = OpInBoundsPtrAccessChain %_ptr_Uniform_float %17 %uint_0 %uint_2
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::EliminateDeadMembersPass>(text, true);
+}
+
+TEST_F(EliminateDeadMemberTest, DontRemoveModfStructResultTypeMembers) {
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main"
+               OpExecutionMode %main OriginUpperLeft
+               OpSource HLSL 600
+      %float = OpTypeFloat 32
+       %void = OpTypeVoid
+         %21 = OpTypeFunction %void
+%ModfStructType = OpTypeStruct %float %float
+%main = OpFunction %void None %21
+         %22 = OpLabel
+         %23 = OpUndef %float
+         %24 = OpExtInst %ModfStructType %1 ModfStruct %23
+         %25 = OpCompositeExtract %float %24 1
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<opt::EliminateDeadMembersPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(EliminateDeadMemberTest, DontChangeInputStructs) {
+  // The input for a shader has to match the type of the output from the
+  // previous shader in the pipeline.  Because of that, we cannot change the
+  // types of input variables.
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %input_var
+               OpExecutionMode %main OriginUpperLeft
+               OpSource HLSL 600
+      %float = OpTypeFloat 32
+       %void = OpTypeVoid
+         %21 = OpTypeFunction %void
+%in_var_type = OpTypeStruct %float %float
+%in_ptr_type = OpTypePointer Input %in_var_type
+%input_var = OpVariable %in_ptr_type Input
+%main = OpFunction %void None %21
+         %22 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<opt::EliminateDeadMembersPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(EliminateDeadMemberTest, DontChangeOutputStructs) {
+  // The output for a shader has to match the type of the output from the
+  // previous shader in the pipeline.  Because of that, we cannot change the
+  // types of output variables.
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %output_var
+               OpExecutionMode %main OriginUpperLeft
+               OpSource HLSL 600
+      %float = OpTypeFloat 32
+       %void = OpTypeVoid
+         %21 = OpTypeFunction %void
+%out_var_type = OpTypeStruct %float %float
+%out_ptr_type = OpTypePointer Output %out_var_type
+%output_var = OpVariable %out_ptr_type Output
+%main = OpFunction %void None %21
+         %22 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndDisassemble<opt::EliminateDeadMembersPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ true);
+  EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+}  // namespace
diff --git a/test/opt/fix_storage_class_test.cpp b/test/opt/fix_storage_class_test.cpp
new file mode 100644
index 0000000..4c8504a
--- /dev/null
+++ b/test/opt/fix_storage_class_test.cpp
@@ -0,0 +1,840 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string>
+
+#include "gmock/gmock.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using FixStorageClassTest = PassTest<::testing::Test>;
+
+TEST_F(FixStorageClassTest, FixAccessChain) {
+  const std::string text = R"(
+; CHECK: OpAccessChain %_ptr_Workgroup_float
+; CHECK: OpAccessChain %_ptr_Uniform_float
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "testMain" %gl_GlobalInvocationID %gl_LocalInvocationID %gl_WorkGroupID
+               OpExecutionMode %1 LocalSize 8 8 1
+               OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
+               OpDecorate %gl_LocalInvocationID BuiltIn LocalInvocationId
+               OpDecorate %gl_WorkGroupID BuiltIn WorkgroupId
+               OpDecorate %8 DescriptorSet 0
+               OpDecorate %8 Binding 0
+               OpDecorate %_runtimearr_float ArrayStride 4
+               OpMemberDecorate %_struct_7 0 Offset 0
+               OpDecorate %_struct_7 BufferBlock
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %float = OpTypeFloat 32
+    %float_2 = OpConstant %float 2
+       %uint = OpTypeInt 32 0
+    %uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%ptr = OpTypePointer Function %_arr_float_uint_10
+%_arr__arr_float_uint_10_uint_10 = OpTypeArray %_arr_float_uint_10 %uint_10
+  %_struct_5 = OpTypeStruct %_arr__arr_float_uint_10_uint_10
+%_ptr_Workgroup__struct_5 = OpTypePointer Workgroup %_struct_5
+%_runtimearr_float = OpTypeRuntimeArray %float
+  %_struct_7 = OpTypeStruct %_runtimearr_float
+%_ptr_Uniform__struct_7 = OpTypePointer Uniform %_struct_7
+     %v3uint = OpTypeVector %uint 3
+%_ptr_Input_v3uint = OpTypePointer Input %v3uint
+       %void = OpTypeVoid
+         %30 = OpTypeFunction %void
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+          %6 = OpVariable %_ptr_Workgroup__struct_5 Workgroup
+          %8 = OpVariable %_ptr_Uniform__struct_7 Uniform
+%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_LocalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_WorkGroupID = OpVariable %_ptr_Input_v3uint Input
+          %1 = OpFunction %void None %30
+         %38 = OpLabel
+         %44 = OpLoad %v3uint %gl_LocalInvocationID
+         %50 = OpAccessChain %_ptr_Function_float %6 %int_0 %int_0 %int_0
+         %51 = OpLoad %float %50
+         %52 = OpFMul %float %float_2 %51
+               OpStore %50 %52
+         %55 = OpLoad %float %50
+         %59 = OpCompositeExtract %uint %44 0
+         %60 = OpAccessChain %_ptr_Uniform_float %8 %int_0 %59
+               OpStore %60 %55
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FixStorageClass>(text, false);
+}
+
+TEST_F(FixStorageClassTest, FixLinkedAccessChain) {
+  const std::string text = R"(
+; CHECK: OpAccessChain %_ptr_Workgroup__arr_float_uint_10
+; CHECK: OpAccessChain %_ptr_Workgroup_float
+; CHECK: OpAccessChain %_ptr_Uniform_float
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "testMain" %gl_GlobalInvocationID %gl_LocalInvocationID %gl_WorkGroupID
+               OpExecutionMode %1 LocalSize 8 8 1
+               OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
+               OpDecorate %gl_LocalInvocationID BuiltIn LocalInvocationId
+               OpDecorate %gl_WorkGroupID BuiltIn WorkgroupId
+               OpDecorate %5 DescriptorSet 0
+               OpDecorate %5 Binding 0
+               OpDecorate %_runtimearr_float ArrayStride 4
+               OpMemberDecorate %_struct_7 0 Offset 0
+               OpDecorate %_struct_7 BufferBlock
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %float = OpTypeFloat 32
+    %float_2 = OpConstant %float 2
+       %uint = OpTypeInt 32 0
+    %uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%_ptr_Function__arr_float_uint_10 = OpTypePointer Function %_arr_float_uint_10
+%_ptr = OpTypePointer Function %_arr_float_uint_10
+%_arr__arr_float_uint_10_uint_10 = OpTypeArray %_arr_float_uint_10 %uint_10
+ %_struct_17 = OpTypeStruct %_arr__arr_float_uint_10_uint_10
+%_ptr_Workgroup__struct_17 = OpTypePointer Workgroup %_struct_17
+%_runtimearr_float = OpTypeRuntimeArray %float
+  %_struct_7 = OpTypeStruct %_runtimearr_float
+%_ptr_Uniform__struct_7 = OpTypePointer Uniform %_struct_7
+     %v3uint = OpTypeVector %uint 3
+%_ptr_Input_v3uint = OpTypePointer Input %v3uint
+       %void = OpTypeVoid
+         %23 = OpTypeFunction %void
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+         %27 = OpVariable %_ptr_Workgroup__struct_17 Workgroup
+          %5 = OpVariable %_ptr_Uniform__struct_7 Uniform
+%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_LocalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_WorkGroupID = OpVariable %_ptr_Input_v3uint Input
+          %1 = OpFunction %void None %23
+         %28 = OpLabel
+         %29 = OpLoad %v3uint %gl_LocalInvocationID
+         %30 = OpAccessChain %_ptr_Function__arr_float_uint_10 %27 %int_0 %int_0
+         %31 = OpAccessChain %_ptr_Function_float %30 %int_0
+         %32 = OpLoad %float %31
+         %33 = OpFMul %float %float_2 %32
+               OpStore %31 %33
+         %34 = OpLoad %float %31
+         %35 = OpCompositeExtract %uint %29 0
+         %36 = OpAccessChain %_ptr_Uniform_float %5 %int_0 %35
+               OpStore %36 %34
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FixStorageClass>(text, false);
+}
+
+TEST_F(FixStorageClassTest, FixCopyObject) {
+  const std::string text = R"(
+; CHECK: OpCopyObject %_ptr_Workgroup__struct_17
+; CHECK: OpAccessChain %_ptr_Workgroup_float
+; CHECK: OpAccessChain %_ptr_Uniform_float
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "testMain" %gl_GlobalInvocationID %gl_LocalInvocationID %gl_WorkGroupID
+               OpExecutionMode %1 LocalSize 8 8 1
+               OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
+               OpDecorate %gl_LocalInvocationID BuiltIn LocalInvocationId
+               OpDecorate %gl_WorkGroupID BuiltIn WorkgroupId
+               OpDecorate %8 DescriptorSet 0
+               OpDecorate %8 Binding 0
+               OpDecorate %_runtimearr_float ArrayStride 4
+               OpMemberDecorate %_struct_7 0 Offset 0
+               OpDecorate %_struct_7 BufferBlock
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %float = OpTypeFloat 32
+    %float_2 = OpConstant %float 2
+       %uint = OpTypeInt 32 0
+    %uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%ptr = OpTypePointer Function %_arr_float_uint_10
+%_arr__arr_float_uint_10_uint_10 = OpTypeArray %_arr_float_uint_10 %uint_10
+  %_struct_17 = OpTypeStruct %_arr__arr_float_uint_10_uint_10
+%_ptr_Workgroup__struct_17 = OpTypePointer Workgroup %_struct_17
+%_ptr_Function__struct_17 = OpTypePointer Function %_struct_17
+%_runtimearr_float = OpTypeRuntimeArray %float
+  %_struct_7 = OpTypeStruct %_runtimearr_float
+%_ptr_Uniform__struct_7 = OpTypePointer Uniform %_struct_7
+     %v3uint = OpTypeVector %uint 3
+%_ptr_Input_v3uint = OpTypePointer Input %v3uint
+       %void = OpTypeVoid
+         %30 = OpTypeFunction %void
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+          %6 = OpVariable %_ptr_Workgroup__struct_17 Workgroup
+          %8 = OpVariable %_ptr_Uniform__struct_7 Uniform
+%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_LocalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_WorkGroupID = OpVariable %_ptr_Input_v3uint Input
+          %1 = OpFunction %void None %30
+         %38 = OpLabel
+         %44 = OpLoad %v3uint %gl_LocalInvocationID
+         %cp = OpCopyObject %_ptr_Function__struct_17 %6
+         %50 = OpAccessChain %_ptr_Function_float %cp %int_0 %int_0 %int_0
+         %51 = OpLoad %float %50
+         %52 = OpFMul %float %float_2 %51
+               OpStore %50 %52
+         %55 = OpLoad %float %50
+         %59 = OpCompositeExtract %uint %44 0
+         %60 = OpAccessChain %_ptr_Uniform_float %8 %int_0 %59
+               OpStore %60 %55
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FixStorageClass>(text, false);
+}
+
+TEST_F(FixStorageClassTest, FixPhiInSelMerge) {
+  const std::string text = R"(
+; CHECK: OpPhi %_ptr_Workgroup__struct_19
+; CHECK: OpAccessChain %_ptr_Workgroup_float
+; CHECK: OpAccessChain %_ptr_Uniform_float
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "testMain" %gl_GlobalInvocationID %gl_LocalInvocationID %gl_WorkGroupID
+               OpExecutionMode %1 LocalSize 8 8 1
+               OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
+               OpDecorate %gl_LocalInvocationID BuiltIn LocalInvocationId
+               OpDecorate %gl_WorkGroupID BuiltIn WorkgroupId
+               OpDecorate %5 DescriptorSet 0
+               OpDecorate %5 Binding 0
+               OpDecorate %_runtimearr_float ArrayStride 4
+               OpMemberDecorate %_struct_7 0 Offset 0
+               OpDecorate %_struct_7 BufferBlock
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %float = OpTypeFloat 32
+    %float_2 = OpConstant %float 2
+       %uint = OpTypeInt 32 0
+    %uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%_ptr_Function__arr_float_uint_10 = OpTypePointer Function %_arr_float_uint_10
+%_arr__arr_float_uint_10_uint_10 = OpTypeArray %_arr_float_uint_10 %uint_10
+ %_struct_19 = OpTypeStruct %_arr__arr_float_uint_10_uint_10
+%_ptr_Workgroup__struct_19 = OpTypePointer Workgroup %_struct_19
+%_ptr_Function__struct_19 = OpTypePointer Function %_struct_19
+%_runtimearr_float = OpTypeRuntimeArray %float
+  %_struct_7 = OpTypeStruct %_runtimearr_float
+%_ptr_Uniform__struct_7 = OpTypePointer Uniform %_struct_7
+     %v3uint = OpTypeVector %uint 3
+%_ptr_Input_v3uint = OpTypePointer Input %v3uint
+       %void = OpTypeVoid
+         %25 = OpTypeFunction %void
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+         %28 = OpVariable %_ptr_Workgroup__struct_19 Workgroup
+         %29 = OpVariable %_ptr_Workgroup__struct_19 Workgroup
+          %5 = OpVariable %_ptr_Uniform__struct_7 Uniform
+%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_LocalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_WorkGroupID = OpVariable %_ptr_Input_v3uint Input
+          %1 = OpFunction %void None %25
+         %30 = OpLabel
+               OpSelectionMerge %31 None
+               OpBranchConditional %true %32 %31
+         %32 = OpLabel
+               OpBranch %31
+         %31 = OpLabel
+         %33 = OpPhi %_ptr_Function__struct_19 %28 %30 %29 %32
+         %34 = OpLoad %v3uint %gl_LocalInvocationID
+         %35 = OpAccessChain %_ptr_Function_float %33 %int_0 %int_0 %int_0
+         %36 = OpLoad %float %35
+         %37 = OpFMul %float %float_2 %36
+               OpStore %35 %37
+         %38 = OpLoad %float %35
+         %39 = OpCompositeExtract %uint %34 0
+         %40 = OpAccessChain %_ptr_Uniform_float %5 %int_0 %39
+               OpStore %40 %38
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FixStorageClass>(text, false);
+}
+
+TEST_F(FixStorageClassTest, FixPhiInLoop) {
+  const std::string text = R"(
+; CHECK: OpPhi %_ptr_Workgroup__struct_19
+; CHECK: OpAccessChain %_ptr_Workgroup_float
+; CHECK: OpAccessChain %_ptr_Uniform_float
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "testMain" %gl_GlobalInvocationID %gl_LocalInvocationID %gl_WorkGroupID
+               OpExecutionMode %1 LocalSize 8 8 1
+               OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
+               OpDecorate %gl_LocalInvocationID BuiltIn LocalInvocationId
+               OpDecorate %gl_WorkGroupID BuiltIn WorkgroupId
+               OpDecorate %5 DescriptorSet 0
+               OpDecorate %5 Binding 0
+               OpDecorate %_runtimearr_float ArrayStride 4
+               OpMemberDecorate %_struct_7 0 Offset 0
+               OpDecorate %_struct_7 BufferBlock
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %float = OpTypeFloat 32
+    %float_2 = OpConstant %float 2
+       %uint = OpTypeInt 32 0
+    %uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%_ptr_Function__arr_float_uint_10 = OpTypePointer Function %_arr_float_uint_10
+%_arr__arr_float_uint_10_uint_10 = OpTypeArray %_arr_float_uint_10 %uint_10
+ %_struct_19 = OpTypeStruct %_arr__arr_float_uint_10_uint_10
+%_ptr_Workgroup__struct_19 = OpTypePointer Workgroup %_struct_19
+%_ptr_Function__struct_19 = OpTypePointer Function %_struct_19
+%_runtimearr_float = OpTypeRuntimeArray %float
+  %_struct_7 = OpTypeStruct %_runtimearr_float
+%_ptr_Uniform__struct_7 = OpTypePointer Uniform %_struct_7
+     %v3uint = OpTypeVector %uint 3
+%_ptr_Input_v3uint = OpTypePointer Input %v3uint
+       %void = OpTypeVoid
+         %25 = OpTypeFunction %void
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+         %28 = OpVariable %_ptr_Workgroup__struct_19 Workgroup
+         %29 = OpVariable %_ptr_Workgroup__struct_19 Workgroup
+          %5 = OpVariable %_ptr_Uniform__struct_7 Uniform
+%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_LocalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_WorkGroupID = OpVariable %_ptr_Input_v3uint Input
+          %1 = OpFunction %void None %25
+         %30 = OpLabel
+               OpSelectionMerge %31 None
+               OpBranchConditional %true %32 %31
+         %32 = OpLabel
+               OpBranch %31
+         %31 = OpLabel
+         %33 = OpPhi %_ptr_Function__struct_19 %28 %30 %29 %32
+         %34 = OpLoad %v3uint %gl_LocalInvocationID
+         %35 = OpAccessChain %_ptr_Function_float %33 %int_0 %int_0 %int_0
+         %36 = OpLoad %float %35
+         %37 = OpFMul %float %float_2 %36
+               OpStore %35 %37
+         %38 = OpLoad %float %35
+         %39 = OpCompositeExtract %uint %34 0
+         %40 = OpAccessChain %_ptr_Uniform_float %5 %int_0 %39
+               OpStore %40 %38
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FixStorageClass>(text, false);
+}
+
+TEST_F(FixStorageClassTest, DontChangeFunctionCalls) {
+  const std::string text = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "testMain"
+OpExecutionMode %1 LocalSize 8 8 1
+OpDecorate %2 DescriptorSet 0
+OpDecorate %2 Binding 0
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%_ptr_Workgroup_int = OpTypePointer Workgroup %int
+%_ptr_Uniform_int = OpTypePointer Uniform %int
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%9 = OpTypeFunction %_ptr_Uniform_int %_ptr_Function_int
+%10 = OpVariable %_ptr_Workgroup_int Workgroup
+%2 = OpVariable %_ptr_Uniform_int Uniform
+%1 = OpFunction %void None %8
+%11 = OpLabel
+%12 = OpFunctionCall %_ptr_Uniform_int %13 %10
+OpReturn
+OpFunctionEnd
+%13 = OpFunction %_ptr_Uniform_int None %9
+%14 = OpFunctionParameter %_ptr_Function_int
+%15 = OpLabel
+OpReturnValue %2
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<FixStorageClass>(text, text, false, false);
+}
+
+TEST_F(FixStorageClassTest, FixSelect) {
+  const std::string text = R"(
+; CHECK: OpSelect %_ptr_Workgroup__struct_19
+; CHECK: OpAccessChain %_ptr_Workgroup_float
+; CHECK: OpAccessChain %_ptr_Uniform_float
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "testMain" %gl_GlobalInvocationID %gl_LocalInvocationID %gl_WorkGroupID
+               OpExecutionMode %1 LocalSize 8 8 1
+               OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
+               OpDecorate %gl_LocalInvocationID BuiltIn LocalInvocationId
+               OpDecorate %gl_WorkGroupID BuiltIn WorkgroupId
+               OpDecorate %5 DescriptorSet 0
+               OpDecorate %5 Binding 0
+               OpDecorate %_runtimearr_float ArrayStride 4
+               OpMemberDecorate %_struct_7 0 Offset 0
+               OpDecorate %_struct_7 BufferBlock
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %float = OpTypeFloat 32
+    %float_2 = OpConstant %float 2
+       %uint = OpTypeInt 32 0
+    %uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%_ptr_Function__arr_float_uint_10 = OpTypePointer Function %_arr_float_uint_10
+%_arr__arr_float_uint_10_uint_10 = OpTypeArray %_arr_float_uint_10 %uint_10
+ %_struct_19 = OpTypeStruct %_arr__arr_float_uint_10_uint_10
+%_ptr_Workgroup__struct_19 = OpTypePointer Workgroup %_struct_19
+%_ptr_Function__struct_19 = OpTypePointer Function %_struct_19
+%_runtimearr_float = OpTypeRuntimeArray %float
+  %_struct_7 = OpTypeStruct %_runtimearr_float
+%_ptr_Uniform__struct_7 = OpTypePointer Uniform %_struct_7
+     %v3uint = OpTypeVector %uint 3
+%_ptr_Input_v3uint = OpTypePointer Input %v3uint
+       %void = OpTypeVoid
+         %25 = OpTypeFunction %void
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+         %28 = OpVariable %_ptr_Workgroup__struct_19 Workgroup
+         %29 = OpVariable %_ptr_Workgroup__struct_19 Workgroup
+          %5 = OpVariable %_ptr_Uniform__struct_7 Uniform
+%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_LocalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_WorkGroupID = OpVariable %_ptr_Input_v3uint Input
+          %1 = OpFunction %void None %25
+         %30 = OpLabel
+         %33 = OpSelect %_ptr_Function__struct_19 %true %28 %29
+         %34 = OpLoad %v3uint %gl_LocalInvocationID
+         %35 = OpAccessChain %_ptr_Function_float %33 %int_0 %int_0 %int_0
+         %36 = OpLoad %float %35
+         %37 = OpFMul %float %float_2 %36
+               OpStore %35 %37
+         %38 = OpLoad %float %35
+         %39 = OpCompositeExtract %uint %34 0
+         %40 = OpAccessChain %_ptr_Uniform_float %5 %int_0 %39
+               OpStore %40 %38
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FixStorageClass>(text, false);
+}
+
+TEST_F(FixStorageClassTest, BitCast) {
+  const std::string text = R"(OpCapability VariablePointersStorageBuffer
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "main"
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%_ptr_Output_void = OpTypePointer Output %void
+%_ptr_Private__ptr_Output_void = OpTypePointer Private %_ptr_Output_void
+%6 = OpVariable %_ptr_Private__ptr_Output_void Private
+%1 = OpFunction %void Inline %3
+%7 = OpLabel
+%8 = OpBitcast %_ptr_Output_void %6
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<FixStorageClass>(text, text, false);
+}
+
+TEST_F(FixStorageClassTest, FixLinkedAccessChain2) {
+  // This case is similar to FixLinkedAccessChain.  The difference is that the
+  // first OpAccessChain instruction starts as workgroup storage class.  Only
+  // the second one needs to change.
+  const std::string text = R"(
+; CHECK: OpAccessChain %_ptr_Workgroup__arr_float_uint_10
+; CHECK: OpAccessChain %_ptr_Workgroup_float
+; CHECK: OpAccessChain %_ptr_Uniform_float
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "testMain" %gl_GlobalInvocationID %gl_LocalInvocationID %gl_WorkGroupID
+               OpExecutionMode %1 LocalSize 8 8 1
+               OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
+               OpDecorate %gl_LocalInvocationID BuiltIn LocalInvocationId
+               OpDecorate %gl_WorkGroupID BuiltIn WorkgroupId
+               OpDecorate %5 DescriptorSet 0
+               OpDecorate %5 Binding 0
+               OpDecorate %_runtimearr_float ArrayStride 4
+               OpMemberDecorate %_struct_7 0 Offset 0
+               OpDecorate %_struct_7 BufferBlock
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %float = OpTypeFloat 32
+    %float_2 = OpConstant %float 2
+       %uint = OpTypeInt 32 0
+    %uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%_ptr_Workgroup__arr_float_uint_10 = OpTypePointer Workgroup %_arr_float_uint_10
+%_ptr = OpTypePointer Function %_arr_float_uint_10
+%_arr__arr_float_uint_10_uint_10 = OpTypeArray %_arr_float_uint_10 %uint_10
+ %_struct_17 = OpTypeStruct %_arr__arr_float_uint_10_uint_10
+%_ptr_Workgroup__struct_17 = OpTypePointer Workgroup %_struct_17
+%_runtimearr_float = OpTypeRuntimeArray %float
+  %_struct_7 = OpTypeStruct %_runtimearr_float
+%_ptr_Uniform__struct_7 = OpTypePointer Uniform %_struct_7
+     %v3uint = OpTypeVector %uint 3
+%_ptr_Input_v3uint = OpTypePointer Input %v3uint
+       %void = OpTypeVoid
+         %23 = OpTypeFunction %void
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+         %27 = OpVariable %_ptr_Workgroup__struct_17 Workgroup
+          %5 = OpVariable %_ptr_Uniform__struct_7 Uniform
+%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_LocalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%gl_WorkGroupID = OpVariable %_ptr_Input_v3uint Input
+          %1 = OpFunction %void None %23
+         %28 = OpLabel
+         %29 = OpLoad %v3uint %gl_LocalInvocationID
+         %30 = OpAccessChain %_ptr_Workgroup__arr_float_uint_10 %27 %int_0 %int_0
+         %31 = OpAccessChain %_ptr_Function_float %30 %int_0
+         %32 = OpLoad %float %31
+         %33 = OpFMul %float %float_2 %32
+               OpStore %31 %33
+         %34 = OpLoad %float %31
+         %35 = OpCompositeExtract %uint %29 0
+         %36 = OpAccessChain %_ptr_Uniform_float %5 %int_0 %35
+               OpStore %36 %34
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FixStorageClass>(text, false);
+}
+
+using FixTypeTest = PassTest<::testing::Test>;
+
+TEST_F(FixTypeTest, FixAccessChain) {
+  const std::string text = R"(
+; CHECK: [[ac1:%\w+]] = OpAccessChain %_ptr_Uniform_S %A %int_0 %uint_0
+; CHECK: [[ac2:%\w+]] = OpAccessChain %_ptr_Uniform_T [[ac1]] %int_0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpSource HLSL 600
+               OpName %type_RWStructuredBuffer_S "type.RWStructuredBuffer.S"
+               OpName %S "S"
+               OpMemberName %S 0 "t"
+               OpName %T "T"
+               OpMemberName %T 0 "a"
+               OpName %A "A"
+               OpName %type_ACSBuffer_counter "type.ACSBuffer.counter"
+               OpMemberName %type_ACSBuffer_counter 0 "counter"
+               OpName %counter_var_A "counter.var.A"
+               OpName %main "main"
+               OpName %S_0 "S"
+               OpMemberName %S_0 0 "t"
+               OpName %T_0 "T"
+               OpMemberName %T_0 0 "a"
+               OpDecorate %A DescriptorSet 0
+               OpDecorate %A Binding 0
+               OpDecorate %counter_var_A DescriptorSet 0
+               OpDecorate %counter_var_A Binding 1
+               OpMemberDecorate %T 0 Offset 0
+               OpMemberDecorate %S 0 Offset 0
+               OpDecorate %_runtimearr_S ArrayStride 4
+               OpMemberDecorate %type_RWStructuredBuffer_S 0 Offset 0
+               OpDecorate %type_RWStructuredBuffer_S BufferBlock
+               OpMemberDecorate %type_ACSBuffer_counter 0 Offset 0
+               OpDecorate %type_ACSBuffer_counter BufferBlock
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+          %T = OpTypeStruct %int
+          %S = OpTypeStruct %T
+%_runtimearr_S = OpTypeRuntimeArray %S
+%type_RWStructuredBuffer_S = OpTypeStruct %_runtimearr_S
+%_ptr_Uniform_type_RWStructuredBuffer_S = OpTypePointer Uniform %type_RWStructuredBuffer_S
+%type_ACSBuffer_counter = OpTypeStruct %int
+%_ptr_Uniform_type_ACSBuffer_counter = OpTypePointer Uniform %type_ACSBuffer_counter
+       %void = OpTypeVoid
+         %18 = OpTypeFunction %void
+        %T_0 = OpTypeStruct %int
+        %S_0 = OpTypeStruct %T_0
+%_ptr_Function_S_0 = OpTypePointer Function %S_0
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+%_ptr_Uniform_T = OpTypePointer Uniform %T
+         %22 = OpTypeFunction %T_0 %_ptr_Function_S_0
+%_ptr_Function_T_0 = OpTypePointer Function %T_0
+          %A = OpVariable %_ptr_Uniform_type_RWStructuredBuffer_S Uniform
+%counter_var_A = OpVariable %_ptr_Uniform_type_ACSBuffer_counter Uniform
+       %main = OpFunction %void None %18
+         %24 = OpLabel
+         %25 = OpVariable %_ptr_Function_T_0 Function
+         %26 = OpVariable %_ptr_Function_S_0 Function
+         %27 = OpAccessChain %_ptr_Uniform_S %A %int_0 %uint_0
+         %28 = OpAccessChain %_ptr_Function_T_0 %27 %int_0
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FixStorageClass>(text, false);
+}
+
+TEST_F(FixTypeTest, FixLoad) {
+  const std::string text = R"(
+; CHECK: [[ac1:%\w+]] = OpAccessChain %_ptr_Uniform_S %A %int_0 %uint_0
+; CHECK: [[ac2:%\w+]] = OpAccessChain %_ptr_Uniform_T [[ac1]] %int_0
+; CHECK: [[ld:%\w+]] = OpLoad %T [[ac2]]
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpSource HLSL 600
+               OpName %type_RWStructuredBuffer_S "type.RWStructuredBuffer.S"
+               OpName %S "S"
+               OpMemberName %S 0 "t"
+               OpName %T "T"
+               OpMemberName %T 0 "a"
+               OpName %A "A"
+               OpName %type_ACSBuffer_counter "type.ACSBuffer.counter"
+               OpMemberName %type_ACSBuffer_counter 0 "counter"
+               OpName %counter_var_A "counter.var.A"
+               OpName %main "main"
+               OpName %S_0 "S"
+               OpMemberName %S_0 0 "t"
+               OpName %T_0 "T"
+               OpMemberName %T_0 0 "a"
+               OpDecorate %A DescriptorSet 0
+               OpDecorate %A Binding 0
+               OpDecorate %counter_var_A DescriptorSet 0
+               OpDecorate %counter_var_A Binding 1
+               OpMemberDecorate %T 0 Offset 0
+               OpMemberDecorate %S 0 Offset 0
+               OpDecorate %_runtimearr_S ArrayStride 4
+               OpMemberDecorate %type_RWStructuredBuffer_S 0 Offset 0
+               OpDecorate %type_RWStructuredBuffer_S BufferBlock
+               OpMemberDecorate %type_ACSBuffer_counter 0 Offset 0
+               OpDecorate %type_ACSBuffer_counter BufferBlock
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+          %T = OpTypeStruct %int
+          %S = OpTypeStruct %T
+%_runtimearr_S = OpTypeRuntimeArray %S
+%type_RWStructuredBuffer_S = OpTypeStruct %_runtimearr_S
+%_ptr_Uniform_type_RWStructuredBuffer_S = OpTypePointer Uniform %type_RWStructuredBuffer_S
+%type_ACSBuffer_counter = OpTypeStruct %int
+%_ptr_Uniform_type_ACSBuffer_counter = OpTypePointer Uniform %type_ACSBuffer_counter
+       %void = OpTypeVoid
+         %18 = OpTypeFunction %void
+        %T_0 = OpTypeStruct %int
+        %S_0 = OpTypeStruct %T_0
+%_ptr_Function_S_0 = OpTypePointer Function %S_0
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+%_ptr_Uniform_T = OpTypePointer Uniform %T
+         %22 = OpTypeFunction %T_0 %_ptr_Function_S_0
+%_ptr_Function_T_0 = OpTypePointer Function %T_0
+          %A = OpVariable %_ptr_Uniform_type_RWStructuredBuffer_S Uniform
+%counter_var_A = OpVariable %_ptr_Uniform_type_ACSBuffer_counter Uniform
+       %main = OpFunction %void None %18
+         %24 = OpLabel
+         %25 = OpVariable %_ptr_Function_T_0 Function
+         %26 = OpVariable %_ptr_Function_S_0 Function
+         %27 = OpAccessChain %_ptr_Uniform_S %A %int_0 %uint_0
+         %28 = OpAccessChain %_ptr_Uniform_T %27 %int_0
+         %29 = OpLoad %T_0 %28
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FixStorageClass>(text, false);
+}
+
+TEST_F(FixTypeTest, FixStore) {
+  const std::string text = R"(
+; CHECK: [[ld:%\w+]] = OpLoad %T
+; CHECK: OpStore
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpSource HLSL 600
+               OpName %type_RWStructuredBuffer_S "type.RWStructuredBuffer.S"
+               OpName %S "S"
+               OpMemberName %S 0 "t"
+               OpName %T "T"
+               OpMemberName %T 0 "a"
+               OpName %A "A"
+               OpName %type_ACSBuffer_counter "type.ACSBuffer.counter"
+               OpMemberName %type_ACSBuffer_counter 0 "counter"
+               OpName %counter_var_A "counter.var.A"
+               OpName %main "main"
+               OpName %S_0 "S"
+               OpMemberName %S_0 0 "t"
+               OpName %T_0 "T"
+               OpMemberName %T_0 0 "a"
+               OpDecorate %A DescriptorSet 0
+               OpDecorate %A Binding 0
+               OpDecorate %counter_var_A DescriptorSet 0
+               OpDecorate %counter_var_A Binding 1
+               OpMemberDecorate %T 0 Offset 0
+               OpMemberDecorate %S 0 Offset 0
+               OpDecorate %_runtimearr_S ArrayStride 4
+               OpMemberDecorate %type_RWStructuredBuffer_S 0 Offset 0
+               OpDecorate %type_RWStructuredBuffer_S BufferBlock
+               OpMemberDecorate %type_ACSBuffer_counter 0 Offset 0
+               OpDecorate %type_ACSBuffer_counter BufferBlock
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+          %T = OpTypeStruct %int
+          %S = OpTypeStruct %T
+%_runtimearr_S = OpTypeRuntimeArray %S
+%type_RWStructuredBuffer_S = OpTypeStruct %_runtimearr_S
+%_ptr_Uniform_type_RWStructuredBuffer_S = OpTypePointer Uniform %type_RWStructuredBuffer_S
+%type_ACSBuffer_counter = OpTypeStruct %int
+%_ptr_Uniform_type_ACSBuffer_counter = OpTypePointer Uniform %type_ACSBuffer_counter
+       %void = OpTypeVoid
+         %18 = OpTypeFunction %void
+        %T_0 = OpTypeStruct %int
+        %S_0 = OpTypeStruct %T_0
+%_ptr_Function_S_0 = OpTypePointer Function %S_0
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+%_ptr_Uniform_T = OpTypePointer Uniform %T
+         %22 = OpTypeFunction %T_0 %_ptr_Function_S_0
+%_ptr_Function_T_0 = OpTypePointer Function %T_0
+          %A = OpVariable %_ptr_Uniform_type_RWStructuredBuffer_S Uniform
+%counter_var_A = OpVariable %_ptr_Uniform_type_ACSBuffer_counter Uniform
+       %main = OpFunction %void None %18
+         %24 = OpLabel
+         %25 = OpVariable %_ptr_Function_T_0 Function
+         %26 = OpVariable %_ptr_Function_S_0 Function
+         %27 = OpAccessChain %_ptr_Uniform_S %A %int_0 %uint_0
+         %28 = OpAccessChain %_ptr_Uniform_T %27 %int_0
+         %29 = OpLoad %T %28
+               OpStore %25 %29
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FixStorageClass>(text, false);
+}
+
+TEST_F(FixTypeTest, FixSelect) {
+  const std::string text = R"(
+; CHECK: OpSelect %_ptr_Uniform__struct_3
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpExecutionMode %1 LocalSize 1 1 1
+               OpSource HLSL 600
+               OpDecorate %2 DescriptorSet 0
+               OpDecorate %2 Binding 0
+               OpMemberDecorate %_struct_3 0 Offset 0
+               OpDecorate %_runtimearr__struct_3 ArrayStride 4
+               OpMemberDecorate %_struct_5 0 Offset 0
+               OpDecorate %_struct_5 BufferBlock
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
+  %_struct_3 = OpTypeStruct %uint
+%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3
+  %_struct_5 = OpTypeStruct %_runtimearr__struct_3
+%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5
+       %void = OpTypeVoid
+         %11 = OpTypeFunction %void
+ %_struct_12 = OpTypeStruct %uint
+%_ptr_Function__struct_12 = OpTypePointer Function %_struct_12
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+       %bool = OpTypeBool
+%_ptr_Uniform__struct_3 = OpTypePointer Uniform %_struct_3
+          %2 = OpVariable %_ptr_Uniform__struct_5 Uniform
+          %1 = OpFunction %void None %11
+         %17 = OpLabel
+         %18 = OpAccessChain %_ptr_Uniform_uint %2 %uint_0 %uint_0 %uint_0
+         %19 = OpLoad %uint %18
+         %20 = OpSGreaterThan %bool %19 %uint_0
+         %21 = OpAccessChain %_ptr_Uniform__struct_3 %2 %uint_0 %uint_0
+         %22 = OpAccessChain %_ptr_Uniform__struct_3 %2 %uint_0 %uint_1
+         %23 = OpSelect %_ptr_Function__struct_12 %20 %21 %22
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FixStorageClass>(text, false);
+}
+
+TEST_F(FixTypeTest, FixPhiInLoop) {
+  const std::string text = R"(
+; CHECK: [[ac_init:%\w+]] = OpAccessChain %_ptr_Uniform__struct_3
+; CHECK: [[ac_phi:%\w+]] = OpPhi %_ptr_Uniform__struct_3 [[ac_init]] {{%\w+}} [[ac_update:%\w+]] {{%\w+}}
+; CHECK: [[ac_update]] = OpPtrAccessChain %_ptr_Uniform__struct_3 [[ac_phi]] %int_1
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpExecutionMode %1 LocalSize 1 1 1
+               OpSource HLSL 600
+               OpDecorate %2 DescriptorSet 0
+               OpDecorate %2 Binding 0
+               OpMemberDecorate %_struct_3 0 Offset 0
+               OpDecorate %_runtimearr__struct_3 ArrayStride 4
+               OpMemberDecorate %_struct_5 0 Offset 0
+               OpDecorate %_struct_5 BufferBlock
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %int_1 = OpConstant %int 1
+  %_struct_3 = OpTypeStruct %int
+  %_struct_9 = OpTypeStruct %int
+%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3
+  %_struct_5 = OpTypeStruct %_runtimearr__struct_3
+%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5
+       %void = OpTypeVoid
+         %12 = OpTypeFunction %void
+       %bool = OpTypeBool
+%_ptr_Uniform__struct_3 = OpTypePointer Uniform %_struct_3
+%_ptr_Function__struct_9 = OpTypePointer Function %_struct_9
+          %2 = OpVariable %_ptr_Uniform__struct_5 Uniform
+          %1 = OpFunction %void None %12
+         %16 = OpLabel
+         %17 = OpAccessChain %_ptr_Uniform__struct_3 %2 %int_0 %int_0
+               OpBranch %18
+         %18 = OpLabel
+         %20 = OpPhi %_ptr_Function__struct_9 %17 %16 %21 %22
+         %23 = OpUndef %bool
+               OpLoopMerge %24 %22 None
+               OpBranchConditional %23 %22 %24
+         %22 = OpLabel
+         %21 = OpPtrAccessChain %_ptr_Function__struct_9 %20 %int_1
+               OpBranch %18
+         %24 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FixStorageClass>(text, false);
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/test/opt/flatten_decoration_test.cpp b/test/opt/flatten_decoration_test.cpp
index fcf2341..d8d8867 100644
--- a/test/opt/flatten_decoration_test.cpp
+++ b/test/opt/flatten_decoration_test.cpp
@@ -81,158 +81,158 @@
   SinglePassRunAndCheck<FlattenDecorationPass>(before, after, false, true);
 }
 
-INSTANTIATE_TEST_CASE_P(NoUses, FlattenDecorationTest,
-                        ::testing::ValuesIn(std::vector<FlattenDecorationCase>{
-                            // No OpDecorationGroup
-                            {"", ""},
+INSTANTIATE_TEST_SUITE_P(NoUses, FlattenDecorationTest,
+                         ::testing::ValuesIn(std::vector<FlattenDecorationCase>{
+                             // No OpDecorationGroup
+                             {"", ""},
 
-                            // OpDecorationGroup without any uses, and
-                            // no OpName.
-                            {"%group = OpDecorationGroup\n", ""},
+                             // OpDecorationGroup without any uses, and
+                             // no OpName.
+                             {"%group = OpDecorationGroup\n", ""},
 
-                            // OpDecorationGroup without any uses, and
-                            // with OpName targeting it. Proves you must
-                            // remove the names as well.
-                            {"OpName %group \"group\"\n"
-                             "%group = OpDecorationGroup\n",
-                             ""},
+                             // OpDecorationGroup without any uses, and
+                             // with OpName targeting it. Proves you must
+                             // remove the names as well.
+                             {"OpName %group \"group\"\n"
+                              "%group = OpDecorationGroup\n",
+                              ""},
 
-                            // OpDecorationGroup with decorations that
-                            // target it, but no uses in OpGroupDecorate
-                            // or OpGroupMemberDecorate instructions.
-                            {"OpDecorate %group Flat\n"
-                             "OpDecorate %group NoPerspective\n"
-                             "%group = OpDecorationGroup\n",
-                             ""},
-                        }), );
+                             // OpDecorationGroup with decorations that
+                             // target it, but no uses in OpGroupDecorate
+                             // or OpGroupMemberDecorate instructions.
+                             {"OpDecorate %group Flat\n"
+                              "OpDecorate %group NoPerspective\n"
+                              "%group = OpDecorationGroup\n",
+                              ""},
+                         }));
 
-INSTANTIATE_TEST_CASE_P(OpGroupDecorate, FlattenDecorationTest,
-                        ::testing::ValuesIn(std::vector<FlattenDecorationCase>{
-                            // One OpGroupDecorate
-                            {"OpName %group \"group\"\n"
-                             "OpDecorate %group Flat\n"
-                             "OpDecorate %group NoPerspective\n"
-                             "%group = OpDecorationGroup\n"
-                             "OpGroupDecorate %group %hue %saturation\n",
-                             "OpDecorate %hue Flat\n"
-                             "OpDecorate %saturation Flat\n"
-                             "OpDecorate %hue NoPerspective\n"
-                             "OpDecorate %saturation NoPerspective\n"},
-                            // Multiple OpGroupDecorate
-                            {"OpName %group \"group\"\n"
-                             "OpDecorate %group Flat\n"
-                             "OpDecorate %group NoPerspective\n"
-                             "%group = OpDecorationGroup\n"
-                             "OpGroupDecorate %group %hue %value\n"
-                             "OpGroupDecorate %group %saturation\n",
-                             "OpDecorate %hue Flat\n"
-                             "OpDecorate %value Flat\n"
-                             "OpDecorate %saturation Flat\n"
-                             "OpDecorate %hue NoPerspective\n"
-                             "OpDecorate %value NoPerspective\n"
-                             "OpDecorate %saturation NoPerspective\n"},
-                            // Two group decorations, interleaved
-                            {"OpName %group0 \"group0\"\n"
-                             "OpName %group1 \"group1\"\n"
-                             "OpDecorate %group0 Flat\n"
-                             "OpDecorate %group1 NoPerspective\n"
-                             "%group0 = OpDecorationGroup\n"
-                             "%group1 = OpDecorationGroup\n"
-                             "OpGroupDecorate %group0 %hue %value\n"
-                             "OpGroupDecorate %group1 %saturation\n",
-                             "OpDecorate %hue Flat\n"
-                             "OpDecorate %value Flat\n"
-                             "OpDecorate %saturation NoPerspective\n"},
-                            // Decoration with operands
-                            {"OpName %group \"group\"\n"
-                             "OpDecorate %group Location 42\n"
-                             "%group = OpDecorationGroup\n"
-                             "OpGroupDecorate %group %hue %saturation\n",
-                             "OpDecorate %hue Location 42\n"
-                             "OpDecorate %saturation Location 42\n"},
-                        }), );
+INSTANTIATE_TEST_SUITE_P(OpGroupDecorate, FlattenDecorationTest,
+                         ::testing::ValuesIn(std::vector<FlattenDecorationCase>{
+                             // One OpGroupDecorate
+                             {"OpName %group \"group\"\n"
+                              "OpDecorate %group Flat\n"
+                              "OpDecorate %group NoPerspective\n"
+                              "%group = OpDecorationGroup\n"
+                              "OpGroupDecorate %group %hue %saturation\n",
+                              "OpDecorate %hue Flat\n"
+                              "OpDecorate %saturation Flat\n"
+                              "OpDecorate %hue NoPerspective\n"
+                              "OpDecorate %saturation NoPerspective\n"},
+                             // Multiple OpGroupDecorate
+                             {"OpName %group \"group\"\n"
+                              "OpDecorate %group Flat\n"
+                              "OpDecorate %group NoPerspective\n"
+                              "%group = OpDecorationGroup\n"
+                              "OpGroupDecorate %group %hue %value\n"
+                              "OpGroupDecorate %group %saturation\n",
+                              "OpDecorate %hue Flat\n"
+                              "OpDecorate %value Flat\n"
+                              "OpDecorate %saturation Flat\n"
+                              "OpDecorate %hue NoPerspective\n"
+                              "OpDecorate %value NoPerspective\n"
+                              "OpDecorate %saturation NoPerspective\n"},
+                             // Two group decorations, interleaved
+                             {"OpName %group0 \"group0\"\n"
+                              "OpName %group1 \"group1\"\n"
+                              "OpDecorate %group0 Flat\n"
+                              "OpDecorate %group1 NoPerspective\n"
+                              "%group0 = OpDecorationGroup\n"
+                              "%group1 = OpDecorationGroup\n"
+                              "OpGroupDecorate %group0 %hue %value\n"
+                              "OpGroupDecorate %group1 %saturation\n",
+                              "OpDecorate %hue Flat\n"
+                              "OpDecorate %value Flat\n"
+                              "OpDecorate %saturation NoPerspective\n"},
+                             // Decoration with operands
+                             {"OpName %group \"group\"\n"
+                              "OpDecorate %group Location 42\n"
+                              "%group = OpDecorationGroup\n"
+                              "OpGroupDecorate %group %hue %saturation\n",
+                              "OpDecorate %hue Location 42\n"
+                              "OpDecorate %saturation Location 42\n"},
+                         }));
 
-INSTANTIATE_TEST_CASE_P(OpGroupMemberDecorate, FlattenDecorationTest,
-                        ::testing::ValuesIn(std::vector<FlattenDecorationCase>{
-                            // One OpGroupMemberDecorate
-                            {"OpName %group \"group\"\n"
-                             "OpDecorate %group Flat\n"
-                             "OpDecorate %group Offset 16\n"
-                             "%group = OpDecorationGroup\n"
-                             "OpGroupMemberDecorate %group %Point 1\n",
-                             "OpMemberDecorate %Point 1 Flat\n"
-                             "OpMemberDecorate %Point 1 Offset 16\n"},
-                            // Multiple OpGroupMemberDecorate using the same
-                            // decoration group.
-                            {"OpName %group \"group\"\n"
-                             "OpDecorate %group Flat\n"
-                             "OpDecorate %group NoPerspective\n"
-                             "OpDecorate %group Offset 8\n"
-                             "%group = OpDecorationGroup\n"
-                             "OpGroupMemberDecorate %group %Point 2\n"
-                             "OpGroupMemberDecorate %group %Camera 1\n",
-                             "OpMemberDecorate %Point 2 Flat\n"
-                             "OpMemberDecorate %Camera 1 Flat\n"
-                             "OpMemberDecorate %Point 2 NoPerspective\n"
-                             "OpMemberDecorate %Camera 1 NoPerspective\n"
-                             "OpMemberDecorate %Point 2 Offset 8\n"
-                             "OpMemberDecorate %Camera 1 Offset 8\n"},
-                            // Two groups of member decorations, interleaved.
-                            // Decoration is with and without operands.
-                            {"OpName %group0 \"group0\"\n"
-                             "OpName %group1 \"group1\"\n"
-                             "OpDecorate %group0 Flat\n"
-                             "OpDecorate %group0 Offset 8\n"
-                             "OpDecorate %group1 NoPerspective\n"
-                             "OpDecorate %group1 Offset 16\n"
-                             "%group0 = OpDecorationGroup\n"
-                             "%group1 = OpDecorationGroup\n"
-                             "OpGroupMemberDecorate %group0 %Point 0\n"
-                             "OpGroupMemberDecorate %group1 %Point 2\n",
-                             "OpMemberDecorate %Point 0 Flat\n"
-                             "OpMemberDecorate %Point 0 Offset 8\n"
-                             "OpMemberDecorate %Point 2 NoPerspective\n"
-                             "OpMemberDecorate %Point 2 Offset 16\n"},
-                        }), );
+INSTANTIATE_TEST_SUITE_P(OpGroupMemberDecorate, FlattenDecorationTest,
+                         ::testing::ValuesIn(std::vector<FlattenDecorationCase>{
+                             // One OpGroupMemberDecorate
+                             {"OpName %group \"group\"\n"
+                              "OpDecorate %group Flat\n"
+                              "OpDecorate %group Offset 16\n"
+                              "%group = OpDecorationGroup\n"
+                              "OpGroupMemberDecorate %group %Point 1\n",
+                              "OpMemberDecorate %Point 1 Flat\n"
+                              "OpMemberDecorate %Point 1 Offset 16\n"},
+                             // Multiple OpGroupMemberDecorate using the same
+                             // decoration group.
+                             {"OpName %group \"group\"\n"
+                              "OpDecorate %group Flat\n"
+                              "OpDecorate %group NoPerspective\n"
+                              "OpDecorate %group Offset 8\n"
+                              "%group = OpDecorationGroup\n"
+                              "OpGroupMemberDecorate %group %Point 2\n"
+                              "OpGroupMemberDecorate %group %Camera 1\n",
+                              "OpMemberDecorate %Point 2 Flat\n"
+                              "OpMemberDecorate %Camera 1 Flat\n"
+                              "OpMemberDecorate %Point 2 NoPerspective\n"
+                              "OpMemberDecorate %Camera 1 NoPerspective\n"
+                              "OpMemberDecorate %Point 2 Offset 8\n"
+                              "OpMemberDecorate %Camera 1 Offset 8\n"},
+                             // Two groups of member decorations, interleaved.
+                             // Decoration is with and without operands.
+                             {"OpName %group0 \"group0\"\n"
+                              "OpName %group1 \"group1\"\n"
+                              "OpDecorate %group0 Flat\n"
+                              "OpDecorate %group0 Offset 8\n"
+                              "OpDecorate %group1 NoPerspective\n"
+                              "OpDecorate %group1 Offset 16\n"
+                              "%group0 = OpDecorationGroup\n"
+                              "%group1 = OpDecorationGroup\n"
+                              "OpGroupMemberDecorate %group0 %Point 0\n"
+                              "OpGroupMemberDecorate %group1 %Point 2\n",
+                              "OpMemberDecorate %Point 0 Flat\n"
+                              "OpMemberDecorate %Point 0 Offset 8\n"
+                              "OpMemberDecorate %Point 2 NoPerspective\n"
+                              "OpMemberDecorate %Point 2 Offset 16\n"},
+                         }));
 
-INSTANTIATE_TEST_CASE_P(UnrelatedDecorations, FlattenDecorationTest,
-                        ::testing::ValuesIn(std::vector<FlattenDecorationCase>{
-                            // A non-group non-member decoration is untouched.
-                            {"OpDecorate %hue Centroid\n"
-                             "OpDecorate %saturation Flat\n",
-                             "OpDecorate %hue Centroid\n"
-                             "OpDecorate %saturation Flat\n"},
-                            // A non-group member decoration is untouched.
-                            {"OpMemberDecorate %Point 0 Offset 0\n"
-                             "OpMemberDecorate %Point 1 Offset 4\n"
-                             "OpMemberDecorate %Point 1 Flat\n",
-                             "OpMemberDecorate %Point 0 Offset 0\n"
-                             "OpMemberDecorate %Point 1 Offset 4\n"
-                             "OpMemberDecorate %Point 1 Flat\n"},
-                            // A non-group non-member decoration survives any
-                            // replacement of group decorations.
-                            {"OpName %group \"group\"\n"
-                             "OpDecorate %group Flat\n"
-                             "OpDecorate %hue Centroid\n"
-                             "OpDecorate %group NoPerspective\n"
-                             "%group = OpDecorationGroup\n"
-                             "OpGroupDecorate %group %hue %saturation\n",
-                             "OpDecorate %hue Flat\n"
-                             "OpDecorate %saturation Flat\n"
-                             "OpDecorate %hue Centroid\n"
-                             "OpDecorate %hue NoPerspective\n"
-                             "OpDecorate %saturation NoPerspective\n"},
-                            // A non-group member decoration survives any
-                            // replacement of group decorations.
-                            {"OpDecorate %group Offset 0\n"
-                             "OpDecorate %group Flat\n"
-                             "OpMemberDecorate %Point 1 Offset 4\n"
-                             "%group = OpDecorationGroup\n"
-                             "OpGroupMemberDecorate %group %Point 0\n",
-                             "OpMemberDecorate %Point 0 Offset 0\n"
-                             "OpMemberDecorate %Point 0 Flat\n"
-                             "OpMemberDecorate %Point 1 Offset 4\n"},
-                        }), );
+INSTANTIATE_TEST_SUITE_P(UnrelatedDecorations, FlattenDecorationTest,
+                         ::testing::ValuesIn(std::vector<FlattenDecorationCase>{
+                             // A non-group non-member decoration is untouched.
+                             {"OpDecorate %hue Centroid\n"
+                              "OpDecorate %saturation Flat\n",
+                              "OpDecorate %hue Centroid\n"
+                              "OpDecorate %saturation Flat\n"},
+                             // A non-group member decoration is untouched.
+                             {"OpMemberDecorate %Point 0 Offset 0\n"
+                              "OpMemberDecorate %Point 1 Offset 4\n"
+                              "OpMemberDecorate %Point 1 Flat\n",
+                              "OpMemberDecorate %Point 0 Offset 0\n"
+                              "OpMemberDecorate %Point 1 Offset 4\n"
+                              "OpMemberDecorate %Point 1 Flat\n"},
+                             // A non-group non-member decoration survives any
+                             // replacement of group decorations.
+                             {"OpName %group \"group\"\n"
+                              "OpDecorate %group Flat\n"
+                              "OpDecorate %hue Centroid\n"
+                              "OpDecorate %group NoPerspective\n"
+                              "%group = OpDecorationGroup\n"
+                              "OpGroupDecorate %group %hue %saturation\n",
+                              "OpDecorate %hue Flat\n"
+                              "OpDecorate %saturation Flat\n"
+                              "OpDecorate %hue Centroid\n"
+                              "OpDecorate %hue NoPerspective\n"
+                              "OpDecorate %saturation NoPerspective\n"},
+                             // A non-group member decoration survives any
+                             // replacement of group decorations.
+                             {"OpDecorate %group Offset 0\n"
+                              "OpDecorate %group Flat\n"
+                              "OpMemberDecorate %Point 1 Offset 4\n"
+                              "%group = OpDecorationGroup\n"
+                              "OpGroupMemberDecorate %group %Point 0\n",
+                              "OpMemberDecorate %Point 0 Offset 0\n"
+                              "OpMemberDecorate %Point 0 Flat\n"
+                              "OpMemberDecorate %Point 1 Offset 4\n"},
+                         }));
 
 }  // namespace
 }  // namespace opt
diff --git a/test/opt/fold_spec_const_op_composite_test.cpp b/test/opt/fold_spec_const_op_composite_test.cpp
index 8ecfd5c..12f0cd1 100644
--- a/test/opt/fold_spec_const_op_composite_test.cpp
+++ b/test/opt/fold_spec_const_op_composite_test.cpp
@@ -224,7 +224,7 @@
 
 // Tests that OpSpecConstantComposite opcodes are replace with
 // OpConstantComposite correctly.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Composite, FoldSpecConstantOpAndCompositePassTest,
     ::testing::ValuesIn(std::vector<
                         FoldSpecConstantOpAndCompositePassTestCase>({
@@ -330,7 +330,7 @@
     })));
 
 // Tests for operations that resulting in different types.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Cast, FoldSpecConstantOpAndCompositePassTest,
     ::testing::ValuesIn(
         std::vector<FoldSpecConstantOpAndCompositePassTestCase>({
@@ -567,7 +567,7 @@
 
 // Tests about boolean scalar logical operations and comparison operations with
 // scalar int/uint type.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     Logical, FoldSpecConstantOpAndCompositePassTest,
     ::testing::ValuesIn(std::vector<
                         FoldSpecConstantOpAndCompositePassTestCase>({
@@ -636,7 +636,7 @@
     })));
 
 // Tests about arithmetic operations for scalar int and uint types.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ScalarArithmetic, FoldSpecConstantOpAndCompositePassTest,
     ::testing::ValuesIn(std::vector<
                         FoldSpecConstantOpAndCompositePassTestCase>({
@@ -821,7 +821,7 @@
     })));
 
 // Tests about arithmetic operations for vector int and uint types.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     VectorArithmetic, FoldSpecConstantOpAndCompositePassTest,
     ::testing::ValuesIn(std::vector<
                         FoldSpecConstantOpAndCompositePassTestCase>({
@@ -1043,7 +1043,7 @@
     })));
 
 // Tests for SpecConstantOp CompositeExtract instruction
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     CompositeExtract, FoldSpecConstantOpAndCompositePassTest,
     ::testing::ValuesIn(std::vector<
                         FoldSpecConstantOpAndCompositePassTestCase>({
@@ -1217,7 +1217,7 @@
     })));
 
 // Tests the swizzle operations for spec const vectors.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     VectorShuffle, FoldSpecConstantOpAndCompositePassTest,
     ::testing::ValuesIn(std::vector<
                         FoldSpecConstantOpAndCompositePassTestCase>({
@@ -1310,7 +1310,7 @@
     })));
 
 // Test with long use-def chain.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LongDefUseChain, FoldSpecConstantOpAndCompositePassTest,
     ::testing::ValuesIn(std::vector<
                         FoldSpecConstantOpAndCompositePassTestCase>({
diff --git a/test/opt/fold_test.cpp b/test/opt/fold_test.cpp
index 1a54421..17cd0d4 100644
--- a/test/opt/fold_test.cpp
+++ b/test/opt/fold_test.cpp
@@ -142,6 +142,7 @@
 %v4double = OpTypeVector %double 4
 %v2float = OpTypeVector %float 2
 %v2double = OpTypeVector %double 2
+%v2half = OpTypeVector %half 2
 %v2bool = OpTypeVector %bool 2
 %struct_v2int_int_int = OpTypeStruct %v2int %int %int
 %_ptr_int = OpTypePointer Function %int
@@ -168,6 +169,7 @@
 %int_2 = OpConstant %int 2
 %int_3 = OpConstant %int 3
 %int_4 = OpConstant %int 4
+%int_n24 = OpConstant %int -24
 %int_min = OpConstant %int -2147483648
 %int_max = OpConstant %int 2147483647
 %long_0 = OpConstant %long 0
@@ -230,6 +232,7 @@
 %v2double_null = OpConstantNull %v2double
 %108 = OpConstant %half 0
 %half_1 = OpConstant %half 1
+%half_0_1 = OpConstantComposite %v2half %108 %half_1
 %106 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
 %v4float_0_0_0_0 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
 %v4float_0_0_0_1 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_1
@@ -264,7 +267,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(TestCase, IntegerInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(TestCase, IntegerInstructionFoldingTest,
                         ::testing::Values(
   // Test case 0: fold 0*n
   InstructionFoldingCase<uint32_t>(
@@ -486,7 +489,7 @@
           "OpReturn\n" +
           "OpFunctionEnd",
       2, 0),
-  // Test case 21: fold signed n >> 42 (undefined, so set to zero).
+  // Test case 23: fold signed n >> 42 (undefined, so set to zero).
   InstructionFoldingCase<uint32_t>(
       Header() + "%main = OpFunction %void None %void_func\n" +
           "%main_lab = OpLabel\n" +
@@ -496,7 +499,7 @@
           "OpReturn\n" +
           "OpFunctionEnd",
       2, 0),
-  // Test case 22: fold n << 42 (undefined, so set to zero).
+  // Test case 24: fold n << 42 (undefined, so set to zero).
   InstructionFoldingCase<uint32_t>(
       Header() + "%main = OpFunction %void None %void_func\n" +
           "%main_lab = OpLabel\n" +
@@ -505,7 +508,47 @@
           "%2 = OpShiftLeftLogical %int %load %uint_42\n" +
           "OpReturn\n" +
           "OpFunctionEnd",
-      2, 0)
+      2, 0),
+  // Test case 25: fold -24 >> 32 (defined as -1)
+  InstructionFoldingCase<uint32_t>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%2 = OpShiftRightArithmetic %int %int_n24 %uint_32\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, -1),
+  // Test case 26: fold 2 >> 32 (signed)
+  InstructionFoldingCase<uint32_t>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%2 = OpShiftRightArithmetic %int %int_2 %uint_32\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, 0),
+  // Test case 27: fold 2 >> 32 (unsigned)
+  InstructionFoldingCase<uint32_t>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%2 = OpShiftRightLogical %int %int_2 %uint_32\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, 0),
+  // Test case 28: fold 2 << 32
+  InstructionFoldingCase<uint32_t>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%2 = OpShiftLeftLogical %int %int_2 %uint_32\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, 0),
+  // Test case 29: fold -INT_MIN
+  InstructionFoldingCase<uint32_t>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%2 = OpSNegate %int %int_min\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, std::numeric_limits<int32_t>::min())
 ));
 // clang-format on
 
@@ -549,7 +592,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(TestCase, IntVectorInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(TestCase, IntVectorInstructionFoldingTest,
 ::testing::Values(
     // Test case 0: fold 0*n
     InstructionFoldingCase<std::vector<uint32_t>>(
@@ -626,7 +669,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(TestCase, BooleanInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(TestCase, BooleanInstructionFoldingTest,
                         ::testing::Values(
   // Test case 0: fold true || n
   InstructionFoldingCase<bool>(
@@ -830,7 +873,7 @@
       2, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(FClampAndCmpLHS, BooleanInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(FClampAndCmpLHS, BooleanInstructionFoldingTest,
 ::testing::Values(
     // Test case 0: fold 0.0 > clamp(n, 0.0, 1.0)
     InstructionFoldingCase<bool>(
@@ -1010,7 +1053,7 @@
         2, false)
 ));
 
-INSTANTIATE_TEST_CASE_P(FClampAndCmpRHS, BooleanInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(FClampAndCmpRHS, BooleanInstructionFoldingTest,
 ::testing::Values(
     // Test case 0: fold clamp(n, 0.0, 1.0) > 1.0
     InstructionFoldingCase<bool>(
@@ -1240,7 +1283,7 @@
 // specification.
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(FloatConstantFoldingTest, FloatInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(FloatConstantFoldingTest, FloatInstructionFoldingTest,
 ::testing::Values(
     // Test case 0: Fold 2.0 - 1.0
     InstructionFoldingCase<float>(
@@ -1383,7 +1426,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(DoubleConstantFoldingTest, DoubleInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(DoubleConstantFoldingTest, DoubleInstructionFoldingTest,
 ::testing::Values(
     // Test case 0: Fold 2.0 - 1.0
     InstructionFoldingCase<double>(
@@ -1493,7 +1536,7 @@
 // clang-format on
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(DoubleOrderedCompareConstantFoldingTest, BooleanInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(DoubleOrderedCompareConstantFoldingTest, BooleanInstructionFoldingTest,
                         ::testing::Values(
   // Test case 0: fold 1.0 == 2.0
   InstructionFoldingCase<bool>(
@@ -1625,7 +1668,7 @@
       2, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(DoubleUnorderedCompareConstantFoldingTest, BooleanInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(DoubleUnorderedCompareConstantFoldingTest, BooleanInstructionFoldingTest,
                         ::testing::Values(
   // Test case 0: fold 1.0 == 2.0
   InstructionFoldingCase<bool>(
@@ -1757,7 +1800,7 @@
       2, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(FloatOrderedCompareConstantFoldingTest, BooleanInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(FloatOrderedCompareConstantFoldingTest, BooleanInstructionFoldingTest,
                         ::testing::Values(
   // Test case 0: fold 1.0 == 2.0
   InstructionFoldingCase<bool>(
@@ -1889,7 +1932,7 @@
       2, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(FloatUnorderedCompareConstantFoldingTest, BooleanInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(FloatUnorderedCompareConstantFoldingTest, BooleanInstructionFoldingTest,
                         ::testing::Values(
   // Test case 0: fold 1.0 == 2.0
   InstructionFoldingCase<bool>(
@@ -2021,7 +2064,7 @@
       2, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(DoubleNaNCompareConstantFoldingTest, BooleanInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(DoubleNaNCompareConstantFoldingTest, BooleanInstructionFoldingTest,
                         ::testing::Values(
   // Test case 0: fold NaN == 0 (ord)
   InstructionFoldingCase<bool>(
@@ -2057,7 +2100,7 @@
       2, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(FloatNaNCompareConstantFoldingTest, BooleanInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(FloatNaNCompareConstantFoldingTest, BooleanInstructionFoldingTest,
                         ::testing::Values(
   // Test case 0: fold NaN == 0 (ord)
   InstructionFoldingCase<bool>(
@@ -2139,7 +2182,7 @@
   }
 }
 // clang-format off
-INSTANTIATE_TEST_CASE_P(TestCase, IntegerInstructionFoldingTestWithMap,
+INSTANTIATE_TEST_SUITE_P(TestCase, IntegerInstructionFoldingTestWithMap,
   ::testing::Values(
       // Test case 0: fold %3 = 0; %3 * n
       InstructionFoldingCaseWithMap<uint32_t>(
@@ -2189,7 +2232,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(TestCase, BooleanInstructionFoldingTestWithMap,
+INSTANTIATE_TEST_SUITE_P(TestCase, BooleanInstructionFoldingTestWithMap,
   ::testing::Values(
       // Test case 0: fold %3 = true; %3 || n
       InstructionFoldingCaseWithMap<bool>(
@@ -2239,7 +2282,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(IntegerArithmeticTestCases, GeneralInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(IntegerArithmeticTestCases, GeneralInstructionFoldingTest,
                         ::testing::Values(
     // Test case 0: Don't fold n * m
     InstructionFoldingCase<uint32_t>(
@@ -2699,7 +2742,7 @@
         2, 3)
 ));
 
-INSTANTIATE_TEST_CASE_P(CompositeExtractFoldingTest, GeneralInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(CompositeExtractFoldingTest, GeneralInstructionFoldingTest,
 ::testing::Values(
     // Test case 0: fold Insert feeding extract
     InstructionFoldingCase<uint32_t>(
@@ -2847,7 +2890,7 @@
         4, INT_7_ID)
 ));
 
-INSTANTIATE_TEST_CASE_P(CompositeConstructFoldingTest, GeneralInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(CompositeConstructFoldingTest, GeneralInstructionFoldingTest,
 ::testing::Values(
     // Test case 0: fold Extracts feeding construct
     InstructionFoldingCase<uint32_t>(
@@ -2910,7 +2953,7 @@
         2, VEC2_0_ID)
 ));
 
-INSTANTIATE_TEST_CASE_P(PhiFoldingTest, GeneralInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(PhiFoldingTest, GeneralInstructionFoldingTest,
 ::testing::Values(
   // Test case 0: Fold phi with the same values for all edges.
   InstructionFoldingCase<uint32_t>(
@@ -2952,7 +2995,7 @@
       2, 0)
 ));
 
-INSTANTIATE_TEST_CASE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTest,
                         ::testing::Values(
     // Test case 0: Don't fold n + 1.0
     InstructionFoldingCase<uint32_t>(
@@ -3130,7 +3173,7 @@
             "OpReturn\n" +
             "OpFunctionEnd",
         3, 2),
-    // Test case 15: Fold vector fsub with null
+    // Test case 17: Fold vector fsub with null
     InstructionFoldingCase<uint32_t>(
         Header() + "%main = OpFunction %void None %void_func\n" +
             "%main_lab = OpLabel\n" +
@@ -3140,7 +3183,7 @@
             "OpReturn\n" +
             "OpFunctionEnd",
         3, 2),
-    // Test case 16: Fold 0.0(half) * n
+    // Test case 18: Fold 0.0(half) * n
     InstructionFoldingCase<uint32_t>(
         Header() + "%main = OpFunction %void None %void_func\n" +
             "%main_lab = OpLabel\n" +
@@ -3150,7 +3193,7 @@
             "OpReturn\n" +
             "OpFunctionEnd",
         2, HALF_0_ID),
-    // Test case 17: Don't fold 1.0(half) * n
+    // Test case 19: Don't fold 1.0(half) * n
     InstructionFoldingCase<uint32_t>(
         Header() + "%main = OpFunction %void None %void_func\n" +
             "%main_lab = OpLabel\n" +
@@ -3160,17 +3203,33 @@
             "OpReturn\n" +
             "OpFunctionEnd",
         2, 0),
-    // Test case 18: Don't fold 1.0 * 1.0 (half)
+    // Test case 20: Don't fold 1.0 * 1.0 (half)
     InstructionFoldingCase<uint32_t>(
         Header() + "%main = OpFunction %void None %void_func\n" +
             "%main_lab = OpLabel\n" +
             "%2 = OpFMul %half %half_1 %half_1\n" +
             "OpReturn\n" +
             "OpFunctionEnd",
+        2, 0),
+    // Test case 21: Don't fold (0.0, 1.0) * (0.0, 1.0) (half)
+    InstructionFoldingCase<uint32_t>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpFMul %v2half %half_0_1 %half_0_1\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 0),
+    // Test case 22: Don't fold (0.0, 1.0) dotp (0.0, 1.0) (half)
+    InstructionFoldingCase<uint32_t>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpDot %half %half_0_1 %half_0_1\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
         2, 0)
 ));
 
-INSTANTIATE_TEST_CASE_P(DoubleRedundantFoldingTest, GeneralInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(DoubleRedundantFoldingTest, GeneralInstructionFoldingTest,
                         ::testing::Values(
     // Test case 0: Don't fold n + 1.0
     InstructionFoldingCase<uint32_t>(
@@ -3330,7 +3389,7 @@
         2, 4)
 ));
 
-INSTANTIATE_TEST_CASE_P(FloatVectorRedundantFoldingTest, GeneralInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(FloatVectorRedundantFoldingTest, GeneralInstructionFoldingTest,
                         ::testing::Values(
     // Test case 0: Don't fold a * vec4(0.0, 0.0, 0.0, 1.0)
     InstructionFoldingCase<uint32_t>(
@@ -3364,7 +3423,7 @@
         2, 3)
 ));
 
-INSTANTIATE_TEST_CASE_P(DoubleVectorRedundantFoldingTest, GeneralInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(DoubleVectorRedundantFoldingTest, GeneralInstructionFoldingTest,
                         ::testing::Values(
     // Test case 0: Don't fold a * vec4(0.0, 0.0, 0.0, 1.0)
     InstructionFoldingCase<uint32_t>(
@@ -3398,7 +3457,7 @@
         2, 3)
 ));
 
-INSTANTIATE_TEST_CASE_P(IntegerRedundantFoldingTest, GeneralInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(IntegerRedundantFoldingTest, GeneralInstructionFoldingTest,
                         ::testing::Values(
     // Test case 0: Don't fold n + 1
     InstructionFoldingCase<uint32_t>(
@@ -3482,7 +3541,7 @@
         2, 3)
 ));
 
-INSTANTIATE_TEST_CASE_P(ClampAndCmpLHS, GeneralInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(ClampAndCmpLHS, GeneralInstructionFoldingTest,
 ::testing::Values(
     // Test case 0: Don't Fold 0.0 < clamp(-1, 1)
     InstructionFoldingCase<uint32_t>(
@@ -3618,7 +3677,7 @@
         2, 0)
 ));
 
-INSTANTIATE_TEST_CASE_P(ClampAndCmpRHS, GeneralInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(ClampAndCmpRHS, GeneralInstructionFoldingTest,
 ::testing::Values(
     // Test case 0: Don't Fold clamp(-1, 1) < 0.0
     InstructionFoldingCase<uint32_t>(
@@ -3754,7 +3813,7 @@
         2, 0)
 ));
 
-INSTANTIATE_TEST_CASE_P(FToIConstantFoldingTest, IntegerInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(FToIConstantFoldingTest, IntegerInstructionFoldingTest,
                         ::testing::Values(
     // Test case 0: Fold int(3.0)
     InstructionFoldingCase<uint32_t>(
@@ -3774,7 +3833,7 @@
         2, 3)
 ));
 
-INSTANTIATE_TEST_CASE_P(IToFConstantFoldingTest, FloatInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(IToFConstantFoldingTest, FloatInstructionFoldingTest,
                         ::testing::Values(
     // Test case 0: Fold float(3)
     InstructionFoldingCase<float>(
@@ -3829,7 +3888,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(FloatRedundantSubFoldingTest, ToNegateFoldingTest,
+INSTANTIATE_TEST_SUITE_P(FloatRedundantSubFoldingTest, ToNegateFoldingTest,
                         ::testing::Values(
     // Test case 0: Don't fold 1.0 - n
     InstructionFoldingCase<uint32_t>(
@@ -3873,7 +3932,7 @@
         2, 3)
 ));
 
-INSTANTIATE_TEST_CASE_P(DoubleRedundantSubFoldingTest, ToNegateFoldingTest,
+INSTANTIATE_TEST_SUITE_P(DoubleRedundantSubFoldingTest, ToNegateFoldingTest,
                         ::testing::Values(
     // Test case 0: Don't fold 1.0 - n
     InstructionFoldingCase<uint32_t>(
@@ -3940,7 +3999,7 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(RedundantIntegerMatching, MatchingInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(RedundantIntegerMatching, MatchingInstructionFoldingTest,
 ::testing::Values(
     // Test case 0: Fold 0 + n (change sign)
     InstructionFoldingCase<bool>(
@@ -3970,7 +4029,7 @@
         2, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(MergeNegateTest, MatchingInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(MergeNegateTest, MatchingInstructionFoldingTest,
 ::testing::Values(
   // Test case 0: fold consecutive fnegate
   // -(-x) = x
@@ -4307,7 +4366,7 @@
         2, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(ReciprocalFDivTest, MatchingInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(ReciprocalFDivTest, MatchingInstructionFoldingTest,
 ::testing::Values(
   // Test case 0: scalar reicprocal
   // x / 0.5 = x * 2.0
@@ -4388,7 +4447,7 @@
     3, false)
 ));
 
-INSTANTIATE_TEST_CASE_P(MergeMulTest, MatchingInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(MergeMulTest, MatchingInstructionFoldingTest,
 ::testing::Values(
   // Test case 0: fold consecutive fmuls
   // (x * 3.0) * 2.0 = x * 6.0
@@ -4804,7 +4863,7 @@
     5, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(MergeDivTest, MatchingInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(MergeDivTest, MatchingInstructionFoldingTest,
 ::testing::Values(
   // Test case 0: merge consecutive fdiv
   // 4.0 / (2.0 / x) = 2.0 * x
@@ -5050,7 +5109,7 @@
     5, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(MergeAddTest, MatchingInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(MergeAddTest, MatchingInstructionFoldingTest,
 ::testing::Values(
   // Test case 0: merge add of negate
   // (-x) + 2 = 2 - x
@@ -5258,7 +5317,7 @@
     4, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(MergeSubTest, MatchingInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(MergeSubTest, MatchingInstructionFoldingTest,
 ::testing::Values(
   // Test case 0: merge sub of negate
   // (-x) - 2 = -2 - x
@@ -5483,7 +5542,7 @@
     4, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(SelectFoldingTest, MatchingInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(SelectFoldingTest, MatchingInstructionFoldingTest,
 ::testing::Values(
   // Test case 0: Fold select with the same values for both sides
   InstructionFoldingCase<bool>(
@@ -5591,7 +5650,7 @@
       4, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(CompositeExtractMatchingTest, MatchingInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(CompositeExtractMatchingTest, MatchingInstructionFoldingTest,
 ::testing::Values(
     // Test case 0: Extracting from result of consecutive shuffles of differing
     // size.
@@ -5734,7 +5793,7 @@
         4, true)
 ));
 
-INSTANTIATE_TEST_CASE_P(DotProductMatchingTest, MatchingInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(DotProductMatchingTest, MatchingInstructionFoldingTest,
 ::testing::Values(
     // Test case 0: Using OpDot to extract last element.
     InstructionFoldingCase<bool>(
@@ -5850,7 +5909,7 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(StoreMatchingTest, MatchingInstructionWithNoResultFoldingTest,
+INSTANTIATE_TEST_SUITE_P(StoreMatchingTest, MatchingInstructionWithNoResultFoldingTest,
 ::testing::Values(
     // Test case 0: Remove store of undef.
     InstructionFoldingCase<bool>(
@@ -5879,7 +5938,7 @@
         0 /* OpStore */, false)
 ));
 
-INSTANTIATE_TEST_CASE_P(VectorShuffleMatchingTest, MatchingInstructionWithNoResultFoldingTest,
+INSTANTIATE_TEST_SUITE_P(VectorShuffleMatchingTest, MatchingInstructionWithNoResultFoldingTest,
 ::testing::Values(
     // Test case 0: Basic test 1
     InstructionFoldingCase<bool>(
@@ -6152,6 +6211,203 @@
         9, true)
 ));
 
+using EntryPointFoldingTest =
+::testing::TestWithParam<InstructionFoldingCase<bool>>;
+
+TEST_P(EntryPointFoldingTest, Case) {
+  const auto& tc = GetParam();
+
+  // Build module.
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.test_body,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ASSERT_NE(nullptr, context);
+
+  // Fold the instruction to test.
+  Instruction* inst = nullptr;
+  inst = &*context->module()->entry_points().begin();
+  assert(inst && "Invalid test.  Could not find entry point instruction to fold.");
+  std::unique_ptr<Instruction> original_inst(inst->Clone(context.get()));
+  bool succeeded = context->get_instruction_folder().FoldInstruction(inst);
+  EXPECT_EQ(succeeded, tc.expected_result);
+  if (succeeded) {
+    Match(tc.test_body, context.get());
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(OpEntryPointFoldingTest, EntryPointFoldingTest,
+::testing::Values(
+    // Test case 0: Basic test 1
+    InstructionFoldingCase<bool>(std::string() +
+                    "; CHECK: OpEntryPoint Fragment %2 \"main\" %3\n" +
+                             "OpCapability Shader\n" +
+                        "%1 = OpExtInstImport \"GLSL.std.450\"\n" +
+                             "OpMemoryModel Logical GLSL450\n" +
+                             "OpEntryPoint Fragment %2 \"main\" %3 %3 %3\n" +
+                             "OpExecutionMode %2 OriginUpperLeft\n" +
+                             "OpSource GLSL 430\n" +
+                             "OpDecorate %3 Location 0\n" +
+                     "%void = OpTypeVoid\n" +
+                        "%5 = OpTypeFunction %void\n" +
+                    "%float = OpTypeFloat 32\n" +
+                  "%v4float = OpTypeVector %float 4\n" +
+      "%_ptr_Output_v4float = OpTypePointer Output %v4float\n" +
+                        "%3 = OpVariable %_ptr_Output_v4float Output\n" +
+                      "%int = OpTypeInt 32 1\n" +
+                    "%int_0 = OpConstant %int 0\n" +
+"%_ptr_PushConstant_v4float = OpTypePointer PushConstant %v4float\n" +
+                        "%2 = OpFunction %void None %5\n" +
+                       "%12 = OpLabel\n" +
+                             "OpReturn\n" +
+                             "OpFunctionEnd\n",
+        9, true),
+    InstructionFoldingCase<bool>(std::string() +
+                    "; CHECK: OpEntryPoint Fragment %2 \"main\" %3 %4\n" +
+                             "OpCapability Shader\n" +
+                        "%1 = OpExtInstImport \"GLSL.std.450\"\n" +
+                             "OpMemoryModel Logical GLSL450\n" +
+                             "OpEntryPoint Fragment %2 \"main\" %3 %4 %3\n" +
+                             "OpExecutionMode %2 OriginUpperLeft\n" +
+                             "OpSource GLSL 430\n" +
+                             "OpDecorate %3 Location 0\n" +
+                     "%void = OpTypeVoid\n" +
+                        "%5 = OpTypeFunction %void\n" +
+                    "%float = OpTypeFloat 32\n" +
+                  "%v4float = OpTypeVector %float 4\n" +
+      "%_ptr_Output_v4float = OpTypePointer Output %v4float\n" +
+                        "%3 = OpVariable %_ptr_Output_v4float Output\n" +
+                        "%4 = OpVariable %_ptr_Output_v4float Output\n" +
+                      "%int = OpTypeInt 32 1\n" +
+                    "%int_0 = OpConstant %int 0\n" +
+"%_ptr_PushConstant_v4float = OpTypePointer PushConstant %v4float\n" +
+                        "%2 = OpFunction %void None %5\n" +
+                       "%12 = OpLabel\n" +
+                             "OpReturn\n" +
+                             "OpFunctionEnd\n",
+        9, true),
+    InstructionFoldingCase<bool>(std::string() +
+                    "; CHECK: OpEntryPoint Fragment %2 \"main\" %4 %3\n" +
+                             "OpCapability Shader\n" +
+                        "%1 = OpExtInstImport \"GLSL.std.450\"\n" +
+                             "OpMemoryModel Logical GLSL450\n" +
+                             "OpEntryPoint Fragment %2 \"main\" %4 %4 %3\n" +
+                             "OpExecutionMode %2 OriginUpperLeft\n" +
+                             "OpSource GLSL 430\n" +
+                             "OpDecorate %3 Location 0\n" +
+                     "%void = OpTypeVoid\n" +
+                        "%5 = OpTypeFunction %void\n" +
+                    "%float = OpTypeFloat 32\n" +
+                  "%v4float = OpTypeVector %float 4\n" +
+      "%_ptr_Output_v4float = OpTypePointer Output %v4float\n" +
+                        "%3 = OpVariable %_ptr_Output_v4float Output\n" +
+                        "%4 = OpVariable %_ptr_Output_v4float Output\n" +
+                      "%int = OpTypeInt 32 1\n" +
+                    "%int_0 = OpConstant %int 0\n" +
+"%_ptr_PushConstant_v4float = OpTypePointer PushConstant %v4float\n" +
+                        "%2 = OpFunction %void None %5\n" +
+                       "%12 = OpLabel\n" +
+                             "OpReturn\n" +
+                             "OpFunctionEnd\n",
+        9, true)
+));
+
+using SPV14FoldingTest =
+::testing::TestWithParam<InstructionFoldingCase<bool>>;
+
+TEST_P(SPV14FoldingTest, Case) {
+  const auto& tc = GetParam();
+
+  // Build module.
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_4, nullptr, tc.test_body,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ASSERT_NE(nullptr, context);
+
+  // Fold the instruction to test.
+  analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
+  Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold);
+  std::unique_ptr<Instruction> original_inst(inst->Clone(context.get()));
+  bool succeeded = context->get_instruction_folder().FoldInstruction(inst);
+  EXPECT_EQ(succeeded, tc.expected_result);
+  if (succeeded) {
+    Match(tc.test_body, context.get());
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(SPV14FoldingTest, SPV14FoldingTest,
+::testing::Values(
+    // Test case 0: select vectors with scalar condition.
+    InstructionFoldingCase<bool>(std::string() +
+"; CHECK-NOT: OpSelect\n" +
+"; CHECK: %3 = OpCopyObject {{%\\w+}} %1\n" +
+"OpCapability Shader\n" +
+"OpCapability Linkage\n" +
+"%void = OpTypeVoid\n" +
+"%bool = OpTypeBool\n" +
+"%true = OpConstantTrue %bool\n" +
+"%int = OpTypeInt 32 0\n" +
+"%int4 = OpTypeVector %int 4\n" +
+"%int_0 = OpConstant %int 0\n" +
+"%int_1 = OpConstant %int 1\n" +
+"%1 = OpUndef %int4\n" +
+"%2 = OpUndef %int4\n" +
+"%void_fn = OpTypeFunction %void\n" +
+"%func = OpFunction %void None %void_fn\n" +
+"%entry = OpLabel\n" +
+"%3 = OpSelect %int4 %true %1 %2\n" +
+"OpReturn\n" +
+"OpFunctionEnd\n"
+,
+                                 3, true),
+    // Test case 1: select struct with scalar condition.
+    InstructionFoldingCase<bool>(std::string() +
+"; CHECK-NOT: OpSelect\n" +
+"; CHECK: %3 = OpCopyObject {{%\\w+}} %2\n" +
+"OpCapability Shader\n" +
+"OpCapability Linkage\n" +
+"%void = OpTypeVoid\n" +
+"%bool = OpTypeBool\n" +
+"%true = OpConstantFalse %bool\n" +
+"%int = OpTypeInt 32 0\n" +
+"%struct = OpTypeStruct %int %int %int %int\n" +
+"%int_0 = OpConstant %int 0\n" +
+"%int_1 = OpConstant %int 1\n" +
+"%1 = OpUndef %struct\n" +
+"%2 = OpUndef %struct\n" +
+"%void_fn = OpTypeFunction %void\n" +
+"%func = OpFunction %void None %void_fn\n" +
+"%entry = OpLabel\n" +
+"%3 = OpSelect %struct %true %1 %2\n" +
+"OpReturn\n" +
+"OpFunctionEnd\n"
+,
+                                 3, true),
+    // Test case 1: select array with scalar condition.
+    InstructionFoldingCase<bool>(std::string() +
+"; CHECK-NOT: OpSelect\n" +
+"; CHECK: %3 = OpCopyObject {{%\\w+}} %2\n" +
+"OpCapability Shader\n" +
+"OpCapability Linkage\n" +
+"%void = OpTypeVoid\n" +
+"%bool = OpTypeBool\n" +
+"%true = OpConstantFalse %bool\n" +
+"%int = OpTypeInt 32 0\n" +
+"%int_0 = OpConstant %int 0\n" +
+"%int_1 = OpConstant %int 1\n" +
+"%int_4 = OpConstant %int 4\n" +
+"%array = OpTypeStruct %int %int %int %int\n" +
+"%1 = OpUndef %array\n" +
+"%2 = OpUndef %array\n" +
+"%void_fn = OpTypeFunction %void\n" +
+"%func = OpFunction %void None %void_fn\n" +
+"%entry = OpLabel\n" +
+"%3 = OpSelect %array %true %1 %2\n" +
+"OpReturn\n" +
+"OpFunctionEnd\n"
+,
+                                 3, true)
+));
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/freeze_spec_const_test.cpp b/test/opt/freeze_spec_const_test.cpp
index 5cc7843..e5999ce 100644
--- a/test/opt/freeze_spec_const_test.cpp
+++ b/test/opt/freeze_spec_const_test.cpp
@@ -47,7 +47,7 @@
 }
 
 // Test each primary type.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PrimaryTypeSpecConst, FreezeSpecConstantValueTypeTest,
     ::testing::ValuesIn(std::vector<FreezeSpecConstantValueTypeTestCase>({
         // Type declaration, original spec constant definition, expected frozen
diff --git a/test/opt/generate_webgpu_initializers_test.cpp b/test/opt/generate_webgpu_initializers_test.cpp
new file mode 100644
index 0000000..f35cf56
--- /dev/null
+++ b/test/opt/generate_webgpu_initializers_test.cpp
@@ -0,0 +1,347 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <vector>
+
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+typedef std::tuple<std::string, bool> GenerateWebGPUInitializersParam;
+
+using GlobalVariableTest =
+    PassTest<::testing::TestWithParam<GenerateWebGPUInitializersParam>>;
+using LocalVariableTest =
+    PassTest<::testing::TestWithParam<GenerateWebGPUInitializersParam>>;
+
+using GenerateWebGPUInitializersTest = PassTest<::testing::Test>;
+
+void operator+=(std::vector<const char*>& lhs, const char* rhs) {
+  lhs.push_back(rhs);
+}
+
+void operator+=(std::vector<const char*>& lhs,
+                const std::vector<const char*>& rhs) {
+  lhs.reserve(lhs.size() + rhs.size());
+  for (auto* c : rhs) lhs.push_back(c);
+}
+
+std::string GetGlobalVariableTestString(std::string ptr_str,
+                                        std::string var_str,
+                                        std::string const_str = "") {
+  std::vector<const char*> result = {
+      // clang-format off
+               "OpCapability Shader",
+               "OpCapability VulkanMemoryModelKHR",
+               "OpExtension \"SPV_KHR_vulkan_memory_model\"",
+               "OpMemoryModel Logical VulkanKHR",
+               "OpEntryPoint Vertex %1 \"shader\"",
+       "%uint = OpTypeInt 32 0",
+                ptr_str.c_str()};
+  // clang-format on
+
+  if (!const_str.empty()) result += const_str.c_str();
+
+  result += {
+      // clang-format off
+                var_str.c_str(),
+     "%uint_0 = OpConstant %uint 0",
+       "%void = OpTypeVoid",
+          "%7 = OpTypeFunction %void",
+          "%1 = OpFunction %void None %7",
+          "%8 = OpLabel",
+               "OpStore %4 %uint_0",
+               "OpReturn",
+               "OpFunctionEnd"
+      // clang-format on
+  };
+  return JoinAllInsts(result);
+}
+
+std::string GetPointerString(std::string storage_type) {
+  std::string result = "%_ptr_";
+  result += storage_type + "_uint = OpTypePointer ";
+  result += storage_type + " %uint";
+  return result;
+}
+
+std::string GetGlobalVariableString(std::string storage_type,
+                                    bool initialized) {
+  std::string result = "%4 = OpVariable %_ptr_";
+  result += storage_type + "_uint ";
+  result += storage_type;
+  if (initialized) result += " %9";
+  return result;
+}
+
+std::string GetUninitializedGlobalVariableTestString(std::string storage_type) {
+  return GetGlobalVariableTestString(
+      GetPointerString(storage_type),
+      GetGlobalVariableString(storage_type, false));
+}
+
+std::string GetNullConstantString() { return "%9 = OpConstantNull %uint"; }
+
+std::string GetInitializedGlobalVariableTestString(std::string storage_type) {
+  return GetGlobalVariableTestString(
+      GetPointerString(storage_type),
+      GetGlobalVariableString(storage_type, true), GetNullConstantString());
+}
+
+TEST_P(GlobalVariableTest, Check) {
+  std::string storage_class = std::get<0>(GetParam());
+  bool changed = std::get<1>(GetParam());
+  std::string input = GetUninitializedGlobalVariableTestString(storage_class);
+  std::string expected =
+      changed ? GetInitializedGlobalVariableTestString(storage_class) : input;
+
+  SinglePassRunAndCheck<GenerateWebGPUInitializersPass>(input, expected,
+                                                        /* skip_nop = */ false);
+}
+
+// clang-format off
+INSTANTIATE_TEST_SUITE_P(
+    GenerateWebGPUInitializers, GlobalVariableTest,
+    ::testing::ValuesIn(std::vector<GenerateWebGPUInitializersParam>({
+       std::make_tuple("Private", true),
+       std::make_tuple("Output", true),
+       std::make_tuple("Function", true),
+       std::make_tuple("UniformConstant", false),
+       std::make_tuple("Input", false),
+       std::make_tuple("Uniform", false),
+       std::make_tuple("Workgroup", false)
+    })));
+// clang-format on
+
+std::string GetLocalVariableTestString(std::string ptr_str, std::string var_str,
+                                       std::string const_str = "") {
+  std::vector<const char*> result = {
+      // clang-format off
+               "OpCapability Shader",
+               "OpCapability VulkanMemoryModelKHR",
+               "OpExtension \"SPV_KHR_vulkan_memory_model\"",
+               "OpMemoryModel Logical VulkanKHR",
+               "OpEntryPoint Vertex %1 \"shader\"",
+       "%uint = OpTypeInt 32 0",
+                ptr_str.c_str(),
+     "%uint_0 = OpConstant %uint 0",
+       "%void = OpTypeVoid",
+          "%6 = OpTypeFunction %void"};
+  // clang-format on
+
+  if (!const_str.empty()) result += const_str.c_str();
+
+  result += {
+      // clang-format off
+          "%1 = OpFunction %void None %6",
+          "%7 = OpLabel",
+                var_str.c_str(),
+               "OpStore %8 %uint_0"
+      // clang-format on
+  };
+  return JoinAllInsts(result);
+}
+
+std::string GetLocalVariableString(std::string storage_type, bool initialized) {
+  std::string result = "%8 = OpVariable %_ptr_";
+  result += storage_type + "_uint ";
+  result += storage_type;
+  if (initialized) result += " %9";
+  return result;
+}
+
+std::string GetUninitializedLocalVariableTestString(std::string storage_type) {
+  return GetLocalVariableTestString(
+      GetPointerString(storage_type),
+      GetLocalVariableString(storage_type, false));
+}
+
+std::string GetInitializedLocalVariableTestString(std::string storage_type) {
+  return GetLocalVariableTestString(GetPointerString(storage_type),
+                                    GetLocalVariableString(storage_type, true),
+                                    GetNullConstantString());
+}
+
+TEST_P(LocalVariableTest, Check) {
+  std::string storage_class = std::get<0>(GetParam());
+  bool changed = std::get<1>(GetParam());
+
+  std::string input = GetUninitializedLocalVariableTestString(storage_class);
+  std::string expected =
+      changed ? GetInitializedLocalVariableTestString(storage_class) : input;
+
+  SinglePassRunAndCheck<GenerateWebGPUInitializersPass>(input, expected,
+                                                        /* skip_nop = */ false);
+}
+
+// clang-format off
+INSTANTIATE_TEST_SUITE_P(
+    GenerateWebGPUInitializers, LocalVariableTest,
+    ::testing::ValuesIn(std::vector<GenerateWebGPUInitializersParam>({
+       std::make_tuple("Private", true),
+       std::make_tuple("Output", true),
+       std::make_tuple("Function", true),
+       std::make_tuple("UniformConstant", false),
+       std::make_tuple("Input", false),
+       std::make_tuple("Uniform", false),
+       std::make_tuple("Workgroup", false)
+    })));
+// clang-format on
+
+TEST_F(GenerateWebGPUInitializersTest, AlreadyInitializedUnchanged) {
+  std::vector<const char*> spirv = {
+      // clang-format off
+                       "OpCapability Shader",
+                       "OpCapability VulkanMemoryModelKHR",
+                       "OpExtension \"SPV_KHR_vulkan_memory_model\"",
+                       "OpMemoryModel Logical VulkanKHR",
+                       "OpEntryPoint Vertex %1 \"shader\"",
+               "%uint = OpTypeInt 32 0",
+  "%_ptr_Private_uint = OpTypePointer Private %uint",
+             "%uint_0 = OpConstant %uint 0",
+                  "%5 = OpVariable %_ptr_Private_uint Private %uint_0",
+               "%void = OpTypeVoid",
+                  "%7 = OpTypeFunction %void",
+                  "%1 = OpFunction %void None %7",
+                  "%8 = OpLabel",
+                       "OpReturn",
+                       "OpFunctionEnd"
+      // clang-format on
+  };
+  std::string str = JoinAllInsts(spirv);
+
+  SinglePassRunAndCheck<GenerateWebGPUInitializersPass>(str, str,
+                                                        /* skip_nop = */ false);
+}
+
+TEST_F(GenerateWebGPUInitializersTest, AmbigiousArrays) {
+  std::vector<const char*> input_spirv = {
+      // clang-format off
+                                   "OpCapability Shader",
+                                   "OpCapability VulkanMemoryModelKHR",
+                                   "OpExtension \"SPV_KHR_vulkan_memory_model\"",
+                                   "OpMemoryModel Logical VulkanKHR",
+                                   "OpEntryPoint Vertex %1 \"shader\"",
+                           "%uint = OpTypeInt 32 0",
+                         "%uint_2 = OpConstant %uint 2",
+               "%_arr_uint_uint_2 = OpTypeArray %uint %uint_2",
+             "%_arr_uint_uint_2_0 = OpTypeArray %uint %uint_2",
+  "%_ptr_Private__arr_uint_uint_2 = OpTypePointer Private %_arr_uint_uint_2",
+"%_ptr_Private__arr_uint_uint_2_0 = OpTypePointer Private %_arr_uint_uint_2_0",
+                              "%8 = OpConstantNull %_arr_uint_uint_2_0",
+                              "%9 = OpVariable %_ptr_Private__arr_uint_uint_2 Private",
+                             "%10 = OpVariable %_ptr_Private__arr_uint_uint_2_0 Private %8",
+                           "%void = OpTypeVoid",
+                             "%12 = OpTypeFunction %void",
+                              "%1 = OpFunction %void None %12",
+                             "%13 = OpLabel",
+                                   "OpReturn",
+                                   "OpFunctionEnd"
+      // clang-format on
+  };
+  std::string input_str = JoinAllInsts(input_spirv);
+
+  std::vector<const char*> expected_spirv = {
+      // clang-format off
+                                   "OpCapability Shader",
+                                   "OpCapability VulkanMemoryModelKHR",
+                                   "OpExtension \"SPV_KHR_vulkan_memory_model\"",
+                                   "OpMemoryModel Logical VulkanKHR",
+                                   "OpEntryPoint Vertex %1 \"shader\"",
+                           "%uint = OpTypeInt 32 0",
+                         "%uint_2 = OpConstant %uint 2",
+               "%_arr_uint_uint_2 = OpTypeArray %uint %uint_2",
+             "%_arr_uint_uint_2_0 = OpTypeArray %uint %uint_2",
+  "%_ptr_Private__arr_uint_uint_2 = OpTypePointer Private %_arr_uint_uint_2",
+"%_ptr_Private__arr_uint_uint_2_0 = OpTypePointer Private %_arr_uint_uint_2_0",
+                              "%8 = OpConstantNull %_arr_uint_uint_2_0",
+                             "%14 = OpConstantNull %_arr_uint_uint_2",
+                              "%9 = OpVariable %_ptr_Private__arr_uint_uint_2 Private %14",
+                             "%10 = OpVariable %_ptr_Private__arr_uint_uint_2_0 Private %8",
+                           "%void = OpTypeVoid",
+                             "%12 = OpTypeFunction %void",
+                              "%1 = OpFunction %void None %12",
+                             "%13 = OpLabel",
+                                   "OpReturn",
+                                   "OpFunctionEnd"
+      // clang-format on
+  };
+  std::string expected_str = JoinAllInsts(expected_spirv);
+
+  SinglePassRunAndCheck<GenerateWebGPUInitializersPass>(input_str, expected_str,
+                                                        /* skip_nop = */ false);
+}
+
+TEST_F(GenerateWebGPUInitializersTest, AmbigiousStructs) {
+  std::vector<const char*> input_spirv = {
+      // clang-format off
+                          "OpCapability Shader",
+                          "OpCapability VulkanMemoryModelKHR",
+                          "OpExtension \"SPV_KHR_vulkan_memory_model\"",
+                          "OpMemoryModel Logical VulkanKHR",
+                          "OpEntryPoint Vertex %1 \"shader\"",
+                  "%uint = OpTypeInt 32 0",
+             "%_struct_3 = OpTypeStruct %uint",
+             "%_struct_4 = OpTypeStruct %uint",
+"%_ptr_Private__struct_3 = OpTypePointer Private %_struct_3",
+"%_ptr_Private__struct_4 = OpTypePointer Private %_struct_4",
+                     "%7 = OpConstantNull %_struct_3",
+                     "%8 = OpVariable %_ptr_Private__struct_3 Private %7",
+                     "%9 = OpVariable %_ptr_Private__struct_4 Private",
+                  "%void = OpTypeVoid",
+                    "%11 = OpTypeFunction %void",
+                     "%1 = OpFunction %void None %11",
+                    "%12 = OpLabel",
+                          "OpReturn",
+                          "OpFunctionEnd"
+      // clang-format on
+  };
+  std::string input_str = JoinAllInsts(input_spirv);
+
+  std::vector<const char*> expected_spirv = {
+      // clang-format off
+                          "OpCapability Shader",
+                          "OpCapability VulkanMemoryModelKHR",
+                          "OpExtension \"SPV_KHR_vulkan_memory_model\"",
+                          "OpMemoryModel Logical VulkanKHR",
+                          "OpEntryPoint Vertex %1 \"shader\"",
+                  "%uint = OpTypeInt 32 0",
+             "%_struct_3 = OpTypeStruct %uint",
+             "%_struct_4 = OpTypeStruct %uint",
+"%_ptr_Private__struct_3 = OpTypePointer Private %_struct_3",
+"%_ptr_Private__struct_4 = OpTypePointer Private %_struct_4",
+                     "%7 = OpConstantNull %_struct_3",
+                     "%8 = OpVariable %_ptr_Private__struct_3 Private %7",
+                    "%13 = OpConstantNull %_struct_4",
+                     "%9 = OpVariable %_ptr_Private__struct_4 Private %13",
+                  "%void = OpTypeVoid",
+                    "%11 = OpTypeFunction %void",
+                     "%1 = OpFunction %void None %11",
+                    "%12 = OpLabel",
+                          "OpReturn",
+                          "OpFunctionEnd"
+      // clang-format on
+  };
+  std::string expected_str = JoinAllInsts(expected_spirv);
+
+  SinglePassRunAndCheck<GenerateWebGPUInitializersPass>(input_str, expected_str,
+                                                        /* skip_nop = */ false);
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/test/opt/inline_test.cpp b/test/opt/inline_test.cpp
index 44a9698..7170812 100644
--- a/test/opt/inline_test.cpp
+++ b/test/opt/inline_test.cpp
@@ -2421,7 +2421,8 @@
 OpStore %38 %float_1
 OpBranch %40
 %43 = OpLabel
-OpUnreachable
+OpStore %38 %float_1
+OpBranch %40
 %41 = OpLabel
 OpBranchConditional %false %39 %40
 %40 = OpLabel
@@ -2446,7 +2447,7 @@
 %36 = OpLabel
 OpReturnValue %float_1
 %35 = OpLabel
-OpUnreachable
+OpReturnValue %float_1
 OpFunctionEnd
 )";
 
@@ -3058,6 +3059,7 @@
 %4 = OpTypeFunction %void
 %float = OpTypeFloat 32
 %_struct_6 = OpTypeStruct %float %float
+%15 = OpConstantNull %_struct_6
 %7 = OpTypeFunction %_struct_6
 %1 = OpFunction %void Pure|Const %4
 %8 = OpLabel
@@ -3067,10 +3069,11 @@
 %9 = OpFunction %_struct_6 None %7
 %10 = OpLabel
 %11 = OpFunctionCall %_struct_6 %9
-OpUnreachable
+OpReturnValue %15
 OpFunctionEnd
 )";
 
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
   SinglePassRunAndCheck<InlineExhaustivePass>(test, test, false, true);
 }
 
@@ -3086,6 +3089,7 @@
 %4 = OpTypeFunction %void
 %float = OpTypeFloat 32
 %_struct_6 = OpTypeStruct %float %float
+%15 = OpConstantNull %_struct_6
 %7 = OpTypeFunction %_struct_6
 %1 = OpFunction %void Pure|Const %4
 %8 = OpLabel
@@ -3095,15 +3099,16 @@
 %9 = OpFunction %_struct_6 None %7
 %10 = OpLabel
 %11 = OpFunctionCall %_struct_6 %12
-OpUnreachable
+OpReturnValue %15
 OpFunctionEnd
 %12 = OpFunction %_struct_6 None %7
 %13 = OpLabel
 %14 = OpFunctionCall %_struct_6 %9
-OpUnreachable
+OpReturnValue %15
 OpFunctionEnd
 )";
 
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
   SinglePassRunAndCheck<InlineExhaustivePass>(test, test, false, true);
 }
 
diff --git a/test/opt/inst_bindless_check_test.cpp b/test/opt/inst_bindless_check_test.cpp
index 1a1a194..deea1ed 100644
--- a/test/opt/inst_bindless_check_test.cpp
+++ b/test/opt/inst_bindless_check_test.cpp
@@ -92,7 +92,8 @@
 )";
 
   const std::string new_annots =
-      R"(OpDecorate %_struct_55 Block
+      R"(OpDecorate %_runtimearr_uint ArrayStride 4
+OpDecorate %_struct_55 Block
 OpMemberDecorate %_struct_55 0 Offset 0
 OpMemberDecorate %_struct_55 1 Offset 4
 OpDecorate %57 DescriptorSet 7
@@ -441,6 +442,7 @@
 OpDecorate %g_sAniso DescriptorSet 0
 OpDecorate %i_vTextureCoords Location 0
 OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %_runtimearr_uint ArrayStride 4
 OpDecorate %_struct_63 Block
 OpMemberDecorate %_struct_63 0 Offset 0
 OpMemberDecorate %_struct_63 1 Offset 4
@@ -628,273 +630,6 @@
       true);
 }
 
-TEST_F(InstBindlessTest, ReuseConstsTypesBuiltins) {
-  // This test verifies that the pass resuses existing constants, types
-  // and builtin variables.  This test was created by editing the SPIR-V
-  // from the Simple test.
-
-  const std::string defs_before =
-      R"(OpCapability Shader
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
-OpExecutionMode %MainPs OriginUpperLeft
-OpSource HLSL 500
-OpName %MainPs "MainPs"
-OpName %g_tColor "g_tColor"
-OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
-OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
-OpName %_ ""
-OpName %g_sAniso "g_sAniso"
-OpName %i_vTextureCoords "i.vTextureCoords"
-OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
-OpDecorate %g_tColor DescriptorSet 3
-OpDecorate %g_tColor Binding 0
-OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
-OpDecorate %PerViewConstantBuffer_t Block
-OpDecorate %g_sAniso DescriptorSet 0
-OpDecorate %i_vTextureCoords Location 0
-OpDecorate %_entryPointOutput_vColor Location 0
-OpDecorate %85 DescriptorSet 7
-OpDecorate %85 Binding 0
-OpDecorate %gl_FragCoord BuiltIn FragCoord
-%void = OpTypeVoid
-%3 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%v2float = OpTypeVector %float 2
-%v4float = OpTypeVector %float 4
-%int = OpTypeInt 32 1
-%int_0 = OpConstant %int 0
-%20 = OpTypeImage %float 2D 0 0 0 1 Unknown
-%uint = OpTypeInt 32 0
-%uint_128 = OpConstant %uint 128
-%_arr_20_uint_128 = OpTypeArray %20 %uint_128
-%_ptr_UniformConstant__arr_20_uint_128 = OpTypePointer UniformConstant %_arr_20_uint_128
-%g_tColor = OpVariable %_ptr_UniformConstant__arr_20_uint_128 UniformConstant
-%PerViewConstantBuffer_t = OpTypeStruct %uint
-%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
-%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
-%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
-%_ptr_UniformConstant_20 = OpTypePointer UniformConstant %20
-%35 = OpTypeSampler
-%_ptr_UniformConstant_35 = OpTypePointer UniformConstant %35
-%g_sAniso = OpVariable %_ptr_UniformConstant_35 UniformConstant
-%39 = OpTypeSampledImage %20
-%_ptr_Input_v2float = OpTypePointer Input %v2float
-%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
-%uint_0 = OpConstant %uint 0
-%bool = OpTypeBool
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_83 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_83 = OpTypePointer StorageBuffer %_struct_83
-%85 = OpVariable %_ptr_StorageBuffer__struct_83 StorageBuffer
-%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-%uint_10 = OpConstant %uint 10
-%uint_4 = OpConstant %uint 4
-%uint_1 = OpConstant %uint 1
-%uint_23 = OpConstant %uint 23
-%uint_2 = OpConstant %uint 2
-%uint_9 = OpConstant %uint 9
-%uint_3 = OpConstant %uint 3
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
-%v4uint = OpTypeVector %uint 4
-%uint_5 = OpConstant %uint 5
-%uint_6 = OpConstant %uint 6
-%uint_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%131 = OpConstantNull %v4float
-)";
-
-  const std::string defs_after =
-      R"(OpCapability Shader
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
-OpExecutionMode %MainPs OriginUpperLeft
-OpSource HLSL 500
-OpName %MainPs "MainPs"
-OpName %g_tColor "g_tColor"
-OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
-OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
-OpName %_ ""
-OpName %g_sAniso "g_sAniso"
-OpName %i_vTextureCoords "i.vTextureCoords"
-OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
-OpDecorate %g_tColor DescriptorSet 3
-OpDecorate %g_tColor Binding 0
-OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
-OpDecorate %PerViewConstantBuffer_t Block
-OpDecorate %g_sAniso DescriptorSet 0
-OpDecorate %i_vTextureCoords Location 0
-OpDecorate %_entryPointOutput_vColor Location 0
-OpDecorate %10 DescriptorSet 7
-OpDecorate %10 Binding 0
-OpDecorate %gl_FragCoord BuiltIn FragCoord
-OpDecorate %_struct_34 Block
-OpMemberDecorate %_struct_34 0 Offset 0
-OpMemberDecorate %_struct_34 1 Offset 4
-OpDecorate %74 DescriptorSet 7
-OpDecorate %74 Binding 0
-%void = OpTypeVoid
-%12 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%v2float = OpTypeVector %float 2
-%v4float = OpTypeVector %float 4
-%int = OpTypeInt 32 1
-%int_0 = OpConstant %int 0
-%18 = OpTypeImage %float 2D 0 0 0 1 Unknown
-%uint = OpTypeInt 32 0
-%uint_128 = OpConstant %uint 128
-%_arr_18_uint_128 = OpTypeArray %18 %uint_128
-%_ptr_UniformConstant__arr_18_uint_128 = OpTypePointer UniformConstant %_arr_18_uint_128
-%g_tColor = OpVariable %_ptr_UniformConstant__arr_18_uint_128 UniformConstant
-%PerViewConstantBuffer_t = OpTypeStruct %uint
-%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
-%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
-%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
-%_ptr_UniformConstant_18 = OpTypePointer UniformConstant %18
-%26 = OpTypeSampler
-%_ptr_UniformConstant_26 = OpTypePointer UniformConstant %26
-%g_sAniso = OpVariable %_ptr_UniformConstant_26 UniformConstant
-%28 = OpTypeSampledImage %18
-%_ptr_Input_v2float = OpTypePointer Input %v2float
-%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
-%uint_0 = OpConstant %uint 0
-%bool = OpTypeBool
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_34 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_34 = OpTypePointer StorageBuffer %_struct_34
-%10 = OpVariable %_ptr_StorageBuffer__struct_34 StorageBuffer
-%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-%uint_10 = OpConstant %uint 10
-%uint_4 = OpConstant %uint 4
-%uint_1 = OpConstant %uint 1
-%uint_23 = OpConstant %uint 23
-%uint_2 = OpConstant %uint 2
-%uint_9 = OpConstant %uint 9
-%uint_3 = OpConstant %uint 3
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
-%v4uint = OpTypeVector %uint 4
-%uint_5 = OpConstant %uint 5
-%uint_6 = OpConstant %uint 6
-%uint_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%50 = OpConstantNull %v4float
-%68 = OpTypeFunction %void %uint %uint %uint %uint
-%74 = OpVariable %_ptr_StorageBuffer__struct_34 StorageBuffer
-%uint_82 = OpConstant %uint 82
-)";
-
-  const std::string func_before =
-      R"(%MainPs = OpFunction %void None %3
-%5 = OpLabel
-%53 = OpLoad %v2float %i_vTextureCoords
-%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
-%64 = OpLoad %uint %63
-%65 = OpAccessChain %_ptr_UniformConstant_20 %g_tColor %64
-%67 = OpLoad %35 %g_sAniso
-%78 = OpLoad %20 %65
-%79 = OpSampledImage %39 %78 %67
-%71 = OpImageSampleImplicitLod %v4float %79 %53
-OpStore %_entryPointOutput_vColor %71
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string func_after =
-      R"(%MainPs = OpFunction %void None %12
-%51 = OpLabel
-%52 = OpLoad %v2float %i_vTextureCoords
-%53 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
-%54 = OpLoad %uint %53
-%55 = OpAccessChain %_ptr_UniformConstant_18 %g_tColor %54
-%56 = OpLoad %26 %g_sAniso
-%57 = OpLoad %18 %55
-%58 = OpSampledImage %28 %57 %56
-%60 = OpULessThan %bool %54 %uint_128
-OpSelectionMerge %61 None
-OpBranchConditional %60 %62 %63
-%62 = OpLabel
-%64 = OpLoad %18 %55
-%65 = OpSampledImage %28 %64 %56
-%66 = OpImageSampleImplicitLod %v4float %65 %52
-OpBranch %61
-%63 = OpLabel
-%105 = OpFunctionCall %void %67 %uint_82 %uint_0 %54 %uint_128
-OpBranch %61
-%61 = OpLabel
-%106 = OpPhi %v4float %66 %62 %50 %63
-OpStore %_entryPointOutput_vColor %106
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string output_func =
-      R"(%67 = OpFunction %void None %68
-%69 = OpFunctionParameter %uint
-%70 = OpFunctionParameter %uint
-%71 = OpFunctionParameter %uint
-%72 = OpFunctionParameter %uint
-%73 = OpLabel
-%75 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_0
-%76 = OpAtomicIAdd %uint %75 %uint_4 %uint_0 %uint_9
-%77 = OpIAdd %uint %76 %uint_9
-%78 = OpArrayLength %uint %74 1
-%79 = OpULessThanEqual %bool %77 %78
-OpSelectionMerge %80 None
-OpBranchConditional %79 %81 %80
-%81 = OpLabel
-%82 = OpIAdd %uint %76 %uint_0
-%83 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %82
-OpStore %83 %uint_9
-%84 = OpIAdd %uint %76 %uint_1
-%85 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %84
-OpStore %85 %uint_23
-%86 = OpIAdd %uint %76 %uint_2
-%87 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %86
-OpStore %87 %69
-%88 = OpIAdd %uint %76 %uint_3
-%89 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %88
-OpStore %89 %uint_4
-%90 = OpLoad %v4float %gl_FragCoord
-%91 = OpBitcast %v4uint %90
-%92 = OpCompositeExtract %uint %91 0
-%93 = OpIAdd %uint %76 %uint_4
-%94 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %93
-OpStore %94 %92
-%95 = OpCompositeExtract %uint %91 1
-%96 = OpIAdd %uint %76 %uint_5
-%97 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %96
-OpStore %97 %95
-%98 = OpIAdd %uint %76 %uint_6
-%99 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %98
-OpStore %99 %70
-%100 = OpIAdd %uint %76 %uint_7
-%101 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %100
-OpStore %101 %71
-%102 = OpIAdd %uint %76 %uint_8
-%103 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %102
-OpStore %103 %72
-OpBranch %80
-%80 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
-
-  // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBindlessCheckPass>(
-      defs_before + func_before, defs_after + func_after + output_func, true,
-      true);
-}
-
 TEST_F(InstBindlessTest, InstrumentOpImage) {
   // This test verifies that the pass will correctly instrument shader
   // using OpImage. This test was created by editing the SPIR-V
@@ -968,6 +703,7 @@
 OpDecorate %PerViewConstantBuffer_t Block
 OpDecorate %i_vTextureCoords Location 0
 OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %_runtimearr_uint ArrayStride 4
 OpDecorate %_struct_51 Block
 OpMemberDecorate %_struct_51 0 Offset 0
 OpMemberDecorate %_struct_51 1 Offset 4
@@ -1193,6 +929,7 @@
 OpDecorate %PerViewConstantBuffer_t Block
 OpDecorate %i_vTextureCoords Location 0
 OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %_runtimearr_uint ArrayStride 4
 OpDecorate %_struct_49 Block
 OpMemberDecorate %_struct_49 0 Offset 0
 OpMemberDecorate %_struct_49 1 Offset 4
@@ -1418,6 +1155,7 @@
 OpDecorate %PerViewConstantBuffer_t Block
 OpDecorate %i_vTextureCoords Location 0
 OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %_runtimearr_uint ArrayStride 4
 OpDecorate %_struct_48 Block
 OpMemberDecorate %_struct_48 0 Offset 0
 OpMemberDecorate %_struct_48 1 Offset 4
@@ -1644,7 +1382,7 @@
 OpExtension "SPV_KHR_storage_buffer_storage_class"
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
-OpEntryPoint Vertex %main "main" %_ %coords2D %gl_VertexID %gl_InstanceID
+OpEntryPoint Vertex %main "main" %_ %coords2D %gl_VertexIndex %gl_InstanceIndex
 OpSource GLSL 450
 OpName %main "main"
 OpName %lod "lod"
@@ -1672,13 +1410,14 @@
 OpDecorate %__0 DescriptorSet 0
 OpDecorate %__0 Binding 5
 OpDecorate %coords2D Location 0
+OpDecorate %_runtimearr_uint ArrayStride 4
 OpDecorate %_struct_61 Block
 OpMemberDecorate %_struct_61 0 Offset 0
 OpMemberDecorate %_struct_61 1 Offset 4
 OpDecorate %63 DescriptorSet 7
 OpDecorate %63 Binding 0
-OpDecorate %gl_VertexID BuiltIn VertexId
-OpDecorate %gl_InstanceID BuiltIn InstanceId
+OpDecorate %gl_VertexIndex BuiltIn VertexIndex
+OpDecorate %gl_InstanceIndex BuiltIn InstanceIndex
 %void = OpTypeVoid
 %12 = OpTypeFunction %void
 %float = OpTypeFloat 32
@@ -1723,8 +1462,8 @@
 %uint_2 = OpConstant %uint 2
 %uint_3 = OpConstant %uint 3
 %_ptr_Input_uint = OpTypePointer Input %uint
-%gl_VertexID = OpVariable %_ptr_Input_uint Input
-%gl_InstanceID = OpVariable %_ptr_Input_uint Input
+%gl_VertexIndex = OpVariable %_ptr_Input_uint Input
+%gl_InstanceIndex = OpVariable %_ptr_Input_uint Input
 %uint_5 = OpConstant %uint 5
 %uint_6 = OpConstant %uint 6
 %uint_7 = OpConstant %uint 7
@@ -1812,11 +1551,11 @@
 %83 = OpIAdd %uint %68 %uint_3
 %84 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %83
 OpStore %84 %uint_0
-%87 = OpLoad %uint %gl_VertexID
+%87 = OpLoad %uint %gl_VertexIndex
 %88 = OpIAdd %uint %68 %uint_4
 %89 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %88
 OpStore %89 %87
-%91 = OpLoad %uint %gl_InstanceID
+%91 = OpLoad %uint %gl_InstanceIndex
 %93 = OpIAdd %uint %68 %uint_5
 %94 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %93
 OpStore %94 %91
@@ -1841,9 +1580,946 @@
       true);
 }
 
+TEST_F(InstBindlessTest, MultipleDebugFunctions) {
+  // Same source as Simple, but compiled -g and not optimized, especially not
+  // inlined. The OpSource has had the source extracted for the sake of brevity.
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+%2 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+%1 = OpString "foo5.frag"
+OpSource HLSL 500 %1
+OpName %MainPs "MainPs"
+OpName %PS_INPUT "PS_INPUT"
+OpMemberName %PS_INPUT 0 "vTextureCoords"
+OpName %PS_OUTPUT "PS_OUTPUT"
+OpMemberName %PS_OUTPUT 0 "vColor"
+OpName %_MainPs_struct_PS_INPUT_vf21_ "@MainPs(struct-PS_INPUT-vf21;"
+OpName %i "i"
+OpName %ps_output "ps_output"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_0 "i"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpName %param "param"
+OpDecorate %g_tColor DescriptorSet 0
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %g_sAniso Binding 1
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%PS_INPUT = OpTypeStruct %v2float
+%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT
+%v4float = OpTypeVector %float 4
+%PS_OUTPUT = OpTypeStruct %v4float
+%13 = OpTypeFunction %PS_OUTPUT %_ptr_Function_PS_INPUT
+%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%21 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%_arr_21_uint_128 = OpTypeArray %21 %uint_128
+%_ptr_UniformConstant__arr_21_uint_128 = OpTypePointer UniformConstant %_arr_21_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_21_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_21 = OpTypePointer UniformConstant %21
+%36 = OpTypeSampler
+%_ptr_UniformConstant_36 = OpTypePointer UniformConstant %36
+%g_sAniso = OpVariable %_ptr_UniformConstant_36 UniformConstant
+%40 = OpTypeSampledImage %21
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
+OpExecutionMode %MainPs OriginUpperLeft
+%5 = OpString "foo5.frag"
+OpSource HLSL 500 %5
+OpName %MainPs "MainPs"
+OpName %PS_INPUT "PS_INPUT"
+OpMemberName %PS_INPUT 0 "vTextureCoords"
+OpName %PS_OUTPUT "PS_OUTPUT"
+OpMemberName %PS_OUTPUT 0 "vColor"
+OpName %_MainPs_struct_PS_INPUT_vf21_ "@MainPs(struct-PS_INPUT-vf21;"
+OpName %i "i"
+OpName %ps_output "ps_output"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_0 "i"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpName %param "param"
+OpDecorate %g_tColor DescriptorSet 0
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %g_sAniso Binding 1
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %_runtimearr_uint ArrayStride 4
+OpDecorate %_struct_77 Block
+OpMemberDecorate %_struct_77 0 Offset 0
+OpMemberDecorate %_struct_77 1 Offset 4
+OpDecorate %79 DescriptorSet 7
+OpDecorate %79 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+%void = OpTypeVoid
+%18 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%PS_INPUT = OpTypeStruct %v2float
+%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT
+%v4float = OpTypeVector %float 4
+%PS_OUTPUT = OpTypeStruct %v4float
+%23 = OpTypeFunction %PS_OUTPUT %_ptr_Function_PS_INPUT
+%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%27 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%_arr_27_uint_128 = OpTypeArray %27 %uint_128
+%_ptr_UniformConstant__arr_27_uint_128 = OpTypePointer UniformConstant %_arr_27_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_27_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_27 = OpTypePointer UniformConstant %27
+%35 = OpTypeSampler
+%_ptr_UniformConstant_35 = OpTypePointer UniformConstant %35
+%g_sAniso = OpVariable %_ptr_UniformConstant_35 UniformConstant
+%37 = OpTypeSampledImage %27
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%uint_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%70 = OpTypeFunction %void %uint %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_77 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_77 = OpTypePointer StorageBuffer %_struct_77
+%79 = OpVariable %_ptr_StorageBuffer__struct_77 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_9 = OpConstant %uint 9
+%uint_4 = OpConstant %uint 4
+%uint_1 = OpConstant %uint 1
+%uint_23 = OpConstant %uint 23
+%uint_2 = OpConstant %uint 2
+%uint_3 = OpConstant %uint 3
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%v4uint = OpTypeVector %uint 4
+%uint_5 = OpConstant %uint 5
+%uint_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%uint_93 = OpConstant %uint 93
+%125 = OpConstantNull %v4float
+)";
+
+  const std::string func1_before =
+      R"(%MainPs = OpFunction %void None %4
+%6 = OpLabel
+%i_0 = OpVariable %_ptr_Function_PS_INPUT Function
+%param = OpVariable %_ptr_Function_PS_INPUT Function
+OpLine %1 21 0
+%54 = OpLoad %v2float %i_vTextureCoords
+%55 = OpAccessChain %_ptr_Function_v2float %i_0 %int_0
+OpStore %55 %54
+%59 = OpLoad %PS_INPUT %i_0
+OpStore %param %59
+%60 = OpFunctionCall %PS_OUTPUT %_MainPs_struct_PS_INPUT_vf21_ %param
+%61 = OpCompositeExtract %v4float %60 0
+OpStore %_entryPointOutput_vColor %61
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string func1_after =
+      R"(%MainPs = OpFunction %void None %18
+%42 = OpLabel
+%i_0 = OpVariable %_ptr_Function_PS_INPUT Function
+%param = OpVariable %_ptr_Function_PS_INPUT Function
+OpLine %5 21 0
+%43 = OpLoad %v2float %i_vTextureCoords
+%44 = OpAccessChain %_ptr_Function_v2float %i_0 %int_0
+OpStore %44 %43
+%45 = OpLoad %PS_INPUT %i_0
+OpStore %param %45
+%46 = OpFunctionCall %PS_OUTPUT %_MainPs_struct_PS_INPUT_vf21_ %param
+%47 = OpCompositeExtract %v4float %46 0
+OpStore %_entryPointOutput_vColor %47
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string func2_before =
+      R"(%_MainPs_struct_PS_INPUT_vf21_ = OpFunction %PS_OUTPUT None %13
+%i = OpFunctionParameter %_ptr_Function_PS_INPUT
+%16 = OpLabel
+%ps_output = OpVariable %_ptr_Function_PS_OUTPUT Function
+OpLine %1 24 0
+%31 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%32 = OpLoad %uint %31
+%34 = OpAccessChain %_ptr_UniformConstant_21 %g_tColor %32
+%35 = OpLoad %21 %34
+%39 = OpLoad %36 %g_sAniso
+%41 = OpSampledImage %40 %35 %39
+%43 = OpAccessChain %_ptr_Function_v2float %i %int_0
+%44 = OpLoad %v2float %43
+%45 = OpImageSampleImplicitLod %v4float %41 %44
+%47 = OpAccessChain %_ptr_Function_v4float %ps_output %int_0
+OpStore %47 %45
+OpLine %1 25 0
+%48 = OpLoad %PS_OUTPUT %ps_output
+OpReturnValue %48
+OpFunctionEnd
+)";
+
+  const std::string func2_after =
+      R"(%_MainPs_struct_PS_INPUT_vf21_ = OpFunction %PS_OUTPUT None %23
+%i = OpFunctionParameter %_ptr_Function_PS_INPUT
+%48 = OpLabel
+%ps_output = OpVariable %_ptr_Function_PS_OUTPUT Function
+OpLine %5 24 0
+%49 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%50 = OpLoad %uint %49
+%51 = OpAccessChain %_ptr_UniformConstant_27 %g_tColor %50
+%52 = OpLoad %27 %51
+%53 = OpLoad %35 %g_sAniso
+%54 = OpSampledImage %37 %52 %53
+%55 = OpAccessChain %_ptr_Function_v2float %i %int_0
+%56 = OpLoad %v2float %55
+%62 = OpULessThan %bool %50 %uint_128
+OpSelectionMerge %63 None
+OpBranchConditional %62 %64 %65
+%64 = OpLabel
+%66 = OpLoad %27 %51
+%67 = OpSampledImage %37 %66 %53
+%68 = OpImageSampleImplicitLod %v4float %67 %56
+OpBranch %63
+%65 = OpLabel
+%124 = OpFunctionCall %void %69 %uint_93 %uint_0 %50 %uint_128
+OpBranch %63
+%63 = OpLabel
+%126 = OpPhi %v4float %68 %64 %125 %65
+%58 = OpAccessChain %_ptr_Function_v4float %ps_output %int_0
+OpStore %58 %126
+OpLine %5 25 0
+%59 = OpLoad %PS_OUTPUT %ps_output
+OpReturnValue %59
+OpFunctionEnd
+)";
+
+  const std::string output_func =
+      R"(%69 = OpFunction %void None %70
+%71 = OpFunctionParameter %uint
+%72 = OpFunctionParameter %uint
+%73 = OpFunctionParameter %uint
+%74 = OpFunctionParameter %uint
+%75 = OpLabel
+%81 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_0
+%84 = OpAtomicIAdd %uint %81 %uint_4 %uint_0 %uint_9
+%85 = OpIAdd %uint %84 %uint_9
+%86 = OpArrayLength %uint %79 1
+%87 = OpULessThanEqual %bool %85 %86
+OpSelectionMerge %88 None
+OpBranchConditional %87 %89 %88
+%89 = OpLabel
+%90 = OpIAdd %uint %84 %uint_0
+%92 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %90
+OpStore %92 %uint_9
+%94 = OpIAdd %uint %84 %uint_1
+%95 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %94
+OpStore %95 %uint_23
+%97 = OpIAdd %uint %84 %uint_2
+%98 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %97
+OpStore %98 %71
+%100 = OpIAdd %uint %84 %uint_3
+%101 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %100
+OpStore %101 %uint_4
+%104 = OpLoad %v4float %gl_FragCoord
+%106 = OpBitcast %v4uint %104
+%107 = OpCompositeExtract %uint %106 0
+%108 = OpIAdd %uint %84 %uint_4
+%109 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %108
+OpStore %109 %107
+%110 = OpCompositeExtract %uint %106 1
+%112 = OpIAdd %uint %84 %uint_5
+%113 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %112
+OpStore %113 %110
+%115 = OpIAdd %uint %84 %uint_6
+%116 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %115
+OpStore %116 %72
+%118 = OpIAdd %uint %84 %uint_7
+%119 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %118
+OpStore %119 %73
+%121 = OpIAdd %uint %84 %uint_8
+%122 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %121
+OpStore %122 %74
+OpBranch %88
+%88 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<InstBindlessCheckPass>(
+      defs_before + func1_before + func2_before,
+      defs_after + func1_after + func2_after + output_func, true, true);
+}
+
+TEST_F(InstBindlessTest, RuntimeArray) {
+  // This test verifies that the pass will correctly instrument shader
+  // with runtime descriptor array. This test was created by editing the
+  // SPIR-V from the Simple test.
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+OpCapability RuntimeDescriptorArrayEXT
+OpExtension "SPV_EXT_descriptor_indexing"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 1
+OpDecorate %g_tColor Binding 2
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 1
+OpDecorate %g_sAniso Binding 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%20 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%_rarr_20 = OpTypeRuntimeArray %20
+%_ptr_UniformConstant__arr_20 = OpTypePointer UniformConstant %_rarr_20
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_20 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_20 = OpTypePointer UniformConstant %20
+%35 = OpTypeSampler
+%_ptr_UniformConstant_35 = OpTypePointer UniformConstant %35
+%g_sAniso = OpVariable %_ptr_UniformConstant_35 UniformConstant
+%39 = OpTypeSampledImage %20
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpCapability RuntimeDescriptorArrayEXT
+OpExtension "SPV_EXT_descriptor_indexing"
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 1
+OpDecorate %g_tColor Binding 2
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 1
+OpDecorate %g_sAniso Binding 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %_runtimearr_uint ArrayStride 4
+OpDecorate %_struct_46 Block
+OpMemberDecorate %_struct_46 0 Offset 0
+OpDecorate %48 DescriptorSet 7
+OpDecorate %48 Binding 1
+OpDecorate %_struct_71 Block
+OpMemberDecorate %_struct_71 0 Offset 0
+OpMemberDecorate %_struct_71 1 Offset 4
+OpDecorate %73 DescriptorSet 7
+OpDecorate %73 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%16 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%_runtimearr_16 = OpTypeRuntimeArray %16
+%_ptr_UniformConstant__runtimearr_16 = OpTypePointer UniformConstant %_runtimearr_16
+%g_tColor = OpVariable %_ptr_UniformConstant__runtimearr_16 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_16 = OpTypePointer UniformConstant %16
+%24 = OpTypeSampler
+%_ptr_UniformConstant_24 = OpTypePointer UniformConstant %24
+%g_sAniso = OpVariable %_ptr_UniformConstant_24 UniformConstant
+%26 = OpTypeSampledImage %16
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%uint_0 = OpConstant %uint 0
+%uint_2 = OpConstant %uint 2
+%41 = OpTypeFunction %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_46 = OpTypeStruct %_runtimearr_uint
+%_ptr_StorageBuffer__struct_46 = OpTypePointer StorageBuffer %_struct_46
+%48 = OpVariable %_ptr_StorageBuffer__struct_46 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%bool = OpTypeBool
+%65 = OpTypeFunction %void %uint %uint %uint %uint
+%_struct_71 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_71 = OpTypePointer StorageBuffer %_struct_71
+%73 = OpVariable %_ptr_StorageBuffer__struct_71 StorageBuffer
+%uint_9 = OpConstant %uint 9
+%uint_4 = OpConstant %uint 4
+%uint_23 = OpConstant %uint 23
+%uint_3 = OpConstant %uint 3
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%v4uint = OpTypeVector %uint 4
+%uint_5 = OpConstant %uint 5
+%uint_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%uint_59 = OpConstant %uint 59
+%116 = OpConstantNull %v4float
+%119 = OpTypeFunction %uint %uint %uint %uint %uint
+)";
+
+  const std::string func_before =
+      R"(%MainPs = OpFunction %void None %3
+%5 = OpLabel
+%53 = OpLoad %v2float %i_vTextureCoords
+%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%64 = OpLoad %uint %63
+%65 = OpAccessChain %_ptr_UniformConstant_20 %g_tColor %64
+%66 = OpLoad %20 %65
+%67 = OpLoad %35 %g_sAniso
+%68 = OpSampledImage %39 %66 %67
+%71 = OpImageSampleImplicitLod %v4float %68 %53
+OpStore %_entryPointOutput_vColor %71
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string func_after =
+      R"(%MainPs = OpFunction %void None %10
+%29 = OpLabel
+%30 = OpLoad %v2float %i_vTextureCoords
+%31 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%32 = OpLoad %uint %31
+%33 = OpAccessChain %_ptr_UniformConstant_16 %g_tColor %32
+%34 = OpLoad %16 %33
+%35 = OpLoad %24 %g_sAniso
+%36 = OpSampledImage %26 %34 %35
+%55 = OpFunctionCall %uint %40 %uint_2 %uint_2
+%57 = OpULessThan %bool %32 %55
+OpSelectionMerge %58 None
+OpBranchConditional %57 %59 %60
+%59 = OpLabel
+%61 = OpLoad %16 %33
+%62 = OpSampledImage %26 %61 %35
+%136 = OpFunctionCall %uint %118 %uint_0 %uint_1 %uint_2 %32
+%137 = OpINotEqual %bool %136 %uint_0
+OpSelectionMerge %138 None
+OpBranchConditional %137 %139 %140
+%139 = OpLabel
+%141 = OpLoad %16 %33
+%142 = OpSampledImage %26 %141 %35
+%143 = OpImageSampleImplicitLod %v4float %142 %30
+OpBranch %138
+%140 = OpLabel
+%144 = OpFunctionCall %void %64 %uint_59 %uint_1 %32 %uint_0
+OpBranch %138
+%138 = OpLabel
+%145 = OpPhi %v4float %143 %139 %116 %140
+OpBranch %58
+%60 = OpLabel
+%115 = OpFunctionCall %void %64 %uint_59 %uint_0 %32 %55
+OpBranch %58
+%58 = OpLabel
+%117 = OpPhi %v4float %145 %138 %116 %60
+OpStore %_entryPointOutput_vColor %117
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string new_funcs =
+      R"(%40 = OpFunction %uint None %41
+%42 = OpFunctionParameter %uint
+%43 = OpFunctionParameter %uint
+%44 = OpLabel
+%50 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0 %42
+%51 = OpLoad %uint %50
+%52 = OpIAdd %uint %51 %43
+%53 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0 %52
+%54 = OpLoad %uint %53
+OpReturnValue %54
+OpFunctionEnd
+%64 = OpFunction %void None %65
+%66 = OpFunctionParameter %uint
+%67 = OpFunctionParameter %uint
+%68 = OpFunctionParameter %uint
+%69 = OpFunctionParameter %uint
+%70 = OpLabel
+%74 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_0
+%77 = OpAtomicIAdd %uint %74 %uint_4 %uint_0 %uint_9
+%78 = OpIAdd %uint %77 %uint_9
+%79 = OpArrayLength %uint %73 1
+%80 = OpULessThanEqual %bool %78 %79
+OpSelectionMerge %81 None
+OpBranchConditional %80 %82 %81
+%82 = OpLabel
+%83 = OpIAdd %uint %77 %uint_0
+%84 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %83
+OpStore %84 %uint_9
+%86 = OpIAdd %uint %77 %uint_1
+%87 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %86
+OpStore %87 %uint_23
+%88 = OpIAdd %uint %77 %uint_2
+%89 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %88
+OpStore %89 %66
+%91 = OpIAdd %uint %77 %uint_3
+%92 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %91
+OpStore %92 %uint_4
+%95 = OpLoad %v4float %gl_FragCoord
+%97 = OpBitcast %v4uint %95
+%98 = OpCompositeExtract %uint %97 0
+%99 = OpIAdd %uint %77 %uint_4
+%100 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %99
+OpStore %100 %98
+%101 = OpCompositeExtract %uint %97 1
+%103 = OpIAdd %uint %77 %uint_5
+%104 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %103
+OpStore %104 %101
+%106 = OpIAdd %uint %77 %uint_6
+%107 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %106
+OpStore %107 %67
+%109 = OpIAdd %uint %77 %uint_7
+%110 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %109
+OpStore %110 %68
+%112 = OpIAdd %uint %77 %uint_8
+%113 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %112
+OpStore %113 %69
+OpBranch %81
+%81 = OpLabel
+OpReturn
+OpFunctionEnd
+%118 = OpFunction %uint None %119
+%120 = OpFunctionParameter %uint
+%121 = OpFunctionParameter %uint
+%122 = OpFunctionParameter %uint
+%123 = OpFunctionParameter %uint
+%124 = OpLabel
+%125 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0 %120
+%126 = OpLoad %uint %125
+%127 = OpIAdd %uint %126 %121
+%128 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0 %127
+%129 = OpLoad %uint %128
+%130 = OpIAdd %uint %129 %122
+%131 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0 %130
+%132 = OpLoad %uint %131
+%133 = OpIAdd %uint %132 %123
+%134 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0 %133
+%135 = OpLoad %uint %134
+OpReturnValue %135
+OpFunctionEnd
+)";
+
+  // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<InstBindlessCheckPass>(
+      defs_before + func_before, defs_after + func_after + new_funcs, true,
+      true);
+}
+
+TEST_F(InstBindlessTest, NoInstrumentNonBindless) {
+  // This test verifies that the pass will correctly not instrument vanilla
+  // texture sample.
+  //
+  // Texture2D g_tColor;
+  //
+  // SamplerState g_sAniso;
+  //
+  // struct PS_INPUT
+  // {
+  //   float2 vTextureCoords : TEXCOORD2;
+  // };
+  //
+  // struct PS_OUTPUT
+  // {
+  //   float4 vColor : SV_Target0;
+  // };
+  //
+  // PS_OUTPUT MainPs(PS_INPUT i)
+  // {
+  //   PS_OUTPUT ps_output;
+  //   ps_output.vColor =
+  //       g_tColor.Sample(g_sAniso, i.vTextureCoords.xy);
+  //   return ps_output;
+  // }
+
+  const std::string whole_file =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 0
+OpDecorate %g_tColor Binding 0
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %g_sAniso Binding 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%12 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12
+%g_tColor = OpVariable %_ptr_UniformConstant_12 UniformConstant
+%14 = OpTypeSampler
+%_ptr_UniformConstant_14 = OpTypePointer UniformConstant %14
+%g_sAniso = OpVariable %_ptr_UniformConstant_14 UniformConstant
+%16 = OpTypeSampledImage %12
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%MainPs = OpFunction %void None %8
+%19 = OpLabel
+%20 = OpLoad %v2float %i_vTextureCoords
+%21 = OpLoad %12 %g_tColor
+%22 = OpLoad %14 %g_sAniso
+%23 = OpSampledImage %16 %21 %22
+%24 = OpImageSampleImplicitLod %v4float %23 %20
+OpStore %_entryPointOutput_vColor %24
+OpReturn
+OpFunctionEnd
+)";
+
+  // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<InstBindlessCheckPass>(whole_file, whole_file, true,
+                                               true);
+}
+
+TEST_F(InstBindlessTest, InstrumentInitCheckOnScalarDescriptor) {
+  // This test verifies that the pass will correctly instrument vanilla
+  // texture sample on a scalar descriptor with an initialization check if the
+  // SPV_EXT_descriptor_checking extension is enabled. This is the same shader
+  // as NoInstrumentNonBindless, but with the extension hacked on in the SPIR-V.
+
+  const std::string defs_before =
+      R"(OpCapability Shader
+OpExtension "SPV_EXT_descriptor_indexing"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 0
+OpDecorate %g_tColor Binding 0
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %g_sAniso Binding 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%12 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12
+%g_tColor = OpVariable %_ptr_UniformConstant_12 UniformConstant
+%14 = OpTypeSampler
+%_ptr_UniformConstant_14 = OpTypePointer UniformConstant %14
+%g_sAniso = OpVariable %_ptr_UniformConstant_14 UniformConstant
+%16 = OpTypeSampledImage %12
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+  const std::string defs_after =
+      R"(OpCapability Shader
+OpExtension "SPV_EXT_descriptor_indexing"
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 0
+OpDecorate %g_tColor Binding 0
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %g_sAniso Binding 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %_runtimearr_uint ArrayStride 4
+OpDecorate %_struct_35 Block
+OpMemberDecorate %_struct_35 0 Offset 0
+OpDecorate %37 DescriptorSet 7
+OpDecorate %37 Binding 1
+OpDecorate %_struct_67 Block
+OpMemberDecorate %_struct_67 0 Offset 0
+OpMemberDecorate %_struct_67 1 Offset 4
+OpDecorate %69 DescriptorSet 7
+OpDecorate %69 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%12 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12
+%g_tColor = OpVariable %_ptr_UniformConstant_12 UniformConstant
+%14 = OpTypeSampler
+%_ptr_UniformConstant_14 = OpTypePointer UniformConstant %14
+%g_sAniso = OpVariable %_ptr_UniformConstant_14 UniformConstant
+%16 = OpTypeSampledImage %12
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%28 = OpTypeFunction %uint %uint %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_35 = OpTypeStruct %_runtimearr_uint
+%_ptr_StorageBuffer__struct_35 = OpTypePointer StorageBuffer %_struct_35
+%37 = OpVariable %_ptr_StorageBuffer__struct_35 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%bool = OpTypeBool
+%uint_1 = OpConstant %uint 1
+%61 = OpTypeFunction %void %uint %uint %uint %uint
+%_struct_67 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_67 = OpTypePointer StorageBuffer %_struct_67
+%69 = OpVariable %_ptr_StorageBuffer__struct_67 StorageBuffer
+%uint_9 = OpConstant %uint 9
+%uint_4 = OpConstant %uint 4
+%uint_23 = OpConstant %uint 23
+%uint_2 = OpConstant %uint 2
+%uint_3 = OpConstant %uint 3
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%v4uint = OpTypeVector %uint 4
+%uint_5 = OpConstant %uint 5
+%uint_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%uint_40 = OpConstant %uint 40
+%113 = OpConstantNull %v4float
+)";
+
+  const std::string func_before =
+      R"(%MainPs = OpFunction %void None %8
+%19 = OpLabel
+%20 = OpLoad %v2float %i_vTextureCoords
+%21 = OpLoad %12 %g_tColor
+%22 = OpLoad %14 %g_sAniso
+%23 = OpSampledImage %16 %21 %22
+%24 = OpImageSampleImplicitLod %v4float %23 %20
+OpStore %_entryPointOutput_vColor %24
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string func_after =
+      R"(%MainPs = OpFunction %void None %8
+%19 = OpLabel
+%20 = OpLoad %v2float %i_vTextureCoords
+%21 = OpLoad %12 %g_tColor
+%22 = OpLoad %14 %g_sAniso
+%23 = OpSampledImage %16 %21 %22
+%50 = OpFunctionCall %uint %27 %uint_0 %uint_0 %uint_0 %uint_0
+%52 = OpINotEqual %bool %50 %uint_0
+OpSelectionMerge %54 None
+OpBranchConditional %52 %55 %56
+%55 = OpLabel
+%57 = OpLoad %12 %g_tColor
+%58 = OpSampledImage %16 %57 %22
+%59 = OpImageSampleImplicitLod %v4float %58 %20
+OpBranch %54
+%56 = OpLabel
+%112 = OpFunctionCall %void %60 %uint_40 %uint_1 %uint_0 %uint_0
+OpBranch %54
+%54 = OpLabel
+%114 = OpPhi %v4float %59 %55 %113 %56
+OpStore %_entryPointOutput_vColor %114
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string new_funcs =
+      R"(%27 = OpFunction %uint None %28
+%29 = OpFunctionParameter %uint
+%30 = OpFunctionParameter %uint
+%31 = OpFunctionParameter %uint
+%32 = OpFunctionParameter %uint
+%33 = OpLabel
+%39 = OpAccessChain %_ptr_StorageBuffer_uint %37 %uint_0 %29
+%40 = OpLoad %uint %39
+%41 = OpIAdd %uint %40 %30
+%42 = OpAccessChain %_ptr_StorageBuffer_uint %37 %uint_0 %41
+%43 = OpLoad %uint %42
+%44 = OpIAdd %uint %43 %31
+%45 = OpAccessChain %_ptr_StorageBuffer_uint %37 %uint_0 %44
+%46 = OpLoad %uint %45
+%47 = OpIAdd %uint %46 %32
+%48 = OpAccessChain %_ptr_StorageBuffer_uint %37 %uint_0 %47
+%49 = OpLoad %uint %48
+OpReturnValue %49
+OpFunctionEnd
+%60 = OpFunction %void None %61
+%62 = OpFunctionParameter %uint
+%63 = OpFunctionParameter %uint
+%64 = OpFunctionParameter %uint
+%65 = OpFunctionParameter %uint
+%66 = OpLabel
+%70 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_0
+%73 = OpAtomicIAdd %uint %70 %uint_4 %uint_0 %uint_9
+%74 = OpIAdd %uint %73 %uint_9
+%75 = OpArrayLength %uint %69 1
+%76 = OpULessThanEqual %bool %74 %75
+OpSelectionMerge %77 None
+OpBranchConditional %76 %78 %77
+%78 = OpLabel
+%79 = OpIAdd %uint %73 %uint_0
+%80 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %79
+OpStore %80 %uint_9
+%82 = OpIAdd %uint %73 %uint_1
+%83 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %82
+OpStore %83 %uint_23
+%85 = OpIAdd %uint %73 %uint_2
+%86 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %85
+OpStore %86 %62
+%88 = OpIAdd %uint %73 %uint_3
+%89 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %88
+OpStore %89 %uint_4
+%92 = OpLoad %v4float %gl_FragCoord
+%94 = OpBitcast %v4uint %92
+%95 = OpCompositeExtract %uint %94 0
+%96 = OpIAdd %uint %73 %uint_4
+%97 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %96
+OpStore %97 %95
+%98 = OpCompositeExtract %uint %94 1
+%100 = OpIAdd %uint %73 %uint_5
+%101 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %100
+OpStore %101 %98
+%103 = OpIAdd %uint %73 %uint_6
+%104 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %103
+OpStore %104 %63
+%106 = OpIAdd %uint %73 %uint_7
+%107 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %106
+OpStore %107 %64
+%109 = OpIAdd %uint %73 %uint_8
+%110 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %109
+OpStore %110 %65
+OpBranch %77
+%77 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<InstBindlessCheckPass>(
+      defs_before + func_before, defs_after + func_after + new_funcs, true,
+      true);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
-// TODO(greg-lunarg): Come up with cases to put here :)
+//   Compute shader
+//   Geometry shader
+//   Tesselation control shader
+//   Tesselation eval shader
+//   OpImage
+//   SampledImage variable
 
 }  // namespace
 }  // namespace opt
diff --git a/test/opt/legalize_vector_shuffle_test.cpp b/test/opt/legalize_vector_shuffle_test.cpp
new file mode 100644
index 0000000..8b9695b
--- /dev/null
+++ b/test/opt/legalize_vector_shuffle_test.cpp
@@ -0,0 +1,81 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <vector>
+
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using LegalizeVectorShuffleTest = PassTest<::testing::Test>;
+
+void operator+=(std::vector<const char*>& lhs, const char* rhs) {
+  lhs.push_back(rhs);
+}
+
+void operator+=(std::vector<const char*>& lhs,
+                const std::vector<const char*> rhs) {
+  for (auto elem : rhs) lhs.push_back(elem);
+}
+
+std::vector<const char*> header = {
+    "OpCapability Shader",
+    "OpCapability VulkanMemoryModelKHR",
+    "OpExtension \"SPV_KHR_vulkan_memory_model\"",
+    "OpMemoryModel Logical VulkanKHR",
+    "OpEntryPoint Vertex %1 \"shader\"",
+    "%uint = OpTypeInt 32 0",
+    "%v3uint = OpTypeVector %uint 3"};
+
+std::string GetTestString(const char* shuffle) {
+  std::vector<const char*> result = header;
+  result += {"%_ptr_Function_v3uint = OpTypePointer Function %v3uint",
+             "%void = OpTypeVoid",
+             "%6 = OpTypeFunction %void",
+             "%1 = OpFunction %void None %6",
+             "%7 = OpLabel",
+             "%8 = OpVariable %_ptr_Function_v3uint Function",
+             "%9 = OpLoad %v3uint %8",
+             "%10 = OpLoad %v3uint %8"};
+  result += shuffle;
+  result += {"OpReturn", "OpFunctionEnd"};
+  return JoinAllInsts(result);
+}
+
+TEST_F(LegalizeVectorShuffleTest, Changed) {
+  std::string input =
+      GetTestString("%11 = OpVectorShuffle %v3uint %9 %10 2 1 0xFFFFFFFF");
+  std::string expected =
+      GetTestString("%11 = OpVectorShuffle %v3uint %9 %10 2 1 0");
+
+  SinglePassRunAndCheck<LegalizeVectorShufflePass>(input, expected,
+                                                   /* skip_nop = */ false);
+}
+
+TEST_F(LegalizeVectorShuffleTest, FunctionUnchanged) {
+  std::string input =
+      GetTestString("%11 = OpVectorShuffle %v3uint %9 %10 2 1 0");
+  std::string expected =
+      GetTestString("%11 = OpVectorShuffle %v3uint %9 %10 2 1 0");
+
+  SinglePassRunAndCheck<LegalizeVectorShufflePass>(input, expected,
+                                                   /* skip_nop = */ false);
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/test/opt/local_access_chain_convert_test.cpp b/test/opt/local_access_chain_convert_test.cpp
index 154824b..1b75b36 100644
--- a/test/opt/local_access_chain_convert_test.cpp
+++ b/test/opt/local_access_chain_convert_test.cpp
@@ -700,6 +700,126 @@
                                                      true);
 }
 
+TEST_F(LocalAccessChainConvertTest, VariablePointersStorageBuffer) {
+  // A case with a storage buffer variable pointer.  We should still convert
+  // the access chain on the function scope symbol.
+  const std::string test =
+      R"(
+; CHECK: OpFunction
+; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Function
+; CHECK: [[ld:%\w+]] = OpLoad {{%\w+}} [[var]]
+; CHECK: OpCompositeExtract {{%\w+}} [[ld]] 0 0
+               OpCapability Shader
+               OpCapability VariablePointersStorageBuffer
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %2 "main"
+               OpExecutionMode %2 LocalSize 1 1 1
+               OpSource GLSL 450
+               OpMemberDecorate %_struct_3 0 Offset 0
+               OpDecorate %_struct_3 Block
+               OpDecorate %4 DescriptorSet 0
+               OpDecorate %4 Binding 0
+               OpDecorate %_ptr_StorageBuffer_int ArrayStride 4
+               OpDecorate %_arr_int_int_128 ArrayStride 4
+       %void = OpTypeVoid
+          %8 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+    %int_128 = OpConstant %int 128
+%_arr_int_int_128 = OpTypeArray %int %int_128
+  %_struct_3 = OpTypeStruct %_arr_int_int_128
+%_ptr_StorageBuffer__struct_3 = OpTypePointer StorageBuffer %_struct_3
+%_ptr_Function__struct_3 = OpTypePointer Function %_struct_3
+          %4 = OpVariable %_ptr_StorageBuffer__struct_3 StorageBuffer
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+      %int_0 = OpConstant %int 0
+      %int_1 = OpConstant %int 1
+%_ptr_StorageBuffer_int = OpTypePointer StorageBuffer %int
+%_ptr_Function_int = OpTypePointer Function %int
+          %2 = OpFunction %void None %8
+         %18 = OpLabel
+         %19 = OpVariable %_ptr_Function__struct_3 Function
+         %20 = OpAccessChain %_ptr_StorageBuffer_int %4 %int_0 %int_0
+               OpBranch %21
+         %21 = OpLabel
+         %22 = OpPhi %_ptr_StorageBuffer_int %20 %18 %23 %24
+               OpLoopMerge %25 %24 None
+               OpBranchConditional %true %26 %25
+         %26 = OpLabel
+               OpStore %22 %int_0
+               OpBranch %24
+         %24 = OpLabel
+         %23 = OpPtrAccessChain %_ptr_StorageBuffer_int %22 %int_1
+               OpBranch %21
+         %25 = OpLabel
+         %27 = OpAccessChain %_ptr_Function_int %19 %int_0 %int_0
+         %28 = OpLoad %int %27
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<LocalAccessChainConvertPass>(test, true);
+}
+
+TEST_F(LocalAccessChainConvertTest, VariablePointers) {
+  // A case with variable pointer capability.  We should not convert
+  // the access chain on the function scope symbol because the variable pointer
+  // could the analysis to miss references to function scope symbols.
+  const std::string test =
+      R"(OpCapability Shader
+OpCapability VariablePointers
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %2 "main"
+OpExecutionMode %2 LocalSize 1 1 1
+OpSource GLSL 450
+OpMemberDecorate %_struct_3 0 Offset 0
+OpDecorate %_struct_3 Block
+OpDecorate %4 DescriptorSet 0
+OpDecorate %4 Binding 0
+OpDecorate %_ptr_StorageBuffer_int ArrayStride 4
+OpDecorate %_arr_int_int_128 ArrayStride 4
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%int_128 = OpConstant %int 128
+%_arr_int_int_128 = OpTypeArray %int %int_128
+%_struct_3 = OpTypeStruct %_arr_int_int_128
+%_ptr_StorageBuffer__struct_3 = OpTypePointer StorageBuffer %_struct_3
+%_ptr_Function__struct_3 = OpTypePointer Function %_struct_3
+%4 = OpVariable %_ptr_StorageBuffer__struct_3 StorageBuffer
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%_ptr_StorageBuffer_int = OpTypePointer StorageBuffer %int
+%_ptr_Function_int = OpTypePointer Function %int
+%2 = OpFunction %void None %8
+%18 = OpLabel
+%19 = OpVariable %_ptr_Function__struct_3 Function
+%20 = OpAccessChain %_ptr_StorageBuffer_int %4 %int_0 %int_0
+OpBranch %21
+%21 = OpLabel
+%22 = OpPhi %_ptr_StorageBuffer_int %20 %18 %23 %24
+OpLoopMerge %25 %24 None
+OpBranchConditional %true %26 %25
+%26 = OpLabel
+OpStore %22 %int_0
+OpBranch %24
+%24 = OpLabel
+%23 = OpPtrAccessChain %_ptr_StorageBuffer_int %22 %int_1
+OpBranch %21
+%25 = OpLabel
+%27 = OpAccessChain %_ptr_Function_int %19 %int_0 %int_0
+%28 = OpLoad %int %27
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<LocalAccessChainConvertPass>(test, test, false, true);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    Assorted vector and matrix types
diff --git a/test/opt/local_single_block_elim.cpp b/test/opt/local_single_block_elim.cpp
index e2efc5e..402352d 100644
--- a/test/opt/local_single_block_elim.cpp
+++ b/test/opt/local_single_block_elim.cpp
@@ -1062,6 +1062,62 @@
   SinglePassRunAndCheck<LocalSingleBlockLoadStoreElimPass>(
       predefs + before, predefs + after, true, true);
 }
+
+TEST_F(LocalSingleBlockLoadStoreElimTest, VariablePointerTest) {
+  // Check that the load of the first variable is still used and that the load
+  // of the third variable is propagated.  The first load has to remain because
+  // of the store to the variable pointer.
+  const std::string text = R"(
+; CHECK: [[v1:%\w+]] = OpVariable
+; CHECK: [[v2:%\w+]] = OpVariable
+; CHECK: [[v3:%\w+]] = OpVariable
+; CHECK: [[phi:%\w+]] = OpPhi
+; CHECK: [[ld1:%\w+]] = OpLoad %int [[v1]]
+; CHECK: OpIAdd %int [[ld1]] %int_0
+               OpCapability Shader
+               OpCapability VariablePointers
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %2 "main"
+               OpExecutionMode %2 LocalSize 1 1 1
+               OpSource GLSL 450
+               OpMemberDecorate %_struct_3 0 Offset 0
+               OpMemberDecorate %_struct_3 1 Offset 4
+       %void = OpTypeVoid
+          %5 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+       %bool = OpTypeBool
+  %_struct_3 = OpTypeStruct %int %int
+%_ptr_Function__struct_3 = OpTypePointer Function %_struct_3
+%_ptr_Function_int = OpTypePointer Function %int
+       %true = OpConstantTrue %bool
+      %int_0 = OpConstant %int 0
+      %int_1 = OpConstant %int 1
+         %13 = OpConstantNull %_struct_3
+          %2 = OpFunction %void None %5
+         %14 = OpLabel
+         %15 = OpVariable %_ptr_Function_int Function
+         %16 = OpVariable %_ptr_Function_int Function
+         %17 = OpVariable %_ptr_Function_int Function
+               OpSelectionMerge %18 None
+               OpBranchConditional %true %19 %20
+         %19 = OpLabel
+               OpBranch %18
+         %20 = OpLabel
+               OpBranch %18
+         %18 = OpLabel
+         %21 = OpPhi %_ptr_Function_int %15 %19 %16 %20
+               OpStore %15 %int_1
+               OpStore %21 %int_0
+         %22 = OpLoad %int %15
+               OpStore %17 %int_0
+         %23 = OpLoad %int %17
+         %24 = OpIAdd %int %22 %23
+               OpReturn
+               OpFunctionEnd
+  )";
+  SinglePassRunAndMatch<LocalSingleBlockLoadStoreElimPass>(text, false);
+}
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    Other target variable types
diff --git a/test/opt/local_single_store_elim_test.cpp b/test/opt/local_single_store_elim_test.cpp
index 5fd0217..5a1650b 100644
--- a/test/opt/local_single_store_elim_test.cpp
+++ b/test/opt/local_single_store_elim_test.cpp
@@ -847,6 +847,61 @@
   SinglePassRunAndCheck<LocalSingleStoreElimPass>(predefs + before,
                                                   predefs + after, true, true);
 }
+
+TEST_F(LocalSingleStoreElimTest, VariablePointerTest) {
+  // Check that the load of the first variable is still used and that the load
+  // of the third variable is propagated.  The first load has to remain because
+  // of the store to the variable pointer.
+  const std::string text = R"(
+; CHECK: [[v1:%\w+]] = OpVariable
+; CHECK: [[v2:%\w+]] = OpVariable
+; CHECK: [[v3:%\w+]] = OpVariable
+; CHECK: [[ld1:%\w+]] = OpLoad %int [[v1]]
+; CHECK: OpIAdd %int [[ld1]] %int_0
+               OpCapability Shader
+               OpCapability VariablePointers
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %2 "main"
+               OpExecutionMode %2 LocalSize 1 1 1
+               OpSource GLSL 450
+               OpMemberDecorate %_struct_3 0 Offset 0
+               OpMemberDecorate %_struct_3 1 Offset 4
+       %void = OpTypeVoid
+          %5 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+       %bool = OpTypeBool
+  %_struct_3 = OpTypeStruct %int %int
+%_ptr_Function__struct_3 = OpTypePointer Function %_struct_3
+%_ptr_Function_int = OpTypePointer Function %int
+       %true = OpConstantTrue %bool
+      %int_0 = OpConstant %int 0
+      %int_1 = OpConstant %int 1
+         %13 = OpConstantNull %_struct_3
+          %2 = OpFunction %void None %5
+         %14 = OpLabel
+         %15 = OpVariable %_ptr_Function_int Function
+         %16 = OpVariable %_ptr_Function_int Function
+         %17 = OpVariable %_ptr_Function_int Function
+               OpStore %15 %int_1
+               OpStore %17 %int_0
+               OpSelectionMerge %18 None
+               OpBranchConditional %true %19 %20
+         %19 = OpLabel
+               OpBranch %18
+         %20 = OpLabel
+               OpBranch %18
+         %18 = OpLabel
+         %21 = OpPhi %_ptr_Function_int %15 %19 %16 %20
+               OpStore %21 %int_0
+         %22 = OpLoad %int %15
+         %23 = OpLoad %int %17
+         %24 = OpIAdd %int %22 %23
+               OpReturn
+               OpFunctionEnd
+  )";
+  SinglePassRunAndMatch<LocalSingleStoreElimPass>(text, false);
+}
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    Other types
diff --git a/test/opt/local_ssa_elim_test.cpp b/test/opt/local_ssa_elim_test.cpp
index 3bb1d53..d2da1f9 100644
--- a/test/opt/local_ssa_elim_test.cpp
+++ b/test/opt/local_ssa_elim_test.cpp
@@ -1759,6 +1759,177 @@
   SinglePassRunAndMatch<SSARewritePass>(spv_asm, true);
 }
 
+// Test that the RelaxedPrecision decoration on the variable to added to the
+// result of the OpPhi instruction.
+TEST_F(LocalSSAElimTest, MultipleEdges) {
+  const std::string spv_asm = R"(
+  ; CHECK: OpSelectionMerge
+  ; CHECK: [[header_bb:%\w+]] = OpLabel
+  ; CHECK-NOT: OpLabel
+  ; CHECK: OpSwitch {{%\w+}} {{%\w+}} 76 [[bb1:%\w+]] 17 [[bb2:%\w+]]
+  ; CHECK-SAME: 4 [[bb2]]
+  ; CHECK: [[bb2]] = OpLabel
+  ; CHECK-NEXT: OpPhi [[type:%\w+]] [[val:%\w+]] [[header_bb]] %int_0 [[bb1]]
+          OpCapability Shader
+     %1 = OpExtInstImport "GLSL.std.450"
+          OpMemoryModel Logical GLSL450
+          OpEntryPoint Fragment %4 "main"
+          OpExecutionMode %4 OriginUpperLeft
+          OpSource ESSL 310
+  %void = OpTypeVoid
+     %3 = OpTypeFunction %void
+   %int = OpTypeInt 32 1
+  %_ptr_Function_int = OpTypePointer Function %int
+  %int_0 = OpConstant %int 0
+  %bool = OpTypeBool
+  %true = OpConstantTrue %bool
+  %false = OpConstantFalse %bool
+  %int_1 = OpConstant %int 1
+     %4 = OpFunction %void None %3
+     %5 = OpLabel
+     %8 = OpVariable %_ptr_Function_int Function
+          OpBranch %10
+    %10 = OpLabel
+          OpLoopMerge %12 %13 None
+          OpBranch %14
+    %14 = OpLabel
+          OpBranchConditional %true %11 %12
+    %11 = OpLabel
+          OpSelectionMerge %19 None
+          OpBranchConditional %false %18 %19
+    %18 = OpLabel
+          OpSelectionMerge %22 None
+          OpSwitch %int_0 %22 76 %20 17 %21 4 %21
+    %20 = OpLabel
+    %23 = OpLoad %int %8
+          OpStore %8 %int_0
+          OpBranch %21
+    %21 = OpLabel
+          OpBranch %22
+    %22 = OpLabel
+          OpBranch %19
+    %19 = OpLabel
+          OpBranch %13
+    %13 = OpLabel
+          OpBranch %10
+    %12 = OpLabel
+          OpReturn
+          OpFunctionEnd
+  )";
+
+  SinglePassRunAndMatch<SSARewritePass>(spv_asm, true);
+}
+
+TEST_F(LocalSSAElimTest, VariablePointerTest1) {
+  // Check that the load of the first variable is still used and that the load
+  // of the third variable is propagated.  The first load has to remain because
+  // of the store to the variable pointer.
+  const std::string text = R"(
+; CHECK: [[v1:%\w+]] = OpVariable
+; CHECK: [[v2:%\w+]] = OpVariable
+; CHECK: [[v3:%\w+]] = OpVariable
+; CHECK: [[ld1:%\w+]] = OpLoad %int [[v1]]
+; CHECK: OpIAdd %int [[ld1]] %int_0
+               OpCapability Shader
+               OpCapability VariablePointers
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %2 "main"
+               OpExecutionMode %2 LocalSize 1 1 1
+               OpSource GLSL 450
+               OpMemberDecorate %_struct_3 0 Offset 0
+               OpMemberDecorate %_struct_3 1 Offset 4
+       %void = OpTypeVoid
+          %5 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+       %bool = OpTypeBool
+  %_struct_3 = OpTypeStruct %int %int
+%_ptr_Function__struct_3 = OpTypePointer Function %_struct_3
+%_ptr_Function_int = OpTypePointer Function %int
+       %true = OpConstantTrue %bool
+      %int_0 = OpConstant %int 0
+      %int_1 = OpConstant %int 1
+         %13 = OpConstantNull %_struct_3
+          %2 = OpFunction %void None %5
+         %14 = OpLabel
+         %15 = OpVariable %_ptr_Function_int Function
+         %16 = OpVariable %_ptr_Function_int Function
+         %17 = OpVariable %_ptr_Function_int Function
+               OpStore %15 %int_1
+               OpStore %17 %int_0
+               OpSelectionMerge %18 None
+               OpBranchConditional %true %19 %20
+         %19 = OpLabel
+               OpBranch %18
+         %20 = OpLabel
+               OpBranch %18
+         %18 = OpLabel
+         %21 = OpPhi %_ptr_Function_int %15 %19 %16 %20
+               OpStore %21 %int_0
+         %22 = OpLoad %int %15
+         %23 = OpLoad %int %17
+         %24 = OpIAdd %int %22 %23
+               OpReturn
+               OpFunctionEnd
+  )";
+  SinglePassRunAndMatch<SSARewritePass>(text, false);
+}
+
+TEST_F(LocalSSAElimTest, VariablePointerTest2) {
+  // Check that the load of the first variable is still used and that the load
+  // of the third variable is propagated.  The first load has to remain because
+  // of the store to the variable pointer.
+  const std::string text = R"(
+; CHECK: [[v1:%\w+]] = OpVariable
+; CHECK: [[v2:%\w+]] = OpVariable
+; CHECK: [[v3:%\w+]] = OpVariable
+; CHECK: [[ld1:%\w+]] = OpLoad %int [[v1]]
+; CHECK: OpIAdd %int [[ld1]] %int_0
+               OpCapability Shader
+               OpCapability VariablePointers
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %2 "main"
+               OpExecutionMode %2 LocalSize 1 1 1
+               OpSource GLSL 450
+               OpMemberDecorate %_struct_3 0 Offset 0
+               OpMemberDecorate %_struct_3 1 Offset 4
+       %void = OpTypeVoid
+          %5 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+       %bool = OpTypeBool
+  %_struct_3 = OpTypeStruct %int %int
+%_ptr_Function__struct_3 = OpTypePointer Function %_struct_3
+%_ptr_Function_int = OpTypePointer Function %int
+       %true = OpConstantTrue %bool
+      %int_0 = OpConstant %int 0
+      %int_1 = OpConstant %int 1
+         %13 = OpConstantNull %_struct_3
+          %2 = OpFunction %void None %5
+         %14 = OpLabel
+         %15 = OpVariable %_ptr_Function_int Function
+         %16 = OpVariable %_ptr_Function_int Function
+         %17 = OpVariable %_ptr_Function_int Function
+               OpStore %15 %int_1
+               OpStore %17 %int_0
+               OpSelectionMerge %18 None
+               OpBranchConditional %true %19 %20
+         %19 = OpLabel
+               OpBranch %18
+         %20 = OpLabel
+               OpBranch %18
+         %18 = OpLabel
+         %21 = OpPhi %_ptr_Function_int %15 %19 %16 %20
+               OpStore %21 %int_0
+         %22 = OpLoad %int %15
+         %23 = OpLoad %int %17
+         %24 = OpIAdd %int %22 %23
+               OpReturn
+               OpFunctionEnd
+  )";
+  SinglePassRunAndMatch<LocalMultiStoreElimPass>(text, false);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    No optimization in the presence of
diff --git a/test/opt/loop_optimizations/hoist_single_nested_loops.cpp b/test/opt/loop_optimizations/hoist_single_nested_loops.cpp
index 7fa1fb0..056f3f0 100644
--- a/test/opt/loop_optimizations/hoist_single_nested_loops.cpp
+++ b/test/opt/loop_optimizations/hoist_single_nested_loops.cpp
@@ -158,6 +158,52 @@
   SinglePassRunAndCheck<LICMPass>(before_hoist, after_hoist, true);
 }
 
+TEST_F(PassClassTest, PreHeaderIsAlsoHeader) {
+  // Move OpSLessThan out of the inner loop.  The preheader for the inner loop
+  // is the header of the outer loop.  The loop merge should not be separated
+  // from the branch in that block.
+  const std::string text = R"(
+  ; CHECK: OpFunction
+  ; CHECK-NEXT: OpLabel
+  ; CHECK-NEXT: OpBranch [[header:%\w+]]
+  ; CHECK: [[header]] = OpLabel
+  ; CHECK-NEXT: OpSLessThan %bool %int_1 %int_1
+  ; CHECK-NEXT: OpLoopMerge
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+       %void = OpTypeVoid
+          %4 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+      %int_1 = OpConstant %int 1
+       %bool = OpTypeBool
+          %2 = OpFunction %void None %4
+         %18 = OpLabel
+               OpBranch %21
+         %21 = OpLabel
+               OpLoopMerge %22 %23 None
+               OpBranch %24
+         %24 = OpLabel
+         %25 = OpSLessThan %bool %int_1 %int_1
+               OpLoopMerge %26 %27 None
+               OpBranchConditional %25 %27 %26
+         %27 = OpLabel
+               OpBranch %24
+         %26 = OpLabel
+               OpBranch %22
+         %23 = OpLabel
+               OpBranch %21
+         %22 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  SinglePassRunAndMatch<LICMPass>(text, true);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/loop_optimizations/unswitch.cpp b/test/opt/loop_optimizations/unswitch.cpp
index 387178a..dc7073f 100644
--- a/test/opt/loop_optimizations/unswitch.cpp
+++ b/test/opt/loop_optimizations/unswitch.cpp
@@ -57,13 +57,18 @@
 ; CHECK-NEXT: [[phi_j:%\w+]] = OpPhi %int %int_0 [[loop_f]] [[iv_j:%\w+]] [[continue]]
 ; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
 ; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}}
-; CHECK-NEXT: OpBranchConditional [[loop_exit]] {{%\w+}} [[merge]]
-; Check that we have i+=1 and j+=2.
-; CHECK: [[phi_j:%\w+]] = OpIAdd %int [[phi_j]] %int_1
-; CHECK: [[iv_i]] = OpIAdd %int [[phi_i]] %int_1
-; CHECK: [[iv_j]] = OpIAdd %int [[phi_j]] %int_1
-; CHECK: [[merge]] = OpLabel
-; CHECK-NEXT: OpBranch [[if_merge]]
+; CHECK-NEXT: OpBranchConditional [[loop_exit]] [[loop_body:%\w+]] [[merge]]
+; [[loop_body]] = OpLabel
+; CHECK: OpSelectionMerge [[sel_merge:%\w+]] None
+; CHECK: OpBranchConditional %false [[bb1:%\w+]] [[bb2:%\w+]]
+; CHECK: [[bb2]] = OpLabel
+; CHECK-NEXT: [[inc_j:%\w+]] = OpIAdd %int [[phi_j]] %int_1
+; CHECK-NEXT: OpBranch [[sel_merge]]
+; CHECK: [[bb1]] = OpLabel
+; CHECK-NEXT: [[inc_i:%\w+]] = OpIAdd %int [[phi_i]] %int_1
+; CHECK-NEXT: OpBranch [[sel_merge]]
+; CHECK: [[sel_merge]] = OpLabel
+; CHECK: OpBranch [[if_merge]]
 
 ; Loop specialized for true.
 ; CHECK: [[loop_t]] = OpLabel
@@ -73,13 +78,18 @@
 ; CHECK-NEXT: [[phi_j:%\w+]] = OpPhi %int %int_0 [[loop_t]] [[iv_j:%\w+]] [[continue]]
 ; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
 ; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}}
-; CHECK-NEXT: OpBranchConditional [[loop_exit]] {{%\w+}} [[merge]]
-; Check that we have i+=2 and j+=1.
-; CHECK: [[phi_i:%\w+]] = OpIAdd %int [[phi_i]] %int_1
-; CHECK: [[iv_i]] = OpIAdd %int [[phi_i]] %int_1
-; CHECK: [[iv_j]] = OpIAdd %int [[phi_j]] %int_1
-; CHECK: [[merge]] = OpLabel
-; CHECK-NEXT: OpBranch [[if_merge]]
+; CHECK-NEXT: OpBranchConditional [[loop_exit]] [[loop_body:%\w+]] [[merge]]
+; [[loop_body]] = OpLabel
+; CHECK: OpSelectionMerge [[sel_merge:%\w+]] None
+; CHECK: OpBranchConditional %true [[bb1:%\w+]] [[bb2:%\w+]]
+; CHECK: [[bb1]] = OpLabel
+; CHECK-NEXT: [[inc_i:%\w+]] = OpIAdd %int [[phi_i]] %int_1
+; CHECK-NEXT: OpBranch [[sel_merge]]
+; CHECK: [[bb2]] = OpLabel
+; CHECK-NEXT: [[inc_j:%\w+]] = OpIAdd %int [[phi_j]] %int_1
+; CHECK-NEXT: OpBranch [[sel_merge]]
+; CHECK: [[sel_merge]] = OpLabel
+; CHECK: OpBranch [[if_merge]]
 
 ; CHECK: [[if_merge]] = OpLabel
 ; CHECK-NEXT: OpReturn
@@ -287,10 +297,10 @@
 ; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[loop_f]] [[iv_i:%\w+]] [[continue:%\w+]]
 ; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
 ; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}}
-; CHECK-NEXT: OpBranchConditional [[loop_exit]] {{%\w+}} [[merge]]
-; Check that we have i+=2.
-; CHECK: [[phi_i:%\w+]] = OpIAdd %int [[phi_i]] %int_1
-; CHECK: [[iv_i]] = OpIAdd %int [[phi_i]] %int_1
+; CHECK-NEXT: OpBranchConditional [[loop_exit]] [[loop_body:%\w+]] [[merge]]
+; CHECK: [[loop_body:%\w+]] = OpLabel
+; CHECK-NEXT: OpSelectionMerge
+; CHECK-NEXT: OpBranchConditional %false
 ; CHECK: [[merge]] = OpLabel
 ; CHECK-NEXT: OpBranch [[if_merge]]
 
@@ -301,9 +311,10 @@
 ; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[loop_t]] [[iv_i:%\w+]] [[continue:%\w+]]
 ; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
 ; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}}
-; CHECK-NEXT: OpBranchConditional [[loop_exit]] {{%\w+}} [[merge]]
-; Check that we have i+=1.
-; CHECK: [[iv_i]] = OpIAdd %int [[phi_i]] %int_1
+; CHECK-NEXT: OpBranchConditional [[loop_exit]] [[loop_body:%\w+]] [[merge]]
+; CHECK: [[loop_body:%\w+]] = OpLabel
+; CHECK-NEXT: OpSelectionMerge
+; CHECK-NEXT: OpBranchConditional %true
 ; CHECK: [[merge]] = OpLabel
 ; CHECK-NEXT: OpBranch [[if_merge]]
 
@@ -514,18 +525,40 @@
 ; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[loop_2]] [[iv_i:%\w+]] [[continue:%\w+]]
 ; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
 ; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}}
-; CHECK-NEXT: OpBranchConditional [[loop_exit]] {{%\w+}} [[merge]]
-; Check that we have i+=1.
-; CHECK: [[iv_i]] = OpIAdd %int [[phi_i]] %int_1
-; CHECK: OpBranch [[loop]]
+; CHECK-NEXT: OpBranchConditional [[loop_exit]] [[loop_body:%\w+]] [[merge]]
+; CHECK: [[loop_body]] = OpLabel
+; CHECK-NEXT: OpSelectionMerge
+; CHECK-NEXT: OpSwitch %int_2
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: OpBranch [[if_merge]]
 
 ; Loop specialized for 1.
 ; CHECK: [[loop_1]] = OpLabel
-; CHECK: OpKill
+; CHECK-NEXT: OpBranch [[loop:%\w+]]
+; CHECK: [[loop]] = OpLabel
+; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[loop_1]] [[iv_i:%\w+]] [[continue:%\w+]]
+; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
+; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}}
+; CHECK-NEXT: OpBranchConditional [[loop_exit]] [[loop_body:%\w+]] [[merge]]
+; CHECK: [[loop_body]] = OpLabel
+; CHECK-NEXT: OpSelectionMerge
+; CHECK-NEXT: OpSwitch %int_1
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: OpBranch [[if_merge]]
 
 ; Loop specialized for 0.
 ; CHECK: [[loop_0]] = OpLabel
-; CHECK: OpReturn
+; CHECK-NEXT: OpBranch [[loop:%\w+]]
+; CHECK: [[loop]] = OpLabel
+; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[loop_0]] [[iv_i:%\w+]] [[continue:%\w+]]
+; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
+; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}}
+; CHECK-NEXT: OpBranchConditional [[loop_exit]] [[loop_body:%\w+]] [[merge]]
+; CHECK: [[loop_body]] = OpLabel
+; CHECK-NEXT: OpSelectionMerge
+; CHECK-NEXT: OpSwitch %int_0
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: OpBranch [[if_merge]]
 
 ; Loop specialized for the default case.
 ; CHECK: [[default]] = OpLabel
@@ -534,10 +567,12 @@
 ; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[default]] [[iv_i:%\w+]] [[continue:%\w+]]
 ; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
 ; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}}
-; CHECK-NEXT: OpBranchConditional [[loop_exit]] {{%\w+}} [[merge]]
-; Check that we have i+=1.
-; CHECK: [[phi_i:%\w+]] = OpIAdd %int [[phi_i]] %int_1
-; CHECK: OpBranch [[loop]]
+; CHECK-NEXT: OpBranchConditional [[loop_exit]] [[loop_body:%\w+]] [[merge]]
+; CHECK: [[loop_body]] = OpLabel
+; CHECK-NEXT: OpSelectionMerge
+; CHECK-NEXT: OpSwitch %uint_3
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: OpBranch [[if_merge]]
 
 ; CHECK: [[if_merge]] = OpLabel
 ; CHECK-NEXT: OpReturn
@@ -623,90 +658,51 @@
       }
     }
   }
-  for (; k < 10; k++) {
-    if (cond) {
-      k++;
-    }
-  }
 }
 */
 TEST_F(UnswitchTest, UnSwitchNested) {
+  // Test that an branch can be unswitched out of two nested loops.
   const std::string text = R"(
 ; CHECK: [[cst_cond:%\w+]] = OpFOrdEqual
 ; CHECK-NEXT: OpSelectionMerge [[if_merge:%\w+]] None
 ; CHECK-NEXT: OpBranchConditional [[cst_cond]] [[loop_t:%\w+]] [[loop_f:%\w+]]
 
-; Loop specialized for false, one loop is killed, j won't change anymore.
+; Loop specialized for false
 ; CHECK: [[loop_f]] = OpLabel
 ; CHECK-NEXT: OpBranch [[loop:%\w+]]
 ; CHECK: [[loop]] = OpLabel
-; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[loop_f]] [[iv_i:%\w+]] [[continue:%\w+]]
-; CHECK-NEXT: [[phi_j:%\w+]] = OpPhi %int %int_0 [[loop_f]] [[iv_j:%\w+]] [[continue]]
+; CHECK-NEXT: {{%\w+}} = OpPhi %int %int_0 [[loop_f]] {{%\w+}} [[continue:%\w+]]
+; CHECK-NEXT: {{%\w+}} = OpPhi %int %int_0 [[loop_f]] {{%\w+}} [[continue]]
 ; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
-; CHECK: [[iv_i]] = OpIAdd %int [[phi_i]] %int_1
-; CHECK-NEXT: OpBranch [[loop]]
-; CHECK: OpReturn
+; CHECK-NOT: [[merge]] = OpLabel
+; CHECK: OpLoopMerge
+; CHECK-NEXT: OpBranch [[bb1:%\w+]]
+; CHECK: [[bb1]] = OpLabel
+; CHECK-NEXT: OpSLessThan
+; CHECK-NEXT: OpBranchConditional {{%\w+}} [[bb2:%\w+]]
+; CHECK: [[bb2]] = OpLabel
+; CHECK-NEXT: OpSelectionMerge
+; CHECK-NEXT: OpBranchConditional %false
+; CHECK: [[merge]] = OpLabel
 
-; Loop specialized for true.
+; Loop specialized for true.  Same as first loop except the branch condition is true.
 ; CHECK: [[loop_t]] = OpLabel
 ; CHECK-NEXT: OpBranch [[loop:%\w+]]
 ; CHECK: [[loop]] = OpLabel
-; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[loop_t]] [[iv_i:%\w+]] [[continue:%\w+]]
-; CHECK-NEXT: [[phi_j:%\w+]] = OpPhi %int %int_0 [[loop_t]] [[iv_j:%\w+]] [[continue]]
+; CHECK-NEXT: {{%\w+}} = OpPhi %int %int_0 [[loop_t]] {{%\w+}} [[continue:%\w+]]
+; CHECK-NEXT: {{%\w+}} = OpPhi %int %int_0 [[loop_t]] {{%\w+}} [[continue]]
 ; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
-; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}}
-; CHECK-NEXT: OpBranchConditional [[loop_exit]] [[pre_loop_inner:%\w+]] [[merge]]
-
-; CHECK: [[pre_loop_inner]] = OpLabel
-; CHECK-NEXT: OpBranch [[loop_inner:%\w+]]
-; CHECK-NEXT: [[loop_inner]] = OpLabel
-; CHECK-NEXT: [[phi2_i:%\w+]] = OpPhi %int [[phi_i]] [[pre_loop_inner]] [[iv2_i:%\w+]] [[continue2:%\w+]]
-; CHECK-NEXT: [[phi2_j:%\w+]] = OpPhi %int [[phi_j]] [[pre_loop_inner]] [[iv2_j:%\w+]] [[continue2]]
-; CHECK-NEXT: OpLoopMerge [[merge2:%\w+]] [[continue2]] None
-
-; CHECK: OpBranch [[continue2]]
-; CHECK: [[merge2]] = OpLabel
-; CHECK: OpBranch [[continue]]
+; CHECK-NOT: [[merge]] = OpLabel
+; CHECK: OpLoopMerge
+; CHECK-NEXT: OpBranch [[bb1:%\w+]]
+; CHECK: [[bb1]] = OpLabel
+; CHECK-NEXT: OpSLessThan
+; CHECK-NEXT: OpBranchConditional {{%\w+}} [[bb2:%\w+]]
+; CHECK: [[bb2]] = OpLabel
+; CHECK-NEXT: OpSelectionMerge
+; CHECK-NEXT: OpBranchConditional %true
 ; CHECK: [[merge]] = OpLabel
 
-; Unswitched double nested loop is done. Test the single remaining one.
-
-; CHECK: [[if_merge]] = OpLabel
-; CHECK-NEXT: OpSelectionMerge [[if_merge:%\w+]] None
-; CHECK-NEXT: OpBranchConditional [[cst_cond]] [[loop_t:%\w+]] [[loop_f:%\w+]]
-
-; Loop specialized for false.
-; CHECK: [[loop_f]] = OpLabel
-; CHECK-NEXT: OpBranch [[loop:%\w+]]
-; CHECK: [[loop]] = OpLabel
-; CHECK-NEXT: [[phi_k:%\w+]] = OpPhi %int %int_0 [[loop_f]] [[iv_k:%\w+]] [[continue:%\w+]]
-; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
-; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_k]] {{%\w+}}
-; CHECK-NEXT: OpBranchConditional [[loop_exit]] {{%\w+}} [[merge]]
-; Check that we have k+=1
-; CHECK: [[iv_k]] = OpIAdd %int [[phi_k]] %int_1
-; CHECK: OpBranch [[loop]]
-; CHECK: [[merge]] = OpLabel
-; CHECK-NEXT: OpBranch [[if_merge]]
-
-; Loop specialized for true.
-; CHECK: [[loop_t]] = OpLabel
-; CHECK-NEXT: OpBranch [[loop:%\w+]]
-; CHECK: [[loop]] = OpLabel
-; CHECK-NEXT: [[phi_k:%\w+]] = OpPhi %int %int_0 [[loop_t]] [[iv_k:%\w+]] [[continue:%\w+]]
-; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
-; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_k]] {{%\w+}}
-; CHECK-NEXT: OpBranchConditional [[loop_exit]] {{%\w+}} [[merge]]
-; Check that we have k+=2.
-; CHECK: [[tmp_k:%\w+]] = OpIAdd %int [[phi_k]] %int_1
-; CHECK: [[iv_k]] = OpIAdd %int [[tmp_k]] %int_1
-; CHECK: OpBranch [[loop]]
-; CHECK: [[merge]] = OpLabel
-; CHECK-NEXT: OpBranch [[if_merge]]
-
-; CHECK: [[if_merge]] = OpLabel
-; CHECK-NEXT: OpReturn
-
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
@@ -777,27 +773,6 @@
          %52 = OpIAdd %int %69 %int_1
                OpBranch %26
          %28 = OpLabel
-               OpBranch %53
-         %53 = OpLabel
-         %71 = OpPhi %int %int_0 %28 %66 %56
-               OpLoopMerge %55 %56 None
-               OpBranch %57
-         %57 = OpLabel
-         %59 = OpSLessThan %bool %71 %int_10
-               OpBranchConditional %59 %54 %55
-         %54 = OpLabel
-               OpSelectionMerge %62 None
-               OpBranchConditional %25 %61 %62
-         %61 = OpLabel
-         %64 = OpIAdd %int %71 %int_1
-               OpBranch %62
-         %62 = OpLabel
-         %72 = OpPhi %int %71 %54 %64 %61
-               OpBranch %56
-         %56 = OpLabel
-         %66 = OpIAdd %int %72 %int_1
-               OpBranch %53
-         %55 = OpLabel
                OpReturn
                OpFunctionEnd
 )";
@@ -906,6 +881,87 @@
   EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
 }
 
+TEST_F(UnswitchTest, DontUnswitchLatch) {
+  // Check that the unswitch is not triggered for the latch branch.
+  const std::string text = R"(
+         OpCapability Shader
+    %1 = OpExtInstImport "GLSL.std.450"
+         OpMemoryModel Logical GLSL450
+         OpEntryPoint Fragment %4 "main"
+         OpExecutionMode %4 OriginUpperLeft
+         OpSource ESSL 310
+ %void = OpTypeVoid
+    %3 = OpTypeFunction %void
+ %bool = OpTypeBool
+%false = OpConstantFalse %bool
+    %4 = OpFunction %void None %3
+    %5 = OpLabel
+         OpBranch %6
+    %6 = OpLabel
+         OpLoopMerge %8 %9 None
+         OpBranch %7
+    %7 = OpLabel
+         OpBranch %9
+    %9 = OpLabel
+         OpBranchConditional %false %6 %8
+    %8 = OpLabel
+         OpReturn
+         OpFunctionEnd
+  )";
+
+  auto result =
+      SinglePassRunAndDisassemble<LoopUnswitchPass>(text, true, false);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(UnswitchTest, DontUnswitchConstantCondition) {
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main"
+               OpExecutionMode %main OriginLowerLeft
+               OpSource GLSL 450
+               OpName %main "main"
+       %void = OpTypeVoid
+          %4 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+      %int_1 = OpConstant %int 1
+       %main = OpFunction %void None %4
+         %10 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+         %12 = OpPhi %int %int_0 %10 %13 %14
+               OpLoopMerge %15 %14 None
+               OpBranch %16
+         %16 = OpLabel
+         %17 = OpSLessThan %bool %12 %int_1
+               OpBranchConditional %17 %18 %15
+         %18 = OpLabel
+               OpSelectionMerge %19 None
+               OpBranchConditional %true %20 %19
+         %20 = OpLabel
+         %21 = OpIAdd %int %12 %int_1
+               OpBranch %19
+         %19 = OpLabel
+         %22 = OpPhi %int %21 %20 %12 %18
+               OpBranch %14
+         %14 = OpLabel
+         %13 = OpIAdd %int %22 %int_1
+               OpBranch %11
+         %15 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  auto result =
+      SinglePassRunAndDisassemble<LoopUnswitchPass>(text, true, false);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/optimizer_test.cpp b/test/opt/optimizer_test.cpp
index 90abc00..549335d 100644
--- a/test/opt/optimizer_test.cpp
+++ b/test/opt/optimizer_test.cpp
@@ -222,6 +222,405 @@
   EXPECT_EQ(msg_level, SPV_MSG_ERROR);
 }
 
+TEST(Optimizer, VulkanToWebGPUSetsCorrectPasses) {
+  Optimizer opt(SPV_ENV_WEBGPU_0);
+  opt.RegisterVulkanToWebGPUPasses();
+  std::vector<const char*> pass_names = opt.GetPassNames();
+
+  std::vector<std::string> registered_passes;
+  for (auto name = pass_names.begin(); name != pass_names.end(); ++name)
+    registered_passes.push_back(*name);
+
+  std::vector<std::string> expected_passes = {"eliminate-dead-branches",
+                                              "eliminate-dead-code-aggressive",
+                                              "eliminate-dead-const",
+                                              "flatten-decorations",
+                                              "strip-debug",
+                                              "strip-atomic-counter-memory",
+                                              "generate-webgpu-initializers",
+                                              "legalize-vector-shuffle"};
+  std::sort(registered_passes.begin(), registered_passes.end());
+  std::sort(expected_passes.begin(), expected_passes.end());
+
+  ASSERT_EQ(registered_passes.size(), expected_passes.size());
+  for (size_t i = 0; i < registered_passes.size(); i++)
+    EXPECT_EQ(registered_passes[i], expected_passes[i]);
+}
+
+struct VulkanToWebGPUPassCase {
+  // Input SPIR-V
+  std::string input;
+  // Expected result SPIR-V
+  std::string expected;
+  // Specific pass under test, used for logging messages.
+  std::string pass;
+};
+
+using VulkanToWebGPUPassTest =
+    PassTest<::testing::TestWithParam<VulkanToWebGPUPassCase>>;
+
+TEST_P(VulkanToWebGPUPassTest, Ran) {
+  std::vector<uint32_t> binary;
+  {
+    SpirvTools tools(SPV_ENV_VULKAN_1_1);
+    tools.Assemble(GetParam().input, &binary);
+  }
+
+  Optimizer opt(SPV_ENV_WEBGPU_0);
+  opt.RegisterVulkanToWebGPUPasses();
+
+  std::vector<uint32_t> optimized;
+  class ValidatorOptions validator_options;
+  ASSERT_TRUE(opt.Run(binary.data(), binary.size(), &optimized,
+                      validator_options, true));
+  std::string disassembly;
+  {
+    SpirvTools tools(SPV_ENV_WEBGPU_0);
+    tools.Disassemble(optimized.data(), optimized.size(), &disassembly);
+  }
+
+  EXPECT_EQ(GetParam().expected, disassembly)
+      << "Was expecting pass '" << GetParam().pass << "' to have been run.\n";
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    Optimizer, VulkanToWebGPUPassTest,
+    ::testing::ValuesIn(std::vector<VulkanToWebGPUPassCase>{
+        // FlattenDecorations
+        {// input
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Fragment %main \"main\" %hue %saturation %value\n"
+         "OpExecutionMode %main OriginUpperLeft\n"
+         "OpDecorate %group Flat\n"
+         "OpDecorate %group NoPerspective\n"
+         "%group = OpDecorationGroup\n"
+         "%void = OpTypeVoid\n"
+         "%void_fn = OpTypeFunction %void\n"
+         "%float = OpTypeFloat 32\n"
+         "%_ptr_Input_float = OpTypePointer Input %float\n"
+         "%hue = OpVariable %_ptr_Input_float Input\n"
+         "%saturation = OpVariable %_ptr_Input_float Input\n"
+         "%value = OpVariable %_ptr_Input_float Input\n"
+         "%main = OpFunction %void None %void_fn\n"
+         "%entry = OpLabel\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // expected
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Fragment %1 \"main\" %2 %3 %4\n"
+         "OpExecutionMode %1 OriginUpperLeft\n"
+         "%void = OpTypeVoid\n"
+         "%7 = OpTypeFunction %void\n"
+         "%float = OpTypeFloat 32\n"
+         "%_ptr_Input_float = OpTypePointer Input %float\n"
+         "%2 = OpVariable %_ptr_Input_float Input\n"
+         "%3 = OpVariable %_ptr_Input_float Input\n"
+         "%4 = OpVariable %_ptr_Input_float Input\n"
+         "%1 = OpFunction %void None %7\n"
+         "%10 = OpLabel\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // pass
+         "flatten-decorations"},
+        // Strip Debug
+        {// input
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %func \"shader\"\n"
+         "OpName %main \"main\"\n"
+         "OpName %void_fn \"void_fn\"\n"
+         "%void = OpTypeVoid\n"
+         "%void_f = OpTypeFunction %void\n"
+         "%func = OpFunction %void None %void_f\n"
+         "%label = OpLabel\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // expected
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %1 \"shader\"\n"
+         "%void = OpTypeVoid\n"
+         "%5 = OpTypeFunction %void\n"
+         "%1 = OpFunction %void None %5\n"
+         "%6 = OpLabel\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // pass
+         "strip-debug"},
+        // Eliminate Dead Constants
+        {// input
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %func \"shader\"\n"
+         "%u32 = OpTypeInt 32 0\n"
+         "%u32_ptr = OpTypePointer Workgroup %u32\n"
+         "%u32_var = OpVariable %u32_ptr Workgroup\n"
+         "%u32_1 = OpConstant %u32 1\n"
+         "%cross_device = OpConstant %u32 0\n"
+         "%relaxed = OpConstant %u32 0\n"
+         "%acquire_release_atomic_counter_workgroup = OpConstant %u32 1288\n"
+         "%void = OpTypeVoid\n"
+         "%void_f = OpTypeFunction %void\n"
+         "%func = OpFunction %void None %void_f\n"
+         "%label = OpLabel\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // expected
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %1 \"shader\"\n"
+         "%uint = OpTypeInt 32 0\n"
+         "%_ptr_Workgroup_uint = OpTypePointer Workgroup %uint\n"
+         "%4 = OpVariable %_ptr_Workgroup_uint Workgroup\n"
+         "%void = OpTypeVoid\n"
+         "%10 = OpTypeFunction %void\n"
+         "%1 = OpFunction %void None %10\n"
+         "%11 = OpLabel\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         "eliminate-dead-const"},
+        // Strip Atomic Counter Memory
+        {// input
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %func \"shader\"\n"
+         "%u32 = OpTypeInt 32 0\n"
+         "%u32_ptr = OpTypePointer Workgroup %u32\n"
+         "%u32_var = OpVariable %u32_ptr Workgroup\n"
+         "%u32_0 = OpConstant %u32 0\n"
+         "%u32_1 = OpConstant %u32 1\n"
+         "%cross_device = OpConstant %u32 0\n"
+         "%acquire_release_atomic_counter_workgroup = OpConstant %u32 1288\n"
+         "%void = OpTypeVoid\n"
+         "%void_f = OpTypeFunction %void\n"
+         "%func = OpFunction %void None %void_f\n"
+         "%label = OpLabel\n"
+         "%val0 = OpAtomicStore %u32_var %cross_device "
+         "%acquire_release_atomic_counter_workgroup %u32_1\n"
+         "%val1 = OpAtomicIIncrement %u32 %u32_var %cross_device "
+         "%acquire_release_atomic_counter_workgroup\n"
+         "%val2 = OpAtomicCompareExchange %u32 %u32_var %cross_device "
+         "%acquire_release_atomic_counter_workgroup "
+         "%acquire_release_atomic_counter_workgroup %u32_0 %u32_0\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // expected
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %1 \"shader\"\n"
+         "%uint = OpTypeInt 32 0\n"
+         "%_ptr_Workgroup_uint = OpTypePointer Workgroup %uint\n"
+         "%4 = OpVariable %_ptr_Workgroup_uint Workgroup\n"
+         "%uint_0 = OpConstant %uint 0\n"
+         "%uint_1 = OpConstant %uint 1\n"
+         "%uint_0_0 = OpConstant %uint 0\n"
+         "%void = OpTypeVoid\n"
+         "%10 = OpTypeFunction %void\n"
+         "%uint_264 = OpConstant %uint 264\n"
+         "%1 = OpFunction %void None %10\n"
+         "%11 = OpLabel\n"
+         "OpAtomicStore %4 %uint_0_0 %uint_264 %uint_1\n"
+         "%12 = OpAtomicIIncrement %uint %4 %uint_0_0 %uint_264\n"
+         "%13 = OpAtomicCompareExchange %uint %4 %uint_0_0 %uint_264 %uint_264 "
+         "%uint_0 %uint_0\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // pass
+         "strip-atomic-counter-memory"},
+        // Generate WebGPU Initializers
+        {// input
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %func \"shader\"\n"
+         "%u32 = OpTypeInt 32 0\n"
+         "%u32_ptr = OpTypePointer Private %u32\n"
+         "%u32_var = OpVariable %u32_ptr Private\n"
+         "%u32_0 = OpConstant %u32 0\n"
+         "%void = OpTypeVoid\n"
+         "%void_f = OpTypeFunction %void\n"
+         "%func = OpFunction %void None %void_f\n"
+         "%label = OpLabel\n"
+         "OpStore %u32_var %u32_0\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // expected
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %1 \"shader\"\n"
+         "%uint = OpTypeInt 32 0\n"
+         "%_ptr_Private_uint = OpTypePointer Private %uint\n"
+         "%9 = OpConstantNull %uint\n"
+         "%4 = OpVariable %_ptr_Private_uint Private %9\n"
+         "%uint_0 = OpConstant %uint 0\n"
+         "%void = OpTypeVoid\n"
+         "%7 = OpTypeFunction %void\n"
+         "%1 = OpFunction %void None %7\n"
+         "%8 = OpLabel\n"
+         "OpStore %4 %uint_0\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // pass
+         "generate-webgpu-initializers"},
+        // Legalize Vector Shuffle
+        {// input
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %1 \"shader\"\n"
+         "%uint = OpTypeInt 32 0\n"
+         "%v3uint = OpTypeVector %uint 3\n"
+         "%_ptr_Function_v3uint = OpTypePointer Function %v3uint\n"
+         "%void = OpTypeVoid\n"
+         "%6 = OpTypeFunction %void\n"
+         "%1 = OpFunction %void None %6\n"
+         "%7 = OpLabel\n"
+         "%8 = OpVariable %_ptr_Function_v3uint Function\n"
+         "%9 = OpLoad %v3uint %8\n"
+         "%10 = OpLoad %v3uint %8\n"
+         "%11 = OpVectorShuffle %v3uint %9 %10 2 1 0xFFFFFFFF\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // expected
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %1 \"shader\"\n"
+         "%uint = OpTypeInt 32 0\n"
+         "%v3uint = OpTypeVector %uint 3\n"
+         "%_ptr_Function_v3uint = OpTypePointer Function %v3uint\n"
+         "%void = OpTypeVoid\n"
+         "%6 = OpTypeFunction %void\n"
+         "%12 = OpConstantNull %v3uint\n"
+         "%1 = OpFunction %void None %6\n"
+         "%7 = OpLabel\n"
+         "%8 = OpVariable %_ptr_Function_v3uint Function %12\n"
+         "%9 = OpLoad %v3uint %8\n"
+         "%10 = OpLoad %v3uint %8\n"
+         "%11 = OpVectorShuffle %v3uint %9 %10 2 1 0\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // pass
+         "legalize-vector-shuffle"}}));
+
+TEST(Optimizer, WebGPUToVulkanSetsCorrectPasses) {
+  Optimizer opt(SPV_ENV_VULKAN_1_1);
+  opt.RegisterWebGPUToVulkanPasses();
+  std::vector<const char*> pass_names = opt.GetPassNames();
+
+  std::vector<std::string> registered_passes;
+  for (auto name = pass_names.begin(); name != pass_names.end(); ++name)
+    registered_passes.push_back(*name);
+
+  std::vector<std::string> expected_passes = {
+      "decompose-initialized-variables"};
+  std::sort(registered_passes.begin(), registered_passes.end());
+  std::sort(expected_passes.begin(), expected_passes.end());
+
+  ASSERT_EQ(registered_passes.size(), expected_passes.size());
+  for (size_t i = 0; i < registered_passes.size(); i++)
+    EXPECT_EQ(registered_passes[i], expected_passes[i]);
+}
+
+struct WebGPUToVulkanPassCase {
+  // Input SPIR-V
+  std::string input;
+  // Expected result SPIR-V
+  std::string expected;
+  // Specific pass under test, used for logging messages.
+  std::string pass;
+};
+
+using WebGPUToVulkanPassTest =
+    PassTest<::testing::TestWithParam<WebGPUToVulkanPassCase>>;
+
+TEST_P(WebGPUToVulkanPassTest, Ran) {
+  std::vector<uint32_t> binary;
+  {
+    SpirvTools tools(SPV_ENV_WEBGPU_0);
+    tools.Assemble(GetParam().input, &binary);
+  }
+
+  Optimizer opt(SPV_ENV_VULKAN_1_1);
+  opt.RegisterWebGPUToVulkanPasses();
+
+  std::vector<uint32_t> optimized;
+  class ValidatorOptions validator_options;
+  ASSERT_TRUE(opt.Run(binary.data(), binary.size(), &optimized,
+                      validator_options, true));
+  std::string disassembly;
+  {
+    SpirvTools tools(SPV_ENV_VULKAN_1_1);
+    tools.Disassemble(optimized.data(), optimized.size(), &disassembly);
+  }
+
+  EXPECT_EQ(GetParam().expected, disassembly)
+      << "Was expecting pass '" << GetParam().pass << "' to have been run.\n";
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    Optimizer, WebGPUToVulkanPassTest,
+    ::testing::ValuesIn(std::vector<WebGPUToVulkanPassCase>{
+        // Decompose Initialized Variables
+        {// input
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %1 \"shader\"\n"
+         "%uint = OpTypeInt 32 0\n"
+         "%_ptr_Function_uint = OpTypePointer Function %uint\n"
+         "%4 = OpConstantNull %uint\n"
+         "%void = OpTypeVoid\n"
+         "%6 = OpTypeFunction %void\n"
+         "%1 = OpFunction %void None %6\n"
+         "%7 = OpLabel\n"
+         "%8 = OpVariable %_ptr_Function_uint Function %4\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // expected
+         "OpCapability Shader\n"
+         "OpCapability VulkanMemoryModelKHR\n"
+         "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+         "OpMemoryModel Logical VulkanKHR\n"
+         "OpEntryPoint Vertex %1 \"shader\"\n"
+         "%uint = OpTypeInt 32 0\n"
+         "%_ptr_Function_uint = OpTypePointer Function %uint\n"
+         "%4 = OpConstantNull %uint\n"
+         "%void = OpTypeVoid\n"
+         "%6 = OpTypeFunction %void\n"
+         "%1 = OpFunction %void None %6\n"
+         "%7 = OpLabel\n"
+         "%8 = OpVariable %_ptr_Function_uint Function\n"
+         "OpStore %8 %4\n"
+         "OpReturn\n"
+         "OpFunctionEnd\n",
+         // pass
+         "decompose-initialized-variables"}}));
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/pass_fixture.h b/test/opt/pass_fixture.h
index da2e00e..10e7c53 100644
--- a/test/opt/pass_fixture.h
+++ b/test/opt/pass_fixture.h
@@ -49,18 +49,18 @@
             [](spv_message_level_t, const char*, const spv_position_t&,
                const char* message) { std::cerr << message << std::endl; }),
         context_(nullptr),
-        tools_(SPV_ENV_UNIVERSAL_1_1),
         manager_(new PassManager()),
         assemble_options_(SpirvTools::kDefaultAssembleOption),
-        disassemble_options_(SpirvTools::kDefaultDisassembleOption) {}
+        disassemble_options_(SpirvTools::kDefaultDisassembleOption),
+        env_(SPV_ENV_UNIVERSAL_1_3) {}
 
   // Runs the given |pass| on the binary assembled from the |original|.
   // Returns a tuple of the optimized binary and the boolean value returned
   // from pass Process() function.
   std::tuple<std::vector<uint32_t>, Pass::Status> OptimizeToBinary(
       Pass* pass, const std::string& original, bool skip_nop) {
-    context_ = std::move(BuildModule(SPV_ENV_UNIVERSAL_1_1, consumer_, original,
-                                     assemble_options_));
+    context_ =
+        std::move(BuildModule(env_, consumer_, original, assemble_options_));
     EXPECT_NE(nullptr, context()) << "Assembling failed for shader:\n"
                                   << original << std::endl;
     if (!context()) {
@@ -97,8 +97,7 @@
     std::tie(optimized_bin, status) = SinglePassRunToBinary<PassT>(
         assembly, skip_nop, std::forward<Args>(args)...);
     if (do_validation) {
-      spv_target_env target_env = SPV_ENV_UNIVERSAL_1_1;
-      spv_context spvContext = spvContextCreate(target_env);
+      spv_context spvContext = spvContextCreate(env_);
       spv_diagnostic diagnostic = nullptr;
       spv_const_binary_t binary = {optimized_bin.data(), optimized_bin.size()};
       spv_result_t error = spvValidateWithOptions(
@@ -109,8 +108,9 @@
       spvContextDestroy(spvContext);
     }
     std::string optimized_asm;
+    SpirvTools tools(env_);
     EXPECT_TRUE(
-        tools_.Disassemble(optimized_bin, &optimized_asm, disassemble_options_))
+        tools.Disassemble(optimized_bin, &optimized_asm, disassemble_options_))
         << "Disassembling failed for shader:\n"
         << assembly << std::endl;
     return std::make_tuple(optimized_asm, status);
@@ -134,8 +134,7 @@
     EXPECT_EQ(original == expected,
               status == Pass::Status::SuccessWithoutChange);
     if (do_validation) {
-      spv_target_env target_env = SPV_ENV_UNIVERSAL_1_1;
-      spv_context spvContext = spvContextCreate(target_env);
+      spv_context spvContext = spvContextCreate(env_);
       spv_diagnostic diagnostic = nullptr;
       spv_const_binary_t binary = {optimized_bin.data(), optimized_bin.size()};
       spv_result_t error = spvValidateWithOptions(
@@ -146,8 +145,9 @@
       spvContextDestroy(spvContext);
     }
     std::string optimized_asm;
+    SpirvTools tools(env_);
     EXPECT_TRUE(
-        tools_.Disassemble(optimized_bin, &optimized_asm, disassemble_options_))
+        tools.Disassemble(optimized_bin, &optimized_asm, disassemble_options_))
         << "Disassembling failed for shader:\n"
         << original << std::endl;
     EXPECT_EQ(expected, optimized_asm);
@@ -202,8 +202,8 @@
   void RunAndCheck(const std::string& original, const std::string& expected) {
     assert(manager_->NumPasses());
 
-    context_ = std::move(BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, original,
-                                     assemble_options_));
+    context_ =
+        std::move(BuildModule(env_, nullptr, original, assemble_options_));
     ASSERT_NE(nullptr, context());
 
     manager_->Run(context());
@@ -212,7 +212,8 @@
     context()->module()->ToBinary(&binary, /* skip_nop = */ false);
 
     std::string optimized;
-    EXPECT_TRUE(tools_.Disassemble(binary, &optimized, disassemble_options_));
+    SpirvTools tools(env_);
+    EXPECT_TRUE(tools.Disassemble(binary, &optimized, disassemble_options_));
     EXPECT_EQ(expected, optimized);
   }
 
@@ -233,14 +234,16 @@
 
   spv_validator_options ValidatorOptions() { return &validator_options_; }
 
+  void SetTargetEnv(spv_target_env env) { env_ = env; }
+
  private:
-  MessageConsumer consumer_;            // Message consumer.
-  std::unique_ptr<IRContext> context_;  // IR context
-  SpirvTools tools_;  // An instance for calling SPIRV-Tools functionalities.
+  MessageConsumer consumer_;              // Message consumer.
+  std::unique_ptr<IRContext> context_;    // IR context
   std::unique_ptr<PassManager> manager_;  // The pass manager.
   uint32_t assemble_options_;
   uint32_t disassemble_options_;
   spv_validator_options_t validator_options_;
+  spv_target_env env_;
 };
 
 }  // namespace opt
diff --git a/test/opt/pass_manager_test.cpp b/test/opt/pass_manager_test.cpp
index c7273e9..22d5e22 100644
--- a/test/opt/pass_manager_test.cpp
+++ b/test/opt/pass_manager_test.cpp
@@ -107,8 +107,7 @@
  public:
   const char* name() const override { return "DuplicateInst"; }
   Status Process() override {
-    auto inst =
-        MakeUnique<Instruction>(*(--context()->debug1_end())->Clone(context()));
+    auto inst = MakeUnique<Instruction>(*(--context()->debug1_end()));
     context()->AddDebug1Inst(std::move(inst));
     return Status::SuccessWithChange;
   }
@@ -121,21 +120,21 @@
 
   AddPass<AppendOpNopPass>();
   AddPass<AppendOpNopPass>();
-  RunAndCheck(text.c_str(), (text + "OpNop\nOpNop\n").c_str());
+  RunAndCheck(text, text + "OpNop\nOpNop\n");
 
   RenewPassManger();
   AddPass<AppendOpNopPass>();
   AddPass<DuplicateInstPass>();
-  RunAndCheck(text.c_str(), (text + "OpNop\nOpNop\n").c_str());
+  RunAndCheck(text, text + "OpNop\nOpNop\n");
 
   RenewPassManger();
   AddPass<DuplicateInstPass>();
   AddPass<AppendOpNopPass>();
-  RunAndCheck(text.c_str(), (text + "OpSource ESSL 310\nOpNop\n").c_str());
+  RunAndCheck(text, text + "OpSource ESSL 310\nOpNop\n");
 
   RenewPassManger();
   AddPass<AppendMultipleOpNopPass>(3);
-  RunAndCheck(text.c_str(), (text + "OpNop\nOpNop\nOpNop\n").c_str());
+  RunAndCheck(text, text + "OpNop\nOpNop\nOpNop\n");
 }
 
 // A pass that appends an OpTypeVoid instruction that uses a given id.
diff --git a/test/opt/pass_merge_return_test.cpp b/test/opt/pass_merge_return_test.cpp
index e49fec9..2f2e74a 100644
--- a/test/opt/pass_merge_return_test.cpp
+++ b/test/opt/pass_merge_return_test.cpp
@@ -1206,7 +1206,7 @@
 ; CHECK: [[bb:%\w+]] = OpLabel
 ; CHECK-NEXT: [[val:%\w+]] = OpUndef %bool
 ; CHECK: [[merge]] = OpLabel
-; CHECK-NEXT: [[phi1:%\w+]] = OpPhi %bool [[val]] [[bb]] {{%\w+}} [[old_ret_block]]
+; CHECK-NEXT: [[phi1:%\w+]] = OpPhi %bool {{%\w+}} [[old_ret_block]] [[val]] [[bb]]
 ; CHECK: OpBranchConditional {{%\w+}} {{%\w+}} [[bb2:%\w+]]
 ; CHECK: [[bb2]] = OpLabel
 ; CHECK: OpBranch [[header2:%\w+]]
@@ -1254,6 +1254,424 @@
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
   SinglePassRunAndMatch<MergeReturnPass>(before, false);
 }
+
+TEST_F(MergeReturnPassTest, GeneratePhiInOuterLoop) {
+  const std::string before =
+      R"(
+      ; CHECK: OpLoopMerge
+      ; CHECK: OpLoopMerge [[merge:%\w+]] [[continue:%\w+]]
+      ; CHECK: [[continue]] = OpLabel
+      ; CHECK-NEXT: [[undef:%\w+]] = OpUndef
+      ; CHECK: [[merge]] = OpLabel
+      ; CHECK-NEXT: [[phi:%\w+]] = OpPhi %bool {{%\w+}} {{%\w+}} [[undef]] [[continue]]
+      ; CHECK: OpCopyObject %bool [[phi]]
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+       %bool = OpTypeBool
+          %8 = OpTypeFunction %bool
+      %false = OpConstantFalse %bool
+          %4 = OpFunction %void None %3
+          %5 = OpLabel
+         %63 = OpFunctionCall %bool %9
+               OpReturn
+               OpFunctionEnd
+          %9 = OpFunction %bool None %8
+         %10 = OpLabel
+               OpBranch %31
+         %31 = OpLabel
+               OpLoopMerge %33 %34 None
+               OpBranch %32
+         %32 = OpLabel
+               OpSelectionMerge %34 None
+               OpBranchConditional %false %46 %34
+         %46 = OpLabel
+               OpLoopMerge %51 %52 None
+               OpBranch %53
+         %53 = OpLabel
+               OpBranchConditional %false %50 %51
+         %50 = OpLabel
+               OpReturnValue %false
+         %52 = OpLabel
+               OpBranch %46
+         %51 = OpLabel
+               OpBranch %34
+         %34 = OpLabel
+         %64 = OpUndef %bool
+               OpBranchConditional %false %31 %33
+         %33 = OpLabel
+               OpBranch %28
+         %28 = OpLabel
+         %60 = OpCopyObject %bool %64
+               OpBranch %17
+         %17 = OpLabel
+               OpReturnValue %false
+               OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<MergeReturnPass>(before, false);
+}
+
+TEST_F(MergeReturnPassTest, SerialLoopsUpdateBlockMapping) {
+  // #2455: This test case triggers phi insertions that use previously inserted
+  // phis. Without the fix, it fails to validate.
+  const std::string spirv = R"(
+; CHECK: OpLoopMerge
+; CHECK: OpLoopMerge
+; CHECK: OpLoopMerge
+; CHECK: OpLoopMerge [[merge:%\w+]]
+; CHECK: [[def:%\w+]] = OpFOrdLessThan
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: [[phi:%\w+]] = OpPhi {{%\w+}} {{%\w+}} {{%\w+}} [[def]]
+; CHECK: OpLoopMerge [[merge:%\w+]] [[cont:%\w+]]
+; CHECK: [[cont]] = OpLabel
+; CHECK-NEXT: OpBranchConditional [[phi]]
+; CHECK-NOT: [[def]]
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %53
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %20 RelaxedPrecision
+               OpDecorate %27 RelaxedPrecision
+               OpDecorate %53 BuiltIn FragCoord
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 3
+          %8 = OpTypeFunction %7
+         %11 = OpTypeBool
+         %12 = OpConstantFalse %11
+         %15 = OpConstant %6 1
+         %16 = OpConstantComposite %7 %15 %15 %15
+         %18 = OpTypeInt 32 1
+         %19 = OpTypePointer Function %18
+         %21 = OpConstant %18 1
+         %28 = OpConstant %18 0
+         %31 = OpTypePointer Function %11
+         %33 = OpConstantTrue %11
+         %51 = OpTypeVector %6 4
+         %52 = OpTypePointer Input %51
+         %53 = OpVariable %52 Input
+         %54 = OpTypeInt 32 0
+         %55 = OpConstant %54 0
+         %56 = OpTypePointer Input %6
+         %59 = OpConstant %6 0
+         %76 = OpUndef %18
+         %77 = OpUndef %11
+         %78 = OpUndef %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %75 = OpFunctionCall %7 %9
+               OpReturn
+               OpFunctionEnd
+          %9 = OpFunction %7 None %8
+         %10 = OpLabel
+         %20 = OpVariable %19 Function
+               OpBranch %14
+         %14 = OpLabel
+               OpBranch %22
+         %22 = OpLabel
+         %27 = OpLoad %18 %20
+               OpLoopMerge %24 %25 None
+               OpBranch %24
+         %25 = OpLabel
+               OpBranch %22
+         %24 = OpLabel
+               OpBranch %34
+         %34 = OpLabel
+               OpLoopMerge %36 %40 None
+               OpBranch %35
+         %35 = OpLabel
+               OpSelectionMerge %40 None
+               OpBranchConditional %77 %39 %40
+         %39 = OpLabel
+               OpReturnValue %16
+         %40 = OpLabel
+               OpBranchConditional %12 %34 %36
+         %36 = OpLabel
+               OpBranch %43
+         %43 = OpLabel
+               OpLoopMerge %45 %49 None
+               OpBranch %44
+         %44 = OpLabel
+               OpSelectionMerge %49 None
+               OpBranchConditional %77 %48 %49
+         %48 = OpLabel
+               OpReturnValue %16
+         %49 = OpLabel
+         %60 = OpFOrdLessThan %11 %15 %59
+               OpBranchConditional %12 %43 %45
+         %45 = OpLabel
+               OpBranch %62
+         %62 = OpLabel
+               OpLoopMerge %64 %68 None
+               OpBranch %63
+         %63 = OpLabel
+               OpSelectionMerge %68 None
+               OpBranchConditional %77 %67 %68
+         %67 = OpLabel
+               OpReturnValue %16
+         %68 = OpLabel
+               OpBranchConditional %60 %62 %64
+         %64 = OpLabel
+               OpReturnValue %16
+               OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<MergeReturnPass>(spirv, true);
+}
+
+TEST_F(MergeReturnPassTest, InnerLoopMergeIsOuterLoopContinue) {
+  const std::string before =
+      R"(
+      ; CHECK: OpLoopMerge
+      ; CHECK-NEXT: OpBranch [[bb1:%\w+]]
+      ; CHECK: [[bb1]] = OpLabel
+      ; CHECK-NEXT: OpBranch [[outer_loop_header:%\w+]]
+      ; CHECK: [[outer_loop_header]] = OpLabel
+      ; CHECK-NEXT: OpLoopMerge [[outer_loop_merge:%\w+]] [[outer_loop_continue:%\w+]] None
+      ; CHECK: [[outer_loop_continue]] = OpLabel
+      ; CHECK-NEXT: OpBranch [[outer_loop_header]]
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+       %void = OpTypeVoid
+          %4 = OpTypeFunction %void
+       %bool = OpTypeBool
+          %6 = OpTypeFunction %bool
+       %true = OpConstantTrue %bool
+          %2 = OpFunction %void None %4
+          %8 = OpLabel
+          %9 = OpFunctionCall %bool %10
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %bool None %6
+         %11 = OpLabel
+               OpBranch %12
+         %12 = OpLabel
+               OpLoopMerge %13 %14 None
+               OpBranchConditional %true %15 %13
+         %15 = OpLabel
+               OpLoopMerge %14 %16 None
+               OpBranchConditional %true %17 %14
+         %17 = OpLabel
+               OpReturnValue %true
+         %16 = OpLabel
+               OpBranch %15
+         %14 = OpLabel
+               OpBranch %12
+         %13 = OpLabel
+               OpReturnValue %true
+               OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<MergeReturnPass>(before, false);
+}
+
+TEST_F(MergeReturnPassTest, BreakFromLoopUseNoLongerDominated) {
+  const std::string spirv = R"(
+; CHECK: [[undef:%\w+]] = OpUndef
+; CHECK: OpLoopMerge
+; CHECK: OpLoopMerge [[merge:%\w+]] [[cont:%\w+]]
+; CHECK-NEXT: OpBranch [[body:%\w+]]
+; CHECK: [[body]] = OpLabel
+; CHECK-NEXT: OpSelectionMerge [[non_ret:%\w+]]
+; CHECK-NEXT: OpBranchConditional {{%\w+}} [[ret:%\w+]] [[non_ret]]
+; CHECK: [[ret]] = OpLabel
+; CHECK-NEXT: OpStore
+; CHECK-NEXT: OpBranch [[merge]]
+; CHECK: [[non_ret]] = OpLabel
+; CHECK-NEXT: [[def:%\w+]] = OpLogicalNot
+; CHECK-NEXT: OpBranchConditional {{%\w+}} [[break:%\w+]] [[cont]]
+; CHECK: [[break]] = OpLabel
+; CHECK-NEXT: OpBranch [[merge]]
+; CHECK: [[cont]] = OpLabel
+; CHECK-NEXT: OpBranchConditional {{%\w+}} {{%\w+}} [[merge]]
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: [[phi:%\w+]] = OpPhi {{%\w+}} [[undef]] [[ret]] [[def]] [[break]] [[def]] [[cont]]
+; CHECK: OpLogicalNot {{%\w+}} [[phi]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %func "func"
+OpExecutionMode %func LocalSize 1 1 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+OpBranch %2
+%2 = OpLabel
+OpLoopMerge %8 %7 None
+OpBranch %3
+%3 = OpLabel
+OpSelectionMerge %5 None
+OpBranchConditional %true %4 %5
+%4 = OpLabel
+OpReturn
+%5 = OpLabel
+%def = OpLogicalNot %bool %true
+OpBranchConditional %true %6 %7
+%6 = OpLabel
+OpBranch %8
+%7 = OpLabel
+OpBranchConditional %true %2 %8
+%8 = OpLabel
+OpBranch %9
+%9 = OpLabel
+%use = OpLogicalNot %bool %def
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<MergeReturnPass>(spirv, true);
+}
+
+TEST_F(MergeReturnPassTest, TwoBreaksFromLoopUsesNoLongerDominated) {
+  const std::string spirv = R"(
+; CHECK: [[undef:%\w+]] = OpUndef
+; CHECK: OpLoopMerge
+; CHECK: OpLoopMerge [[merge:%\w+]] [[cont:%\w+]]
+; CHECK-NEXT: OpBranch [[body:%\w+]]
+; CHECK: [[body]] = OpLabel
+; CHECK-NEXT: OpSelectionMerge [[body2:%\w+]]
+; CHECK-NEXT: OpBranchConditional {{%\w+}} [[ret1:%\w+]] [[body2]]
+; CHECK: [[ret1]] = OpLabel
+; CHECK-NEXT: OpStore
+; CHECK-NEXT: OpBranch [[merge]]
+; CHECK: [[body2]] = OpLabel
+; CHECK-NEXT: [[def1:%\w+]] = OpLogicalNot
+; CHECK-NEXT: OpSelectionMerge [[body3:%\w+]]
+; CHECK-NEXT: OpBranchConditional {{%\w+}} [[ret2:%\w+]] [[body3:%\w+]]
+; CHECK: [[ret2]] = OpLabel
+; CHECK-NEXT: OpStore
+; CHECK-NEXT: OpBranch [[merge]]
+; CHECK: [[body3]] = OpLabel
+; CHECK-NEXT: [[def2:%\w+]] = OpLogicalAnd
+; CHECK-NEXT: OpBranchConditional {{%\w+}} [[break:%\w+]] [[cont]]
+; CHECK: [[break]] = OpLabel
+; CHECK-NEXT: OpBranch [[merge]]
+; CHECK: [[cont]] = OpLabel
+; CHECK-NEXT: OpBranchConditional {{%\w+}} {{%\w+}} [[merge]]
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: [[phi1:%\w+]] = OpPhi {{%\w+}} [[undef]] [[ret1]] [[undef]] [[ret2]] [[def1]] [[break]] [[def1]] [[cont]]
+; CHECK-NEXT: [[phi2:%\w+]] = OpPhi {{%\w+}} [[undef]] [[ret1]] [[undef]] [[ret2]] [[def2]] [[break]] [[def2]] [[cont]]
+; CHECK: OpLogicalNot {{%\w+}} [[phi1]]
+; CHECK: OpLogicalAnd {{%\w+}} [[phi2]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %func "func"
+OpExecutionMode %func LocalSize 1 1 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+OpBranch %2
+%2 = OpLabel
+OpLoopMerge %10 %9 None
+OpBranch %3
+%3 = OpLabel
+OpSelectionMerge %5 None
+OpBranchConditional %true %4 %5
+%4 = OpLabel
+OpReturn
+%5 = OpLabel
+%def1 = OpLogicalNot %bool %true
+OpSelectionMerge %7 None
+OpBranchConditional %true %6 %7
+%6 = OpLabel
+OpReturn
+%7 = OpLabel
+%def2 = OpLogicalAnd %bool %true %true
+OpBranchConditional %true %8 %9
+%8 = OpLabel
+OpBranch %10
+%9 = OpLabel
+OpBranchConditional %true %2 %10
+%10 = OpLabel
+OpBranch %11
+%11 = OpLabel
+%use1 = OpLogicalNot %bool %def1
+%use2 = OpLogicalAnd %bool %def2 %true
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<MergeReturnPass>(spirv, true);
+}
+
+TEST_F(MergeReturnPassTest, PredicateBreakBlock) {
+  const std::string spirv = R"(
+; IDs are being preserved so we can rely on basic block labels.
+; CHECK: [[undef:%\w+]] = OpUndef
+; CHECK: [[undef:%\w+]] = OpUndef
+; CHECK: %13 = OpLabel
+; CHECK-NEXT: [[def:%\w+]] = OpLogicalNot
+; CHECK: %8 = OpLabel
+; CHECK-NEXT: [[phi:%\w+]] = OpPhi {{%\w+}} [[undef]] {{%\w+}} [[undef]] {{%\w+}} [[def]] %13 [[undef]] {{%\w+}}
+; CHECK: OpLogicalAnd {{%\w+}} [[phi]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "func"
+OpExecutionMode %1 LocalSize 1 1 1
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpUndef %bool
+%1 = OpFunction %void None %3
+%6 = OpLabel
+OpBranch %7
+%7 = OpLabel
+OpLoopMerge %8 %9 None
+OpBranch %10
+%10 = OpLabel
+OpSelectionMerge %11 None
+OpBranchConditional %true %12 %13
+%12 = OpLabel
+OpLoopMerge %14 %15 None
+OpBranch %16
+%16 = OpLabel
+OpReturn
+%15 = OpLabel
+OpBranch %12
+%14 = OpLabel
+OpUnreachable
+%13 = OpLabel
+%17 = OpLogicalNot %bool %true
+OpBranch %8
+%11 = OpLabel
+OpUnreachable
+%9 = OpLabel
+OpBranch %7
+%8 = OpLabel
+OpBranch %18
+%18 = OpLabel
+%19 = OpLogicalAnd %bool %17 %true
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<MergeReturnPass>(spirv, true);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/private_to_local_test.cpp b/test/opt/private_to_local_test.cpp
index 3ec74fa..d154840 100644
--- a/test/opt/private_to_local_test.cpp
+++ b/test/opt/private_to_local_test.cpp
@@ -308,6 +308,117 @@
   SinglePassRunAndMatch<PrivateToLocalPass>(text, false);
 }
 
+TEST_F(PrivateToLocalTest, SPV14RemoveFromInterface) {
+  const std::string text = R"(
+; CHECK-NOT: OpEntryPoint GLCompute %foo "foo" %in %priv
+; CHECK: OpEntryPoint GLCompute %foo "foo" %in
+; CHECK: %priv = OpVariable {{%\w+}} Function
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %foo "foo" %in %priv
+OpExecutionMode %foo LocalSize 1 1 1
+OpName %foo "foo"
+OpName %in "in"
+OpName %priv "priv"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%ptr_private_int = OpTypePointer Private %int
+%in = OpVariable %ptr_ssbo_int StorageBuffer
+%priv = OpVariable %ptr_private_int Private
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+%ld = OpLoad %int %in
+OpStore %priv %ld
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  SinglePassRunAndMatch<PrivateToLocalPass>(text, true);
+}
+
+TEST_F(PrivateToLocalTest, SPV14RemoveFromInterfaceMultipleEntryPoints) {
+  const std::string text = R"(
+; CHECK-NOT: OpEntryPoint GLCompute %foo "foo" %in %priv
+; CHECK-NOT: OpEntryPoint GLCompute %foo "bar" %in %priv
+; CHECK: OpEntryPoint GLCompute %foo "foo" %in
+; CHECK: OpEntryPoint GLCompute %foo "bar" %in
+; CHECK: %priv = OpVariable {{%\w+}} Function
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %foo "foo" %in %priv
+OpEntryPoint GLCompute %foo "bar" %in %priv
+OpExecutionMode %foo LocalSize 1 1 1
+OpName %foo "foo"
+OpName %in "in"
+OpName %priv "priv"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%ptr_private_int = OpTypePointer Private %int
+%in = OpVariable %ptr_ssbo_int StorageBuffer
+%priv = OpVariable %ptr_private_int Private
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+%ld = OpLoad %int %in
+OpStore %priv %ld
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  SinglePassRunAndMatch<PrivateToLocalPass>(text, true);
+}
+
+TEST_F(PrivateToLocalTest, SPV14RemoveFromInterfaceMultipleVariables) {
+  const std::string text = R"(
+; CHECK-NOT: OpEntryPoint GLCompute %foo "foo" %in %priv1 %priv2
+; CHECK: OpEntryPoint GLCompute %foo "foo" %in
+; CHECK: %priv1 = OpVariable {{%\w+}} Function
+; CHECK: %priv2 = OpVariable {{%\w+}} Function
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %foo "foo" %in %priv1 %priv2
+OpExecutionMode %foo LocalSize 1 1 1
+OpName %foo "foo"
+OpName %in "in"
+OpName %priv1 "priv1"
+OpName %priv2 "priv2"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%ptr_private_int = OpTypePointer Private %int
+%in = OpVariable %ptr_ssbo_int StorageBuffer
+%priv1 = OpVariable %ptr_private_int Private
+%priv2 = OpVariable %ptr_private_int Private
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+%1 = OpFunctionCall %void %bar1
+%2 = OpFunctionCall %void %bar2
+OpReturn
+OpFunctionEnd
+%bar1 = OpFunction %void None %void_fn
+%3 = OpLabel
+%ld1 = OpLoad %int %in
+OpStore %priv1 %ld1
+OpReturn
+OpFunctionEnd
+%bar2 = OpFunction %void None %void_fn
+%4 = OpLabel
+%ld2 = OpLoad %int %in
+OpStore %priv2 %ld2
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  SinglePassRunAndMatch<PrivateToLocalPass>(text, true);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/reduce_load_size_test.cpp b/test/opt/reduce_load_size_test.cpp
index 50dc501..7b850e3 100644
--- a/test/opt/reduce_load_size_test.cpp
+++ b/test/opt/reduce_load_size_test.cpp
@@ -321,6 +321,34 @@
   SinglePassRunAndCheck<ReduceLoadSize>(test, test, true, false);
 }
 
+TEST_F(ReduceLoadSizeTest, extract_with_no_index) {
+  const std::string test =
+      R"(
+               OpCapability ImageGatherExtended
+               OpExtension ""
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "P�Ma'" %12 %17
+               OpExecutionMode %4 OriginUpperLeft
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+  %_struct_7 = OpTypeStruct %float %float
+%_ptr_Input__struct_7 = OpTypePointer Input %_struct_7
+%_ptr_Output__struct_7 = OpTypePointer Output %_struct_7
+         %12 = OpVariable %_ptr_Input__struct_7 Input
+         %17 = OpVariable %_ptr_Output__struct_7 Output
+          %4 = OpFunction %void DontInline|Pure|Const %3
+        %245 = OpLabel
+         %13 = OpLoad %_struct_7 %12
+         %33 = OpCompositeExtract %_struct_7 %13
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  auto result = SinglePassRunAndDisassemble<ReduceLoadSize>(test, true, true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/scalar_replacement_test.cpp b/test/opt/scalar_replacement_test.cpp
index 7058cc5..a53f09d 100644
--- a/test/opt/scalar_replacement_test.cpp
+++ b/test/opt/scalar_replacement_test.cpp
@@ -1149,7 +1149,6 @@
 ; CHECK: OpVariable [[float_ptr]] Function
 ; CHECK: OpVariable [[float_ptr]] Function
 ; CHECK: OpVariable [[float_ptr]] Function
-; CHECK: OpVariable [[float_ptr]] Function
 ; CHECK-NOT: OpVariable
 ;
 OpCapability Shader
@@ -1555,6 +1554,72 @@
   EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
 }
 
+TEST_F(ScalarReplacementTest, AmbigousPointer) {
+  const std::string text = R"(
+; CHECK: [[s1:%\w+]] = OpTypeStruct %uint
+; CHECK: [[s2:%\w+]] = OpTypeStruct %uint
+; CHECK: [[s3:%\w+]] = OpTypeStruct [[s2]]
+; CHECK: [[s3_const:%\w+]] = OpConstantComposite [[s3]]
+; CHECK: [[s2_ptr:%\w+]] = OpTypePointer Function [[s2]]
+; CHECK: OpCompositeExtract [[s2]] [[s3_const]]
+
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+       %void = OpTypeVoid
+          %5 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+  %_struct_7 = OpTypeStruct %uint
+  %_struct_8 = OpTypeStruct %uint
+  %_struct_9 = OpTypeStruct %_struct_8
+     %uint_1 = OpConstant %uint 1
+         %11 = OpConstantComposite %_struct_8 %uint_1
+         %12 = OpConstantComposite %_struct_9 %11
+%_ptr_Function__struct_9 = OpTypePointer Function %_struct_9
+%_ptr_Function__struct_7 = OpTypePointer Function %_struct_7
+          %2 = OpFunction %void None %5
+         %15 = OpLabel
+        %var = OpVariable %_ptr_Function__struct_9 Function
+               OpStore %var %12
+         %ld = OpLoad %_struct_9 %var
+         %ex = OpCompositeExtract %_struct_8 %ld 0
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+// Test that scalar replacement does not crash when there is an OpAccessChain
+// with no index.  If we choose to handle this case in the future, then the
+// result can change.
+TEST_F(ScalarReplacementTest, TestAccessChainWithNoIndexes) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %1 "main"
+               OpExecutionMode %1 OriginLowerLeft
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+  %_struct_5 = OpTypeStruct %float
+%_ptr_Function__struct_5 = OpTypePointer Function %_struct_5
+          %1 = OpFunction %void None %3
+          %7 = OpLabel
+          %8 = OpVariable %_ptr_Function__struct_5 Function
+          %9 = OpAccessChain %_ptr_Function__struct_5 %8
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  auto result =
+      SinglePassRunAndDisassemble<ScalarReplacementPass>(text, true, false);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/set_spec_const_default_value_test.cpp b/test/opt/set_spec_const_default_value_test.cpp
index 161674f..5e63862 100644
--- a/test/opt/set_spec_const_default_value_test.cpp
+++ b/test/opt/set_spec_const_default_value_test.cpp
@@ -50,7 +50,7 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidString, DefaultValuesStringParsingTest,
     ::testing::ValuesIn(std::vector<DefaultValuesStringParsingTestCase>{
         // 0. empty map
@@ -93,7 +93,7 @@
         {"100:1.5e-13", true, SpecIdToValueStrMap{{100, "1.5e-13"}}},
     }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     InvalidString, DefaultValuesStringParsingTest,
     ::testing::ValuesIn(std::vector<DefaultValuesStringParsingTestCase>{
         // 0. missing default value
@@ -148,7 +148,7 @@
       tc.code, tc.expected, /* skip_nop = */ false, tc.default_values);
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidCases, SetSpecConstantDefaultValueInStringFormParamTest,
     ::testing::ValuesIn(std::vector<
                         SetSpecConstantDefaultValueInStringFormTestCase>{
@@ -445,7 +445,7 @@
         },
     }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     InvalidCases, SetSpecConstantDefaultValueInStringFormParamTest,
     ::testing::ValuesIn(std::vector<
                         SetSpecConstantDefaultValueInStringFormTestCase>{
@@ -610,7 +610,7 @@
       tc.code, tc.expected, /* skip_nop = */ false, tc.default_values);
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidCases, SetSpecConstantDefaultValueInBitPatternFormParamTest,
     ::testing::ValuesIn(std::vector<
                         SetSpecConstantDefaultValueInBitPatternFormTestCase>{
@@ -937,7 +937,7 @@
         },
     }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     InvalidCases, SetSpecConstantDefaultValueInBitPatternFormParamTest,
     ::testing::ValuesIn(std::vector<
                         SetSpecConstantDefaultValueInBitPatternFormTestCase>{
diff --git a/test/opt/strip_atomic_counter_memory_test.cpp b/test/opt/strip_atomic_counter_memory_test.cpp
new file mode 100644
index 0000000..6287a13
--- /dev/null
+++ b/test/opt/strip_atomic_counter_memory_test.cpp
@@ -0,0 +1,406 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <vector>
+
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+typedef std::tuple<std::string, std::string> StripAtomicCounterMemoryParam;
+
+using MemorySemanticsModified =
+    PassTest<::testing::TestWithParam<StripAtomicCounterMemoryParam>>;
+using NonMemorySemanticsUnmodifiedTest = PassTest<::testing::Test>;
+
+void operator+=(std::vector<const char*>& lhs, const char* rhs) {
+  lhs.push_back(rhs);
+}
+
+std::string GetConstDecl(std::string val) {
+  std::string decl;
+  decl += "%uint_" + val + " = OpConstant %uint " + val;
+  return decl;
+}
+
+std::string GetUnchangedString(std::string(generate_inst)(std::string),
+                               std::string val) {
+  std::string decl = GetConstDecl(val);
+  std::string inst = generate_inst(val);
+
+  std::vector<const char*> result = {
+      // clang-format off
+              "OpCapability Shader",
+              "OpCapability VulkanMemoryModelKHR",
+              "OpExtension \"SPV_KHR_vulkan_memory_model\"",
+              "OpMemoryModel Logical VulkanKHR",
+              "OpEntryPoint Vertex %1 \"shader\"",
+      "%uint = OpTypeInt 32 0",
+"%_ptr_Workgroup_uint = OpTypePointer Workgroup %uint",
+         "%4 = OpVariable %_ptr_Workgroup_uint Workgroup",
+    "%uint_0 = OpConstant %uint 0",
+    "%uint_1 = OpConstant %uint 1",
+      "%void = OpTypeVoid",
+         "%8 = OpTypeFunction %void",
+               decl.c_str(),
+         "%1 = OpFunction %void None %8",
+        "%10 = OpLabel",
+               inst.c_str(),
+              "OpReturn",
+              "OpFunctionEnd"
+      // clang-format on
+  };
+  return JoinAllInsts(result);
+}
+
+std::string GetChangedString(std::string(generate_inst)(std::string),
+                             std::string orig, std::string changed) {
+  std::string orig_decl = GetConstDecl(orig);
+  std::string changed_decl = GetConstDecl(changed);
+  std::string inst = generate_inst(changed);
+
+  std::vector<const char*> result = {
+      // clang-format off
+              "OpCapability Shader",
+              "OpCapability VulkanMemoryModelKHR",
+              "OpExtension \"SPV_KHR_vulkan_memory_model\"",
+              "OpMemoryModel Logical VulkanKHR",
+              "OpEntryPoint Vertex %1 \"shader\"",
+      "%uint = OpTypeInt 32 0",
+"%_ptr_Workgroup_uint = OpTypePointer Workgroup %uint",
+         "%4 = OpVariable %_ptr_Workgroup_uint Workgroup",
+    "%uint_0 = OpConstant %uint 0",
+    "%uint_1 = OpConstant %uint 1",
+      "%void = OpTypeVoid",
+         "%8 = OpTypeFunction %void",
+               orig_decl.c_str() };
+  // clang-format on
+  if (changed != "0") result += changed_decl.c_str();
+  result += "%1 = OpFunction %void None %8";
+  result += "%10 = OpLabel";
+  result += inst.c_str();
+  result += "OpReturn";
+  result += "OpFunctionEnd";
+  return JoinAllInsts(result);
+}
+
+std::tuple<std::string, std::string> GetInputAndExpected(
+    std::string(generate_inst)(std::string),
+    StripAtomicCounterMemoryParam param) {
+  std::string orig = std::get<0>(param);
+  std::string changed = std::get<1>(param);
+  std::string input = GetUnchangedString(generate_inst, orig);
+  std::string expected = orig == changed
+                             ? GetUnchangedString(generate_inst, changed)
+                             : GetChangedString(generate_inst, orig, changed);
+  return std::make_tuple(input, expected);
+}
+
+std::string GetOpControlBarrierInst(std::string val) {
+  return "OpControlBarrier %uint_1 %uint_1 %uint_" + val;
+}
+
+TEST_P(MemorySemanticsModified, OpControlBarrier) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpControlBarrierInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpMemoryBarrierInst(std::string val) {
+  return "OpMemoryBarrier %uint_1 %uint_" + val;
+}
+
+TEST_P(MemorySemanticsModified, OpMemoryBarrier) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpMemoryBarrierInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicLoadInst(std::string val) {
+  return "%11 = OpAtomicLoad %uint %4 %uint_1 %uint_" + val;
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicLoad) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicLoadInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicStoreInst(std::string val) {
+  return "OpAtomicStore %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicStore) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicStoreInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicExchangeInst(std::string val) {
+  return "%11 = OpAtomicExchange %uint %4 %uint_1 %uint_" + val + " %uint_0";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicExchange) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicExchangeInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicCompareExchangeInst(std::string val) {
+  return "%11 = OpAtomicCompareExchange %uint %4 %uint_1 %uint_" + val +
+         " %uint_" + val + " %uint_0 %uint_0";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicCompareExchange) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicCompareExchangeInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicCompareExchangeWeakInst(std::string val) {
+  return "%11 = OpAtomicCompareExchangeWeak %uint %4 %uint_1 %uint_" + val +
+         " %uint_" + val + " %uint_0 %uint_0";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicCompareExchangeWeak) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicCompareExchangeWeakInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicIIncrementInst(std::string val) {
+  return "%11 = OpAtomicIIncrement %uint %4 %uint_1 %uint_" + val;
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicIIncrement) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicIIncrementInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicIDecrementInst(std::string val) {
+  return "%11 = OpAtomicIDecrement %uint %4 %uint_1 %uint_" + val;
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicIDecrement) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicIDecrementInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicIAddInst(std::string val) {
+  return "%11 = OpAtomicIAdd %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicIAdd) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicIAddInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicISubInst(std::string val) {
+  return "%11 = OpAtomicISub %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicISub) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicISubInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicSMinInst(std::string val) {
+  return "%11 = OpAtomicSMin %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicSMin) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicSMinInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicUMinInst(std::string val) {
+  return "%11 = OpAtomicUMin %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicUMin) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicUMinInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicSMaxInst(std::string val) {
+  return "%11 = OpAtomicSMax %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicSMax) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicSMaxInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicUMaxInst(std::string val) {
+  return "%11 = OpAtomicUMax %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicUMax) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicUMaxInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicAndInst(std::string val) {
+  return "%11 = OpAtomicAnd %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicAnd) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicAndInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicOrInst(std::string val) {
+  return "%11 = OpAtomicOr %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicOr) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicOrInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicXorInst(std::string val) {
+  return "%11 = OpAtomicXor %uint %4 %uint_1 %uint_" + val + " %uint_1";
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicXor) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicXorInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicFlagTestAndSetInst(std::string val) {
+  return "%11 = OpAtomicFlagTestAndSet %uint %4 %uint_1 %uint_" + val;
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicFlagTestAndSet) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicFlagTestAndSetInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpAtomicFlagClearInst(std::string val) {
+  return "OpAtomicFlagClear %4 %uint_1 %uint_" + val;
+}
+
+TEST_P(MemorySemanticsModified, OpAtomicFlagClear) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpAtomicFlagClearInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetOpMemoryNamedBarrierInst(std::string val) {
+  return "OpMemoryNamedBarrier %4 %uint_1 %uint_" + val;
+}
+
+TEST_P(MemorySemanticsModified, OpMemoryNamedBarrier) {
+  std::string input, expected;
+  std::tie(input, expected) =
+      GetInputAndExpected(GetOpMemoryNamedBarrierInst, GetParam());
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+// clang-format off
+INSTANTIATE_TEST_SUITE_P(
+    StripAtomicCounterMemoryTest, MemorySemanticsModified,
+    ::testing::ValuesIn(std::vector<StripAtomicCounterMemoryParam>({
+       std::make_tuple("1024", "0"),
+       std::make_tuple("5", "5"),
+       std::make_tuple("1288", "264"),
+       std::make_tuple("264", "264")
+    })));
+// clang-format on
+
+std::string GetNoMemorySemanticsPresentInst(std::string val) {
+  return "%11 = OpVariable %_ptr_Workgroup_uint Workgroup %uint_" + val;
+}
+
+TEST_F(NonMemorySemanticsUnmodifiedTest, NoMemorySemanticsPresent) {
+  std::string input, expected;
+  StripAtomicCounterMemoryParam param = std::make_tuple("1288", "1288");
+  std::tie(input, expected) =
+      GetInputAndExpected(GetNoMemorySemanticsPresentInst, param);
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+std::string GetMemorySemanticsPresentInst(std::string val) {
+  return "%11 = OpAtomicIAdd %uint %4 %uint_1 %uint_" + val + " %uint_1288";
+}
+
+TEST_F(NonMemorySemanticsUnmodifiedTest, MemorySemanticsPresent) {
+  std::string input, expected;
+  StripAtomicCounterMemoryParam param = std::make_tuple("1288", "264");
+  std::tie(input, expected) =
+      GetInputAndExpected(GetMemorySemanticsPresentInst, param);
+  SinglePassRunAndCheck<StripAtomicCounterMemoryPass>(input, expected,
+                                                      /* skip_nop = */ false);
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/test/opt/strip_debug_info_test.cpp b/test/opt/strip_debug_info_test.cpp
index f40ed38..25cf7d8 100644
--- a/test/opt/strip_debug_info_test.cpp
+++ b/test/opt/strip_debug_info_test.cpp
@@ -89,7 +89,7 @@
 
 // Test each possible non-line debug instruction.
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SingleKindDebugInst, StripDebugInfoTest,
     ::testing::ValuesIn(std::vector<const char*>({
         "OpSourceContinued \"I'm a happy shader! Yay! ;)\"",
diff --git a/test/opt/struct_cfg_analysis_test.cpp b/test/opt/struct_cfg_analysis_test.cpp
index 13f9022..3a980fe 100644
--- a/test/opt/struct_cfg_analysis_test.cpp
+++ b/test/opt/struct_cfg_analysis_test.cpp
@@ -461,6 +461,26 @@
   }
 }
 
+TEST_F(StructCFGAnalysisTest, EmptyFunctionTest) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %func LinkageAttributes "x" Import
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+OpFunctionEnd
+)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+
+  // #2451: This segfaulted on empty functions.
+  StructuredCFGAnalysis analysis(context.get());
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/type_manager_test.cpp b/test/opt/type_manager_test.cpp
index fc9089e..1072c36 100644
--- a/test/opt/type_manager_test.cpp
+++ b/test/opt/type_manager_test.cpp
@@ -200,6 +200,7 @@
     %pipe    = OpTypePipe ReadOnly
     %ps      = OpTypePipeStorage
     %nb      = OpTypeNamedBarrier
+    %rtacc   = OpTypeAccelerationStructureNV
   )";
 
   std::vector<std::pair<uint32_t, std::string>> type_id_strs = {
@@ -231,6 +232,7 @@
       {26, "pipe(0)"},
       {27, "pipe_storage"},
       {28, "named_barrier"},
+      {29, "accelerationStructureNV"},
   };
 
   std::unique_ptr<IRContext> context =
diff --git a/test/opt/unify_const_test.cpp b/test/opt/unify_const_test.cpp
index 37728cc..6ed2173 100644
--- a/test/opt/unify_const_test.cpp
+++ b/test/opt/unify_const_test.cpp
@@ -354,8 +354,14 @@
   Check(expected_builder, test_builder);
 }
 
-INSTANTIATE_TEST_CASE_P(Case, UnifyFrontEndConstantParamTest,
-                        ::testing::ValuesIn(std::vector<UnifyConstantTestCase>({
+INSTANTIATE_TEST_SUITE_P(
+    Case, UnifyFrontEndConstantParamTest,
+    ::
+        testing::
+            ValuesIn(
+                std::
+                    vector<UnifyConstantTestCase>(
+                        {
                             // clang-format off
         // basic tests for scalar constants
         {
diff --git a/test/opt/upgrade_memory_model_test.cpp b/test/opt/upgrade_memory_model_test.cpp
index 90ad601..9d2d762 100644
--- a/test/opt/upgrade_memory_model_test.cpp
+++ b/test/opt/upgrade_memory_model_test.cpp
@@ -23,7 +23,6 @@
 
 using UpgradeMemoryModelTest = opt::PassTest<::testing::Test>;
 
-#ifdef SPIRV_EFFCEE
 TEST_F(UpgradeMemoryModelTest, InvalidMemoryModelOpenCL) {
   const std::string text = R"(
 ; CHECK: OpMemoryModel Logical OpenCL
@@ -80,8 +79,8 @@
 TEST_F(UpgradeMemoryModelTest, WorkgroupVariable) {
   const std::string text = R"(
 ; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 2
-; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
-; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
 OpCapability Shader
 OpCapability Linkage
 OpMemoryModel Logical GLSL450
@@ -104,8 +103,8 @@
 TEST_F(UpgradeMemoryModelTest, WorkgroupFunctionParameter) {
   const std::string text = R"(
 ; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 2
-; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
-; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
 OpCapability Shader
 OpCapability Linkage
 OpMemoryModel Logical GLSL450
@@ -129,8 +128,8 @@
   const std::string text = R"(
 ; CHECK-NOT: OpDecorate
 ; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
-; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile|MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
-; CHECK: OpStore {{%\w+}} {{%\w+}} Volatile|MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile|MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} Volatile|MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
 OpCapability Shader
 OpCapability Linkage
 OpMemoryModel Logical GLSL450
@@ -156,8 +155,8 @@
   const std::string text = R"(
 ; CHECK-NOT: OpDecorate
 ; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
-; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile|MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
-; CHECK: OpStore {{%\w+}} {{%\w+}} Volatile|MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile|MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} Volatile|MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
 OpCapability Shader
 OpCapability Linkage
 OpMemoryModel Logical GLSL450
@@ -209,8 +208,8 @@
   const std::string text = R"(
 ; CHECK-NOT: OpDecorate
 ; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
-; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile|MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
-; CHECK: OpStore {{%\w+}} {{%\w+}} Volatile|MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile|MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} Volatile|MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
 OpCapability Shader
 OpCapability Linkage
 OpMemoryModel Logical GLSL450
@@ -237,8 +236,8 @@
   const std::string text = R"(
 ; CHECK-NOT: OpDecorate
 ; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
-; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile|MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
-; CHECK: OpStore {{%\w+}} {{%\w+}} Volatile|MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile|MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} Volatile|MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
 OpCapability Shader
 OpCapability Linkage
 OpMemoryModel Logical GLSL450
@@ -266,8 +265,8 @@
   const std::string text = R"(
 ; CHECK-NOT: OpDecorate
 ; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
-; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile|MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
-; CHECK: OpStore {{%\w+}} {{%\w+}} Volatile|MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile|MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} Volatile|MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
 OpCapability Shader
 OpCapability Linkage
 OpMemoryModel Logical GLSL450
@@ -298,8 +297,8 @@
   const std::string text = R"(
 ; CHECK-NOT: OpDecorate
 ; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
-; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile|MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
-; CHECK: OpStore {{%\w+}} {{%\w+}} Volatile|MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile|MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} Volatile|MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
 OpCapability Shader
 OpCapability Linkage
 OpMemoryModel Logical GLSL450
@@ -331,8 +330,8 @@
   const std::string text = R"(
 ; CHECK-NOT: OpDecorate
 ; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
-; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile|MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
-; CHECK: OpStore {{%\w+}} {{%\w+}} Volatile|MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile|MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} Volatile|MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
 OpCapability Shader
 OpCapability Linkage
 OpCapability VariablePointers
@@ -364,8 +363,8 @@
   const std::string text = R"(
 ; CHECK-NOT: OpDecorate
 ; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
-; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile|MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
-; CHECK: OpStore {{%\w+}} {{%\w+}} Volatile|MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile|MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} Volatile|MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
 OpCapability Shader
 OpCapability Linkage
 OpCapability VariablePointers
@@ -397,8 +396,8 @@
   const std::string text = R"(
 ; CHECK-NOT: OpDecorate {{%\w+}} Coherent
 ; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
-; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
-; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
 OpCapability Shader
 OpCapability Linkage
 OpCapability VariablePointers
@@ -440,8 +439,8 @@
   const std::string text = R"(
 ; CHECK-NOT: OpMemberDecorate
 ; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
-; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
-; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
 OpCapability Shader
 OpCapability Linkage
 OpExtension "SPV_KHR_storage_buffer_storage_class"
@@ -471,8 +470,8 @@
   const std::string text = R"(
 ; CHECK-NOT: OpMemberDecorate
 ; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
-; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
-; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
 OpCapability Shader
 OpCapability Linkage
 OpExtension "SPV_KHR_storage_buffer_storage_class"
@@ -530,8 +529,8 @@
   const std::string text = R"(
 ; CHECK-NOT: OpMemberDecorate
 ; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
-; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
-; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
 OpCapability Shader
 OpCapability Linkage
 OpExtension "SPV_KHR_storage_buffer_storage_class"
@@ -600,8 +599,8 @@
   const std::string text = R"(
 ; CHECK-NOT: OpMemberDecorate
 ; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
-; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
-; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
 OpCapability Shader
 OpCapability Linkage
 OpExtension "SPV_KHR_storage_buffer_storage_class"
@@ -682,8 +681,8 @@
   const std::string text = R"(
 ; CHECK-NOT: OpMemberDecorate
 ; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
-; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
-; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
 OpCapability Shader
 OpCapability Linkage
 OpExtension "SPV_KHR_storage_buffer_storage_class"
@@ -723,8 +722,8 @@
   const std::string text = R"(
 ; CHECK-NOT: OpMemberDecorate
 ; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
-; CHECK-NOT: MakePointerAvailableKHR
-; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK-NOT: MakePointerVisibleKHR
+; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
 OpCapability Shader
 OpCapability Linkage
 OpExtension "SPV_KHR_storage_buffer_storage_class"
@@ -823,7 +822,7 @@
 ; CHECK-NOT: OpDecorate
 ; CHECK-DAG: [[queuefamily:%\w+]] = OpConstant {{%\w+}} 5
 ; CHECK-DAG: [[workgroup:%\w+]] = OpConstant {{%\w+}} 2
-; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} MakePointerAvailableKHR|MakePointerVisibleKHR|NonPrivatePointerKHR [[queuefamily]] [[workgroup]]
+; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} MakePointerAvailableKHR|MakePointerVisibleKHR|NonPrivatePointerKHR [[workgroup]] [[queuefamily]]
 OpCapability Shader
 OpCapability Linkage
 OpExtension "SPV_KHR_storage_buffer_storage_class"
@@ -883,8 +882,8 @@
   const std::string text = R"(
 ; CHECK-NOT: OpDecorate
 ; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
-; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
-; CHECK: OpImageRead {{%\w+}} {{%\w+}} {{%\w+}} MakeTexelAvailableKHR|NonPrivateTexelKHR [[scope]] 
+; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpImageRead {{%\w+}} {{%\w+}} {{%\w+}} MakeTexelVisibleKHR|NonPrivateTexelKHR [[scope]] 
 OpCapability Shader
 OpCapability Linkage
 OpCapability StorageImageReadWithoutFormat
@@ -917,9 +916,9 @@
 ; CHECK-NOT: OpDecorate
 ; CHECK: [[image:%\w+]] = OpTypeImage
 ; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
-; CHECK: OpLoad [[image]] {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpLoad [[image]] {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
 ; CHECK-NOT: NonPrivatePointerKHR
-; CHECK: OpImageRead {{%\w+}} {{%\w+}} {{%\w+}} MakeTexelAvailableKHR|NonPrivateTexelKHR [[scope]]
+; CHECK: OpImageRead {{%\w+}} {{%\w+}} {{%\w+}} MakeTexelVisibleKHR|NonPrivateTexelKHR [[scope]]
 OpCapability Shader
 OpCapability Linkage
 OpCapability StorageImageReadWithoutFormat
@@ -990,8 +989,8 @@
   const std::string text = R"(
 ; CHECK-NOT: OpDecorate
 ; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
-; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR
-; CHECK: OpImageWrite {{%\w+}} {{%\w+}} {{%\w+}} MakeTexelVisibleKHR|NonPrivateTexelKHR [[scope]]
+; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR
+; CHECK: OpImageWrite {{%\w+}} {{%\w+}} {{%\w+}} MakeTexelAvailableKHR|NonPrivateTexelKHR [[scope]]
 OpCapability Shader
 OpCapability Linkage
 OpCapability StorageImageWriteWithoutFormat
@@ -1023,9 +1022,9 @@
   const std::string text = R"(
 ; CHECK-NOT: OpDecorate
 ; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
-; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR
+; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR
 ; CHECK-NOT: NonPrivatePointerKHR
-; CHECK: OpImageWrite {{%\w+}} {{%\w+}} {{%\w+}} MakeTexelVisibleKHR|NonPrivateTexelKHR [[scope]]
+; CHECK: OpImageWrite {{%\w+}} {{%\w+}} {{%\w+}} MakeTexelAvailableKHR|NonPrivateTexelKHR [[scope]]
 OpCapability Shader
 OpCapability Linkage
 OpCapability StorageImageWriteWithoutFormat
@@ -1098,8 +1097,8 @@
   const std::string text = R"(
 ; CHECK-NOT: OpDecorate
 ; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
-; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
-; CHECK: OpImageSparseRead {{%\w+}} {{%\w+}} {{%\w+}} MakeTexelAvailableKHR|NonPrivateTexelKHR [[scope]] 
+; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpImageSparseRead {{%\w+}} {{%\w+}} {{%\w+}} MakeTexelVisibleKHR|NonPrivateTexelKHR [[scope]] 
 OpCapability Shader
 OpCapability Linkage
 OpCapability StorageImageReadWithoutFormat
@@ -1135,9 +1134,9 @@
 ; CHECK-NOT: OpDecorate
 ; CHECK: [[image:%\w+]] = OpTypeImage
 ; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
-; CHECK: OpLoad [[image]] {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpLoad [[image]] {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
 ; CHECK-NOT: NonPrivatePointerKHR
-; CHECK: OpImageSparseRead {{%\w+}} {{%\w+}} {{%\w+}} MakeTexelAvailableKHR|NonPrivateTexelKHR [[scope]]
+; CHECK: OpImageSparseRead {{%\w+}} {{%\w+}} {{%\w+}} MakeTexelVisibleKHR|NonPrivateTexelKHR [[scope]]
 OpCapability Shader
 OpCapability Linkage
 OpCapability StorageImageReadWithoutFormat
@@ -1430,6 +1429,288 @@
   SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
 }
 
-#endif
+TEST_F(UpgradeMemoryModelTest, UpgradeModfNoFlags) {
+  const std::string text = R"(
+; CHECK: [[float:%\w+]] = OpTypeFloat 32
+; CHECK: [[float_0:%\w+]] = OpConstant [[float]] 0
+; CHECK: [[ptr:%\w+]] = OpTypePointer StorageBuffer [[float]]
+; CHECK: [[var:%\w+]] = OpVariable [[ptr]] StorageBuffer
+; CHECK: [[struct:%\w+]] = OpTypeStruct [[float]] [[float]]
+; CHECK: [[modfstruct:%\w+]] = OpExtInst [[struct]] {{%\w+}} ModfStruct [[float_0]]
+; CHECK: [[ex0:%\w+]] = OpCompositeExtract [[float]] [[modfstruct]] 0
+; CHECK: [[ex1:%\w+]] = OpCompositeExtract [[float]] [[modfstruct]] 1
+; CHECK: OpStore [[var]] [[ex1]]
+; CHECK-NOT: NonPrivatePointerKHR
+; CHECK: OpFAdd [[float]] [[float_0]] [[ex0]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+%import = OpExtInstImport "GLSL.std.450"
+OpEntryPoint GLCompute %func "func"
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%ptr_ssbo_float = OpTypePointer StorageBuffer %float
+%ssbo_var = OpVariable %ptr_ssbo_float StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%2 = OpExtInst %float %import Modf %float_0 %ssbo_var
+%3 = OpFAdd %float %float_0 %2
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, UpgradeModfWorkgroupCoherent) {
+  const std::string text = R"(
+; CHECK: [[float:%\w+]] = OpTypeFloat 32
+; CHECK: [[float_0:%\w+]] = OpConstant [[float]] 0
+; CHECK: [[ptr:%\w+]] = OpTypePointer Workgroup [[float]]
+; CHECK: [[var:%\w+]] = OpVariable [[ptr]] Workgroup
+; CHECK: [[struct:%\w+]] = OpTypeStruct [[float]] [[float]]
+; CHECK: [[wg_scope:%\w+]] = OpConstant {{%\w+}} 2
+; CHECK: [[modfstruct:%\w+]] = OpExtInst [[struct]] {{%\w+}} ModfStruct [[float_0]]
+; CHECK: [[ex0:%\w+]] = OpCompositeExtract [[float]] [[modfstruct]] 0
+; CHECK: [[ex1:%\w+]] = OpCompositeExtract [[float]] [[modfstruct]] 1
+; CHECK: OpStore [[var]] [[ex1]] MakePointerAvailableKHR|NonPrivatePointerKHR [[wg_scope]]
+; CHECK: OpFAdd [[float]] [[float_0]] [[ex0]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+%import = OpExtInstImport "GLSL.std.450"
+OpEntryPoint GLCompute %func "func"
+OpDecorate %wg_var Coherent
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%ptr_wg_float = OpTypePointer Workgroup %float
+%wg_var = OpVariable %ptr_wg_float Workgroup
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%2 = OpExtInst %float %import Modf %float_0 %wg_var
+%3 = OpFAdd %float %float_0 %2
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, UpgradeModfSSBOCoherent) {
+  const std::string text = R"(
+; CHECK: [[float:%\w+]] = OpTypeFloat 32
+; CHECK: [[float_0:%\w+]] = OpConstant [[float]] 0
+; CHECK: [[ptr:%\w+]] = OpTypePointer StorageBuffer [[float]]
+; CHECK: [[var:%\w+]] = OpVariable [[ptr]] StorageBuffer
+; CHECK: [[struct:%\w+]] = OpTypeStruct [[float]] [[float]]
+; CHECK: [[qf_scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: [[modfstruct:%\w+]] = OpExtInst [[struct]] {{%\w+}} ModfStruct [[float_0]]
+; CHECK: [[ex0:%\w+]] = OpCompositeExtract [[float]] [[modfstruct]] 0
+; CHECK: [[ex1:%\w+]] = OpCompositeExtract [[float]] [[modfstruct]] 1
+; CHECK: OpStore [[var]] [[ex1]] MakePointerAvailableKHR|NonPrivatePointerKHR [[qf_scope]]
+; CHECK: OpFAdd [[float]] [[float_0]] [[ex0]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+%import = OpExtInstImport "GLSL.std.450"
+OpEntryPoint GLCompute %func "func"
+OpDecorate %ssbo_var Coherent
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%ptr_ssbo_float = OpTypePointer StorageBuffer %float
+%ssbo_var = OpVariable %ptr_ssbo_float StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%2 = OpExtInst %float %import Modf %float_0 %ssbo_var
+%3 = OpFAdd %float %float_0 %2
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, UpgradeModfSSBOVolatile) {
+  const std::string text = R"(
+; CHECK: [[float:%\w+]] = OpTypeFloat 32
+; CHECK: [[float_0:%\w+]] = OpConstant [[float]] 0
+; CHECK: [[ptr:%\w+]] = OpTypePointer StorageBuffer [[float]]
+; CHECK: [[var:%\w+]] = OpVariable [[ptr]] StorageBuffer
+; CHECK: [[struct:%\w+]] = OpTypeStruct [[float]] [[float]]
+; CHECK: [[modfstruct:%\w+]] = OpExtInst [[struct]] {{%\w+}} ModfStruct [[float_0]]
+; CHECK: [[ex0:%\w+]] = OpCompositeExtract [[float]] [[modfstruct]] 0
+; CHECK: [[ex1:%\w+]] = OpCompositeExtract [[float]] [[modfstruct]] 1
+; CHECK: OpStore [[var]] [[ex1]] Volatile
+; CHECK: OpFAdd [[float]] [[float_0]] [[ex0]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+%import = OpExtInstImport "GLSL.std.450"
+OpEntryPoint GLCompute %func "func"
+OpDecorate %wg_var Volatile
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%ptr_ssbo_float = OpTypePointer StorageBuffer %float
+%wg_var = OpVariable %ptr_ssbo_float StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%2 = OpExtInst %float %import Modf %float_0 %wg_var
+%3 = OpFAdd %float %float_0 %2
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, UpgradeFrexpNoFlags) {
+  const std::string text = R"(
+; CHECK: [[float:%\w+]] = OpTypeFloat 32
+; CHECK: [[float_0:%\w+]] = OpConstant [[float]] 0
+; CHECK: [[int:%\w+]] = OpTypeInt 32 0
+; CHECK: [[ptr:%\w+]] = OpTypePointer StorageBuffer [[int]]
+; CHECK: [[var:%\w+]] = OpVariable [[ptr]] StorageBuffer
+; CHECK: [[struct:%\w+]] = OpTypeStruct [[float]] [[int]]
+; CHECK: [[modfstruct:%\w+]] = OpExtInst [[struct]] {{%\w+}} FrexpStruct [[float_0]]
+; CHECK: [[ex0:%\w+]] = OpCompositeExtract [[float]] [[modfstruct]] 0
+; CHECK: [[ex1:%\w+]] = OpCompositeExtract [[int]] [[modfstruct]] 1
+; CHECK: OpStore [[var]] [[ex1]]
+; CHECK-NOT: NonPrivatePointerKHR
+; CHECK: OpFAdd [[float]] [[float_0]] [[ex0]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+%import = OpExtInstImport "GLSL.std.450"
+OpEntryPoint GLCompute %func "func"
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%int = OpTypeInt 32 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%ssbo_var = OpVariable %ptr_ssbo_int StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%2 = OpExtInst %float %import Frexp %float_0 %ssbo_var
+%3 = OpFAdd %float %float_0 %2
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, UpgradeFrexpWorkgroupCoherent) {
+  const std::string text = R"(
+; CHECK: [[float:%\w+]] = OpTypeFloat 32
+; CHECK: [[float_0:%\w+]] = OpConstant [[float]] 0
+; CHECK: [[int:%\w+]] = OpTypeInt 32 0
+; CHECK: [[ptr:%\w+]] = OpTypePointer Workgroup [[int]]
+; CHECK: [[var:%\w+]] = OpVariable [[ptr]] Workgroup
+; CHECK: [[struct:%\w+]] = OpTypeStruct [[float]] [[int]]
+; CHECK: [[wg_scope:%\w+]] = OpConstant {{%\w+}} 2
+; CHECK: [[modfstruct:%\w+]] = OpExtInst [[struct]] {{%\w+}} FrexpStruct [[float_0]]
+; CHECK: [[ex0:%\w+]] = OpCompositeExtract [[float]] [[modfstruct]] 0
+; CHECK: [[ex1:%\w+]] = OpCompositeExtract [[int]] [[modfstruct]] 1
+; CHECK: OpStore [[var]] [[ex1]] MakePointerAvailableKHR|NonPrivatePointerKHR [[wg_scope]]
+; CHECK: OpFAdd [[float]] [[float_0]] [[ex0]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+%import = OpExtInstImport "GLSL.std.450"
+OpEntryPoint GLCompute %func "func"
+OpDecorate %wg_var Coherent
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%int = OpTypeInt 32 0
+%ptr_wg_int = OpTypePointer Workgroup %int
+%wg_var = OpVariable %ptr_wg_int Workgroup
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%2 = OpExtInst %float %import Frexp %float_0 %wg_var
+%3 = OpFAdd %float %float_0 %2
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, UpgradeFrexpSSBOCoherent) {
+  const std::string text = R"(
+; CHECK: [[float:%\w+]] = OpTypeFloat 32
+; CHECK: [[float_0:%\w+]] = OpConstant [[float]] 0
+; CHECK: [[int:%\w+]] = OpTypeInt 32 0
+; CHECK: [[ptr:%\w+]] = OpTypePointer StorageBuffer [[int]]
+; CHECK: [[var:%\w+]] = OpVariable [[ptr]] StorageBuffer
+; CHECK: [[struct:%\w+]] = OpTypeStruct [[float]] [[int]]
+; CHECK: [[qf_scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: [[modfstruct:%\w+]] = OpExtInst [[struct]] {{%\w+}} FrexpStruct [[float_0]]
+; CHECK: [[ex0:%\w+]] = OpCompositeExtract [[float]] [[modfstruct]] 0
+; CHECK: [[ex1:%\w+]] = OpCompositeExtract [[int]] [[modfstruct]] 1
+; CHECK: OpStore [[var]] [[ex1]] MakePointerAvailableKHR|NonPrivatePointerKHR [[qf_scope]]
+; CHECK: OpFAdd [[float]] [[float_0]] [[ex0]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+%import = OpExtInstImport "GLSL.std.450"
+OpEntryPoint GLCompute %func "func"
+OpDecorate %ssbo_var Coherent
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%int = OpTypeInt 32 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%ssbo_var = OpVariable %ptr_ssbo_int StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%2 = OpExtInst %float %import Frexp %float_0 %ssbo_var
+%3 = OpFAdd %float %float_0 %2
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, UpgradeFrexpSSBOVolatile) {
+  const std::string text = R"(
+; CHECK: [[float:%\w+]] = OpTypeFloat 32
+; CHECK: [[float_0:%\w+]] = OpConstant [[float]] 0
+; CHECK: [[int:%\w+]] = OpTypeInt 32 0
+; CHECK: [[ptr:%\w+]] = OpTypePointer StorageBuffer [[int]]
+; CHECK: [[var:%\w+]] = OpVariable [[ptr]] StorageBuffer
+; CHECK: [[struct:%\w+]] = OpTypeStruct [[float]] [[int]]
+; CHECK: [[modfstruct:%\w+]] = OpExtInst [[struct]] {{%\w+}} FrexpStruct [[float_0]]
+; CHECK: [[ex0:%\w+]] = OpCompositeExtract [[float]] [[modfstruct]] 0
+; CHECK: [[ex1:%\w+]] = OpCompositeExtract [[int]] [[modfstruct]] 1
+; CHECK: OpStore [[var]] [[ex1]] Volatile
+; CHECK: OpFAdd [[float]] [[float_0]] [[ex0]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+%import = OpExtInstImport "GLSL.std.450"
+OpEntryPoint GLCompute %func "func"
+OpDecorate %wg_var Volatile
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%int = OpTypeInt 32 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%wg_var = OpVariable %ptr_ssbo_int StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%2 = OpExtInst %float %import Frexp %float_0 %wg_var
+%3 = OpFAdd %float %float_0 %2
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
 
 }  // namespace
diff --git a/test/opt/utils_test.cpp b/test/opt/utils_test.cpp
index 9bb82a3..5ce146b 100644
--- a/test/opt/utils_test.cpp
+++ b/test/opt/utils_test.cpp
@@ -72,7 +72,7 @@
       << " expected string: " << GetParam().expected_str;
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SubstringReplacement, FindAndReplaceTest,
     ::testing::ValuesIn(std::vector<SubstringReplacementTestCase>({
         // orig string, find substring, replace substring, expected string,
diff --git a/test/opt/vector_dce_test.cpp b/test/opt/vector_dce_test.cpp
index c19159b..a978a07 100644
--- a/test/opt/vector_dce_test.cpp
+++ b/test/opt/vector_dce_test.cpp
@@ -490,6 +490,7 @@
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %OutColor = OpVariable %_ptr_Output_v4float Output
 %27 = OpUndef %v4float
+%55 = OpUndef %v4float
 )";
 
   const std::string before =
@@ -536,25 +537,31 @@
       R"(%main = OpFunction %void None %10
 %28 = OpLabel
 %29 = OpLoad %v4float %In0
+%30 = OpLoad %float %In1
+%31 = OpLoad %float %In2
+%32 = OpFAdd %float %30 %31
+%33 = OpCompositeInsert %v4float %32 %29 1
 %34 = OpAccessChain %_ptr_Uniform_uint %_ %int_0
 %35 = OpLoad %uint %34
 %36 = OpINotEqual %bool %35 %uint_0
 OpSelectionMerge %37 None
 OpBranchConditional %36 %38 %37
 %38 = OpLabel
-%39 = OpCompositeInsert %v4float %float_1 %29 0
+%39 = OpCompositeInsert %v4float %float_1 %55 0
 OpBranch %37
 %37 = OpLabel
 %40 = OpPhi %v4float %29 %28 %39 %38
 %41 = OpCompositeExtract %float %40 0
-%42 = OpCompositeInsert %v4float %41 %27 0
+%42 = OpCompositeInsert %v4float %41 %55 0
+%43 = OpCompositeExtract %float %40 1
+%44 = OpCompositeInsert %v4float %43 %42 1
 %45 = OpAccessChain %_ptr_Uniform_uint %_ %int_1
 %46 = OpLoad %uint %45
 %47 = OpINotEqual %bool %46 %uint_0
 OpSelectionMerge %48 None
 OpBranchConditional %47 %49 %48
 %49 = OpLabel
-%50 = OpCompositeInsert %v4float %float_0 %42 0
+%50 = OpCompositeInsert %v4float %float_0 %55 0
 OpBranch %48
 %48 = OpLabel
 %51 = OpPhi %v4float %42 %37 %50 %49
@@ -566,8 +573,8 @@
 OpFunctionEnd
 )";
 
-  SinglePassRunAndCheck<DeadInsertElimPass>(before_predefs + before,
-                                            after_predefs + after, true, true);
+  SinglePassRunAndCheck<VectorDCE>(before_predefs + before,
+                                   after_predefs + after, true, true);
 }
 
 TEST_F(VectorDCETest, InsertObjectLive) {
@@ -608,7 +615,7 @@
 )";
 
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<DeadInsertElimPass>(before, before, true, true);
+  SinglePassRunAndCheck<VectorDCE>(before, before, true, true);
 }
 
 TEST_F(VectorDCETest, DeadInsertInCycle) {
@@ -1148,7 +1155,75 @@
 OpFunctionEnd
 )";
 
-  SinglePassRunAndCheck<DeadInsertElimPass>(text, text, true, true);
+  SinglePassRunAndCheck<VectorDCE>(text, text, true, true);
+}
+
+TEST_F(VectorDCETest, InsertWithNoIndices) {
+  const std::string text = R"(
+; CHECK: OpEntryPoint Fragment {{%\w+}} "PSMain" [[in1:%\w+]] [[in2:%\w+]] [[out:%\w+]]
+; CHECK: OpFunction
+; CHECK: [[ld:%\w+]] = OpLoad %v4float [[in2]]
+; CHECK: OpStore [[out]] [[ld]]
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %1 "PSMain" %2 %14 %3
+               OpExecutionMode %1 OriginUpperLeft
+       %void = OpTypeVoid
+          %5 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+          %2 = OpVariable %_ptr_Input_v4float Input
+          %14 = OpVariable %_ptr_Input_v4float Input
+          %3 = OpVariable %_ptr_Output_v4float Output
+          %1 = OpFunction %void None %5
+         %10 = OpLabel
+         %13 = OpLoad %v4float %14
+         %11 = OpLoad %v4float %2
+         %12 = OpCompositeInsert %v4float %13 %11
+               OpStore %3 %12
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<VectorDCE>(text, true);
+}
+
+TEST_F(VectorDCETest, ExtractWithNoIndices) {
+  const std::string text = R"(
+; CHECK: OpLoad %float
+; CHECK: [[ld:%\w+]] = OpLoad %v4float
+; CHECK: [[ex1:%\w+]] = OpCompositeExtract %v4float [[ld]]
+; CHECK: [[ex2:%\w+]] = OpCompositeExtract %float [[ex1]] 1
+; CHECK: OpStore {{%\w+}} [[ex2]]
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %1 "PSMain" %2 %14 %3
+               OpExecutionMode %1 OriginUpperLeft
+       %void = OpTypeVoid
+          %5 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_float = OpTypePointer Input %float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_float = OpTypePointer Output %float
+          %2 = OpVariable %_ptr_Input_v4float Input
+          %14 = OpVariable %_ptr_Input_float Input
+          %3 = OpVariable %_ptr_Output_float Output
+          %1 = OpFunction %void None %5
+         %10 = OpLabel
+         %13 = OpLoad %float %14
+         %11 = OpLoad %v4float %2
+         %12 = OpCompositeInsert %v4float %13 %11 0
+         %20 = OpCompositeExtract %v4float %12
+         %21 = OpCompositeExtract %float %20 1
+               OpStore %3 %21
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<VectorDCE>(text, true);
 }
 
 }  // namespace
diff --git a/test/reduce/CMakeLists.txt b/test/reduce/CMakeLists.txt
index cdd27b9..964abdd 100644
--- a/test/reduce/CMakeLists.txt
+++ b/test/reduce/CMakeLists.txt
@@ -13,15 +13,23 @@
 # limitations under the License.
 
 add_spvtools_unittest(TARGET reduce
-        SRCS operand_to_constant_reduction_pass_test.cpp
-        operand_to_dominating_id_reduction_pass_test.cpp
+        SRCS
+        merge_blocks_test.cpp
+        operand_to_constant_test.cpp
+        operand_to_undef_test.cpp
+        operand_to_dominating_id_test.cpp
         reduce_test_util.cpp
         reduce_test_util.h
         reducer_test.cpp
-        remove_opname_instruction_reduction_pass_test.cpp
-        remove_unreferenced_instruction_reduction_pass_test.cpp
-        structured_loop_to_selection_reduction_pass_test.cpp
+        remove_block_test.cpp
+        remove_function_test.cpp
+        remove_opname_instruction_test.cpp
+        remove_selection_test.cpp
+        remove_unreferenced_instruction_test.cpp
+        structured_loop_to_selection_test.cpp
         validation_during_reduction_test.cpp
+        conditional_branch_to_simple_conditional_branch_test.cpp
+        simple_conditional_branch_to_branch_test.cpp
         LIBS SPIRV-Tools-reduce
         )
 
diff --git a/test/reduce/conditional_branch_to_simple_conditional_branch_test.cpp b/test/reduce/conditional_branch_to_simple_conditional_branch_test.cpp
new file mode 100644
index 0000000..69ef1f4
--- /dev/null
+++ b/test/reduce/conditional_branch_to_simple_conditional_branch_test.cpp
@@ -0,0 +1,501 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h"
+
+#include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "source/reduce/reduction_pass.h"
+#include "test/reduce/reduce_test_util.h"
+
+namespace spvtools {
+namespace reduce {
+namespace {
+
+const spv_target_env kEnv = SPV_ENV_UNIVERSAL_1_3;
+
+TEST(ConditionalBranchToSimpleConditionalBranchTest, Diamond) {
+  // A test with the following structure.
+  //
+  // selection header
+  // OpBranchConditional
+  //  |         |
+  //  b         b
+  //  |         |
+  //  selection merge
+  //
+  // There should be two opportunities for redirecting the OpBranchConditional
+  // targets: redirecting the true to false, and vice-versa. E.g. false to true:
+  //
+  // selection header
+  // OpBranchConditional
+  //  ||
+  //  b         b
+  //  |         |
+  //  selection merge
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpSelectionMerge %11 None
+               OpBranchConditional %8 %12 %13
+         %12 = OpLabel
+               OpBranch %11
+         %13 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+    )";
+
+  auto context = BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(kEnv, context.get());
+
+  auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
+                 .GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(2, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ASSERT_TRUE(ops[1]->PreconditionHolds());
+  ops[0]->TryToApply();
+  // The other opportunity should now be disabled.
+  ASSERT_FALSE(ops[1]->PreconditionHolds());
+
+  CheckValid(kEnv, context.get());
+
+  {
+    std::string after = R"(
+                 OpCapability Shader
+            %1 = OpExtInstImport "GLSL.std.450"
+                 OpMemoryModel Logical GLSL450
+                 OpEntryPoint Fragment %2 "main"
+                 OpExecutionMode %2 OriginUpperLeft
+                 OpSource ESSL 310
+                 OpName %2 "main"
+            %3 = OpTypeVoid
+            %4 = OpTypeFunction %3
+            %5 = OpTypeInt 32 1
+            %6 = OpTypePointer Function %5
+            %7 = OpTypeBool
+            %8 = OpConstantTrue %7
+            %2 = OpFunction %3 None %4
+            %9 = OpLabel
+                 OpBranch %10
+           %10 = OpLabel
+                 OpSelectionMerge %11 None
+                 OpBranchConditional %8 %12 %12
+           %12 = OpLabel
+                 OpBranch %11
+           %13 = OpLabel
+                 OpBranch %11
+           %11 = OpLabel
+                 OpReturn
+                 OpFunctionEnd
+      )";
+    CheckEqual(kEnv, after, context.get());
+  }
+
+  ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
+            .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(0, ops.size());
+
+  // Start again, and apply the other op.
+  context = BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(kEnv, context.get());
+
+  ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
+            .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(2, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ASSERT_TRUE(ops[1]->PreconditionHolds());
+  ops[1]->TryToApply();
+  // The other opportunity should now be disabled.
+  ASSERT_FALSE(ops[0]->PreconditionHolds());
+
+  CheckValid(kEnv, context.get());
+
+  {
+    std::string after2 = R"(
+                 OpCapability Shader
+            %1 = OpExtInstImport "GLSL.std.450"
+                 OpMemoryModel Logical GLSL450
+                 OpEntryPoint Fragment %2 "main"
+                 OpExecutionMode %2 OriginUpperLeft
+                 OpSource ESSL 310
+                 OpName %2 "main"
+            %3 = OpTypeVoid
+            %4 = OpTypeFunction %3
+            %5 = OpTypeInt 32 1
+            %6 = OpTypePointer Function %5
+            %7 = OpTypeBool
+            %8 = OpConstantTrue %7
+            %2 = OpFunction %3 None %4
+            %9 = OpLabel
+                 OpBranch %10
+           %10 = OpLabel
+                 OpSelectionMerge %11 None
+                 OpBranchConditional %8 %13 %13
+           %12 = OpLabel
+                 OpBranch %11
+           %13 = OpLabel
+                 OpBranch %11
+           %11 = OpLabel
+                 OpReturn
+                 OpFunctionEnd
+      )";
+    CheckEqual(kEnv, after2, context.get());
+  }
+}
+
+TEST(ConditionalBranchToSimpleConditionalBranchTest, AlreadySimplified) {
+  // A test with the following structure.
+  //
+  // selection header
+  // OpBranchConditional
+  //  ||
+  //  b         b
+  //  |         |
+  //  selection merge
+  //
+  // There should be no opportunities for redirecting the OpBranchConditional
+  // as it is already simplified.
+  //
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpSelectionMerge %11 None
+               OpBranchConditional %8 %12 %12
+         %12 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+    )";
+
+  auto context = BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(kEnv, context.get());
+
+  auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
+                 .GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(ConditionalBranchToSimpleConditionalBranchTest, DontRemoveBackEdge) {
+  // A test with the following structure. The loop has a continue construct that
+  // ends with OpBranchConditional. The OpBranchConditional can be simplified,
+  // but only to point to the loop header, otherwise we have removed the
+  // back-edge. Thus, there should be one opportunity instead of two.
+  //
+  // loop header
+  //   |
+  //   loop continue target and back-edge block
+  //   OpBranchConditional
+  //        |         |
+  // loop merge       (to loop header^)
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %12 None
+               OpBranch %12
+         %12 = OpLabel
+               OpBranchConditional %8 %11 %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto context =
+      BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(kEnv, context.get());
+
+  auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
+                 .GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(kEnv, context.get());
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %12 None
+               OpBranch %12
+         %12 = OpLabel
+               OpBranchConditional %8 %10 %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+  CheckEqual(kEnv, after, context.get());
+
+  ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
+            .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(ConditionalBranchToSimpleConditionalBranchTest,
+     DontRemoveBackEdgeCombinedHeaderContinue) {
+  // A test with the following structure.
+  //
+  // loop header and continue target and back-edge block
+  //   OpBranchConditional
+  //        |         |
+  // loop merge       (to loop header^)
+  //
+  // The OpBranchConditional-to-header edge must not be removed, so there should
+  // only be one opportunity. It should change both targets to be to the loop
+  // header.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %10 None
+               OpBranchConditional %8 %11 %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto context =
+      BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(kEnv, context.get());
+
+  auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
+                 .GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(kEnv, context.get());
+
+  std::string after = R"(
+                              OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %10 None
+               OpBranchConditional %8 %10 %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+  CheckEqual(kEnv, after, context.get());
+
+  ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
+            .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(ConditionalBranchToSimpleConditionalBranchTest, BackEdgeUnreachable) {
+  // A test with the following structure. I.e. a loop with an unreachable
+  // continue construct that ends with OpBranchConditional.
+  //
+  // loop header
+  //   |
+  //   | loop continue target (unreachable)
+  //   |      |
+  //   | back-edge block (unreachable)
+  //   | OpBranchConditional
+  //   |     |         |
+  // loop merge       (to loop header^)
+  //
+  // The branch to the loop header must not be removed, even though the continue
+  // construct is unreachable. So there should only be one opportunity to make
+  // the true and false targets of the OpBranchConditional to point to the loop
+  // header.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %12 None
+               OpBranch %11
+         %12 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+               OpBranchConditional %8 %11 %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto context =
+      BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(kEnv, context.get());
+
+  auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
+                 .GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(kEnv, context.get());
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %12 None
+               OpBranch %11
+         %12 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+               OpBranchConditional %8 %10 %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+  CheckEqual(kEnv, after, context.get());
+
+  ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
+            .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+}  // namespace
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/test/reduce/merge_blocks_test.cpp b/test/reduce/merge_blocks_test.cpp
new file mode 100644
index 0000000..dfb614e
--- /dev/null
+++ b/test/reduce/merge_blocks_test.cpp
@@ -0,0 +1,652 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/merge_blocks_reduction_opportunity_finder.h"
+
+#include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "test/reduce/reduce_test_util.h"
+
+namespace spvtools {
+namespace reduce {
+namespace {
+
+TEST(MergeBlocksReductionPassTest, BasicCheck) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+         %11 = OpConstant %6 3
+         %12 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpBranch %13
+         %13 = OpLabel
+               OpStore %8 %9
+               OpBranch %14
+         %14 = OpLabel
+               OpStore %8 %10
+               OpBranch %15
+         %15 = OpLabel
+               OpStore %8 %11
+               OpBranch %16
+         %16 = OpLabel
+               OpStore %8 %12
+               OpBranch %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  const auto ops =
+      MergeBlocksReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(5, ops.size());
+
+  // Try order 3, 0, 2, 4, 1
+
+  ASSERT_TRUE(ops[3]->PreconditionHolds());
+  ops[3]->TryToApply();
+
+  std::string after_op_3 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+         %11 = OpConstant %6 3
+         %12 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpBranch %13
+         %13 = OpLabel
+               OpStore %8 %9
+               OpBranch %14
+         %14 = OpLabel
+               OpStore %8 %10
+               OpBranch %15
+         %15 = OpLabel
+               OpStore %8 %11
+               OpStore %8 %12
+               OpBranch %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_op_3, context.get());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  std::string after_op_0 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+         %11 = OpConstant %6 3
+         %12 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %14
+         %14 = OpLabel
+               OpStore %8 %10
+               OpBranch %15
+         %15 = OpLabel
+               OpStore %8 %11
+               OpStore %8 %12
+               OpBranch %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_op_0, context.get());
+
+  ASSERT_TRUE(ops[2]->PreconditionHolds());
+  ops[2]->TryToApply();
+
+  std::string after_op_2 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+         %11 = OpConstant %6 3
+         %12 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %14
+         %14 = OpLabel
+               OpStore %8 %10
+               OpStore %8 %11
+               OpStore %8 %12
+               OpBranch %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_op_2, context.get());
+
+  ASSERT_TRUE(ops[4]->PreconditionHolds());
+  ops[4]->TryToApply();
+
+  std::string after_op_4 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+         %11 = OpConstant %6 3
+         %12 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %14
+         %14 = OpLabel
+               OpStore %8 %10
+               OpStore %8 %11
+               OpStore %8 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_op_4, context.get());
+
+  ASSERT_TRUE(ops[1]->PreconditionHolds());
+  ops[1]->TryToApply();
+
+  std::string after_op_1 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+         %11 = OpConstant %6 3
+         %12 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpStore %8 %10
+               OpStore %8 %11
+               OpStore %8 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_op_1, context.get());
+}
+
+TEST(MergeBlocksReductionPassTest, Loops) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %10 "i"
+               OpName %29 "i"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 0
+         %18 = OpConstant %6 10
+         %19 = OpTypeBool
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %29 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %45
+         %45 = OpLabel
+               OpStore %10 %11
+               OpBranch %12
+         %12 = OpLabel
+               OpLoopMerge %14 %15 None
+               OpBranch %16
+         %16 = OpLabel
+         %17 = OpLoad %6 %10
+               OpBranch %46
+         %46 = OpLabel
+         %20 = OpSLessThan %19 %17 %18
+               OpBranchConditional %20 %13 %14
+         %13 = OpLabel
+         %21 = OpLoad %6 %10
+               OpBranch %47
+         %47 = OpLabel
+         %22 = OpLoad %6 %8
+         %23 = OpIAdd %6 %22 %21
+               OpStore %8 %23
+         %24 = OpLoad %6 %10
+         %25 = OpLoad %6 %8
+         %26 = OpIAdd %6 %25 %24
+               OpStore %8 %26
+               OpBranch %48
+         %48 = OpLabel
+               OpBranch %15
+         %15 = OpLabel
+         %27 = OpLoad %6 %10
+         %28 = OpIAdd %6 %27 %9
+               OpStore %10 %28
+               OpBranch %12
+         %14 = OpLabel
+               OpStore %29 %11
+               OpBranch %49
+         %49 = OpLabel
+               OpBranch %30
+         %30 = OpLabel
+               OpLoopMerge %32 %33 None
+               OpBranch %34
+         %34 = OpLabel
+         %35 = OpLoad %6 %29
+         %36 = OpSLessThan %19 %35 %18
+               OpBranch %50
+         %50 = OpLabel
+               OpBranchConditional %36 %31 %32
+         %31 = OpLabel
+         %37 = OpLoad %6 %29
+         %38 = OpLoad %6 %8
+         %39 = OpIAdd %6 %38 %37
+               OpStore %8 %39
+         %40 = OpLoad %6 %29
+         %41 = OpLoad %6 %8
+         %42 = OpIAdd %6 %41 %40
+               OpStore %8 %42
+               OpBranch %33
+         %33 = OpLabel
+         %43 = OpLoad %6 %29
+         %44 = OpIAdd %6 %43 %9
+               OpBranch %51
+         %51 = OpLabel
+               OpStore %29 %44
+               OpBranch %30
+         %32 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  const auto ops =
+      MergeBlocksReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(11, ops.size());
+
+  for (auto& ri : ops) {
+    ASSERT_TRUE(ri->PreconditionHolds());
+    ri->TryToApply();
+  }
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %10 "i"
+               OpName %29 "i"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 0
+         %18 = OpConstant %6 10
+         %19 = OpTypeBool
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %29 = OpVariable %7 Function
+               OpStore %8 %9
+               OpStore %10 %11
+               OpBranch %12
+         %12 = OpLabel
+         %17 = OpLoad %6 %10
+         %20 = OpSLessThan %19 %17 %18
+               OpLoopMerge %14 %13 None
+               OpBranchConditional %20 %13 %14
+         %13 = OpLabel
+         %21 = OpLoad %6 %10
+         %22 = OpLoad %6 %8
+         %23 = OpIAdd %6 %22 %21
+               OpStore %8 %23
+         %24 = OpLoad %6 %10
+         %25 = OpLoad %6 %8
+         %26 = OpIAdd %6 %25 %24
+               OpStore %8 %26
+         %27 = OpLoad %6 %10
+         %28 = OpIAdd %6 %27 %9
+               OpStore %10 %28
+               OpBranch %12
+         %14 = OpLabel
+               OpStore %29 %11
+               OpBranch %30
+         %30 = OpLabel
+         %35 = OpLoad %6 %29
+         %36 = OpSLessThan %19 %35 %18
+               OpLoopMerge %32 %31 None
+               OpBranchConditional %36 %31 %32
+         %31 = OpLabel
+         %37 = OpLoad %6 %29
+         %38 = OpLoad %6 %8
+         %39 = OpIAdd %6 %38 %37
+               OpStore %8 %39
+         %40 = OpLoad %6 %29
+         %41 = OpLoad %6 %8
+         %42 = OpIAdd %6 %41 %40
+               OpStore %8 %42
+         %43 = OpLoad %6 %29
+         %44 = OpIAdd %6 %43 %9
+               OpStore %29 %44
+               OpBranch %30
+         %32 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after, context.get());
+}
+
+TEST(MergeBlocksReductionPassTest, MergeWithOpPhi) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %10 "y"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+               OpStore %8 %9
+         %11 = OpLoad %6 %8
+               OpBranch %12
+         %12 = OpLabel
+         %13 = OpPhi %6 %11 %5
+               OpStore %10 %13
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  const auto ops =
+      MergeBlocksReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %10 "y"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+               OpStore %8 %9
+         %11 = OpLoad %6 %8
+               OpStore %10 %11
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after, context.get());
+}
+
+void MergeBlocksReductionPassTest_LoopReturn_Helper(bool reverse) {
+  // A merge block opportunity stores a block that can be merged with its
+  // predecessor.
+  // Given blocks A -> B -> C:
+  // This test demonstrates how merging B->C can invalidate
+  // the opportunity of merging A->B, and vice-versa. E.g.
+  // B->C are merged: B is now terminated with OpReturn.
+  // A->B can now no longer be merged because A is a loop header, which
+  // cannot be terminated with OpReturn.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantFalse %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel                   ; A (loop header)
+               OpLoopMerge %13 %12 None
+               OpBranch %11
+         %12 = OpLabel                   ; (unreachable continue block)
+               OpBranch %10
+         %11 = OpLabel                   ; B
+               OpBranch %15
+         %15 = OpLabel                   ; C
+               OpReturn
+         %13 = OpLabel                   ; (unreachable merge block)
+               OpReturn
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  ASSERT_NE(context.get(), nullptr);
+  auto opportunities =
+      MergeBlocksReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+
+  // A->B and B->C
+  ASSERT_EQ(opportunities.size(), 2);
+
+  // Test applying opportunities in both orders.
+  if (reverse) {
+    std::reverse(opportunities.begin(), opportunities.end());
+  }
+
+  size_t num_applied = 0;
+  for (auto& ri : opportunities) {
+    if (ri->PreconditionHolds()) {
+      ri->TryToApply();
+      ++num_applied;
+    }
+  }
+
+  // Only 1 opportunity can be applied, as both disable each other.
+  ASSERT_EQ(num_applied, 1);
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantFalse %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel                   ; A-B (loop header)
+               OpLoopMerge %13 %12 None
+               OpBranch %15
+         %12 = OpLabel                   ; (unreachable continue block)
+               OpBranch %10
+         %15 = OpLabel                   ; C
+               OpReturn
+         %13 = OpLabel                   ; (unreachable merge block)
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  // The only difference is the labels.
+  std::string after_reversed = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantFalse %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel                   ; A (loop header)
+               OpLoopMerge %13 %12 None
+               OpBranch %11
+         %12 = OpLabel                   ; (unreachable continue block)
+               OpBranch %10
+         %11 = OpLabel                   ; B-C
+               OpReturn
+         %13 = OpLabel                   ; (unreachable merge block)
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, reverse ? after_reversed : after, context.get());
+}
+
+TEST(MergeBlocksReductionPassTest, LoopReturn) {
+  MergeBlocksReductionPassTest_LoopReturn_Helper(false);
+}
+
+TEST(MergeBlocksReductionPassTest, LoopReturnReverse) {
+  MergeBlocksReductionPassTest_LoopReturn_Helper(true);
+}
+
+}  // namespace
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/test/reduce/operand_to_constant_reduction_pass_test.cpp b/test/reduce/operand_to_constant_test.cpp
similarity index 92%
rename from test/reduce/operand_to_constant_reduction_pass_test.cpp
rename to test/reduce/operand_to_constant_test.cpp
index 34cc4a1..b2f67ee 100644
--- a/test/reduce/operand_to_constant_reduction_pass_test.cpp
+++ b/test/reduce/operand_to_constant_test.cpp
@@ -12,9 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "reduce_test_util.h"
+#include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
+
 #include "source/opt/build_module.h"
-#include "source/reduce/operand_to_const_reduction_pass.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "test/reduce/reduce_test_util.h"
 
 namespace spvtools {
 namespace reduce {
@@ -97,8 +99,9 @@
   const auto consumer = nullptr;
   const auto context =
       BuildModule(env, consumer, original, kReduceAssembleOption);
-  const auto pass = TestSubclass<OperandToConstReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops =
+      OperandToConstReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
   ASSERT_EQ(17, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
   ops[0]->TryToApply();
@@ -146,8 +149,9 @@
   const auto consumer = nullptr;
   const auto context =
       BuildModule(env, consumer, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<OperandToConstReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops =
+      OperandToConstReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
   ASSERT_EQ(0, ops.size());
 }
 
diff --git a/test/reduce/operand_to_dominating_id_reduction_pass_test.cpp b/test/reduce/operand_to_dominating_id_test.cpp
similarity index 94%
rename from test/reduce/operand_to_dominating_id_reduction_pass_test.cpp
rename to test/reduce/operand_to_dominating_id_test.cpp
index cc0de65..cd5b2c6 100644
--- a/test/reduce/operand_to_dominating_id_reduction_pass_test.cpp
+++ b/test/reduce/operand_to_dominating_id_test.cpp
@@ -12,9 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "source/reduce/operand_to_dominating_id_reduction_pass.h"
-#include "reduce_test_util.h"
+#include "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h"
+
 #include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "test/reduce/reduce_test_util.h"
 
 namespace spvtools {
 namespace reduce {
@@ -53,8 +55,8 @@
   const auto consumer = nullptr;
   const auto context =
       BuildModule(env, consumer, original, kReduceAssembleOption);
-  const auto pass = TestSubclass<OperandToDominatingIdReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = OperandToDominatingIdReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
   ASSERT_EQ(10, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
   ops[0]->TryToApply();
diff --git a/test/reduce/operand_to_undef_test.cpp b/test/reduce/operand_to_undef_test.cpp
new file mode 100644
index 0000000..fa64bd5
--- /dev/null
+++ b/test/reduce/operand_to_undef_test.cpp
@@ -0,0 +1,230 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/operand_to_undef_reduction_opportunity_finder.h"
+
+#include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "test/reduce/reduce_test_util.h"
+
+namespace spvtools {
+namespace reduce {
+namespace {
+
+TEST(OperandToUndefReductionPassTest, BasicCheck) {
+  // The following shader has 10 opportunities for replacing with undef.
+
+  //    #version 310 es
+  //
+  //    precision highp float;
+  //
+  //    layout(location=0) out vec4 _GLF_color;
+  //
+  //    layout(set = 0, binding = 0) uniform buf0 {
+  //        vec2 uniform1;
+  //    };
+  //
+  //    void main()
+  //    {
+  //        _GLF_color =
+  //            vec4(                          // opportunity
+  //                uniform1.x / 2.0,          // opportunity x2 (2.0 is const)
+  //                uniform1.y / uniform1.x,   // opportunity x3
+  //                uniform1.x + uniform1.x,   // opportunity x3
+  //                uniform1.y);               // opportunity
+  //    }
+
+  std::string original = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %9
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %9 "_GLF_color"
+               OpName %11 "buf0"
+               OpMemberName %11 0 "uniform1"
+               OpName %13 ""
+               OpDecorate %9 Location 0
+               OpMemberDecorate %11 0 Offset 0
+               OpDecorate %11 Block
+               OpDecorate %13 DescriptorSet 0
+               OpDecorate %13 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+          %8 = OpTypePointer Output %7
+          %9 = OpVariable %8 Output
+         %10 = OpTypeVector %6 2
+         %11 = OpTypeStruct %10
+         %12 = OpTypePointer Uniform %11
+         %13 = OpVariable %12 Uniform
+         %14 = OpTypeInt 32 1
+         %15 = OpConstant %14 0
+         %16 = OpTypeInt 32 0
+         %17 = OpConstant %16 0
+         %18 = OpTypePointer Uniform %6
+         %21 = OpConstant %6 2
+         %23 = OpConstant %16 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %19 = OpAccessChain %18 %13 %15 %17
+         %20 = OpLoad %6 %19
+         %22 = OpFDiv %6 %20 %21                         ; opportunity %20 (%21 is const)
+         %24 = OpAccessChain %18 %13 %15 %23
+         %25 = OpLoad %6 %24
+         %26 = OpAccessChain %18 %13 %15 %17
+         %27 = OpLoad %6 %26
+         %28 = OpFDiv %6 %25 %27                         ; opportunity %25 %27
+         %29 = OpAccessChain %18 %13 %15 %17
+         %30 = OpLoad %6 %29
+         %31 = OpAccessChain %18 %13 %15 %17
+         %32 = OpLoad %6 %31
+         %33 = OpFAdd %6 %30 %32                         ; opportunity %30 %32
+         %34 = OpAccessChain %18 %13 %15 %23
+         %35 = OpLoad %6 %34
+         %36 = OpCompositeConstruct %7 %22 %28 %33 %35   ; opportunity %22 %28 %33 %35
+               OpStore %9 %36                            ; opportunity %36
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  // This is the same as original, except where noted.
+  std::string expected = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %9
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %9 "_GLF_color"
+               OpName %11 "buf0"
+               OpMemberName %11 0 "uniform1"
+               OpName %13 ""
+               OpDecorate %9 Location 0
+               OpMemberDecorate %11 0 Offset 0
+               OpDecorate %11 Block
+               OpDecorate %13 DescriptorSet 0
+               OpDecorate %13 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+          %8 = OpTypePointer Output %7
+          %9 = OpVariable %8 Output
+         %10 = OpTypeVector %6 2
+         %11 = OpTypeStruct %10
+         %12 = OpTypePointer Uniform %11
+         %13 = OpVariable %12 Uniform
+         %14 = OpTypeInt 32 1
+         %15 = OpConstant %14 0
+         %16 = OpTypeInt 32 0
+         %17 = OpConstant %16 0
+         %18 = OpTypePointer Uniform %6
+         %21 = OpConstant %6 2
+         %23 = OpConstant %16 1
+         %37 = OpUndef %6                            ; Added undef float as %37
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %19 = OpAccessChain %18 %13 %15 %17
+         %20 = OpLoad %6 %19
+         %22 = OpFDiv %6 %37 %21                     ; Replaced with %37
+         %24 = OpAccessChain %18 %13 %15 %23
+         %25 = OpLoad %6 %24
+         %26 = OpAccessChain %18 %13 %15 %17
+         %27 = OpLoad %6 %26
+         %28 = OpFDiv %6 %37 %37                     ; Replaced with %37 twice
+         %29 = OpAccessChain %18 %13 %15 %17
+         %30 = OpLoad %6 %29
+         %31 = OpAccessChain %18 %13 %15 %17
+         %32 = OpLoad %6 %31
+         %33 = OpFAdd %6 %30 %32
+         %34 = OpAccessChain %18 %13 %15 %23
+         %35 = OpLoad %6 %34
+         %36 = OpCompositeConstruct %7 %22 %28 %33 %35
+               OpStore %9 %36
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, original, kReduceAssembleOption);
+  const auto ops =
+      OperandToUndefReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+
+  ASSERT_EQ(10, ops.size());
+
+  // Apply first three opportunities.
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  ASSERT_TRUE(ops[1]->PreconditionHolds());
+  ops[1]->TryToApply();
+  ASSERT_TRUE(ops[2]->PreconditionHolds());
+  ops[2]->TryToApply();
+
+  CheckEqual(env, expected, context.get());
+}
+
+TEST(OperandToUndefReductionPassTest, WithCalledFunction) {
+  // The following shader has no opportunities.
+  // Most importantly, the noted function operand is not changed.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %10 %12
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+          %8 = OpTypeFunction %7
+          %9 = OpTypePointer Output %7
+         %10 = OpVariable %9 Output
+         %11 = OpTypePointer Input %7
+         %12 = OpVariable %11 Input
+         %13 = OpConstant %6 0
+         %14 = OpConstantComposite %7 %13 %13 %13 %13
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %15 = OpFunctionCall %7 %16            ; do not replace %16 with undef
+               OpReturn
+               OpFunctionEnd
+         %16 = OpFunction %7 None %8
+         %17 = OpLabel
+               OpReturnValue %14
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  const auto ops =
+      OperandToUndefReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+}  // namespace
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/test/reduce/reduce_test_util.cpp b/test/reduce/reduce_test_util.cpp
index 19ef749..0c23411 100644
--- a/test/reduce/reduce_test_util.cpp
+++ b/test/reduce/reduce_test_util.cpp
@@ -12,7 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "reduce_test_util.h"
+#include "test/reduce/reduce_test_util.h"
+
+#include <iostream>
+
+#include "tools/io.h"
 
 namespace spvtools {
 namespace reduce {
@@ -68,5 +72,41 @@
                    const spv_position_t& /*position*/,
                    const char* /*message*/) {}
 
+void CLIMessageConsumer(spv_message_level_t level, const char*,
+                        const spv_position_t& position, const char* message) {
+  switch (level) {
+    case SPV_MSG_FATAL:
+    case SPV_MSG_INTERNAL_ERROR:
+    case SPV_MSG_ERROR:
+      std::cerr << "error: line " << position.index << ": " << message
+                << std::endl;
+      break;
+    case SPV_MSG_WARNING:
+      std::cout << "warning: line " << position.index << ": " << message
+                << std::endl;
+      break;
+    case SPV_MSG_INFO:
+      std::cout << "info: line " << position.index << ": " << message
+                << std::endl;
+      break;
+    default:
+      break;
+  }
+}
+
+void DumpShader(opt::IRContext* context, const char* filename) {
+  std::vector<uint32_t> binary;
+  context->module()->ToBinary(&binary, false);
+  DumpShader(binary, filename);
+}
+
+void DumpShader(const std::vector<uint32_t>& binary, const char* filename) {
+  auto write_file_succeeded =
+      WriteFile(filename, "wb", &binary[0], binary.size());
+  if (!write_file_succeeded) {
+    std::cerr << "Failed to dump shader" << std::endl;
+  }
+}
+
 }  // namespace reduce
 }  // namespace spvtools
diff --git a/test/reduce/reduce_test_util.h b/test/reduce/reduce_test_util.h
index 499c774..b9ad12f 100644
--- a/test/reduce/reduce_test_util.h
+++ b/test/reduce/reduce_test_util.h
@@ -16,7 +16,6 @@
 #define TEST_REDUCE_REDUCE_TEST_UTIL_H_
 
 #include "gtest/gtest.h"
-
 #include "source/opt/ir_context.h"
 #include "source/reduce/reduction_opportunity.h"
 #include "spirv-tools/libspirv.h"
@@ -24,23 +23,6 @@
 namespace spvtools {
 namespace reduce {
 
-// A helper class that subclasses a given reduction pass class in order to
-// provide a wrapper for its protected methods.
-template <class ReductionPassT>
-class TestSubclass : public ReductionPassT {
- public:
-  // Creates an instance of the reduction pass subclass with respect to target
-  // environment |env|.
-  explicit TestSubclass(const spv_target_env env) : ReductionPassT(env) {}
-  ~TestSubclass() = default;
-
-  // A wrapper for GetAvailableOpportunities(...)
-  std::vector<std::unique_ptr<ReductionOpportunity>>
-  WrapGetAvailableOpportunities(opt::IRContext* context) const {
-    return ReductionPassT::GetAvailableOpportunities(context);
-  }
-};
-
 // Checks whether the given binaries are bit-wise equal.
 void CheckEqual(spv_target_env env,
                 const std::vector<uint32_t>& expected_binary,
@@ -76,6 +58,17 @@
 void NopDiagnostic(spv_message_level_t /*level*/, const char* /*source*/,
                    const spv_position_t& /*position*/, const char* /*message*/);
 
+// Prints reducer messages (for debugging).
+void CLIMessageConsumer(spv_message_level_t level, const char*,
+                        const spv_position_t& position, const char* message);
+
+// Dumps the SPIRV-V module in |context| to file |filename|. Useful for
+// interactive debugging.
+void DumpShader(opt::IRContext* context, const char* filename);
+
+// Dumps |binary| to file |filename|. Useful for interactive debugging.
+void DumpShader(const std::vector<uint32_t>& binary, const char* filename);
+
 }  // namespace reduce
 }  // namespace spvtools
 
diff --git a/test/reduce/reducer_test.cpp b/test/reduce/reducer_test.cpp
index 88fc5e4..8787733 100644
--- a/test/reduce/reducer_test.cpp
+++ b/test/reduce/reducer_test.cpp
@@ -12,12 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "reduce_test_util.h"
-
-#include "source/reduce/operand_to_const_reduction_pass.h"
 #include "source/reduce/reducer.h"
-#include "source/reduce/remove_opname_instruction_reduction_pass.h"
-#include "source/reduce/remove_unreferenced_instruction_reduction_pass.h"
+
+#include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
+#include "source/reduce/remove_opname_instruction_reduction_opportunity_finder.h"
+#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
+#include "test/reduce/reduce_test_util.h"
 
 namespace spvtools {
 namespace reduce {
@@ -217,9 +217,10 @@
       [&](const std::vector<uint32_t>& binary, uint32_t) -> bool {
         return ping_pong_interesting.IsInteresting(binary);
       });
-  reducer.AddReductionPass(MakeUnique<OperandToConstReductionPass>(env));
   reducer.AddReductionPass(
-      MakeUnique<RemoveUnreferencedInstructionReductionPass>(env));
+      MakeUnique<OperandToConstReductionOpportunityFinder>());
+  reducer.AddReductionPass(
+      MakeUnique<RemoveUnreferencedInstructionReductionOpportunityFinder>());
 
   std::vector<uint32_t> binary_in;
   SpirvTools t(env);
@@ -228,8 +229,13 @@
   std::vector<uint32_t> binary_out;
   spvtools::ReducerOptions reducer_options;
   reducer_options.set_step_limit(500);
+  reducer_options.set_fail_on_validation_error(true);
+  spvtools::ValidatorOptions validator_options;
 
-  reducer.Run(std::move(binary_in), &binary_out, reducer_options);
+  Reducer::ReductionResultStatus status = reducer.Run(
+      std::move(binary_in), &binary_out, reducer_options, validator_options);
+
+  ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete);
 
   CheckEqual(env, expected, binary_out);
 }
@@ -254,7 +260,7 @@
          %10 = OpLabel
           %3 = OpVariable %8 Function
           %4 = OpLoad %7 %3
-               OpStore %3 %7
+               OpStore %3 %9
                OpReturn
                OpFunctionEnd
   )";
@@ -279,7 +285,7 @@
 
   spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
   Reducer reducer(env);
-  // Make ping-pong interesting very quickly, as there are not much
+  // Make ping-pong interesting very quickly, as there are not many
   // opportunities.
   PingPongInteresting ping_pong_interesting(1);
   reducer.SetMessageConsumer(NopDiagnostic);
@@ -288,9 +294,9 @@
         return ping_pong_interesting.IsInteresting(binary);
       });
   reducer.AddReductionPass(
-      MakeUnique<RemoveOpNameInstructionReductionPass>(env));
+      MakeUnique<RemoveOpNameInstructionReductionOpportunityFinder>());
   reducer.AddReductionPass(
-      MakeUnique<RemoveUnreferencedInstructionReductionPass>(env));
+      MakeUnique<RemoveUnreferencedInstructionReductionOpportunityFinder>());
 
   std::vector<uint32_t> binary_in;
   SpirvTools t(env);
@@ -299,12 +305,17 @@
   std::vector<uint32_t> binary_out;
   spvtools::ReducerOptions reducer_options;
   reducer_options.set_step_limit(500);
+  reducer_options.set_fail_on_validation_error(true);
+  spvtools::ValidatorOptions validator_options;
 
-  reducer.Run(std::move(binary_in), &binary_out, reducer_options);
+  Reducer::ReductionResultStatus status = reducer.Run(
+      std::move(binary_in), &binary_out, reducer_options, validator_options);
+
+  ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete);
 
   CheckEqual(env, expected, binary_out);
 }
 
 }  // namespace
 }  // namespace reduce
-}  // namespace spvtools
\ No newline at end of file
+}  // namespace spvtools
diff --git a/test/reduce/remove_block_test.cpp b/test/reduce/remove_block_test.cpp
new file mode 100644
index 0000000..f31cc9d
--- /dev/null
+++ b/test/reduce/remove_block_test.cpp
@@ -0,0 +1,358 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_block_reduction_opportunity_finder.h"
+
+#include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "test/reduce/reduce_test_util.h"
+
+namespace spvtools {
+namespace reduce {
+namespace {
+
+TEST(RemoveBlockReductionPassTest, BasicCheck) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+         %11 = OpConstant %6 3
+         %12 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpBranch %14
+         %13 = OpLabel ; unreachable
+               OpStore %8 %9
+               OpBranch %14
+         %14 = OpLabel
+               OpStore %8 %10
+               OpBranch %16
+         %15 = OpLabel ; unreachable
+               OpStore %8 %11
+               OpBranch %16
+         %16 = OpLabel
+               OpStore %8 %12
+               OpBranch %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  const auto ops =
+      RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(2, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  std::string after_op_0 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+         %11 = OpConstant %6 3
+         %12 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpBranch %14
+         %14 = OpLabel
+               OpStore %8 %10
+               OpBranch %16
+         %15 = OpLabel
+               OpStore %8 %11
+               OpBranch %16
+         %16 = OpLabel
+               OpStore %8 %12
+               OpBranch %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_op_0, context.get());
+
+  ASSERT_TRUE(ops[1]->PreconditionHolds());
+  ops[1]->TryToApply();
+
+  std::string after_op_1 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+         %11 = OpConstant %6 3
+         %12 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpBranch %14
+         %14 = OpLabel
+               OpStore %8 %10
+               OpBranch %16
+         %16 = OpLabel
+               OpStore %8 %12
+               OpBranch %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_op_1, context.get());
+}
+
+TEST(RemoveBlockReductionPassTest, UnreachableContinueAndMerge) {
+  // Loop with unreachable merge and continue target. There should be no
+  // opportunities.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+               OpLoopMerge %16 %15 None
+               OpBranch %14
+         %14 = OpLabel
+               OpReturn
+         %15 = OpLabel
+               OpBranch %13
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  const auto ops =
+      RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveBlockReductionPassTest, OneBlock) {
+  // Function with just one block. There should be no opportunities.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  const auto ops =
+      RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveBlockReductionPassTest, UnreachableBlocksWithOutsideIdUses) {
+  // A function with two unreachable blocks A -> B. A defines ID %9 and B uses
+  // %9. There are no references to A, but removing A would be invalid because
+  // of B's use of %9, so there should be no opportunities.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeInt 32 1
+          %5 = OpTypeFunction %3
+          %6 = OpConstant %4 1
+          %2 = OpFunction %3 None %5
+          %7 = OpLabel
+               OpReturn
+          %8 = OpLabel          ; A
+          %9 = OpUndef %4
+               OpBranch %10
+         %10 = OpLabel          ; B
+         %11 = OpIAdd %4 %6 %9  ; uses %9 from A, so A cannot be removed
+               OpReturn
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  const auto ops =
+      RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveBlockReductionPassTest, UnreachableBlocksWithInsideIdUses) {
+  // Similar to the above test.
+
+  // A function with two unreachable blocks A -> B. Both blocks create and use
+  // IDs, but the uses are contained within each block, so A should be removed.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeInt 32 1
+          %5 = OpTypeFunction %3
+          %6 = OpConstant %4 1
+          %2 = OpFunction %3 None %5
+          %7 = OpLabel
+               OpReturn
+          %8 = OpLabel                     ; A
+          %9 = OpUndef %4                  ; define %9
+         %10 = OpIAdd %4 %6 %9             ; use %9
+               OpBranch %11
+         %11 = OpLabel                     ; B
+         %12 = OpUndef %4                  ; define %12
+         %13 = OpIAdd %4 %6 %12            ; use %12
+               OpReturn
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  auto ops = RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
+      context.get());
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+
+  ops[0]->TryToApply();
+
+  // Same as above, but block A is removed.
+  std::string after_op_0 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeInt 32 1
+          %5 = OpTypeFunction %3
+          %6 = OpConstant %4 1
+          %2 = OpFunction %3 None %5
+          %7 = OpLabel
+               OpReturn
+         %11 = OpLabel
+         %12 = OpUndef %4
+         %13 = OpIAdd %4 %6 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_op_0, context.get());
+
+  // Find opportunities again. There are no reference to B. B should now be
+  // removed.
+
+  ops = RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
+      context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+
+  ops[0]->TryToApply();
+
+  // Same as above, but block B is removed.
+  std::string after_op_0_again = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeInt 32 1
+          %5 = OpTypeFunction %3
+          %6 = OpConstant %4 1
+          %2 = OpFunction %3 None %5
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_op_0_again, context.get());
+}
+
+}  // namespace
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/test/reduce/remove_function_test.cpp b/test/reduce/remove_function_test.cpp
new file mode 100644
index 0000000..576b603
--- /dev/null
+++ b/test/reduce/remove_function_test.cpp
@@ -0,0 +1,295 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_function_reduction_opportunity_finder.h"
+
+#include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "test/reduce/reduce_test_util.h"
+
+namespace spvtools {
+namespace reduce {
+namespace {
+
+// Helper to count the number of functions in the module.
+// Remove if there turns out to be a more direct way to do this.
+uint32_t count_functions(opt::IRContext* context) {
+  uint32_t result = 0;
+  for (auto& function : *context->module()) {
+    (void)(function);
+    ++result;
+  }
+  return result;
+}
+
+TEST(RemoveFunctionTest, BasicCheck) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+         %10 = OpFunctionCall %2 %6
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+
+  ASSERT_EQ(3, count_functions(context.get()));
+
+  auto ops =
+      RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  ASSERT_EQ(2, count_functions(context.get()));
+
+  std::string after_first = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_first, context.get());
+
+  ops = RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
+      context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  ASSERT_EQ(1, count_functions(context.get()));
+
+  std::string after_second = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_second, context.get());
+}
+
+TEST(RemoveFunctionTest, NothingToRemove) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpFunctionCall %2 %8
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+         %10 = OpFunctionCall %2 %6
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  auto ops =
+      RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveFunctionTest, TwoRemovableFunctions) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+
+  ASSERT_EQ(3, count_functions(context.get()));
+
+  auto ops =
+      RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(2, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  ASSERT_EQ(2, count_functions(context.get()));
+  ASSERT_TRUE(ops[1]->PreconditionHolds());
+  ops[1]->TryToApply();
+  ASSERT_EQ(1, count_functions(context.get()));
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after, context.get());
+}
+
+TEST(RemoveFunctionTest, NoRemovalsDueToOpName) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "foo("
+               OpName %8 "bar("
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  auto ops =
+      RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveFunctionTest, NoRemovalDueToLinkageDecoration) {
+  // The non-entry point function is not removable because it is referenced by a
+  // linkage decoration. Thus no function can be removed.
+  std::string shader = R"(
+               OpCapability Shader
+               OpCapability Linkage
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %1 "main"
+               OpName %1 "main"
+               OpDecorate %2 LinkageAttributes "ExportedFunc" Export
+          %4 = OpTypeVoid
+          %5 = OpTypeFunction %4
+          %1 = OpFunction %4 None %5
+          %6 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %2 = OpFunction %4 None %5
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  auto ops =
+      RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+}  // namespace
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/test/reduce/remove_opname_instruction_reduction_pass_test.cpp b/test/reduce/remove_opname_instruction_test.cpp
similarity index 83%
rename from test/reduce/remove_opname_instruction_reduction_pass_test.cpp
rename to test/reduce/remove_opname_instruction_test.cpp
index 38a2d7f..9d40cfc 100644
--- a/test/reduce/remove_opname_instruction_reduction_pass_test.cpp
+++ b/test/reduce/remove_opname_instruction_test.cpp
@@ -12,11 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "reduce_test_util.h"
+#include "source/reduce/remove_opname_instruction_reduction_opportunity_finder.h"
 
 #include "source/opt/build_module.h"
 #include "source/reduce/reduction_opportunity.h"
-#include "source/reduce/remove_opname_instruction_reduction_pass.h"
+#include "source/reduce/reduction_pass.h"
+#include "test/reduce/reduce_test_util.h"
 
 namespace spvtools {
 namespace reduce {
@@ -42,8 +43,8 @@
   const auto consumer = nullptr;
   const auto context =
       BuildModule(env, consumer, source, kReduceAssembleOption);
-  const auto pass = TestSubclass<RemoveOpNameInstructionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = RemoveOpNameInstructionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
   ASSERT_EQ(0, ops.size());
 }
 
@@ -76,8 +77,8 @@
   const auto consumer = nullptr;
   const auto context =
       BuildModule(env, consumer, original, kReduceAssembleOption);
-  const auto pass = TestSubclass<RemoveOpNameInstructionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = RemoveOpNameInstructionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
   ASSERT_EQ(1, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
   ops[0]->TryToApply();
@@ -126,14 +127,14 @@
   const std::string expected = prologue + epilogue;
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
-  auto pass = TestSubclass<RemoveOpNameInstructionReductionPass>(env);
 
   {
     // Check the right number of opportunities is detected
     const auto consumer = nullptr;
     const auto context =
         BuildModule(env, consumer, original, kReduceAssembleOption);
-    const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+    const auto ops = RemoveOpNameInstructionReductionOpportunityFinder()
+                         .GetAvailableOpportunities(context.get());
     ASSERT_EQ(5, ops.size());
   }
 
@@ -142,7 +143,11 @@
     std::vector<uint32_t> binary;
     SpirvTools t(env);
     ASSERT_TRUE(t.Assemble(original, &binary, kReduceAssembleOption));
-    auto reduced_binary = pass.TryApplyReduction(binary);
+    auto reduced_binary =
+        ReductionPass(env,
+                      spvtools::MakeUnique<
+                          RemoveOpNameInstructionReductionOpportunityFinder>())
+            .TryApplyReduction(binary);
     CheckEqual(env, expected, reduced_binary);
   }
 }
@@ -190,14 +195,14 @@
   const std::string expected = prologue + epilogue;
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
-  auto pass = TestSubclass<RemoveOpNameInstructionReductionPass>(env);
 
   {
     // Check the right number of opportunities is detected
     const auto consumer = nullptr;
     const auto context =
         BuildModule(env, consumer, original, kReduceAssembleOption);
-    const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+    const auto ops = RemoveOpNameInstructionReductionOpportunityFinder()
+                         .GetAvailableOpportunities(context.get());
     ASSERT_EQ(6, ops.size());
   }
 
@@ -206,7 +211,11 @@
     std::vector<uint32_t> binary;
     SpirvTools t(env);
     ASSERT_TRUE(t.Assemble(original, &binary, kReduceAssembleOption));
-    auto reduced_binary = pass.TryApplyReduction(binary);
+    auto reduced_binary =
+        ReductionPass(env,
+                      spvtools::MakeUnique<
+                          RemoveOpNameInstructionReductionOpportunityFinder>())
+            .TryApplyReduction(binary);
     CheckEqual(env, expected, reduced_binary);
   }
 }
diff --git a/test/reduce/remove_selection_test.cpp b/test/reduce/remove_selection_test.cpp
new file mode 100644
index 0000000..f8acd5d
--- /dev/null
+++ b/test/reduce/remove_selection_test.cpp
@@ -0,0 +1,557 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_selection_reduction_opportunity_finder.h"
+
+#include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "test/reduce/reduce_test_util.h"
+
+namespace spvtools {
+namespace reduce {
+namespace {
+
+TEST(RemoveSelectionTest, OpportunityBecauseSameTargetBlock) {
+  // A test with the following structure. The OpSelectionMerge instruction
+  // should be removed.
+  //
+  // header
+  // ||
+  // block
+  // |
+  // merge
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpSelectionMerge %10 None
+               OpBranchConditional %8 %11 %11
+         %11 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+
+  auto ops =
+      RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(env, context.get());
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranchConditional %8 %11 %11
+         %11 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+  CheckEqual(env, after, context.get());
+
+  ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
+      context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveSelectionTest, OpportunityBecauseSameTargetBlockMerge) {
+  // A test with the following structure. The OpSelectionMerge instruction
+  // should be removed.
+  //
+  // header
+  // ||
+  // merge
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpSelectionMerge %10 None
+               OpBranchConditional %8 %10 %10
+         %10 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+
+  auto ops =
+      RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(env, context.get());
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranchConditional %8 %10 %10
+         %10 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+  CheckEqual(env, after, context.get());
+
+  ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
+      context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveSelectionTest, NoOpportunityBecauseDifferentTargetBlocksOneMerge) {
+  // A test with the following structure. The OpSelectionMerge instruction
+  // should NOT be removed.
+  //
+  // header
+  // |  |
+  // | block
+  // |  |
+  // merge
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpSelectionMerge %10 None
+               OpBranchConditional %8 %10 %11
+         %11 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+
+  auto ops =
+      RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveSelectionTest, NoOpportunityBecauseDifferentTargetBlocks) {
+  // A test with the following structure. The OpSelectionMerge instruction
+  // should NOT be removed.
+  //
+  // header
+  // | |
+  // b b
+  // | |
+  // merge
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpSelectionMerge %10 None
+               OpBranchConditional %8 %11 %12
+         %11 = OpLabel
+               OpBranch %10
+         %12 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+
+  auto ops =
+      RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveSelectionTest, NoOpportunityBecauseMergeUsed) {
+  // A test with the following structure. The OpSelectionMerge instruction
+  // should NOT be removed.
+  //
+  // header
+  // ||
+  // block
+  // |  |
+  // | block
+  // |  |
+  // merge
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpSelectionMerge %10 None
+               OpBranchConditional %8 %11 %12
+         %11 = OpLabel
+               OpBranchConditional %8 %10 %12
+         %12 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+
+  auto ops =
+      RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveSelectionTest, OpportunityBecauseLoopMergeUsed) {
+  // A test with the following structure. The OpSelectionMerge instruction
+  // should be removed.
+  //
+  // loop header
+  //    |
+  //    |
+  //   s.header
+  //    ||
+  //   block
+  //    |    |
+  //    |     |
+  //    |      |    ^ (to loop header)
+  //   s.merge |    |
+  //    |     /   loop continue target (unreachable)
+  // loop merge
+  //
+  //
+  // which becomes:
+  //
+  // loop header
+  //    |
+  //    |
+  //   block
+  //    ||
+  //   block
+  //    |    |
+  //    |     |
+  //    |      |    ^ (to loop header)
+  //   block   |    |
+  //    |     /   loop continue target (unreachable)
+  // loop merge
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %12 None
+               OpBranch %13
+         %13 = OpLabel
+               OpSelectionMerge %14 None
+               OpBranchConditional %8 %15 %15
+         %15 = OpLabel
+               OpBranchConditional %8 %14 %11
+         %14 = OpLabel
+               OpBranch %11
+         %12 = OpLabel
+               OpBranch %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(env, context.get());
+
+  auto ops =
+      RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(env, context.get());
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %12 None
+               OpBranch %13
+         %13 = OpLabel
+               OpBranchConditional %8 %15 %15
+         %15 = OpLabel
+               OpBranchConditional %8 %14 %11
+         %14 = OpLabel
+               OpBranch %11
+         %12 = OpLabel
+               OpBranch %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+  CheckEqual(env, after, context.get());
+
+  ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
+      context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveSelectionTest, OpportunityBecauseLoopContinueUsed) {
+  // A test with the following structure. The OpSelectionMerge instruction
+  // should be removed.
+  //
+  // loop header
+  //    |
+  //    |
+  //   s.header
+  //    ||
+  //   block
+  //    |    |
+  //    |     |
+  //    |      |    ^ (to loop header)
+  //   s.merge |    |
+  //    |     loop continue target
+  // loop merge
+  //
+  //
+  // which becomes:
+  //
+  // loop header
+  //    |
+  //    |
+  //   block
+  //    ||
+  //   block
+  //    |    |
+  //    |     |
+  //    |      |    ^ (to loop header)
+  //   block   |    |
+  //    |     loop continue target
+  // loop merge
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %12 None
+               OpBranch %13
+         %13 = OpLabel
+               OpSelectionMerge %14 None
+               OpBranchConditional %8 %15 %15
+         %15 = OpLabel
+               OpBranchConditional %8 %14 %12
+         %14 = OpLabel
+               OpBranch %11
+         %12 = OpLabel
+               OpBranch %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(env, context.get());
+
+  auto ops =
+      RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(env, context.get());
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %12 None
+               OpBranch %13
+         %13 = OpLabel
+               OpBranchConditional %8 %15 %15
+         %15 = OpLabel
+               OpBranchConditional %8 %14 %12
+         %14 = OpLabel
+               OpBranch %11
+         %12 = OpLabel
+               OpBranch %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+  CheckEqual(env, after, context.get());
+
+  ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
+      context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+}  // namespace
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/test/reduce/remove_unreferenced_instruction_reduction_pass_test.cpp b/test/reduce/remove_unreferenced_instruction_reduction_pass_test.cpp
deleted file mode 100644
index a002fa3..0000000
--- a/test/reduce/remove_unreferenced_instruction_reduction_pass_test.cpp
+++ /dev/null
@@ -1,230 +0,0 @@
-// Copyright (c) 2018 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "reduce_test_util.h"
-
-#include "source/opt/build_module.h"
-#include "source/reduce/reduction_opportunity.h"
-#include "source/reduce/remove_unreferenced_instruction_reduction_pass.h"
-
-namespace spvtools {
-namespace reduce {
-namespace {
-
-TEST(RemoveUnreferencedInstructionReductionPassTest, RemoveStores) {
-  const std::string prologue = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-               OpName %4 "main"
-               OpName %8 "a"
-               OpName %10 "b"
-               OpName %12 "c"
-               OpName %14 "d"
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeInt 32 1
-          %7 = OpTypePointer Function %6
-          %9 = OpConstant %6 10
-         %11 = OpConstant %6 20
-         %13 = OpConstant %6 30
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-          %8 = OpVariable %7 Function
-         %10 = OpVariable %7 Function
-         %12 = OpVariable %7 Function
-         %14 = OpVariable %7 Function
-  )";
-
-  const std::string epilogue = R"(
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  const std::string original = prologue + R"(
-               OpStore %8 %9
-               OpStore %10 %11
-               OpStore %12 %13
-         %15 = OpLoad %6 %8
-               OpStore %14 %15
-  )" + epilogue;
-
-  const std::string expected = prologue + R"(
-               OpStore %12 %13
-         %15 = OpLoad %6 %8
-               OpStore %14 %15
-  )" + epilogue;
-
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-  const auto consumer = nullptr;
-  const auto context =
-      BuildModule(env, consumer, original, kReduceAssembleOption);
-  const auto pass =
-      TestSubclass<RemoveUnreferencedInstructionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
-  ASSERT_EQ(4, ops.size());
-  ASSERT_TRUE(ops[0]->PreconditionHolds());
-  ops[0]->TryToApply();
-  ASSERT_TRUE(ops[1]->PreconditionHolds());
-  ops[1]->TryToApply();
-
-  CheckEqual(env, expected, context.get());
-}
-
-TEST(RemoveUnreferencedInstructionReductionPassTest, ApplyReduction) {
-  const std::string prologue = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-               OpName %4 "main"
-               OpName %8 "a"
-               OpName %10 "b"
-               OpName %12 "c"
-               OpName %14 "d"
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeInt 32 1
-          %7 = OpTypePointer Function %6
-          %9 = OpConstant %6 10
-         %11 = OpConstant %6 20
-         %13 = OpConstant %6 30
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-          %8 = OpVariable %7 Function
-         %10 = OpVariable %7 Function
-         %12 = OpVariable %7 Function
-         %14 = OpVariable %7 Function
-  )";
-
-  const std::string epilogue = R"(
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  const std::string original = prologue + R"(
-               OpStore %8 %9
-               OpStore %10 %11
-               OpStore %12 %13
-         %15 = OpLoad %6 %8
-               OpStore %14 %15
-  )" + epilogue;
-
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-
-  std::vector<uint32_t> binary;
-  SpirvTools t(env);
-  ASSERT_TRUE(t.Assemble(original, &binary, kReduceAssembleOption));
-
-  auto pass = TestSubclass<RemoveUnreferencedInstructionReductionPass>(env);
-
-  {
-    // Attempt 1 should remove everything removable.
-    const std::string expected_reduced = prologue + R"(
-         %15 = OpLoad %6 %8
-    )" + epilogue;
-    auto reduced_binary = pass.TryApplyReduction(binary);
-    CheckEqual(env, expected_reduced, reduced_binary);
-  }
-
-  // Attempt 2 should fail as pass with granularity 4 got to end.
-  ASSERT_EQ(0, pass.TryApplyReduction(binary).size());
-
-  {
-    // Attempt 3 should remove first two removable statements.
-    const std::string expected_reduced = prologue + R"(
-               OpStore %12 %13
-         %15 = OpLoad %6 %8
-               OpStore %14 %15
-    )" + epilogue;
-    auto reduced_binary = pass.TryApplyReduction(binary);
-    CheckEqual(env, expected_reduced, reduced_binary);
-  }
-
-  {
-    // Attempt 4 should remove last two removable statements.
-    const std::string expected_reduced = prologue + R"(
-               OpStore %8 %9
-               OpStore %10 %11
-         %15 = OpLoad %6 %8
-    )" + epilogue;
-    auto reduced_binary = pass.TryApplyReduction(binary);
-    CheckEqual(env, expected_reduced, reduced_binary);
-  }
-
-  // Attempt 5 should fail as pass with granularity 2 got to end.
-  ASSERT_EQ(0, pass.TryApplyReduction(binary).size());
-
-  {
-    // Attempt 6 should remove first removable statement.
-    const std::string expected_reduced = prologue + R"(
-               OpStore %10 %11
-               OpStore %12 %13
-         %15 = OpLoad %6 %8
-               OpStore %14 %15
-    )" + epilogue;
-    auto reduced_binary = pass.TryApplyReduction(binary);
-    CheckEqual(env, expected_reduced, reduced_binary);
-  }
-
-  {
-    // Attempt 7 should remove second removable statement.
-    const std::string expected_reduced = prologue + R"(
-               OpStore %8 %9
-               OpStore %12 %13
-         %15 = OpLoad %6 %8
-               OpStore %14 %15
-    )" + epilogue;
-    auto reduced_binary = pass.TryApplyReduction(binary);
-    CheckEqual(env, expected_reduced, reduced_binary);
-  }
-
-  {
-    // Attempt 8 should remove third removable statement.
-    const std::string expected_reduced = prologue + R"(
-               OpStore %8 %9
-               OpStore %10 %11
-         %15 = OpLoad %6 %8
-               OpStore %14 %15
-    )" + epilogue;
-    auto reduced_binary = pass.TryApplyReduction(binary);
-    CheckEqual(env, expected_reduced, reduced_binary);
-  }
-
-  {
-    // Attempt 9 should remove fourth removable statement.
-    const std::string expected_reduced = prologue + R"(
-               OpStore %8 %9
-               OpStore %10 %11
-               OpStore %12 %13
-         %15 = OpLoad %6 %8
-    )" + epilogue;
-    auto reduced_binary = pass.TryApplyReduction(binary);
-    CheckEqual(env, expected_reduced, reduced_binary);
-  }
-
-  // Attempt 10 should fail as pass with granularity 1 got to end.
-  ASSERT_EQ(0, pass.TryApplyReduction(binary).size());
-
-  ASSERT_TRUE(pass.ReachedMinimumGranularity());
-}
-
-}  // namespace
-}  // namespace reduce
-}  // namespace spvtools
diff --git a/test/reduce/remove_unreferenced_instruction_test.cpp b/test/reduce/remove_unreferenced_instruction_test.cpp
new file mode 100644
index 0000000..0babf78
--- /dev/null
+++ b/test/reduce/remove_unreferenced_instruction_test.cpp
@@ -0,0 +1,101 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
+
+#include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "source/util/make_unique.h"
+#include "test/reduce/reduce_test_util.h"
+
+namespace spvtools {
+namespace reduce {
+namespace {
+
+TEST(RemoveUnreferencedInstructionReductionPassTest, RemoveStores) {
+  const std::string prologue = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "a"
+               OpName %10 "b"
+               OpName %12 "c"
+               OpName %14 "d"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 10
+         %11 = OpConstant %6 20
+         %13 = OpConstant %6 30
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %12 = OpVariable %7 Function
+         %14 = OpVariable %7 Function
+  )";
+
+  const std::string epilogue = R"(
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const std::string original = prologue + R"(
+               OpStore %8 %9
+               OpStore %10 %11
+               OpStore %12 %13
+         %15 = OpLoad %6 %8
+               OpStore %14 %15
+  )" + epilogue;
+
+  const std::string expected_after_2 = prologue + R"(
+               OpStore %12 %13
+         %15 = OpLoad %6 %8
+               OpStore %14 %15
+  )" + epilogue;
+
+  const std::string expected_after_4 = prologue + R"(
+         %15 = OpLoad %6 %8
+  )" + epilogue;
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, original, kReduceAssembleOption);
+  const auto ops = RemoveUnreferencedInstructionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(4, ops.size());
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  ASSERT_TRUE(ops[1]->PreconditionHolds());
+  ops[1]->TryToApply();
+
+  CheckEqual(env, expected_after_2, context.get());
+
+  ASSERT_TRUE(ops[2]->PreconditionHolds());
+  ops[2]->TryToApply();
+  ASSERT_TRUE(ops[3]->PreconditionHolds());
+  ops[3]->TryToApply();
+
+  CheckEqual(env, expected_after_4, context.get());
+}
+
+}  // namespace
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/test/reduce/simple_conditional_branch_to_branch_test.cpp b/test/reduce/simple_conditional_branch_to_branch_test.cpp
new file mode 100644
index 0000000..d55e691
--- /dev/null
+++ b/test/reduce/simple_conditional_branch_to_branch_test.cpp
@@ -0,0 +1,486 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h"
+
+#include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "source/reduce/reduction_pass.h"
+#include "test/reduce/reduce_test_util.h"
+
+namespace spvtools {
+namespace reduce {
+namespace {
+
+const spv_target_env kEnv = SPV_ENV_UNIVERSAL_1_3;
+
+TEST(SimpleConditionalBranchToBranchTest, Diamond) {
+  // A test with the following structure.
+  //
+  // selection header
+  // OpBranchConditional
+  //  ||
+  //  b        b
+  //  |        |
+  //  selection merge
+  //
+  // The conditional branch cannot be simplified because selection headers
+  // cannot end with OpBranch.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpSelectionMerge %11 None
+               OpBranchConditional %8 %12 %12
+         %12 = OpLabel
+               OpBranch %11
+         %13 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+    )";
+
+  auto context = BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(kEnv, context.get());
+
+  auto ops = SimpleConditionalBranchToBranchOpportunityFinder()
+                 .GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(SimpleConditionalBranchToBranchTest, DiamondNoSelection) {
+  // A test with the following structure.
+  //
+  // OpBranchConditional
+  //  ||
+  //  b  b
+  //  | /
+  //  b
+  //
+  // The conditional branch can be simplified.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpBranchConditional %8 %12 %12
+         %12 = OpLabel
+               OpBranch %11
+         %13 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  auto context = BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(kEnv, context.get());
+
+  auto ops = SimpleConditionalBranchToBranchOpportunityFinder()
+                 .GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(kEnv, context.get());
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpBranch %12
+         %12 = OpLabel
+               OpBranch %11
+         %13 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  CheckEqual(kEnv, after, context.get());
+
+  ops = SimpleConditionalBranchToBranchOpportunityFinder()
+            .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(SimpleConditionalBranchToBranchTest, ConditionalBranchesButNotSimple) {
+  // A test with the following structure.
+  //
+  // selection header
+  // OpBranchConditional
+  //  |        |
+  //  b        OpBranchConditional
+  //  |        |   |
+  //  |        b   |
+  //  |        |   |
+  //  selection merge
+  //
+  // None of the conditional branches can be simplified; the first is not simple
+  // AND part of a selection header; the second is just not simple (where
+  // "simple" means it only has one target).
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpSelectionMerge %11 None
+               OpBranchConditional %8 %12 %13
+         %12 = OpLabel
+               OpBranch %11
+         %13 = OpLabel
+               OpBranchConditional %8 %14 %11
+         %14 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  auto context = BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(kEnv, context.get());
+
+  auto ops = SimpleConditionalBranchToBranchOpportunityFinder()
+                 .GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(SimpleConditionalBranchToBranchTest, SimplifyBackEdge) {
+  // A test with the following structure. The loop has a continue construct that
+  // ends with OpBranchConditional. The OpBranchConditional can be simplified.
+  //
+  // loop header
+  //   |
+  //   loop continue target and back-edge block
+  //   OpBranchConditional
+  //                  ||
+  // loop merge       (to loop header^)
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %12 None
+               OpBranch %12
+         %12 = OpLabel
+               OpBranchConditional %8 %10 %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto context =
+      BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(kEnv, context.get());
+
+  auto ops = SimpleConditionalBranchToBranchOpportunityFinder()
+                 .GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(kEnv, context.get());
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %12 None
+               OpBranch %12
+         %12 = OpLabel
+               OpBranch %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+  CheckEqual(kEnv, after, context.get());
+
+  ops = SimpleConditionalBranchToBranchOpportunityFinder()
+            .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(SimpleConditionalBranchToBranchTest,
+     DontRemoveBackEdgeCombinedHeaderContinue) {
+  // A test with the following structure.
+  //
+  // loop header and continue target and back-edge block
+  //   OpBranchConditional
+  //                  ||
+  // loop merge       (to loop header^)
+  //
+  // The conditional branch can be simplified.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %10 None
+               OpBranchConditional %8 %10 %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto context =
+      BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(kEnv, context.get());
+
+  auto ops = SimpleConditionalBranchToBranchOpportunityFinder()
+                 .GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(kEnv, context.get());
+
+  std::string after = R"(
+          OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %10 None
+               OpBranch %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+  CheckEqual(kEnv, after, context.get());
+
+  ops = SimpleConditionalBranchToBranchOpportunityFinder()
+            .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(SimpleConditionalBranchToBranchTest, BackEdgeUnreachable) {
+  // A test with the following structure. I.e. a loop with an unreachable
+  // continue construct that ends with OpBranchConditional.
+  //
+  // loop header
+  //   |
+  //   | loop continue target (unreachable)
+  //   |      |
+  //   | back-edge block (unreachable)
+  //   | OpBranchConditional
+  //   |               ||
+  // loop merge       (to loop header^)
+  //
+  // The conditional branch can be simplified.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %12 None
+               OpBranch %11
+         %12 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+               OpBranchConditional %8 %10 %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto context =
+      BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(kEnv, context.get());
+
+  auto ops = SimpleConditionalBranchToBranchOpportunityFinder()
+                 .GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(kEnv, context.get());
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %11 %12 None
+               OpBranch %11
+         %12 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+               OpBranch %10
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+  CheckEqual(kEnv, after, context.get());
+
+  ops = SimpleConditionalBranchToBranchOpportunityFinder()
+            .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+}  // namespace
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/test/reduce/structured_loop_to_selection_reduction_pass_test.cpp b/test/reduce/structured_loop_to_selection_test.cpp
similarity index 90%
rename from test/reduce/structured_loop_to_selection_reduction_pass_test.cpp
rename to test/reduce/structured_loop_to_selection_test.cpp
index 8388cb2..95b5f4f 100644
--- a/test/reduce/structured_loop_to_selection_reduction_pass_test.cpp
+++ b/test/reduce/structured_loop_to_selection_test.cpp
@@ -12,9 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "source/reduce/structured_loop_to_selection_reduction_pass.h"
-#include "reduce_test_util.h"
+#include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h"
+
 #include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "test/reduce/reduce_test_util.h"
 
 namespace spvtools {
 namespace reduce {
@@ -62,8 +64,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -208,8 +210,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
   ASSERT_EQ(4, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -677,8 +679,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
   ASSERT_EQ(0, ops.size());
 }
 
@@ -755,8 +757,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
 
   // Initially there are two opportunities.
   ASSERT_EQ(2, ops.size());
@@ -878,8 +880,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -953,8 +955,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -1021,8 +1023,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -1221,8 +1223,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
 
   ASSERT_EQ(2, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -1688,8 +1690,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
 
   ASSERT_EQ(2, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2005,8 +2007,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2153,8 +2155,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2310,8 +2312,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2418,8 +2420,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2508,8 +2510,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
 
   // There should be no opportunities.
   ASSERT_EQ(0, ops.size());
@@ -2552,8 +2554,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
 
   // There should be no opportunities.
   ASSERT_EQ(0, ops.size());
@@ -2592,8 +2594,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
 
   ASSERT_EQ(1, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2667,8 +2669,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
 
   ASSERT_EQ(1, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2730,8 +2732,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
 
   ASSERT_EQ(1, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2787,8 +2789,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
 
   ASSERT_EQ(1, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2871,8 +2873,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
 
   ASSERT_EQ(1, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2967,8 +2969,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
 
   ASSERT_EQ(2, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -3086,8 +3088,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
 
   ASSERT_EQ(2, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -3206,8 +3208,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                 .GetAvailableOpportunities(context.get());
 
   // We cannot transform the inner loop due to its header jumping straight to
   // the outer loop merge (the inner loop's merge does not post-dominate its
@@ -3251,7 +3253,8 @@
   CheckEqual(env, after_op_0, context.get());
 
   // Now look again for more opportunities.
-  ops = pass.WrapGetAvailableOpportunities(context.get());
+  ops = StructuredLoopToSelectionReductionOpportunityFinder()
+            .GetAvailableOpportunities(context.get());
 
   // What was the inner loop should now be transformable, as the jump to the
   // outer loop's merge has been redirected.
@@ -3418,8 +3421,8 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
-  const auto pass = TestSubclass<StructuredLoopToSelectionReductionPass>(env);
-  auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                 .GetAvailableOpportunities(context.get());
 
   ASSERT_EQ(1, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -3435,6 +3438,191 @@
   // CheckEqual(env, expected, context.get());
 }
 
+TEST(StructuredLoopToSelectionReductionPassTest, LoopyShaderWithOpDecorate) {
+  // A shader containing a function that contains a loop and some definitions
+  // that are "used" in OpDecorate instructions (outside the function). These
+  // "uses" were causing segfaults because we try to calculate their dominance
+  // information, which doesn't make sense.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %9
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %9 "_GLF_color"
+               OpName %14 "buf0"
+               OpMemberName %14 0 "a"
+               OpName %16 ""
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %9 Location 0
+               OpMemberDecorate %14 0 RelaxedPrecision
+               OpMemberDecorate %14 0 Offset 0
+               OpDecorate %14 Block
+               OpDecorate %16 DescriptorSet 0
+               OpDecorate %16 Binding 0
+               OpDecorate %21 RelaxedPrecision
+               OpDecorate %35 RelaxedPrecision
+               OpDecorate %36 RelaxedPrecision
+               OpDecorate %39 RelaxedPrecision
+               OpDecorate %40 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+          %8 = OpTypePointer Output %7
+          %9 = OpVariable %8 Output
+         %10 = OpConstant %6 1
+         %11 = OpConstantComposite %7 %10 %10 %10 %10
+         %14 = OpTypeStruct %6
+         %15 = OpTypePointer Uniform %14
+         %16 = OpVariable %15 Uniform
+         %17 = OpTypeInt 32 1
+         %18 = OpConstant %17 0
+         %19 = OpTypePointer Uniform %6
+         %28 = OpConstant %6 2
+         %29 = OpTypeBool
+         %31 = OpTypeInt 32 0
+         %32 = OpConstant %31 0
+         %33 = OpTypePointer Output %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpStore %9 %11
+         %20 = OpAccessChain %19 %16 %18
+         %21 = OpLoad %6 %20
+               OpBranch %22
+         %22 = OpLabel
+         %40 = OpPhi %6 %21 %5 %39 %23
+         %30 = OpFOrdLessThan %29 %40 %28
+               OpLoopMerge %24 %23 None
+               OpBranchConditional %30 %23 %24
+         %23 = OpLabel
+         %34 = OpAccessChain %33 %9 %32
+         %35 = OpLoad %6 %34
+         %36 = OpFAdd %6 %35 %10
+               OpStore %34 %36
+         %39 = OpFAdd %6 %40 %10
+               OpBranch %22
+         %24 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(env, context.get());
+
+  std::string after_op_0 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %9
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %9 "_GLF_color"
+               OpName %14 "buf0"
+               OpMemberName %14 0 "a"
+               OpName %16 ""
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %9 Location 0
+               OpMemberDecorate %14 0 RelaxedPrecision
+               OpMemberDecorate %14 0 Offset 0
+               OpDecorate %14 Block
+               OpDecorate %16 DescriptorSet 0
+               OpDecorate %16 Binding 0
+               OpDecorate %21 RelaxedPrecision
+               OpDecorate %35 RelaxedPrecision
+               OpDecorate %36 RelaxedPrecision
+               OpDecorate %39 RelaxedPrecision
+               OpDecorate %40 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+          %8 = OpTypePointer Output %7
+          %9 = OpVariable %8 Output
+         %10 = OpConstant %6 1
+         %11 = OpConstantComposite %7 %10 %10 %10 %10
+         %14 = OpTypeStruct %6
+         %15 = OpTypePointer Uniform %14
+         %16 = OpVariable %15 Uniform
+         %17 = OpTypeInt 32 1
+         %18 = OpConstant %17 0
+         %19 = OpTypePointer Uniform %6
+         %28 = OpConstant %6 2
+         %29 = OpTypeBool
+         %31 = OpTypeInt 32 0
+         %32 = OpConstant %31 0
+         %33 = OpTypePointer Output %6
+         %41 = OpUndef %6                          ; Added
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpStore %9 %11
+         %20 = OpAccessChain %19 %16 %18
+         %21 = OpLoad %6 %20
+               OpBranch %22
+         %22 = OpLabel
+         %40 = OpPhi %6 %21 %5 %41 %23             ; Changed
+         %30 = OpFOrdLessThan %29 %40 %28
+               OpSelectionMerge %24 None           ; Changed
+               OpBranchConditional %30 %24 %24
+         %23 = OpLabel
+         %34 = OpAccessChain %33 %9 %32
+         %35 = OpLoad %6 %34
+         %36 = OpFAdd %6 %35 %10
+               OpStore %34 %36
+         %39 = OpFAdd %6 %41 %10                   ; Changed
+               OpBranch %22
+         %24 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, after_op_0, context.get());
+}
+
+TEST(StructuredLoopToSelectionReductionPassTest,
+     LoopWithCombinedHeaderAndContinue) {
+  // A shader containing a loop where the header is also the continue target.
+  // For now, we don't simplify such loops.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool
+         %30 = OpConstantFalse %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %10
+         %10 = OpLabel                       ; loop header and continue target
+               OpLoopMerge %12 %10 None
+               OpBranchConditional %30 %10 %12
+         %12 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
 }  // namespace
 }  // namespace reduce
 }  // namespace spvtools
diff --git a/test/reduce/validation_during_reduction_test.cpp b/test/reduce/validation_during_reduction_test.cpp
index bb7d14e..612b657 100644
--- a/test/reduce/validation_during_reduction_test.cpp
+++ b/test/reduce/validation_during_reduction_test.cpp
@@ -12,38 +12,39 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "reduce_test_util.h"
-
 #include "source/reduce/reducer.h"
-#include "source/reduce/reduction_pass.h"
+
+#include "source/reduce/reduction_opportunity.h"
 #include "source/reduce/remove_instruction_reduction_opportunity.h"
+#include "test/reduce/reduce_test_util.h"
 
 namespace spvtools {
 namespace reduce {
 namespace {
 
-// A dumb reduction pass that removes global values regardless of whether they
-// are referenced. This is very likely to make the resulting module invalid.  We
-// use this to test the reducer's behavior in the scenario where a bad reduction
-// pass leads to an invalid module.
-class BlindlyRemoveGlobalValuesPass : public ReductionPass {
- public:
-  // Creates the reduction pass in the context of the given target environment
-  // |target_env|
-  explicit BlindlyRemoveGlobalValuesPass(const spv_target_env target_env)
-      : ReductionPass(target_env) {}
+using opt::Function;
+using opt::IRContext;
+using opt::Instruction;
 
-  ~BlindlyRemoveGlobalValuesPass() override = default;
+// A dumb reduction opportunity finder that finds opportunities to remove global
+// values regardless of whether they are referenced. This is very likely to make
+// the resulting module invalid.  We use this to test the reducer's behavior in
+// the scenario where a bad reduction pass leads to an invalid module.
+class BlindlyRemoveGlobalValuesReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  BlindlyRemoveGlobalValuesReductionOpportunityFinder() = default;
+
+  ~BlindlyRemoveGlobalValuesReductionOpportunityFinder() override = default;
 
   // The name of this pass.
   std::string GetName() const final { return "BlindlyRemoveGlobalValuesPass"; };
 
- protected:
-  // Adds opportunities to remove all global values.  Assuming they are all
+  // Finds opportunities to remove all global values.  Assuming they are all
   // referenced (directly or indirectly) from elsewhere in the module, each such
   // opportunity will make the module invalid.
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final {
+      IRContext* context) const final {
     std::vector<std::unique_ptr<ReductionOpportunity>> result;
     for (auto& inst : context->module()->types_values()) {
       if (inst.HasResultId()) {
@@ -55,9 +56,70 @@
   }
 };
 
+// A dumb reduction opportunity that exists at the start of every function whose
+// first instruction is an OpVariable instruction. When applied, the OpVariable
+// instruction is duplicated (with a fresh result id). This allows each
+// reduction step to increase the number of variables to check if the validator
+// limits are enforced.
+class OpVariableDuplicatorReductionOpportunity : public ReductionOpportunity {
+ public:
+  OpVariableDuplicatorReductionOpportunity(Function* function)
+      : function_(function) {}
+
+  bool PreconditionHolds() override {
+    Instruction* first_instruction = &*function_->begin()[0].begin();
+    return first_instruction->opcode() == SpvOpVariable;
+  }
+
+ protected:
+  void Apply() override {
+    // Duplicate the first OpVariable instruction.
+
+    Instruction* first_instruction = &*function_->begin()[0].begin();
+    assert(first_instruction->opcode() == SpvOpVariable &&
+           "Expected first instruction to be OpVariable");
+    IRContext* context = first_instruction->context();
+    Instruction* cloned_instruction = first_instruction->Clone(context);
+    cloned_instruction->SetResultId(context->TakeNextId());
+    cloned_instruction->InsertBefore(first_instruction);
+  }
+
+ private:
+  Function* function_;
+};
+
+// A reduction opportunity finder that finds
+// OpVariableDuplicatorReductionOpportunity.
+class OpVariableDuplicatorReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  OpVariableDuplicatorReductionOpportunityFinder() = default;
+
+  ~OpVariableDuplicatorReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final {
+    return "LocalVariableAdderReductionOpportunityFinder";
+  };
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      IRContext* context) const final {
+    std::vector<std::unique_ptr<ReductionOpportunity>> result;
+    for (auto& function : *context->module()) {
+      Instruction* first_instruction = &*function.begin()[0].begin();
+      if (first_instruction->opcode() == SpvOpVariable) {
+        result.push_back(
+            MakeUnique<OpVariableDuplicatorReductionOpportunity>(&function));
+      }
+    }
+    return result;
+  }
+};
+
 TEST(ValidationDuringReductionTest, CheckInvalidPassMakesNoProgress) {
   // A module whose global values are all referenced, so that any application of
-  // MakeModuleInvalidPass will make the module invalid.
+  // MakeModuleInvalidPass will make the module invalid. Check that the reducer
+  // makes no progress, as every step will be invalid and treated as
+  // uninteresting.
   std::string original = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
@@ -153,7 +215,8 @@
   reducer.SetInterestingnessFunction(
       [](const std::vector<uint32_t>&, uint32_t) -> bool { return true; });
 
-  reducer.AddReductionPass(MakeUnique<BlindlyRemoveGlobalValuesPass>(env));
+  reducer.AddReductionPass(
+      MakeUnique<BlindlyRemoveGlobalValuesReductionOpportunityFinder>());
 
   std::vector<uint32_t> binary_in;
   SpirvTools t(env);
@@ -162,8 +225,14 @@
   std::vector<uint32_t> binary_out;
   spvtools::ReducerOptions reducer_options;
   reducer_options.set_step_limit(500);
+  // Don't fail on a validation error; just treat it as uninteresting.
+  reducer_options.set_fail_on_validation_error(false);
+  spvtools::ValidatorOptions validator_options;
 
-  reducer.Run(std::move(binary_in), &binary_out, reducer_options);
+  Reducer::ReductionResultStatus status = reducer.Run(
+      std::move(binary_in), &binary_out, reducer_options, validator_options);
+
+  ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete);
 
   // The reducer should have no impact.
   CheckEqual(env, original, binary_out);
@@ -357,7 +426,8 @@
   reducer.SetInterestingnessFunction(
       [](const std::vector<uint32_t>&, uint32_t) -> bool { return true; });
 
-  reducer.AddReductionPass(MakeUnique<BlindlyRemoveGlobalValuesPass>(env));
+  reducer.AddReductionPass(
+      MakeUnique<BlindlyRemoveGlobalValuesReductionOpportunityFinder>());
 
   std::vector<uint32_t> binary_in;
   SpirvTools t(env);
@@ -366,11 +436,149 @@
   std::vector<uint32_t> binary_out;
   spvtools::ReducerOptions reducer_options;
   reducer_options.set_step_limit(500);
+  // Don't fail on a validation error; just treat it as uninteresting.
+  reducer_options.set_fail_on_validation_error(false);
+  spvtools::ValidatorOptions validator_options;
 
-  reducer.Run(std::move(binary_in), &binary_out, reducer_options);
+  Reducer::ReductionResultStatus status = reducer.Run(
+      std::move(binary_in), &binary_out, reducer_options, validator_options);
+
+  ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete);
+
   CheckEqual(env, expected, binary_out);
 }
 
+// Sets up a Reducer for use in the CheckValidationOptions test; avoids
+// repetition.
+void SetupReducerForCheckValidationOptions(Reducer* reducer) {
+  reducer->SetMessageConsumer(NopDiagnostic);
+
+  // Say that every module is interesting.
+  reducer->SetInterestingnessFunction(
+      [](const std::vector<uint32_t>&, uint32_t) -> bool { return true; });
+
+  // Each "reduction" step will duplicate the first OpVariable instruction in
+  // the function.
+  reducer->AddReductionPass(
+      MakeUnique<OpVariableDuplicatorReductionOpportunityFinder>());
+}
+
+TEST(ValidationDuringReductionTest, CheckValidationOptions) {
+  // A module that only validates when the "skip-block-layout" validator option
+  // is used. Also, the entry point's first instruction creates a local
+  // variable; this instruction will be duplicated on each reduction step.
+  std::string original = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %2 "Main" %3
+               OpSource HLSL 600
+               OpDecorate %3 BuiltIn Position
+               OpDecorate %4 DescriptorSet 0
+               OpDecorate %4 Binding 99
+               OpDecorate %5 ArrayStride 16
+               OpMemberDecorate %6 0 Offset 0
+               OpMemberDecorate %6 1 Offset 32
+               OpMemberDecorate %6 1 MatrixStride 16
+               OpMemberDecorate %6 1 ColMajor
+               OpMemberDecorate %6 2 Offset 96
+               OpMemberDecorate %6 3 Offset 100
+               OpMemberDecorate %6 4 Offset 112
+               OpMemberDecorate %6 4 MatrixStride 16
+               OpMemberDecorate %6 4 ColMajor
+               OpMemberDecorate %6 5 Offset 176
+               OpDecorate %6 Block
+          %7 = OpTypeFloat 32
+          %8 = OpTypeVector %7 4
+          %9 = OpTypeMatrix %8 4
+         %10 = OpTypeVector %7 2
+         %11 = OpTypeInt 32 1
+         %12 = OpTypeInt 32 0
+         %13 = OpConstant %12 2
+         %14 = OpConstant %11 1
+         %15 = OpConstant %11 5
+          %5 = OpTypeArray %8 %13
+          %6 = OpTypeStruct %5 %9 %12 %10 %9 %7
+         %16 = OpTypePointer Uniform %6
+         %17 = OpTypePointer Output %8
+         %18 = OpTypeVoid
+         %19 = OpTypeFunction %18
+         %20 = OpTypePointer Uniform %7
+          %4 = OpVariable %16 Uniform
+          %3 = OpVariable %17 Output
+         %21 = OpTypePointer Function %11
+          %2 = OpFunction %18 None %19
+         %22 = OpLabel
+         %23 = OpVariable %21 Function
+         %24 = OpAccessChain %20 %4 %15
+         %25 = OpLoad %7 %24
+         %26 = OpCompositeConstruct %8 %25 %25 %25 %25
+               OpStore %3 %26
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  std::vector<uint32_t> binary_in;
+  SpirvTools t(env);
+
+  ASSERT_TRUE(t.Assemble(original, &binary_in, kReduceAssembleOption));
+  std::vector<uint32_t> binary_out;
+  spvtools::ReducerOptions reducer_options;
+  spvtools::ValidatorOptions validator_options;
+
+  reducer_options.set_step_limit(3);
+  reducer_options.set_fail_on_validation_error(true);
+
+  // Reduction should fail because the initial state is invalid without the
+  // "skip-block-layout" validator option. Note that the interestingness test
+  // always returns true.
+  {
+    Reducer reducer(env);
+    SetupReducerForCheckValidationOptions(&reducer);
+
+    Reducer::ReductionResultStatus status =
+        reducer.Run(std::vector<uint32_t>(binary_in), &binary_out,
+                    reducer_options, validator_options);
+
+    ASSERT_EQ(status, Reducer::ReductionResultStatus::kInitialStateInvalid);
+  }
+
+  // Try again with validator option.
+  validator_options.SetSkipBlockLayout(true);
+
+  // Reduction should hit step limit; module is seen as valid, interestingness
+  // test always succeeds, and the finder yields infinite opportunities.
+  {
+    Reducer reducer(env);
+    SetupReducerForCheckValidationOptions(&reducer);
+
+    Reducer::ReductionResultStatus status =
+        reducer.Run(std::vector<uint32_t>(binary_in), &binary_out,
+                    reducer_options, validator_options);
+
+    ASSERT_EQ(status, Reducer::ReductionResultStatus::kReachedStepLimit);
+  }
+
+  // Now set a limit on the number of local variables.
+  validator_options.SetUniversalLimit(spv_validator_limit_max_local_variables,
+                                      2);
+
+  // Reduction should now fail due to reaching an invalid state; after one step,
+  // a local variable is added and the module becomes "invalid" given the
+  // validator limits.
+  {
+    Reducer reducer(env);
+    SetupReducerForCheckValidationOptions(&reducer);
+
+    Reducer::ReductionResultStatus status =
+        reducer.Run(std::vector<uint32_t>(binary_in), &binary_out,
+                    reducer_options, validator_options);
+
+    ASSERT_EQ(status, Reducer::ReductionResultStatus::kStateInvalid);
+  }
+}
+
 }  // namespace
 }  // namespace reduce
 }  // namespace spvtools
diff --git a/test/target_env_test.cpp b/test/target_env_test.cpp
index f962464..9f80e83 100644
--- a/test/target_env_test.cpp
+++ b/test/target_env_test.cpp
@@ -45,8 +45,8 @@
   ASSERT_THAT(spirv_version, AnyOf(0x10000, 0x10100, 0x10200, 0x10300));
 }
 
-INSTANTIATE_TEST_CASE_P(AllTargetEnvs, TargetEnvTest,
-                        ValuesIn(spvtest::AllTargetEnvironments()));
+INSTANTIATE_TEST_SUITE_P(AllTargetEnvs, TargetEnvTest,
+                         ValuesIn(spvtest::AllTargetEnvironments()));
 
 TEST(GetContextTest, InvalidTargetEnvProducesNull) {
   // Use a value beyond the last valid enum value.
@@ -70,7 +70,7 @@
   EXPECT_THAT(env, Eq(GetParam().env));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TargetParsing, TargetParseTest,
     ValuesIn(std::vector<ParseCase>{
         {"spv1.0", true, SPV_ENV_UNIVERSAL_1_0},
diff --git a/test/test_fixture.h b/test/test_fixture.h
index e85015c..436993e 100644
--- a/test/test_fixture.h
+++ b/test/test_fixture.h
@@ -61,6 +61,8 @@
   // compilation success. Returns the compiled code.
   SpirvVector CompileSuccessfully(const std::string& txt,
                                   spv_target_env env = SPV_ENV_UNIVERSAL_1_0) {
+    DestroyBinary();
+    DestroyDiagnostic();
     spv_result_t status =
         spvTextToBinary(ScopedContext(env).context, txt.c_str(), txt.size(),
                         &binary, &diagnostic);
@@ -79,6 +81,8 @@
   // Returns the error message(s).
   std::string CompileFailure(const std::string& txt,
                              spv_target_env env = SPV_ENV_UNIVERSAL_1_0) {
+    DestroyBinary();
+    DestroyDiagnostic();
     EXPECT_NE(SPV_SUCCESS,
               spvTextToBinary(ScopedContext(env).context, txt.c_str(),
                               txt.size(), &binary, &diagnostic))
@@ -94,6 +98,7 @@
       uint32_t disassemble_options = SPV_BINARY_TO_TEXT_OPTION_NONE,
       spv_target_env env = SPV_ENV_UNIVERSAL_1_0) {
     DestroyBinary();
+    DestroyDiagnostic();
     ScopedContext context(env);
     disassemble_options |= SPV_BINARY_TO_TEXT_OPTION_NO_HEADER;
     spv_result_t error = spvTextToBinary(context.context, txt.c_str(),
@@ -126,6 +131,8 @@
   // Returns the error message.
   std::string EncodeSuccessfullyDecodeFailed(
       const std::string& txt, const SpirvVector& words_to_append) {
+    DestroyBinary();
+    DestroyDiagnostic();
     SpirvVector code =
         spvtest::Concatenate({CompileSuccessfully(txt), words_to_append});
 
@@ -169,6 +176,12 @@
     binary = nullptr;
   }
 
+  // Destroys the diagnostic, if it exists.
+  void DestroyDiagnostic() {
+    spvDiagnosticDestroy(diagnostic);
+    diagnostic = nullptr;
+  }
+
   spv_diagnostic diagnostic;
 
   std::string textString;
diff --git a/test/text_literal_test.cpp b/test/text_literal_test.cpp
index 7028089..28e3500 100644
--- a/test/text_literal_test.cpp
+++ b/test/text_literal_test.cpp
@@ -106,7 +106,7 @@
   EXPECT_EQ(std::get<1>(GetParam()), l.str);
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextLiteral, GoodStringTest,
     ::testing::ValuesIn(std::vector<std::pair<const char*, const char*>>{
         {R"("-")", "-"},
@@ -121,7 +121,7 @@
         {"\"\xE4\xBA\xB2\"", "\xE4\xBA\xB2"},
         {"\"\\\xE4\xBA\xB2\"", "\xE4\xBA\xB2"},
         {"\"this \\\" and this \\\\ and \\\xE4\xBA\xB2\"",
-         "this \" and this \\ and \xE4\xBA\xB2"}}), );
+         "this \" and this \\ and \xE4\xBA\xB2"}}));
 
 TEST(TextLiteral, StringTooLong) {
   spv_literal_t l;
@@ -230,7 +230,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DecimalIntegers, IntegerTest,
     ::testing::ValuesIn(std::vector<TextLiteralCase>{
         // Check max value and overflow value for 1-bit numbers.
@@ -277,7 +277,7 @@
         Make_Ok__Unsigned(64, "18446744073709551615", {0xffffffff, 0xffffffff}),
         Make_Ok__Signed(64, "-9223372036854775808", {0x00000000, 0x80000000}),
 
-    }),);
+    }));
 // clang-format on
 
 using IntegerLeadingMinusTest =
@@ -290,7 +290,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DecimalAndHexIntegers, IntegerLeadingMinusTest,
     ::testing::ValuesIn(std::vector<TextLiteralCase>{
         // Unsigned numbers never allow a leading minus sign.
@@ -303,10 +303,10 @@
         Make_Bad_Unsigned(64, "-0"),
         Make_Bad_Unsigned(64, "-0x0"),
         Make_Bad_Unsigned(64, "-0x1"),
-    }),);
+    }));
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     HexIntegers, IntegerTest,
     ::testing::ValuesIn(std::vector<TextLiteralCase>{
         // Check 0x and 0X prefices.
@@ -370,7 +370,7 @@
         Make_Ok__Signed(64, "0x8000000000000000", {0x00000000, 0x80000000}),
         Make_Ok__Unsigned(64, "0x7fffffffffffffff", {0xffffffff, 0x7fffffff}),
         Make_Ok__Unsigned(64, "0x8000000000000000", {0x00000000, 0x80000000}),
-    }),);
+    }));
 // clang-format on
 
 TEST(OverflowIntegerParse, Decimal) {
diff --git a/test/text_to_binary.annotation_test.cpp b/test/text_to_binary.annotation_test.cpp
index 7aec905..69a4861 100644
--- a/test/text_to_binary.annotation_test.cpp
+++ b/test/text_to_binary.annotation_test.cpp
@@ -60,8 +60,31 @@
       Eq(input.str()));
 }
 
+// Like above, but parameters to the decoration are IDs.
+using OpDecorateSimpleIdTest =
+    spvtest::TextToBinaryTestBase<::testing::TestWithParam<
+        std::tuple<spv_target_env, EnumCase<SpvDecoration>>>>;
+
+TEST_P(OpDecorateSimpleIdTest, AnySimpleDecoration) {
+  // This string should assemble, but should not validate.
+  std::stringstream input;
+  input << "OpDecorateId %1 " << std::get<1>(GetParam()).name();
+  for (auto operand : std::get<1>(GetParam()).operands())
+    input << " %" << operand;
+  input << std::endl;
+  EXPECT_THAT(CompiledInstructions(input.str(), std::get<0>(GetParam())),
+              Eq(MakeInstruction(SpvOpDecorateId,
+                                 {1, uint32_t(std::get<1>(GetParam()).value())},
+                                 std::get<1>(GetParam()).operands())));
+  // Also check disassembly.
+  EXPECT_THAT(
+      EncodeAndDecodeSuccessfully(input.str(), SPV_BINARY_TO_TEXT_OPTION_NONE,
+                                  std::get<0>(GetParam())),
+      Eq(input.str()));
+}
+
 #define CASE(NAME) SpvDecoration##NAME, #NAME
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryDecorateSimple, OpDecorateSimpleTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCase<SpvDecoration>>{
@@ -106,12 +129,27 @@
                 {CASE(NoContraction), {}},
                 {CASE(InputAttachmentIndex), {102}},
                 {CASE(Alignment), {16}},
-            })), );
+            })));
 
-INSTANTIATE_TEST_CASE_P(TextToBinaryDecorateSimpleV11, OpDecorateSimpleTest,
-                        Combine(Values(SPV_ENV_UNIVERSAL_1_1),
-                                Values(EnumCase<SpvDecoration>{
-                                    CASE(MaxByteOffset), {128}})), );
+INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateSimpleV11, OpDecorateSimpleTest,
+                         Combine(Values(SPV_ENV_UNIVERSAL_1_1),
+                                 Values(EnumCase<SpvDecoration>{
+                                     CASE(MaxByteOffset), {128}})));
+
+INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateSimpleV14, OpDecorateSimpleTest,
+                         Combine(Values(SPV_ENV_UNIVERSAL_1_4),
+                                 ValuesIn(std::vector<EnumCase<SpvDecoration>>{
+                                     {CASE(Uniform), {}},
+                                 })));
+
+INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateSimpleIdV14,
+                         OpDecorateSimpleIdTest,
+                         Combine(Values(SPV_ENV_UNIVERSAL_1_4),
+                                 ValuesIn(std::vector<EnumCase<SpvDecoration>>{
+                                     // In 1.4, UniformId decoration takes a
+                                     // scope Id.
+                                     {CASE(UniformId), {1}},
+                                 })));
 #undef CASE
 
 TEST_F(OpDecorateSimpleTest, WrongDecoration) {
@@ -164,7 +202,7 @@
 // clang-format off
 #define CASE(NAME) \
   { SpvBuiltIn##NAME, #NAME, SpvDecorationBuiltIn, "BuiltIn" }
-INSTANTIATE_TEST_CASE_P(TextToBinaryDecorateBuiltIn, OpDecorateEnumTest,
+INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateBuiltIn, OpDecorateEnumTest,
                         ::testing::ValuesIn(std::vector<DecorateEnumCase>{
                             CASE(Position),
                             CASE(PointSize),
@@ -209,7 +247,7 @@
                             CASE(SubgroupLocalInvocationId),
                             CASE(VertexIndex),
                             CASE(InstanceIndex),
-                        }),);
+                        }));
 #undef CASE
 // clang-format on
 
@@ -222,7 +260,7 @@
 // clang-format off
 #define CASE(NAME) \
   { SpvFunctionParameterAttribute##NAME, #NAME, SpvDecorationFuncParamAttr, "FuncParamAttr" }
-INSTANTIATE_TEST_CASE_P(TextToBinaryDecorateFuncParamAttr, OpDecorateEnumTest,
+INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateFuncParamAttr, OpDecorateEnumTest,
                         ::testing::ValuesIn(std::vector<DecorateEnumCase>{
                             CASE(Zext),
                             CASE(Sext),
@@ -232,7 +270,7 @@
                             CASE(NoCapture),
                             CASE(NoWrite),
                             CASE(NoReadWrite),
-                      }),);
+                      }));
 #undef CASE
 // clang-format on
 
@@ -245,13 +283,13 @@
 // clang-format off
 #define CASE(NAME) \
   { SpvFPRoundingMode##NAME, #NAME, SpvDecorationFPRoundingMode, "FPRoundingMode" }
-INSTANTIATE_TEST_CASE_P(TextToBinaryDecorateFPRoundingMode, OpDecorateEnumTest,
+INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateFPRoundingMode, OpDecorateEnumTest,
                         ::testing::ValuesIn(std::vector<DecorateEnumCase>{
                             CASE(RTE),
                             CASE(RTZ),
                             CASE(RTP),
                             CASE(RTN),
-                      }),);
+                      }));
 #undef CASE
 // clang-format on
 
@@ -268,7 +306,7 @@
 // clang-format off
 #define CASE(ENUM,NAME) \
   { SpvFPFastMathMode##ENUM, #NAME, SpvDecorationFPFastMathMode, "FPFastMathMode" }
-INSTANTIATE_TEST_CASE_P(TextToBinaryDecorateFPFastMathMode, OpDecorateEnumTest,
+INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateFPFastMathMode, OpDecorateEnumTest,
                         ::testing::ValuesIn(std::vector<DecorateEnumCase>{
                             CASE(MaskNone, None),
                             CASE(NotNaNMask, NotNaN),
@@ -276,7 +314,7 @@
                             CASE(NSZMask, NSZ),
                             CASE(AllowRecipMask, AllowRecip),
                             CASE(FastMask, Fast),
-                      }),);
+                      }));
 #undef CASE
 // clang-format on
 
@@ -329,13 +367,13 @@
 
 // clang-format off
 #define CASE(ENUM) SpvLinkageType##ENUM, #ENUM
-INSTANTIATE_TEST_CASE_P(TextToBinaryDecorateLinkage, OpDecorateLinkageTest,
+INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateLinkage, OpDecorateLinkageTest,
                         ::testing::ValuesIn(std::vector<DecorateLinkageCase>{
                             { CASE(Import), "a" },
                             { CASE(Export), "foo" },
                             { CASE(Import), "some kind of long name with spaces etc." },
                             // TODO(dneto): utf-8, escaping, quoting cases.
-                      }),);
+                      }));
 #undef CASE
 // clang-format on
 
@@ -423,7 +461,7 @@
 }
 
 #define CASE(NAME) SpvDecoration##NAME, #NAME
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryDecorateSimple, OpMemberDecorateSimpleTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCase<SpvDecoration>>{
@@ -468,12 +506,12 @@
                 {CASE(NoContraction), {}},
                 {CASE(InputAttachmentIndex), {102}},
                 {CASE(Alignment), {16}},
-            })), );
+            })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryDecorateSimpleV11, OpMemberDecorateSimpleTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_1),
-            Values(EnumCase<SpvDecoration>{CASE(MaxByteOffset), {128}})), );
+            Values(EnumCase<SpvDecoration>{CASE(MaxByteOffset), {128}})));
 #undef CASE
 
 TEST_F(OpMemberDecorateSimpleTest, WrongDecoration) {
diff --git a/test/text_to_binary.composite_test.cpp b/test/text_to_binary.composite_test.cpp
new file mode 100644
index 0000000..6ae1cd3
--- /dev/null
+++ b/test/text_to_binary.composite_test.cpp
@@ -0,0 +1,49 @@
+// Copyright (c) 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Assembler tests for instructions in the "Group Instrucions" section of the
+// SPIR-V spec.
+
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "test/test_fixture.h"
+#include "test/unit_spirv.h"
+
+using ::testing::Eq;
+using ::testing::HasSubstr;
+
+namespace spvtools {
+namespace {
+
+using spvtest::Concatenate;
+
+using CompositeRoundTripTest = RoundTripTest;
+
+TEST_F(CompositeRoundTripTest, Good) {
+  std::string spirv = "%2 = OpCopyLogical %1 %3\n";
+  std::string disassembly = EncodeAndDecodeSuccessfully(
+      spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(CompositeRoundTripTest, V13Bad) {
+  std::string spirv = "%2 = OpCopyLogical %1 %3\n";
+  std::string err = CompileFailure(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_THAT(err, HasSubstr("Invalid Opcode name 'OpCopyLogical'"));
+}
+
+}  // namespace
+}  // namespace spvtools
diff --git a/test/text_to_binary.constant_test.cpp b/test/text_to_binary.constant_test.cpp
index 1a24b52..6624d41 100644
--- a/test/text_to_binary.constant_test.cpp
+++ b/test/text_to_binary.constant_test.cpp
@@ -47,7 +47,7 @@
 
 // clang-format off
 #define CASE(NAME) { SpvSamplerAddressingMode##NAME, #NAME }
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinarySamplerAddressingMode, SamplerAddressingModeTest,
     ::testing::ValuesIn(std::vector<EnumCase<SpvSamplerAddressingMode>>{
         CASE(None),
@@ -55,7 +55,7 @@
         CASE(Clamp),
         CASE(Repeat),
         CASE(RepeatMirrored),
-    }),);
+    }));
 #undef CASE
 // clang-format on
 
@@ -79,12 +79,12 @@
 
 // clang-format off
 #define CASE(NAME) { SpvSamplerFilterMode##NAME, #NAME}
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinarySamplerFilterMode, SamplerFilterModeTest,
     ::testing::ValuesIn(std::vector<EnumCase<SpvSamplerFilterMode>>{
         CASE(Nearest),
         CASE(Linear),
-    }),);
+    }));
 #undef CASE
 // clang-format on
 
@@ -114,7 +114,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryOpConstantValid, OpConstantValidTest,
     ::testing::ValuesIn(std::vector<ConstantTestCase>{
       // Check 16 bits
@@ -232,7 +232,7 @@
       {"OpTypeInt 64 1", "0x7fffffff",
         Concatenate({MakeInstruction(SpvOpTypeInt, {1, 64, 1}),
          MakeInstruction(SpvOpConstant, {1, 2, 0x7fffffffu, 0})})},
-    }),);
+    }));
 // clang-format on
 
 // A test case for checking OpConstant with invalid literals with a leading
@@ -255,7 +255,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryOpConstantInvalidLeadingMinus, OpConstantInvalidLeadingMinusTest,
     ::testing::ValuesIn(std::vector<InvalidLeadingMinusCase>{
       {"OpTypeInt 16 0", "-0"},
@@ -267,7 +267,7 @@
       {"OpTypeInt 64 0", "-0"},
       {"OpTypeInt 64 0", "-0x0"},
       {"OpTypeInt 64 0", "-1"},
-    }),);
+    }));
 // clang-format on
 
 // A test case for invalid floating point literals.
@@ -293,7 +293,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryInvalidFloatConstant, OpConstantInvalidFloatConstant,
     ::testing::ValuesIn(std::vector<InvalidFloatConstantCase>{
         {16, "abc"},
@@ -323,7 +323,7 @@
         {64, "++1"},
         {32, "1e400"}, // Overflow is an error for 64-bit floats.
         {32, "-1e400"},
-    }),);
+    }));
 // clang-format on
 
 using OpConstantInvalidTypeTest =
@@ -339,7 +339,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryOpConstantInvalidValidType, OpConstantInvalidTypeTest,
     ::testing::ValuesIn(std::vector<std::string>{
       {"OpTypeVoid",
@@ -364,7 +364,7 @@
         // At least one thing that isn't a type at all
        "OpNot %a %b"
       },
-    }),);
+    }));
 // clang-format on
 
 using OpSpecConstantValidTest =
@@ -381,7 +381,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryOpSpecConstantValid, OpSpecConstantValidTest,
     ::testing::ValuesIn(std::vector<ConstantTestCase>{
       // Check 16 bits
@@ -433,7 +433,7 @@
       {"OpTypeInt 64 1", "-42",
         Concatenate({MakeInstruction(SpvOpTypeInt, {1, 64, 1}),
          MakeInstruction(SpvOpSpecConstant, {1, 2, uint32_t(-42), uint32_t(-1)})})},
-    }),);
+    }));
 // clang-format on
 
 using OpSpecConstantInvalidTypeTest =
@@ -449,7 +449,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryOpSpecConstantInvalidValidType, OpSpecConstantInvalidTypeTest,
     ::testing::ValuesIn(std::vector<std::string>{
       {"OpTypeVoid",
@@ -474,14 +474,14 @@
         // At least one thing that isn't a type at all
        "OpNot %a %b"
       },
-    }),);
+    }));
 // clang-format on
 
 const int64_t kMaxUnsigned48Bit = (int64_t(1) << 48) - 1;
 const int64_t kMaxSigned48Bit = (int64_t(1) << 47) - 1;
 const int64_t kMinSigned48Bit = -kMaxSigned48Bit - 1;
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpConstantRoundTrip, RoundTripTest,
     ::testing::ValuesIn(std::vector<std::string>{
         // 16 bit
@@ -522,9 +522,9 @@
         "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0\n",
         "%1 = OpTypeFloat 64\n%2 = OpConstant %1 1.79767e+308\n",
         "%1 = OpTypeFloat 64\n%2 = OpConstant %1 -1.79767e+308\n",
-    }), );
+    }));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpConstantHalfRoundTrip, RoundTripTest,
     ::testing::ValuesIn(std::vector<std::string>{
         "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x0p+0\n",
@@ -557,11 +557,11 @@
         "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.ffp+16\n",   // -nan
         "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.ffcp+16\n",  // -nan
         "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.004p+16\n",  // -nan
-    }), );
+    }));
 
 // clang-format off
 // (Clang-format really wants to break up these strings across lines.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpConstantRoundTripNonFinite, RoundTripTest,
     ::testing::ValuesIn(std::vector<std::string>{
   "%1 = OpTypeFloat 32\n%2 = OpConstant %1 -0x1p+128\n",         // -inf
@@ -588,10 +588,10 @@
   "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0x1.0000000000001p+1024\n",   // -nan
   "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0x1.00003p+1024\n",           // -nan
   "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0x1.fffffffffffffp+1024\n",   // -nan
-    }),);
+    }));
 // clang-format on
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpSpecConstantRoundTrip, RoundTripTest,
     ::testing::ValuesIn(std::vector<std::string>{
         // 16 bit
@@ -632,7 +632,7 @@
         "%1 = OpTypeFloat 64\n%2 = OpSpecConstant %1 0\n",
         "%1 = OpTypeFloat 64\n%2 = OpSpecConstant %1 1.79767e+308\n",
         "%1 = OpTypeFloat 64\n%2 = OpSpecConstant %1 -1.79767e+308\n",
-    }), );
+    }));
 
 // Test OpSpecConstantOp
 
@@ -662,7 +662,7 @@
 #define CASE4(NAME) { SpvOp##NAME, #NAME, {3, 4, 5, 6} }
 #define CASE5(NAME) { SpvOp##NAME, #NAME, {3, 4, 5, 6, 7} }
 #define CASE6(NAME) { SpvOp##NAME, #NAME, {3, 4, 5, 6, 7, 8} }
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryOpSpecConstantOp, OpSpecConstantOpTestWithIds,
     ::testing::ValuesIn(std::vector<EnumCase<SpvOp>>{
         // Conversion
@@ -738,7 +738,7 @@
         CASE2(InBoundsPtrAccessChain),
         CASE3(InBoundsPtrAccessChain),
         CASE6(InBoundsPtrAccessChain),
-    }),);
+    }));
 #undef CASE1
 #undef CASE2
 #undef CASE3
@@ -768,7 +768,7 @@
 }
 
 #define CASE(NAME) SpvOp##NAME, #NAME
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryOpSpecConstantOp,
     OpSpecConstantOpTestWithTwoIdsThenLiteralNumbers,
     ::testing::ValuesIn(std::vector<EnumCase<SpvOp>>{
@@ -783,7 +783,7 @@
         // composite, and then literal indices.
         {CASE(CompositeInsert), {0}},
         {CASE(CompositeInsert), {4, 3, 99, 1}},
-    }), );
+    }));
 
 using OpSpecConstantOpTestWithOneIdThenLiteralNumbers =
     spvtest::TextToBinaryTestBase<::testing::TestWithParam<EnumCase<SpvOp>>>;
@@ -806,7 +806,7 @@
 }
 
 #define CASE(NAME) SpvOp##NAME, #NAME
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryOpSpecConstantOp,
     OpSpecConstantOpTestWithOneIdThenLiteralNumbers,
     ::testing::ValuesIn(std::vector<EnumCase<SpvOp>>{
@@ -814,7 +814,7 @@
         // indices.  Let's only test a few.
         {CASE(CompositeExtract), {0}},
         {CASE(CompositeExtract), {0, 99, 42, 16, 17, 12, 19}},
-    }), );
+    }));
 
 // TODO(dneto): OpConstantTrue
 // TODO(dneto): OpConstantFalse
diff --git a/test/text_to_binary.control_flow_test.cpp b/test/text_to_binary.control_flow_test.cpp
index 07f1108..01cc8e6 100644
--- a/test/text_to_binary.control_flow_test.cpp
+++ b/test/text_to_binary.control_flow_test.cpp
@@ -51,12 +51,12 @@
 
 // clang-format off
 #define CASE(VALUE,NAME) { SpvSelectionControl##VALUE, NAME}
-INSTANTIATE_TEST_CASE_P(TextToBinarySelectionMerge, OpSelectionMergeTest,
+INSTANTIATE_TEST_SUITE_P(TextToBinarySelectionMerge, OpSelectionMergeTest,
                         ValuesIn(std::vector<EnumCase<SpvSelectionControlMask>>{
                             CASE(MaskNone, "None"),
                             CASE(FlattenMask, "Flatten"),
                             CASE(DontFlattenMask, "DontFlatten"),
-                        }),);
+                        }));
 #undef CASE
 // clang-format on
 
@@ -95,7 +95,7 @@
   {                                       \
     SpvLoopControl##VALUE, NAME, { PARM } \
   }
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryLoopMerge, OpLoopMergeTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCase<int>>{
@@ -104,9 +104,9 @@
                 CASE(UnrollMask, "Unroll"),
                 CASE(DontUnrollMask, "DontUnroll"),
                 // clang-format on
-            })), );
+            })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryLoopMergeV11, OpLoopMergeTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCase<int>>{
@@ -116,7 +116,7 @@
                 {SpvLoopControlUnrollMask|SpvLoopControlDependencyLengthMask,
                       "DependencyLength|Unroll", {33}},
                 // clang-format on
-            })), );
+            })));
 #undef CASE
 #undef CASE1
 
@@ -251,7 +251,7 @@
                            Concatenate({{2, 3}, encoded_case_value, {4}}))})}};
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryOpSwitchValid1Word, OpSwitchValidTest,
     ValuesIn(std::vector<SwitchTestCase>({
         MakeSwitchTestCase(32, 0, "42", {42}, "100", {100}),
@@ -270,10 +270,10 @@
         MakeSwitchTestCase(16, 1, "0x8000", {0xffff8000}, "0x8100",
                            {0xffff8100}),
         MakeSwitchTestCase(16, 0, "0x8000", {0x00008000}, "0x8100", {0x8100}),
-    })), );
+    })));
 
 // NB: The words LOW ORDER bits show up first.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryOpSwitchValid2Words, OpSwitchValidTest,
     ValuesIn(std::vector<SwitchTestCase>({
         MakeSwitchTestCase(33, 0, "101", {101, 0}, "500", {500, 0}),
@@ -291,9 +291,9 @@
         MakeSwitchTestCase(63, 0, "0x500000000", {0, 5}, "12", {12, 0}),
         MakeSwitchTestCase(64, 0, "0x600000000", {0, 6}, "12", {12, 0}),
         MakeSwitchTestCase(64, 1, "0x700000123", {0x123, 7}, "12", {12, 0}),
-    })), );
+    })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpSwitchRoundTripUnsignedIntegers, RoundTripTest,
     ValuesIn(std::vector<std::string>({
         // Unsigned 16-bit.
@@ -307,9 +307,9 @@
         // Unsigned 64-bit, three non-default cases.
         "%1 = OpTypeInt 64 0\n%2 = OpConstant %1 9223372036854775807\n"
         "OpSwitch %2 %3 100 %4 102 %5 9000000000000000000 %6\n",
-    })), );
+    })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     OpSwitchRoundTripSignedIntegers, RoundTripTest,
     ValuesIn(std::vector<std::string>{
         // Signed 16-bit, with two non-default cases
@@ -332,7 +332,7 @@
         "OpSwitch %2 %3 100 %4 7000000000 %5 -1000000000000000000 %6\n",
         "%1 = OpTypeInt 64 1\n%2 = OpConstant %1 -9223372036854775808\n"
         "OpSwitch %2 %3 100 %4 7000000000 %5 -1000000000000000000 %6\n",
-    }), );
+    }));
 
 using OpSwitchInvalidTypeTestCase =
     spvtest::TextToBinaryTestBase<TestWithParam<std::string>>;
@@ -349,7 +349,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryOpSwitchInvalidTests, OpSwitchInvalidTypeTestCase,
     ValuesIn(std::vector<std::string>{
       {"OpTypeVoid",
@@ -375,7 +375,7 @@
            // At least one thing that isn't a type at all
        "OpNot %a %b"
       },
-    }),);
+    }));
 // clang-format on
 
 // TODO(dneto): OpPhi
diff --git a/test/text_to_binary.debug_test.cpp b/test/text_to_binary.debug_test.cpp
index b85650e..f9a4645 100644
--- a/test/text_to_binary.debug_test.cpp
+++ b/test/text_to_binary.debug_test.cpp
@@ -73,8 +73,8 @@
                                                GetParam().version})));
 }
 
-INSTANTIATE_TEST_CASE_P(TextToBinaryTestDebug, OpSourceTest,
-                        ::testing::ValuesIn(kLanguageCases), );
+INSTANTIATE_TEST_SUITE_P(TextToBinaryTestDebug, OpSourceTest,
+                         ::testing::ValuesIn(kLanguageCases));
 
 TEST_F(OpSourceTest, WrongLanguage) {
   EXPECT_THAT(CompileFailure("OpSource xxyyzz 12345"),
@@ -113,9 +113,9 @@
 }
 
 // TODO(dneto): utf-8, quoting, escaping
-INSTANTIATE_TEST_CASE_P(TextToBinaryTestDebug, OpSourceContinuedTest,
-                        ::testing::ValuesIn(std::vector<const char*>{
-                            "", "foo bar this and that"}), );
+INSTANTIATE_TEST_SUITE_P(TextToBinaryTestDebug, OpSourceContinuedTest,
+                         ::testing::ValuesIn(std::vector<const char*>{
+                             "", "foo bar this and that"}));
 
 // Test OpSourceExtension
 
@@ -132,9 +132,9 @@
 }
 
 // TODO(dneto): utf-8, quoting, escaping
-INSTANTIATE_TEST_CASE_P(TextToBinaryTestDebug, OpSourceExtensionTest,
-                        ::testing::ValuesIn(std::vector<const char*>{
-                            "", "foo bar this and that"}), );
+INSTANTIATE_TEST_SUITE_P(TextToBinaryTestDebug, OpSourceExtensionTest,
+                         ::testing::ValuesIn(std::vector<const char*>{
+                             "", "foo bar this and that"}));
 
 TEST_F(TextToBinaryTest, OpLine) {
   EXPECT_THAT(CompiledInstructions("OpLine %srcfile 42 99"),
@@ -158,9 +158,9 @@
 }
 
 // TODO(dneto): utf-8, quoting, escaping
-INSTANTIATE_TEST_CASE_P(TextToBinaryTestDebug, OpStringTest,
-                        ::testing::ValuesIn(std::vector<const char*>{
-                            "", "foo bar this and that"}), );
+INSTANTIATE_TEST_SUITE_P(TextToBinaryTestDebug, OpStringTest,
+                         ::testing::ValuesIn(std::vector<const char*>{
+                             "", "foo bar this and that"}));
 
 using OpNameTest =
     spvtest::TextToBinaryTestBase<::testing::TestWithParam<const char*>>;
@@ -174,8 +174,8 @@
 
 // UTF-8, quoting, escaping, etc. are covered in the StringLiterals tests in
 // BinaryToText.Literal.cpp.
-INSTANTIATE_TEST_CASE_P(TextToBinaryTestDebug, OpNameTest,
-                        ::testing::Values("", "foo bar this and that"), );
+INSTANTIATE_TEST_SUITE_P(TextToBinaryTestDebug, OpNameTest,
+                         ::testing::Values("", "foo bar this and that"));
 
 using OpMemberNameTest =
     spvtest::TextToBinaryTestBase<::testing::TestWithParam<const char*>>;
@@ -190,9 +190,9 @@
 }
 
 // TODO(dneto): utf-8, quoting, escaping
-INSTANTIATE_TEST_CASE_P(TextToBinaryTestDebug, OpMemberNameTest,
-                        ::testing::ValuesIn(std::vector<const char*>{
-                            "", "foo bar this and that"}), );
+INSTANTIATE_TEST_SUITE_P(TextToBinaryTestDebug, OpMemberNameTest,
+                         ::testing::ValuesIn(std::vector<const char*>{
+                             "", "foo bar this and that"}));
 
 // TODO(dneto): Parse failures?
 
@@ -207,8 +207,8 @@
       Eq(MakeInstruction(SpvOpModuleProcessed, MakeVector(GetParam()))));
 }
 
-INSTANTIATE_TEST_CASE_P(TextToBinaryTestDebug, OpModuleProcessedTest,
-                        ::testing::Values("", "foo bar this and that"), );
+INSTANTIATE_TEST_SUITE_P(TextToBinaryTestDebug, OpModuleProcessedTest,
+                         ::testing::Values("", "foo bar this and that"));
 
 }  // namespace
 }  // namespace spvtools
diff --git a/test/text_to_binary.device_side_enqueue_test.cpp b/test/text_to_binary.device_side_enqueue_test.cpp
index 25c100b..03d7e74 100644
--- a/test/text_to_binary.device_side_enqueue_test.cpp
+++ b/test/text_to_binary.device_side_enqueue_test.cpp
@@ -49,7 +49,7 @@
                                  GetParam().local_size_operands)));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryTest, OpEnqueueKernelGood,
     ::testing::ValuesIn(std::vector<KernelEnqueueCase>{
         // Provide IDs for pointer-to-local arguments for the
@@ -71,7 +71,7 @@
          {13, 14, 15, 16, 17, 18, 19, 20, 21}},
         {"%l0 %l1 %l2 %l3 %l4 %l5 %l6 %l7 %l8 %l9",
          {13, 14, 15, 16, 17, 18, 19, 20, 21, 22}},
-    }), );
+    }));
 
 // Test some bad parses of OpEnqueueKernel.  For other cases, we're relying
 // on the uniformity of the parsing algorithm.  The following two tests, ensure
diff --git a/test/text_to_binary.extension_test.cpp b/test/text_to_binary.extension_test.cpp
index 5c0bf98..84552b5 100644
--- a/test/text_to_binary.extension_test.cpp
+++ b/test/text_to_binary.extension_test.cpp
@@ -135,7 +135,7 @@
 
 // SPV_KHR_shader_ballot
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_KHR_shader_ballot, ExtensionRoundTripTest,
     // We'll get coverage over operand tables by trying the universal
     // environments, and at least one specific environment.
@@ -164,9 +164,9 @@
                 {"OpDecorate %1 BuiltIn SubgroupLtMask\n",
                  MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn,
                                                  SpvBuiltInSubgroupLtMaskKHR})},
-            })), );
+            })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_KHR_shader_ballot_vulkan_1_1, ExtensionRoundTripTest,
     // In SPIR-V 1.3 and Vulkan 1.1 we can drop the KHR suffix on the
     // builtin enums.
@@ -194,11 +194,11 @@
                 {"OpDecorate %1 BuiltIn SubgroupLtMask\n",
                  MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn,
                                                  SpvBuiltInSubgroupLtMask})},
-            })), );
+            })));
 
 // The old builtin names (with KHR suffix) still work in the assmebler, and
 // map to the enums without the KHR.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_KHR_shader_ballot_vulkan_1_1_alias_check, ExtensionAssemblyTest,
     // In SPIR-V 1.3 and Vulkan 1.1 we can drop the KHR suffix on the
     // builtin enums.
@@ -219,11 +219,11 @@
                 {"OpDecorate %1 BuiltIn SubgroupLtMaskKHR\n",
                  MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn,
                                                  SpvBuiltInSubgroupLtMask})},
-            })), );
+            })));
 
 // SPV_KHR_shader_draw_parameters
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_KHR_shader_draw_parameters, ExtensionRoundTripTest,
     // We'll get coverage over operand tables by trying the universal
     // environments, and at least one specific environment.
@@ -241,11 +241,11 @@
             {"OpDecorate %1 BuiltIn DrawIndex\n",
              MakeInstruction(SpvOpDecorate,
                              {1, SpvDecorationBuiltIn, SpvBuiltInDrawIndex})},
-        })), );
+        })));
 
 // SPV_KHR_subgroup_vote
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_KHR_subgroup_vote, ExtensionRoundTripTest,
     // We'll get coverage over operand tables by trying the universal
     // environments, and at least one specific environment.
@@ -260,11 +260,11 @@
                  MakeInstruction(SpvOpSubgroupAllKHR, {1, 2, 3})},
                 {"%2 = OpSubgroupAllEqualKHR %1 %3\n",
                  MakeInstruction(SpvOpSubgroupAllEqualKHR, {1, 2, 3})},
-            })), );
+            })));
 
 // SPV_KHR_16bit_storage
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_KHR_16bit_storage, ExtensionRoundTripTest,
     // We'll get coverage over operand tables by trying the universal
     // environments, and at least one specific environment.
@@ -276,11 +276,11 @@
                 {"OpCapability StorageBuffer16BitAccess\n",
                  MakeInstruction(SpvOpCapability,
                                  {SpvCapabilityStorageBuffer16BitAccess})},
-                {"OpCapability StorageUniform16\n",
+                {"OpCapability UniformAndStorageBuffer16BitAccess\n",
                  MakeInstruction(
                      SpvOpCapability,
                      {SpvCapabilityUniformAndStorageBuffer16BitAccess})},
-                {"OpCapability StorageUniform16\n",
+                {"OpCapability UniformAndStorageBuffer16BitAccess\n",
                  MakeInstruction(SpvOpCapability,
                                  {SpvCapabilityStorageUniform16})},
                 {"OpCapability StoragePushConstant16\n",
@@ -289,9 +289,9 @@
                 {"OpCapability StorageInputOutput16\n",
                  MakeInstruction(SpvOpCapability,
                                  {SpvCapabilityStorageInputOutput16})},
-            })), );
+            })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_KHR_16bit_storage_alias_check, ExtensionAssemblyTest,
     Combine(ValuesIn(CommonVulkanEnvs()),
             ValuesIn(std::vector<AssemblyCase>{
@@ -303,11 +303,11 @@
                 {"OpCapability UniformAndStorageBuffer16BitAccess\n",
                  MakeInstruction(SpvOpCapability,
                                  {SpvCapabilityStorageUniform16})},
-            })), );
+            })));
 
 // SPV_KHR_device_group
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_KHR_device_group, ExtensionRoundTripTest,
     // We'll get coverage over operand tables by trying the universal
     // environments, and at least one specific environment.
@@ -318,11 +318,11 @@
                 {"OpDecorate %1 BuiltIn DeviceIndex\n",
                  MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn,
                                                  SpvBuiltInDeviceIndex})},
-            })), );
+            })));
 
 // SPV_KHR_8bit_storage
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_KHR_8bit_storage, ExtensionRoundTripTest,
     // We'll get coverage over operand tables by trying the universal
     // environments, and at least one specific environment.
@@ -338,11 +338,11 @@
             {"OpCapability StoragePushConstant8\n",
              MakeInstruction(SpvOpCapability,
                              {SpvCapabilityStoragePushConstant8})},
-        })), );
+        })));
 
 // SPV_KHR_multiview
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_KHR_multiview, ExtensionRoundTripTest,
     // We'll get coverage over operand tables by trying the universal
     // environments, and at least one specific environment.
@@ -354,13 +354,13 @@
                 {"OpDecorate %1 BuiltIn ViewIndex\n",
                  MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn,
                                                  SpvBuiltInViewIndex})},
-            })), );
+            })));
 
 // SPV_AMD_shader_explicit_vertex_parameter
 
 #define PREAMBLE \
   "%1 = OpExtInstImport \"SPV_AMD_shader_explicit_vertex_parameter\"\n"
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_AMD_shader_explicit_vertex_parameter, ExtensionRoundTripTest,
     // We'll get coverage over operand tables by trying the universal
     // environments, and at least one specific environment.
@@ -374,13 +374,13 @@
                       SpvOpExtInstImport, {1},
                       MakeVector("SPV_AMD_shader_explicit_vertex_parameter")),
                   MakeInstruction(SpvOpExtInst, {2, 3, 1, 1, 4, 5})})},
-        })), );
+        })));
 #undef PREAMBLE
 
 // SPV_AMD_shader_trinary_minmax
 
 #define PREAMBLE "%1 = OpExtInstImport \"SPV_AMD_shader_trinary_minmax\"\n"
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_AMD_shader_trinary_minmax, ExtensionRoundTripTest,
     // We'll get coverage over operand tables by trying the universal
     // environments, and at least one specific environment.
@@ -433,13 +433,13 @@
                  {MakeInstruction(SpvOpExtInstImport, {1},
                                   MakeVector("SPV_AMD_shader_trinary_minmax")),
                   MakeInstruction(SpvOpExtInst, {2, 3, 1, 9, 4, 5, 6})})},
-        })), );
+        })));
 #undef PREAMBLE
 
 // SPV_AMD_gcn_shader
 
 #define PREAMBLE "%1 = OpExtInstImport \"SPV_AMD_gcn_shader\"\n"
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_AMD_gcn_shader, ExtensionRoundTripTest,
     // We'll get coverage over operand tables by trying the universal
     // environments, and at least one specific environment.
@@ -458,13 +458,13 @@
                  Concatenate({MakeInstruction(SpvOpExtInstImport, {1},
                                               MakeVector("SPV_AMD_gcn_shader")),
                               MakeInstruction(SpvOpExtInst, {2, 3, 1, 3})})},
-            })), );
+            })));
 #undef PREAMBLE
 
 // SPV_AMD_shader_ballot
 
 #define PREAMBLE "%1 = OpExtInstImport \"SPV_AMD_shader_ballot\"\n"
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_AMD_shader_ballot, ExtensionRoundTripTest,
     // We'll get coverage over operand tables by trying the universal
     // environments, and at least one specific environment.
@@ -490,12 +490,12 @@
              Concatenate({MakeInstruction(SpvOpExtInstImport, {1},
                                           MakeVector("SPV_AMD_shader_ballot")),
                           MakeInstruction(SpvOpExtInst, {2, 3, 1, 4, 4})})},
-        })), );
+        })));
 #undef PREAMBLE
 
 // SPV_KHR_variable_pointers
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_KHR_variable_pointers, ExtensionRoundTripTest,
     // We'll get coverage over operand tables by trying the universal
     // environments, and at least one specific environment.
@@ -508,11 +508,11 @@
                 {"OpCapability VariablePointersStorageBuffer\n",
                  MakeInstruction(SpvOpCapability,
                                  {SpvCapabilityVariablePointersStorageBuffer})},
-            })), );
+            })));
 
 // SPV_KHR_vulkan_memory_model
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_KHR_vulkan_memory_model, ExtensionRoundTripTest,
     // We'll get coverage over operand tables by trying the universal
     // environments, and at least one specific environment.
@@ -652,11 +652,15 @@
             // constant integer referenced by Id. There is no token for
             // them, and so no assembler or disassembler support required.
             // Similar for Scope ID.
-        })), );
+        })));
 
 // SPV_GOOGLE_decorate_string
 
-INSTANTIATE_TEST_CASE_P(
+// Now that OpDecorateString is the preferred spelling for
+// OpDecorateStringGOOGLE use that name in round trip tests, and the GOOGLE
+// name in an assembly-only test.
+
+INSTANTIATE_TEST_SUITE_P(
     SPV_GOOGLE_decorate_string, ExtensionRoundTripTest,
     Combine(
         // We'll get coverage over operand tables by trying the universal
@@ -664,6 +668,32 @@
         Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
                SPV_ENV_UNIVERSAL_1_2, SPV_ENV_VULKAN_1_0),
         ValuesIn(std::vector<AssemblyCase>{
+            {"OpDecorateString %1 UserSemantic \"ABC\"\n",
+             MakeInstruction(SpvOpDecorateStringGOOGLE,
+                             {1, SpvDecorationHlslSemanticGOOGLE},
+                             MakeVector("ABC"))},
+            {"OpDecorateString %1 UserSemantic \"ABC\"\n",
+             MakeInstruction(SpvOpDecorateString,
+                             {1, SpvDecorationUserSemantic},
+                             MakeVector("ABC"))},
+            {"OpMemberDecorateString %1 3 UserSemantic \"DEF\"\n",
+             MakeInstruction(SpvOpMemberDecorateStringGOOGLE,
+                             {1, 3, SpvDecorationUserSemantic},
+                             MakeVector("DEF"))},
+            {"OpMemberDecorateString %1 3 UserSemantic \"DEF\"\n",
+             MakeInstruction(SpvOpMemberDecorateString,
+                             {1, 3, SpvDecorationUserSemantic},
+                             MakeVector("DEF"))},
+        })));
+
+INSTANTIATE_TEST_SUITE_P(
+    SPV_GOOGLE_decorate_string, ExtensionAssemblyTest,
+    Combine(
+        // We'll get coverage over operand tables by trying the universal
+        // environments, and at least one specific environment.
+        Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
+               SPV_ENV_UNIVERSAL_1_2, SPV_ENV_VULKAN_1_0),
+        ValuesIn(std::vector<AssemblyCase>{
             {"OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE \"ABC\"\n",
              MakeInstruction(SpvOpDecorateStringGOOGLE,
                              {1, SpvDecorationHlslSemanticGOOGLE},
@@ -672,11 +702,14 @@
              MakeInstruction(SpvOpMemberDecorateStringGOOGLE,
                              {1, 3, SpvDecorationHlslSemanticGOOGLE},
                              MakeVector("DEF"))},
-        })), );
+        })));
 
 // SPV_GOOGLE_hlsl_functionality1
 
-INSTANTIATE_TEST_CASE_P(
+// Now that CounterBuffer is the preferred spelling for HlslCounterBufferGOOGLE,
+// use that name in round trip tests, and the GOOGLE name in an assembly-only
+// test.
+INSTANTIATE_TEST_SUITE_P(
     SPV_GOOGLE_hlsl_functionality1, ExtensionRoundTripTest,
     Combine(
         // We'll get coverage over operand tables by trying the universal
@@ -686,14 +719,32 @@
         // HlslSemanticGOOGLE is tested in SPV_GOOGLE_decorate_string, since
         // they are coupled together.
         ValuesIn(std::vector<AssemblyCase>{
+            {"OpDecorateId %1 CounterBuffer %2\n",
+             MakeInstruction(SpvOpDecorateId,
+                             {1, SpvDecorationHlslCounterBufferGOOGLE, 2})},
+            {"OpDecorateId %1 CounterBuffer %2\n",
+             MakeInstruction(SpvOpDecorateId,
+                             {1, SpvDecorationCounterBuffer, 2})},
+        })));
+
+INSTANTIATE_TEST_SUITE_P(
+    SPV_GOOGLE_hlsl_functionality1, ExtensionAssemblyTest,
+    Combine(
+        // We'll get coverage over operand tables by trying the universal
+        // environments, and at least one specific environment.
+        Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
+               SPV_ENV_UNIVERSAL_1_2, SPV_ENV_VULKAN_1_0),
+        // HlslSemanticGOOGLE is tested in SPV_GOOGLE_decorate_string, since
+        // they are coupled together.
+        ValuesIn(std::vector<AssemblyCase>{
             {"OpDecorateId %1 HlslCounterBufferGOOGLE %2\n",
              MakeInstruction(SpvOpDecorateId,
                              {1, SpvDecorationHlslCounterBufferGOOGLE, 2})},
-        })), );
+        })));
 
 // SPV_NV_viewport_array2
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_NV_viewport_array2, ExtensionRoundTripTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
                    SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_3,
@@ -717,11 +768,11 @@
                 {"OpDecorate %1 BuiltIn ViewportMaskNV\n",
                  MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn,
                                                  SpvBuiltInViewportMaskNV})},
-            })), );
+            })));
 
 // SPV_NV_shader_subgroup_partitioned
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_NV_shader_subgroup_partitioned, ExtensionRoundTripTest,
     Combine(
         Values(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_VULKAN_1_1),
@@ -759,11 +810,11 @@
                               SpvGroupOperationPartitionedExclusiveScanNV, 4})},
             {"%2 = OpGroupIAdd %1 %3 PartitionedExclusiveScanNV %4\n",
              MakeInstruction(SpvOpGroupIAdd, {1, 2, 3, 8, 4})},
-        })), );
+        })));
 
 // SPV_EXT_descriptor_indexing
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SPV_EXT_descriptor_indexing, ExtensionRoundTripTest,
     Combine(
         Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
@@ -851,7 +902,7 @@
              MakeInstruction(SpvOpDecorate, {1, SpvDecorationNonUniformEXT})},
             {"OpDecorate %1 NonUniformEXT\n",
              MakeInstruction(SpvOpDecorate, {1, 5300})},
-        })), );
+        })));
 
 }  // namespace
 }  // namespace spvtools
diff --git a/test/text_to_binary.function_test.cpp b/test/text_to_binary.function_test.cpp
index 748461f..55a8e6c 100644
--- a/test/text_to_binary.function_test.cpp
+++ b/test/text_to_binary.function_test.cpp
@@ -45,14 +45,14 @@
 
 // clang-format off
 #define CASE(VALUE,NAME) { SpvFunctionControl##VALUE, NAME }
-INSTANTIATE_TEST_CASE_P(TextToBinaryFunctionTest, OpFunctionControlTest,
+INSTANTIATE_TEST_SUITE_P(TextToBinaryFunctionTest, OpFunctionControlTest,
                         ::testing::ValuesIn(std::vector<EnumCase<SpvFunctionControlMask>>{
                             CASE(MaskNone, "None"),
                             CASE(InlineMask, "Inline"),
                             CASE(DontInlineMask, "DontInline"),
                             CASE(PureMask, "Pure"),
                             CASE(ConstMask, "Const"),
-                        }),);
+                        }));
 #undef CASE
 // clang-format on
 
diff --git a/test/text_to_binary.group_test.cpp b/test/text_to_binary.group_test.cpp
index 2f4b76d..becc3aa 100644
--- a/test/text_to_binary.group_test.cpp
+++ b/test/text_to_binary.group_test.cpp
@@ -44,12 +44,12 @@
 
 // clang-format off
 #define CASE(NAME) { SpvGroupOperation##NAME, #NAME}
-INSTANTIATE_TEST_CASE_P(TextToBinaryGroupOperation, GroupOperationTest,
+INSTANTIATE_TEST_SUITE_P(TextToBinaryGroupOperation, GroupOperationTest,
                         ::testing::ValuesIn(std::vector<EnumCase<SpvGroupOperation>>{
                             CASE(Reduce),
                             CASE(InclusiveScan),
                             CASE(ExclusiveScan),
-                        }),);
+                        }));
 #undef CASE
 // clang-format on
 
diff --git a/test/text_to_binary.image_test.cpp b/test/text_to_binary.image_test.cpp
index c1adedf..d445369 100644
--- a/test/text_to_binary.image_test.cpp
+++ b/test/text_to_binary.image_test.cpp
@@ -50,7 +50,7 @@
 }
 
 #define MASK(NAME) SpvImageOperands##NAME##Mask
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryImageOperandsAny, ImageOperandsTest,
     ::testing::ValuesIn(std::vector<ImageOperandsCase>{
         // TODO(dneto): Rev32 adds many more values, and rearranges their
@@ -66,10 +66,10 @@
         {" ConstOffsets %5", {MASK(ConstOffsets), 5}},
         {" Sample %5", {MASK(Sample), 5}},
         {" MinLod %5", {MASK(MinLod), 5}},
-    }), );
+    }));
 #undef MASK
 #define MASK(NAME) static_cast<uint32_t>(SpvImageOperands##NAME##Mask)
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryImageOperandsCombination, ImageOperandsTest,
     ::testing::ValuesIn(std::vector<ImageOperandsCase>{
         // TODO(dneto): Rev32 adds many more values, and rearranges their
@@ -95,7 +95,7 @@
          " %5 %6 %7 %8 %9 %10 %11 %12",
          {MASK(Bias) | MASK(Lod) | MASK(Grad) | MASK(ConstOffset) |
               MASK(Offset) | MASK(ConstOffsets) | MASK(Sample),
-          5, 6, 7, 8, 9, 10, 11, 12}}}), );
+          5, 6, 7, 8, 9, 10, 11, 12}}}));
 #undef MASK
 
 TEST_F(ImageOperandsTest, WrongOperand) {
@@ -173,24 +173,24 @@
 }
 
 #define MASK(NAME) SpvImageOperands##NAME##Mask
-INSTANTIATE_TEST_CASE_P(ImageSparseReadImageOperandsAny,
-                        ImageSparseReadImageOperandsTest,
-                        ::testing::ValuesIn(std::vector<ImageOperandsCase>{
-                            // Image operands are optional.
-                            {"", {}},
-                            // Test each kind, alone.
-                            {" Bias %5", {MASK(Bias), 5}},
-                            {" Lod %5", {MASK(Lod), 5}},
-                            {" Grad %5 %6", {MASK(Grad), 5, 6}},
-                            {" ConstOffset %5", {MASK(ConstOffset), 5}},
-                            {" Offset %5", {MASK(Offset), 5}},
-                            {" ConstOffsets %5", {MASK(ConstOffsets), 5}},
-                            {" Sample %5", {MASK(Sample), 5}},
-                            {" MinLod %5", {MASK(MinLod), 5}},
-                        }), );
+INSTANTIATE_TEST_SUITE_P(ImageSparseReadImageOperandsAny,
+                         ImageSparseReadImageOperandsTest,
+                         ::testing::ValuesIn(std::vector<ImageOperandsCase>{
+                             // Image operands are optional.
+                             {"", {}},
+                             // Test each kind, alone.
+                             {" Bias %5", {MASK(Bias), 5}},
+                             {" Lod %5", {MASK(Lod), 5}},
+                             {" Grad %5 %6", {MASK(Grad), 5, 6}},
+                             {" ConstOffset %5", {MASK(ConstOffset), 5}},
+                             {" Offset %5", {MASK(Offset), 5}},
+                             {" ConstOffsets %5", {MASK(ConstOffsets), 5}},
+                             {" Sample %5", {MASK(Sample), 5}},
+                             {" MinLod %5", {MASK(MinLod), 5}},
+                         }));
 #undef MASK
 #define MASK(NAME) static_cast<uint32_t>(SpvImageOperands##NAME##Mask)
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ImageSparseReadImageOperandsCombination, ImageSparseReadImageOperandsTest,
     ::testing::ValuesIn(std::vector<ImageOperandsCase>{
         // values.
@@ -212,7 +212,7 @@
           5, 6, 7, 8, 9, 10, 11, 12}},
         // Don't try the masks reversed, since this is a round trip test,
         // and the disassembler will sort them.
-    }), );
+    }));
 #undef MASK
 
 TEST_F(OpImageSparseReadTest, InvalidTypeOperand) {
diff --git a/test/text_to_binary.memory_test.cpp b/test/text_to_binary.memory_test.cpp
index ead08e6..c83c847 100644
--- a/test/text_to_binary.memory_test.cpp
+++ b/test/text_to_binary.memory_test.cpp
@@ -30,6 +30,7 @@
 using spvtest::MakeInstruction;
 using spvtest::TextToBinaryTest;
 using ::testing::Eq;
+using ::testing::HasSubstr;
 
 // Test assembly of Memory Access masks
 
@@ -45,14 +46,14 @@
                                  GetParam().operands())));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryMemoryAccessTest, MemoryAccessTest,
     ::testing::ValuesIn(std::vector<EnumCase<SpvMemoryAccessMask>>{
         {SpvMemoryAccessMaskNone, "None", {}},
         {SpvMemoryAccessVolatileMask, "Volatile", {}},
         {SpvMemoryAccessAlignedMask, "Aligned", {16}},
         {SpvMemoryAccessNontemporalMask, "Nontemporal", {}},
-    }), );
+    }));
 
 TEST_F(TextToBinaryTest, CombinedMemoryAccessMask) {
   const std::string input = "OpStore %ptr %value Volatile|Aligned 16";
@@ -76,7 +77,7 @@
 
 // clang-format off
 #define CASE(NAME) { SpvStorageClass##NAME, #NAME, {} }
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryStorageClassTest, StorageClassTest,
     ::testing::ValuesIn(std::vector<EnumCase<SpvStorageClass>>{
         CASE(UniformConstant),
@@ -91,16 +92,330 @@
         CASE(PushConstant),
         CASE(AtomicCounter),
         CASE(Image),
-    }),);
+    }));
 #undef CASE
 // clang-format on
 
+using MemoryRoundTripTest = RoundTripTest;
+
+// OpPtrEqual appeared in SPIR-V 1.4
+
+TEST_F(MemoryRoundTripTest, OpPtrEqualGood) {
+  std::string spirv = "%2 = OpPtrEqual %1 %3 %4\n";
+  EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_4),
+              Eq(MakeInstruction(SpvOpPtrEqual, {1, 2, 3, 4})));
+  std::string disassembly = EncodeAndDecodeSuccessfully(
+      spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpPtrEqualV13Bad) {
+  std::string spirv = "%2 = OpPtrEqual %1 %3 %4\n";
+  std::string err = CompileFailure(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_THAT(err, HasSubstr("Invalid Opcode name 'OpPtrEqual'"));
+}
+
+// OpPtrNotEqual appeared in SPIR-V 1.4
+
+TEST_F(MemoryRoundTripTest, OpPtrNotEqualGood) {
+  std::string spirv = "%2 = OpPtrNotEqual %1 %3 %4\n";
+  EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_4),
+              Eq(MakeInstruction(SpvOpPtrNotEqual, {1, 2, 3, 4})));
+  std::string disassembly = EncodeAndDecodeSuccessfully(
+      spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpPtrNotEqualV13Bad) {
+  std::string spirv = "%2 = OpPtrNotEqual %1 %3 %4\n";
+  std::string err = CompileFailure(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_THAT(err, HasSubstr("Invalid Opcode name 'OpPtrNotEqual'"));
+}
+
+// OpPtrDiff appeared in SPIR-V 1.4
+
+TEST_F(MemoryRoundTripTest, OpPtrDiffGood) {
+  std::string spirv = "%2 = OpPtrDiff %1 %3 %4\n";
+  EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_4),
+              Eq(MakeInstruction(SpvOpPtrDiff, {1, 2, 3, 4})));
+  std::string disassembly = EncodeAndDecodeSuccessfully(
+      spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpPtrDiffV13Good) {
+  // OpPtrDiff is enabled by a capability as well, so we can assemble
+  // it even in older SPIR-V environments.  We do that so we can
+  // write tests.
+  std::string spirv = "%2 = OpPtrDiff %1 %3 %4\n";
+  std::string disassembly = EncodeAndDecodeSuccessfully(
+      spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_ENV_UNIVERSAL_1_4);
+}
+
+// OpCopyMemory
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryNoMemAccessGood) {
+  std::string spirv = "OpCopyMemory %1 %2\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemory, {1, 2})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryTooFewArgsBad) {
+  std::string spirv = "OpCopyMemory %1\n";
+  std::string err = CompileFailure(spirv);
+  EXPECT_THAT(err, HasSubstr("Expected operand, found end of stream"));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryTooManyArgsBad) {
+  std::string spirv = "OpCopyMemory %1 %2 %3\n";
+  std::string err = CompileFailure(spirv);
+  EXPECT_THAT(err, HasSubstr("Invalid memory access operand '%3'"));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessNoneGood) {
+  std::string spirv = "OpCopyMemory %1 %2 None\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 0})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessVolatileGood) {
+  std::string spirv = "OpCopyMemory %1 %2 Volatile\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 1})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessAligned8Good) {
+  std::string spirv = "OpCopyMemory %1 %2 Aligned 8\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 2, 8})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessNontemporalGood) {
+  std::string spirv = "OpCopyMemory %1 %2 Nontemporal\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 4})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessAvGood) {
+  std::string spirv = "OpCopyMemory %1 %2 MakePointerAvailableKHR %3\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 8, 3})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessVisGood) {
+  std::string spirv = "OpCopyMemory %1 %2 MakePointerVisibleKHR %3\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 16, 3})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessNonPrivateGood) {
+  std::string spirv = "OpCopyMemory %1 %2 NonPrivatePointerKHR\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 32})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessMixedGood) {
+  std::string spirv =
+      "OpCopyMemory %1 %2 "
+      "Volatile|Aligned|Nontemporal|MakePointerAvailableKHR|"
+      "MakePointerVisibleKHR|NonPrivatePointerKHR 16 %3 %4\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 63, 16, 3, 4})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryTwoAccessV13Good) {
+  std::string spirv = "OpCopyMemory %1 %2 Volatile Volatile\n";
+  // Note: This will assemble but should not validate for SPIR-V 1.3
+  EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_3),
+              Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 1, 1})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryTwoAccessV14Good) {
+  std::string spirv = "OpCopyMemory %1 %2 Volatile Volatile\n";
+  EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_4),
+              Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 1, 1})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemoryTwoAccessMixedV14Good) {
+  std::string spirv =
+      "OpCopyMemory %1 %2 Volatile|Nontemporal|"
+      "MakePointerVisibleKHR %3 "
+      "Aligned|MakePointerAvailableKHR|NonPrivatePointerKHR 16 %4\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 21, 3, 42, 16, 4})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+// OpCopyMemorySized
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedNoMemAccessGood) {
+  std::string spirv = "OpCopyMemorySized %1 %2 %3\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedTooFewArgsBad) {
+  std::string spirv = "OpCopyMemorySized %1 %2\n";
+  std::string err = CompileFailure(spirv);
+  EXPECT_THAT(err, HasSubstr("Expected operand, found end of stream"));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedTooManyArgsBad) {
+  std::string spirv = "OpCopyMemorySized %1 %2 %3 %4\n";
+  std::string err = CompileFailure(spirv);
+  EXPECT_THAT(err, HasSubstr("Invalid memory access operand '%4'"));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessNoneGood) {
+  std::string spirv = "OpCopyMemorySized %1 %2 %3 None\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 0})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessVolatileGood) {
+  std::string spirv = "OpCopyMemorySized %1 %2 %3 Volatile\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 1})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessAligned8Good) {
+  std::string spirv = "OpCopyMemorySized %1 %2 %3 Aligned 8\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 2, 8})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessNontemporalGood) {
+  std::string spirv = "OpCopyMemorySized %1 %2 %3 Nontemporal\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 4})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessAvGood) {
+  std::string spirv = "OpCopyMemorySized %1 %2 %3 MakePointerAvailableKHR %4\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 8, 4})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessVisGood) {
+  std::string spirv = "OpCopyMemorySized %1 %2 %3 MakePointerVisibleKHR %4\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 16, 4})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessNonPrivateGood) {
+  std::string spirv = "OpCopyMemorySized %1 %2 %3 NonPrivatePointerKHR\n";
+  EXPECT_THAT(CompiledInstructions(spirv),
+              Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 32})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessMixedGood) {
+  std::string spirv =
+      "OpCopyMemorySized %1 %2 %3 "
+      "Volatile|Aligned|Nontemporal|MakePointerAvailableKHR|"
+      "MakePointerVisibleKHR|NonPrivatePointerKHR 16 %4 %5\n";
+  EXPECT_THAT(
+      CompiledInstructions(spirv),
+      Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 63, 16, 4, 5})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedTwoAccessV13Good) {
+  std::string spirv = "OpCopyMemorySized %1 %2 %3 Volatile Volatile\n";
+  // Note: This will assemble but should not validate for SPIR-V 1.3
+  EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_3),
+              Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 1, 1})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedTwoAccessV14Good) {
+  std::string spirv = "OpCopyMemorySized %1 %2 %3 Volatile Volatile\n";
+  EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_4),
+              Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 1, 1})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
+TEST_F(MemoryRoundTripTest, OpCopyMemorySizedTwoAccessMixedV14Good) {
+  std::string spirv =
+      "OpCopyMemorySized %1 %2 %3 Volatile|Nontemporal|"
+      "MakePointerVisibleKHR %4 "
+      "Aligned|MakePointerAvailableKHR|NonPrivatePointerKHR 16 %5\n";
+  EXPECT_THAT(
+      CompiledInstructions(spirv),
+      Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 21, 4, 42, 16, 5})));
+  std::string disassembly =
+      EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
+  EXPECT_THAT(disassembly, Eq(spirv));
+}
+
 // TODO(dneto): OpVariable with initializers
 // TODO(dneto): OpImageTexelPointer
 // TODO(dneto): OpLoad
 // TODO(dneto): OpStore
-// TODO(dneto): OpCopyMemory
-// TODO(dneto): OpCopyMemorySized
 // TODO(dneto): OpAccessChain
 // TODO(dneto): OpInBoundsAccessChain
 // TODO(dneto): OpPtrAccessChain
diff --git a/test/text_to_binary.mode_setting_test.cpp b/test/text_to_binary.mode_setting_test.cpp
index ed4fa2f..d1b69dd 100644
--- a/test/text_to_binary.mode_setting_test.cpp
+++ b/test/text_to_binary.mode_setting_test.cpp
@@ -69,7 +69,7 @@
         #MEMORY                                                          \
   }
 // clang-format off
-INSTANTIATE_TEST_CASE_P(TextToBinaryMemoryModel, OpMemoryModelTest,
+INSTANTIATE_TEST_SUITE_P(TextToBinaryMemoryModel, OpMemoryModelTest,
                         ValuesIn(std::vector<MemoryModelCase>{
                           // These cases exercise each addressing model, and
                           // each memory model, but not necessarily in
@@ -78,7 +78,7 @@
                             CASE(Logical,GLSL450),
                             CASE(Physical32,OpenCL),
                             CASE(Physical64,OpenCL),
-                        }),);
+                        }));
 #undef CASE
 // clang-format on
 
@@ -116,7 +116,7 @@
 
 // clang-format off
 #define CASE(NAME) SpvExecutionModel##NAME, #NAME
-INSTANTIATE_TEST_CASE_P(TextToBinaryEntryPoint, OpEntryPointTest,
+INSTANTIATE_TEST_SUITE_P(TextToBinaryEntryPoint, OpEntryPointTest,
                         ValuesIn(std::vector<EntryPointCase>{
                           { CASE(Vertex), "" },
                           { CASE(TessellationControl), "my tess" },
@@ -125,7 +125,7 @@
                           { CASE(Fragment), "FAT32" },
                           { CASE(GLCompute), "cubic" },
                           { CASE(Kernel), "Sanders" },
-                        }),);
+                        }));
 #undef CASE
 // clang-format on
 
@@ -151,7 +151,7 @@
 }
 
 #define CASE(NAME) SpvExecutionMode##NAME, #NAME
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryExecutionMode, OpExecutionModeTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCase<SpvExecutionMode>>{
@@ -188,16 +188,16 @@
                 {CASE(OutputTriangleStrip), {}},
                 {CASE(VecTypeHint), {96}},
                 {CASE(ContractionOff), {}},
-            })), );
+            })));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryExecutionModeV11, OpExecutionModeTest,
     Combine(Values(SPV_ENV_UNIVERSAL_1_1),
             ValuesIn(std::vector<EnumCase<SpvExecutionMode>>{
                 {CASE(Initializer)},
                 {CASE(Finalizer)},
                 {CASE(SubgroupSize), {12}},
-                {CASE(SubgroupsPerWorkgroup), {64}}})), );
+                {CASE(SubgroupsPerWorkgroup), {64}}})));
 #undef CASE
 
 TEST_F(OpExecutionModeTest, WrongMode) {
@@ -224,7 +224,7 @@
 
 // clang-format off
 #define CASE(NAME) { SpvCapability##NAME, #NAME }
-INSTANTIATE_TEST_CASE_P(TextToBinaryCapability, OpCapabilityTest,
+INSTANTIATE_TEST_SUITE_P(TextToBinaryCapability, OpCapabilityTest,
                         ValuesIn(std::vector<EnumCase<SpvCapability>>{
                             CASE(Matrix),
                             CASE(Shader),
@@ -280,7 +280,7 @@
                             CASE(DerivativeControl),
                             CASE(InterpolationFunction),
                             CASE(TransformFeedback),
-                        }),);
+                        }));
 #undef CASE
 // clang-format on
 
diff --git a/test/text_to_binary.type_declaration_test.cpp b/test/text_to_binary.type_declaration_test.cpp
index c6f158f..1589188 100644
--- a/test/text_to_binary.type_declaration_test.cpp
+++ b/test/text_to_binary.type_declaration_test.cpp
@@ -48,7 +48,7 @@
 
 // clang-format off
 #define CASE(NAME) {SpvDim##NAME, #NAME}
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryDim, DimTest,
     ::testing::ValuesIn(std::vector<EnumCase<SpvDim>>{
         CASE(1D),
@@ -58,7 +58,7 @@
         CASE(Rect),
         CASE(Buffer),
         CASE(SubpassData),
-    }),);
+    }));
 #undef CASE
 // clang-format on
 
@@ -84,7 +84,7 @@
 
 // clang-format off
 #define CASE(NAME) {SpvImageFormat##NAME, #NAME}
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryImageFormat, ImageFormatTest,
     ::testing::ValuesIn(std::vector<EnumCase<SpvImageFormat>>{
         CASE(Unknown),
@@ -127,7 +127,7 @@
         CASE(Rg8ui),
         CASE(R16ui),
         CASE(R8ui),
-    }),);
+    }));
 #undef CASE
 // clang-format on
 
@@ -153,13 +153,13 @@
 
 // clang-format off
 #define CASE(NAME) {SpvAccessQualifier##NAME, #NAME}
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     AccessQualifier, ImageAccessQualifierTest,
     ::testing::ValuesIn(std::vector<EnumCase<SpvAccessQualifier>>{
       CASE(ReadOnly),
       CASE(WriteOnly),
       CASE(ReadWrite),
-    }),);
+    }));
 // clang-format on
 #undef CASE
 
@@ -178,13 +178,13 @@
 
 // clang-format off
 #define CASE(NAME) {SpvAccessQualifier##NAME, #NAME}
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TextToBinaryTypePipe, OpTypePipeTest,
     ::testing::ValuesIn(std::vector<EnumCase<SpvAccessQualifier>>{
                             CASE(ReadOnly),
                             CASE(WriteOnly),
                             CASE(ReadWrite),
-    }),);
+    }));
 #undef CASE
 // clang-format on
 
diff --git a/test/text_to_binary_test.cpp b/test/text_to_binary_test.cpp
index 4ba37ad..57f0a6c 100644
--- a/test/text_to_binary_test.cpp
+++ b/test/text_to_binary_test.cpp
@@ -58,7 +58,7 @@
   spvContextDestroy(context);
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ParseMask, GoodMaskParseTest,
     ::testing::ValuesIn(std::vector<MaskCase>{
         {SPV_OPERAND_TYPE_FP_FAST_MATH_MODE, 0, "None"},
@@ -87,7 +87,7 @@
         {SPV_OPERAND_TYPE_FUNCTION_CONTROL, 4, "Pure"},
         {SPV_OPERAND_TYPE_FUNCTION_CONTROL, 8, "Const"},
         {SPV_OPERAND_TYPE_FUNCTION_CONTROL, 0xd, "Inline|Const|Pure"},
-    }), );
+    }));
 
 using BadFPFastMathMaskParseTest = ::testing::TestWithParam<const char*>;
 
@@ -102,12 +102,12 @@
   spvContextDestroy(context);
 }
 
-INSTANTIATE_TEST_CASE_P(ParseMask, BadFPFastMathMaskParseTest,
-                        ::testing::ValuesIn(std::vector<const char*>{
-                            nullptr, "", "NotValidEnum", "|", "NotInf|",
-                            "|NotInf", "NotInf||NotNaN",
-                            "Unroll"  // A good word, but for the wrong enum
-                        }), );
+INSTANTIATE_TEST_SUITE_P(ParseMask, BadFPFastMathMaskParseTest,
+                         ::testing::ValuesIn(std::vector<const char*>{
+                             nullptr, "", "NotValidEnum", "|", "NotInf|",
+                             "|NotInf", "NotInf||NotNaN",
+                             "Unroll"  // A good word, but for the wrong enum
+                         }));
 
 TEST_F(TextToBinaryTest, InvalidText) {
   ASSERT_EQ(SPV_ERROR_INVALID_TEXT,
@@ -197,7 +197,7 @@
                                               {1, 2, GetParam().second})})));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     FloatValues, TextToBinaryFloatValueTest,
     ::testing::ValuesIn(std::vector<std::pair<std::string, uint32_t>>{
         {"0.0", 0x00000000},          // +0
@@ -213,7 +213,7 @@
         {"-2.5", 0xc0200000},
         {"!0xff800000", 0xff800000},  // -inf
         {"!0xff800001", 0xff800001},  // NaN
-    }), );
+    }));
 
 using TextToBinaryHalfValueTest = spvtest::TextToBinaryTestBase<
     ::testing::TestWithParam<std::pair<std::string, uint32_t>>>;
@@ -227,7 +227,7 @@
                                               {1, 2, GetParam().second})})));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     HalfValues, TextToBinaryHalfValueTest,
     ::testing::ValuesIn(std::vector<std::pair<std::string, uint32_t>>{
         {"0.0", 0x00000000},
@@ -245,7 +245,7 @@
         {"0x1.8p4", 0x00004e00},
         {"0x1.801p4", 0x00004e00},
         {"0x1.804p4", 0x00004e01},
-    }), );
+    }));
 
 TEST(CreateContext, InvalidEnvironment) {
   spv_target_env env;
diff --git a/test/tools/expect.py b/test/tools/expect.py
index c959650..56ddaec 100755
--- a/test/tools/expect.py
+++ b/test/tools/expect.py
@@ -18,15 +18,20 @@
 methods in the mixin classes.
 """
 import difflib
+import functools
 import os
 import re
 import subprocess
+import traceback
 from spirv_test_framework import SpirvTest
+from builtins import bytes
 
+DEFAULT_SPIRV_VERSION = 0x010000
 
 def convert_to_unix_line_endings(source):
   """Converts all line endings in source to be unix line endings."""
-  return source.replace('\r\n', '\n').replace('\r', '\n')
+  result = source.replace('\r\n', '\n').replace('\r', '\n')
+  return result
 
 
 def substitute_file_extension(filename, extension):
@@ -133,7 +138,7 @@
       word = binary[index * 4:(index + 1) * 4]
       if little_endian:
         word = reversed(word)
-      return reduce(lambda w, b: (w << 8) | ord(b), word, 0)
+      return functools.reduce(lambda w, b: (w << 8) | b, word, 0)
 
     def check_endianness(binary):
       """Checks the endianness of the given SPIR-V binary.
@@ -169,7 +174,7 @@
     # profile
 
     if version != spv_version and version != 0:
-      return False, 'Incorrect SPV binary: wrong version number'
+      return False, 'Incorrect SPV binary: wrong version number: ' + hex(version) + ' expected ' + hex(spv_version)
     # Shaderc-over-Glslang (0x000d....) or
     # SPIRV-Tools (0x0007....) generator number
     if read_word(preamble, 2, little_endian) != 0x000d0007 and \
@@ -185,7 +190,9 @@
 class CorrectObjectFilePreamble(CorrectBinaryLengthAndPreamble):
   """Provides methods for verifying preamble for a SPV object file."""
 
-  def verify_object_file_preamble(self, filename, spv_version=0x10000):
+  def verify_object_file_preamble(self,
+                                  filename,
+                                  spv_version=DEFAULT_SPIRV_VERSION):
     """Checks that the given SPIR-V binary file has correct preamble."""
 
     success, message = verify_file_non_empty(filename)
@@ -254,6 +261,21 @@
     return True, ''
 
 
+class ValidObjectFile1_4(ReturnCodeIsZero, CorrectObjectFilePreamble):
+  """Mixin class for checking that every input file generates a valid SPIR-V 1.4
+    object file following the object file naming rule, and there is no output on
+    stdout/stderr."""
+
+  def check_object_file_preamble(self, status):
+    for input_filename in status.input_filenames:
+      object_filename = get_object_filename(input_filename)
+      success, message = self.verify_object_file_preamble(
+          os.path.join(status.directory, object_filename), 0x10400)
+      if not success:
+        return False, message
+    return True, ''
+
+
 class ValidObjectFileWithAssemblySubstr(SuccessfulReturn,
                                         CorrectObjectFilePreamble):
   """Mixin class for checking that every input file generates a valid object
@@ -479,7 +501,7 @@
     if not status.stderr:
       return False, 'Expected error message, but no output on stderr'
     if self.expected_error_substr not in convert_to_unix_line_endings(
-        status.stderr):
+        status.stderr.decode('utf8')):
       return False, ('Incorrect stderr output:\n{act}\n'
                      'Expected substring not found in stderr:\n{exp}'.format(
                          act=status.stderr, exp=self.expected_error_substr))
@@ -499,7 +521,7 @@
                      ' command execution')
     if not status.stderr:
       return False, 'Expected warning message, but no output on stderr'
-    if self.expected_warning != convert_to_unix_line_endings(status.stderr):
+    if self.expected_warning != convert_to_unix_line_endings(status.stderr.decode('utf8')):
       return False, ('Incorrect stderr output:\n{act}\n'
                      'Expected:\n{exp}'.format(
                          act=status.stderr, exp=self.expected_warning))
@@ -559,16 +581,16 @@
       if not status.stdout:
         return False, 'Expected something on stdout'
     elif type(self.expected_stdout) == str:
-      if self.expected_stdout != convert_to_unix_line_endings(status.stdout):
+      if self.expected_stdout != convert_to_unix_line_endings(status.stdout.decode('utf8')):
         return False, ('Incorrect stdout output:\n{ac}\n'
                        'Expected:\n{ex}'.format(
                            ac=status.stdout, ex=self.expected_stdout))
     else:
-      if not self.expected_stdout.search(
-          convert_to_unix_line_endings(status.stdout)):
+      converted = convert_to_unix_line_endings(status.stdout.decode('utf8'))
+      if not self.expected_stdout.search(converted):
         return False, ('Incorrect stdout output:\n{ac}\n'
                        'Expected to match regex:\n{ex}'.format(
-                           ac=status.stdout, ex=self.expected_stdout.pattern))
+                           ac=status.stdout.decode('utf8'), ex=self.expected_stdout.pattern))
     return True, ''
 
 
@@ -593,13 +615,13 @@
       if not status.stderr:
         return False, 'Expected something on stderr'
     elif type(self.expected_stderr) == str:
-      if self.expected_stderr != convert_to_unix_line_endings(status.stderr):
+      if self.expected_stderr != convert_to_unix_line_endings(status.stderr.decode('utf8')):
         return False, ('Incorrect stderr output:\n{ac}\n'
                        'Expected:\n{ex}'.format(
                            ac=status.stderr, ex=self.expected_stderr))
     else:
       if not self.expected_stderr.search(
-          convert_to_unix_line_endings(status.stderr)):
+          convert_to_unix_line_endings(status.stderr.decode('utf8'))):
         return False, ('Incorrect stderr output:\n{ac}\n'
                        'Expected to match regex:\n{ex}'.format(
                            ac=status.stderr, ex=self.expected_stderr.pattern))
@@ -664,7 +686,7 @@
     # Collect all the output lines containing a pass name.
     pass_names = []
     pass_name_re = re.compile(r'.*IR before pass (?P<pass_name>[\S]+)')
-    for line in status.stderr.splitlines():
+    for line in status.stderr.decode('utf8').splitlines():
       match = pass_name_re.match(line)
       if match:
         pass_names.append(match.group('pass_name'))
diff --git a/test/tools/opt/flags.py b/test/tools/opt/flags.py
index 69462fc..ca873c7 100644
--- a/test/tools/opt/flags.py
+++ b/test/tools/opt/flags.py
@@ -34,7 +34,7 @@
 
 
 @inside_spirv_testsuite('SpirvOptBase')
-class TestAssemblyFileAsOnlyParameter(expect.ValidObjectFile1_3):
+class TestAssemblyFileAsOnlyParameter(expect.ValidObjectFile1_4):
   """Tests that spirv-opt accepts a SPIR-V object file."""
 
   shader = placeholder.FileSPIRVShader(empty_main_assembly(), '.spvasm')
@@ -52,7 +52,7 @@
 
 
 @inside_spirv_testsuite('SpirvOptFlags')
-class TestValidPassFlags(expect.ValidObjectFile1_3,
+class TestValidPassFlags(expect.ValidObjectFile1_4,
                          expect.ExecutedListOfPasses):
   """Tests that spirv-opt accepts all valid optimization flags."""
 
@@ -129,7 +129,7 @@
 
 
 @inside_spirv_testsuite('SpirvOptFlags')
-class TestPerformanceOptimizationPasses(expect.ValidObjectFile1_3,
+class TestPerformanceOptimizationPasses(expect.ValidObjectFile1_4,
                                         expect.ExecutedListOfPasses):
   """Tests that spirv-opt schedules all the passes triggered by -O."""
 
@@ -176,7 +176,7 @@
 
 
 @inside_spirv_testsuite('SpirvOptFlags')
-class TestSizeOptimizationPasses(expect.ValidObjectFile1_3,
+class TestSizeOptimizationPasses(expect.ValidObjectFile1_4,
                                  expect.ExecutedListOfPasses):
   """Tests that spirv-opt schedules all the passes triggered by -Os."""
 
@@ -215,7 +215,7 @@
 
 
 @inside_spirv_testsuite('SpirvOptFlags')
-class TestLegalizationPasses(expect.ValidObjectFile1_3,
+class TestLegalizationPasses(expect.ValidObjectFile1_4,
                              expect.ExecutedListOfPasses):
   """Tests that spirv-opt schedules all the passes triggered by --legalize-hlsl.
   """
@@ -227,6 +227,7 @@
       'inline-entry-points-exhaustive',
       'eliminate-dead-functions',
       'private-to-local',
+      'fix-storage-class',
       'eliminate-local-single-block',
       'eliminate-local-single-store',
       'eliminate-dead-code-aggressive',
diff --git a/test/tools/opt/oconfig.py b/test/tools/opt/oconfig.py
index 3372379..899d93e 100644
--- a/test/tools/opt/oconfig.py
+++ b/test/tools/opt/oconfig.py
@@ -56,3 +56,18 @@
 --loop-unroll
 """, '.cfg')
   spirv_args = [shader, '-o', placeholder.TempFileName('output.spv'), config]
+
+@inside_spirv_testsuite('SpirvOptConfigFile')
+class TestOconfigComments(expect.SuccessfulReturn):
+  """Tests empty config files are accepted.
+
+  https://github.com/KhronosGroup/SPIRV-Tools/issues/1778
+  """
+
+  shader = placeholder.FileSPIRVShader(empty_main_assembly(), '.spvasm')
+  config = placeholder.ConfigFlagsFile("""
+# This is a comment.
+-O
+--relax-struct-store
+""", '.cfg')
+  spirv_args = [shader, '-o', placeholder.TempFileName('output.spv'), config]
diff --git a/test/val/CMakeLists.txt b/test/val/CMakeLists.txt
index 9f58424..9f538c2 100644
--- a/test/val/CMakeLists.txt
+++ b/test/val/CMakeLists.txt
@@ -15,6 +15,8 @@
 set(VAL_TEST_COMMON_SRCS
   ${CMAKE_CURRENT_SOURCE_DIR}/../test_fixture.h
   ${CMAKE_CURRENT_SOURCE_DIR}/../unit_spirv.h
+  ${CMAKE_CURRENT_SOURCE_DIR}/val_code_generator.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/val_code_generator.h
   ${CMAKE_CURRENT_SOURCE_DIR}/val_fixtures.h
 )
 
@@ -29,6 +31,7 @@
        val_capability_test.cpp
        val_cfg_test.cpp
        val_composites_test.cpp
+       val_constants_test.cpp
        val_conversion_test.cpp
        val_data_test.cpp
        val_decoration_test.cpp
@@ -45,10 +48,12 @@
   SRCS val_limits_test.cpp
        ${VAL_TEST_COMMON_SRCS}
   LIBS ${SPIRV_TOOLS}
+  PCH_FILE pch_test_val
 )
 
-add_spvtools_unittest(TARGET val_ijklmnop
+add_spvtools_unittest(TARGET val_fghijklmnop
   SRCS
+       val_function_test.cpp
        val_id_test.cpp
        val_image_test.cpp
        val_interfaces_test.cpp
diff --git a/test/val/val_arithmetics_test.cpp b/test/val/val_arithmetics_test.cpp
index 87e006c..b82fc97 100644
--- a/test/val/val_arithmetics_test.cpp
+++ b/test/val/val_arithmetics_test.cpp
@@ -1165,6 +1165,150 @@
                 "vector size of the right operand: OuterProduct"));
 }
 
+std::string GenerateCoopMatCode(const std::string& extra_types,
+                                const std::string& main_body) {
+  const std::string prefix =
+      R"(
+OpCapability Shader
+OpCapability Float16
+OpCapability CooperativeMatrixNV
+OpExtension "SPV_NV_cooperative_matrix"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f16 = OpTypeFloat 16
+%f32 = OpTypeFloat 32
+%u32 = OpTypeInt 32 0
+%s32 = OpTypeInt 32 1
+
+%u32_8 = OpConstant %u32 8
+%u32_16 = OpConstant %u32 16
+%u32_4 = OpConstant %u32 4
+%subgroup = OpConstant %u32 3
+
+%f16mat = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_8 %u32_8
+%u32mat = OpTypeCooperativeMatrixNV %u32 %subgroup %u32_8 %u32_8
+%s32mat = OpTypeCooperativeMatrixNV %s32 %subgroup %u32_8 %u32_8
+
+%f16_1 = OpConstant %f16 1
+%f32_1 = OpConstant %f32 1
+%u32_1 = OpConstant %u32 1
+%s32_1 = OpConstant %s32 1
+
+%f16mat_1 = OpConstantComposite %f16mat %f16_1
+%u32mat_1 = OpConstantComposite %u32mat %u32_1
+%s32mat_1 = OpConstantComposite %s32mat %s32_1
+
+%u32_c1 = OpSpecConstant %u32 1
+%u32_c2 = OpSpecConstant %u32 2
+
+%f16matc = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_c1 %u32_c2
+%f16matc_1 = OpConstantComposite %f16matc %f16_1
+
+%mat16x4 = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_16 %u32_4
+%mat4x16 = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_4 %u32_16
+%mat16x16 = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_16 %u32_16
+%f16mat_16x4_1 = OpConstantComposite %mat16x4 %f16_1
+%f16mat_4x16_1 = OpConstantComposite %mat4x16 %f16_1
+%f16mat_16x16_1 = OpConstantComposite %mat16x16 %f16_1)";
+
+  const std::string func_begin =
+      R"(
+%main = OpFunction %void None %func
+%main_entry = OpLabel)";
+
+  const std::string suffix =
+      R"(
+OpReturn
+OpFunctionEnd)";
+
+  return prefix + extra_types + func_begin + main_body + suffix;
+}
+
+TEST_F(ValidateArithmetics, CoopMatSuccess) {
+  const std::string body = R"(
+%val1 = OpFAdd %f16mat %f16mat_1 %f16mat_1
+%val2 = OpFSub %f16mat %f16mat_1 %f16mat_1
+%val3 = OpFDiv %f16mat %f16mat_1 %f16mat_1
+%val4 = OpFNegate %f16mat %f16mat_1
+%val5 = OpIAdd %u32mat %u32mat_1 %u32mat_1
+%val6 = OpISub %u32mat %u32mat_1 %u32mat_1
+%val7 = OpUDiv %u32mat %u32mat_1 %u32mat_1
+%val8 = OpIAdd %s32mat %s32mat_1 %s32mat_1
+%val9 = OpISub %s32mat %s32mat_1 %s32mat_1
+%val10 = OpSDiv %s32mat %s32mat_1 %s32mat_1
+%val11 = OpSNegate %s32mat %s32mat_1
+%val12 = OpMatrixTimesScalar %f16mat %f16mat_1 %f16_1
+%val13 = OpMatrixTimesScalar %u32mat %u32mat_1 %u32_1
+%val14 = OpMatrixTimesScalar %s32mat %s32mat_1 %s32_1
+%val15 = OpCooperativeMatrixMulAddNV %mat16x16 %f16mat_16x4_1 %f16mat_4x16_1 %f16mat_16x16_1
+%val16 = OpCooperativeMatrixMulAddNV %f16matc %f16matc_1 %f16matc_1 %f16matc_1
+)";
+
+  CompileSuccessfully(GenerateCoopMatCode("", body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateArithmetics, CoopMatFMulFail) {
+  const std::string body = R"(
+%val1 = OpFMul %f16mat %f16mat_1 %f16mat_1
+)";
+
+  CompileSuccessfully(GenerateCoopMatCode("", body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Expected floating scalar or vector type as Result Type: FMul"));
+}
+
+TEST_F(ValidateArithmetics, CoopMatMatrixTimesScalarMismatchFail) {
+  const std::string body = R"(
+%val1 = OpMatrixTimesScalar %f16mat %f16mat_1 %f32_1
+)";
+
+  CompileSuccessfully(GenerateCoopMatCode("", body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Expected scalar operand type to be equal to the component "
+                "type of the matrix operand: MatrixTimesScalar"));
+}
+
+TEST_F(ValidateArithmetics, CoopMatScopeFail) {
+  const std::string types = R"(
+%workgroup = OpConstant %u32 2
+
+%mat16x16_wg = OpTypeCooperativeMatrixNV %f16 %workgroup %u32_16 %u32_16
+%f16matwg_16x16_1 = OpConstantComposite %mat16x16_wg %f16_1
+)";
+
+  const std::string body = R"(
+%val1 = OpCooperativeMatrixMulAddNV %mat16x16 %f16mat_16x4_1 %f16mat_4x16_1 %f16matwg_16x16_1
+)";
+
+  CompileSuccessfully(GenerateCoopMatCode(types, body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Cooperative matrix scopes must match: CooperativeMatrixMulAddNV"));
+}
+
+TEST_F(ValidateArithmetics, CoopMatDimFail) {
+  const std::string body = R"(
+%val1 = OpCooperativeMatrixMulAddNV %mat16x16 %f16mat_4x16_1 %f16mat_16x4_1 %f16mat_16x16_1
+)";
+
+  CompileSuccessfully(GenerateCoopMatCode("", body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Cooperative matrix 'M' mismatch: CooperativeMatrixMulAddNV"));
+}
+
 TEST_F(ValidateArithmetics, IAddCarrySuccess) {
   const std::string body = R"(
 %val1 = OpIAddCarry %struct_u32_u32 %u32_0 %u32_1
diff --git a/test/val/val_atomics_test.cpp b/test/val/val_atomics_test.cpp
index 98a2149..1be19c5 100644
--- a/test/val/val_atomics_test.cpp
+++ b/test/val/val_atomics_test.cpp
@@ -225,7 +225,7 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateAtomics, AtomicLoadVulkanSuccess) {
+TEST_F(ValidateAtomics, AtomicLoadInt32VulkanSuccess) {
   const std::string body = R"(
 %val1 = OpAtomicLoad %u32 %u32_var %device %relaxed
 %val2 = OpAtomicLoad %u32 %u32_var %workgroup %acquire
@@ -235,6 +235,41 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
 }
 
+TEST_F(ValidateAtomics, AtomicLoadFloatVulkan) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %f32 %f32_var %device %relaxed
+%val2 = OpAtomicLoad %f32 %f32_var %workgroup %acquire
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected Result Type to be int scalar type"));
+}
+
+TEST_F(ValidateAtomics, AtomicLoadInt64WithCapabilityVulkanSuccess) {
+  const std::string body = R"(
+  %val1 = OpAtomicLoad %u64 %u64_var %device %relaxed
+  %val2 = OpAtomicLoad %u64 %u64_var %workgroup %acquire
+  )";
+
+  CompileSuccessfully(GenerateShaderCode(body, "OpCapability Int64Atomics\n"),
+                      SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateAtomics, AtomicLoadInt64WithoutCapabilityVulkan) {
+  const std::string body = R"(
+  %val1 = OpAtomicLoad %u64 %u64_var %device %relaxed
+  %val2 = OpAtomicLoad %u64 %u64_var %workgroup %acquire
+  )";
+
+  CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("64-bit atomics require the Int64Atomics capability"));
+}
+
 TEST_F(ValidateAtomics, AtomicStoreOpenCLFunctionPointerStorageTypeSuccess) {
   const std::string body = R"(
 %f32_var_function = OpVariable %f32_ptr_function Function
@@ -338,30 +373,37 @@
           "AtomicLoad: 64-bit atomics require the Int64Atomics capability"));
 }
 
-TEST_F(ValidateAtomics, AtomicLoadWebGPUShaderSuccess) {
+TEST_F(ValidateAtomics, AtomicLoadWebGPUSuccess) {
   const std::string body = R"(
-%val1 = OpAtomicLoad %u32 %u32_var %device %relaxed
-%val2 = OpAtomicLoad %u32 %u32_var %workgroup %acquire
+%val1 = OpAtomicLoad %u32 %u32_var %queuefamily %relaxed
+%val2 = OpAtomicLoad %u32 %u32_var %workgroup %relaxed
 )";
 
   CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
 }
 
-TEST_F(ValidateAtomics, AtomicLoadWebGPUShaderSequentiallyConsistentFailure) {
+TEST_F(ValidateAtomics, AtomicLoadWebGPUNonRelaxedFailure) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %u32 %u32_var %queuefamily %acquire
+%val2 = OpAtomicLoad %u32 %u32_var %workgroup %release
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("WebGPU spec disallows, for OpAtomic*, any bit masks"));
+}
+
+TEST_F(ValidateAtomics, AtomicLoadWebGPUSequentiallyConsistentFailure) {
   const std::string body = R"(
 %val3 = OpAtomicLoad %u32 %u32_var %subgroup %sequentially_consistent
 )";
 
   CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr(
-          "WebGPU spec disallows any bit masks in Memory Semantics that are "
-          "not Acquire, Release, AcquireRelease, UniformMemory, "
-          "WorkgroupMemory, ImageMemory, OutputMemoryKHR, MakeAvailableKHR, or "
-          "MakeVisibleKHR\n  %34 = OpAtomicLoad %uint %29 %uint_3 %uint_16\n"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("WebGPU spec disallows, for OpAtomic*, any bit masks"));
 }
 
 TEST_F(ValidateAtomics, VK_KHR_shader_atomic_int64Success) {
@@ -544,27 +586,33 @@
 
 TEST_F(ValidateAtomics, AtomicStoreWebGPUSuccess) {
   const std::string body = R"(
-OpAtomicStore %u32_var %device %release %u32_1
+OpAtomicStore %u32_var %queuefamily %relaxed %u32_1
 )";
 
   CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
 }
 
-TEST_F(ValidateAtomics, AtomicStoreWebGPUSequentiallyConsistent) {
+TEST_F(ValidateAtomics, AtomicStoreWebGPUNonRelaxedFailure) {
   const std::string body = R"(
-OpAtomicStore %u32_var %device %sequentially_consistent %u32_1
+OpAtomicStore %u32_var %queuefamily %release %u32_1
 )";
 
   CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr(
-          "WebGPU spec disallows any bit masks in Memory Semantics that are "
-          "not Acquire, Release, AcquireRelease, UniformMemory, "
-          "WorkgroupMemory, ImageMemory, OutputMemoryKHR, MakeAvailableKHR, or "
-          "MakeVisibleKHR\n  OpAtomicStore %29 %uint_1_0 %uint_16 %uint_1\n"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("WebGPU spec disallows, for OpAtomic*, any bit masks"));
+}
+
+TEST_F(ValidateAtomics, AtomicStoreWebGPUSequentiallyConsistent) {
+  const std::string body = R"(
+OpAtomicStore %u32_var %queuefamily %sequentially_consistent %u32_1
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("WebGPU spec disallows, for OpAtomic*, any bit masks"));
 }
 
 TEST_F(ValidateAtomics, AtomicStoreWrongPointerType) {
@@ -1158,24 +1206,18 @@
                         "requires capability Shader"));
 }
 
-// Disabling this test until
-// https://github.com/KhronosGroup/glslang/issues/1618 is resolved.
-// TEST_F(ValidateAtomics, AtomicCounterMemorySemanticsNoCapability) {
-//  const std::string body = R"(
-// OpAtomicStore %u32_var %device %relaxed %u32_1
-//%val1 = OpAtomicIIncrement %u32 %u32_var %device
-//%acquire_release_atomic_counter_workgroup
-//)";
-//
-//  CompileSuccessfully(GenerateKernelCode(body));
-//  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-//  EXPECT_THAT(
-//      getDiagnosticString(),
-//      HasSubstr("AtomicIIncrement: Memory Semantics AtomicCounterMemory "
-//                "requires capability AtomicStorage\n  %40 = OpAtomicIIncrement
-//                "
-//                "%uint %30 %uint_1_0 %uint_1288\n"));
-//}
+// Lack of the AtomicStorage capability is intentionally ignored, see
+// https://github.com/KhronosGroup/glslang/issues/1618 for the reasoning why.
+TEST_F(ValidateAtomics, AtomicCounterMemorySemanticsNoCapability) {
+  const std::string body = R"(
+ OpAtomicStore %u32_var %device %relaxed %u32_1
+%val1 = OpAtomicIIncrement %u32 %u32_var %device
+%acquire_release_atomic_counter_workgroup
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
 
 TEST_F(ValidateAtomics, AtomicCounterMemorySemanticsWithCapability) {
   const std::string body = R"(
@@ -1864,6 +1906,75 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
 }
 
+TEST_F(ValidateAtomics, WebGPUCrossDeviceMemoryScopeBad) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %u32 %u32_var %cross_device %relaxed
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicLoad: in WebGPU environment Memory Scope is limited to "
+                "Workgroup, Subgroup and QueuFamilyKHR\n"
+                "  %34 = OpAtomicLoad %uint %29 %uint_0_0 %uint_0_1\n"));
+}
+
+TEST_F(ValidateAtomics, WebGPUDeviceMemoryScopeBad) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %u32 %u32_var %device %relaxed
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicLoad: in WebGPU environment Memory Scope is limited to "
+                "Workgroup, Subgroup and QueuFamilyKHR\n"
+                "  %34 = OpAtomicLoad %uint %29 %uint_1_0 %uint_0_1\n"));
+}
+
+TEST_F(ValidateAtomics, WebGPUWorkgroupMemoryScopeGood) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %u32 %u32_var %workgroup %relaxed
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
+TEST_F(ValidateAtomics, WebGPUSubgroupMemoryScopeGood) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %u32 %u32_var %subgroup %relaxed
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
+TEST_F(ValidateAtomics, WebGPUInvocationMemoryScopeBad) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %u32 %u32_var %invocation %relaxed
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicLoad: in WebGPU environment Memory Scope is limited to "
+                "Workgroup, Subgroup and QueuFamilyKHR\n"
+                "  %34 = OpAtomicLoad %uint %29 %uint_4 %uint_0_1\n"));
+}
+
+TEST_F(ValidateAtomics, WebGPUQueueFamilyMemoryScopeGood) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %u32 %u32_var %queuefamily %relaxed
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_barriers_test.cpp b/test/val/val_barriers_test.cpp
index 264d130..cf81e42 100644
--- a/test/val/val_barriers_test.cpp
+++ b/test/val/val_barriers_test.cpp
@@ -78,9 +78,13 @@
 %acquire_and_release = OpConstant %u32 6
 %sequentially_consistent = OpConstant %u32 16
 %acquire_release_uniform_workgroup = OpConstant %u32 328
+%acquire_uniform_workgroup = OpConstant %u32 322
+%release_uniform_workgroup = OpConstant %u32 324
 %acquire_and_release_uniform = OpConstant %u32 70
 %acquire_release_subgroup = OpConstant %u32 136
 %uniform = OpConstant %u32 64
+%uniform_workgroup = OpConstant %u32 320
+
 
 %main = OpFunction %void None %func
 %main_entry = OpLabel
@@ -245,9 +249,8 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
 }
 
-TEST_F(ValidateBarriers, OpControlBarrierWebGPUSuccess) {
+TEST_F(ValidateBarriers, OpControlBarrierWebGPUAcquireReleaseSuccess) {
   const std::string body = R"(
-OpControlBarrier %workgroup %queuefamily %none
 OpControlBarrier %workgroup %workgroup %acquire_release_uniform_workgroup
 )";
 
@@ -255,6 +258,41 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
 }
 
+TEST_F(ValidateBarriers, OpControlBarrierWebGPURelaxedFailure) {
+  const std::string body = R"(
+OpControlBarrier %workgroup %workgroup %uniform_workgroup
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("WebGPU spec requires AcquireRelease to set"));
+}
+
+TEST_F(ValidateBarriers, OpControlBarrierWebGPUAcquireFailure) {
+  const std::string body = R"(
+OpControlBarrier %workgroup %workgroup %acquire_uniform_workgroup
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("WebGPU spec disallows any bit masks in Memory Semantics"));
+}
+
+TEST_F(ValidateBarriers, OpControlBarrierWebGPUReleaseFailure) {
+  const std::string body = R"(
+OpControlBarrier %workgroup %workgroup %release_uniform_workgroup
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("WebGPU spec disallows any bit masks in Memory Semantics"));
+}
+
 TEST_F(ValidateBarriers, OpControlBarrierExecutionModelFragmentSpirv12) {
   const std::string body = R"(
 OpControlBarrier %device %device %none
@@ -630,6 +668,50 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
 }
 
+TEST_F(ValidateBarriers, OpMemoryBarrierWebGPUAcquireReleaseSuccess) {
+  const std::string body = R"(
+OpMemoryBarrier %workgroup %acquire_release_uniform_workgroup
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
+TEST_F(ValidateBarriers, OpMemoryBarrierWebGPURelaxedFailure) {
+  const std::string body = R"(
+OpMemoryBarrier %workgroup %uniform_workgroup
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("WebGPU spec requires AcquireRelease to set"));
+}
+
+TEST_F(ValidateBarriers, OpMemoryBarrierWebGPUAcquireFailure) {
+  const std::string body = R"(
+OpMemoryBarrier %workgroup %acquire_uniform_workgroup
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("WebGPU spec disallows any bit masks in Memory Semantics"));
+}
+
+TEST_F(ValidateBarriers, OpMemoryBarrierWebGPUReleaseFailure) {
+  const std::string body = R"(
+OpMemoryBarrier %workgroup %release_uniform_workgroup
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("WebGPU spec disallows any bit masks in Memory Semantics"));
+}
+
 TEST_F(ValidateBarriers, OpMemoryBarrierFloatMemoryScope) {
   const std::string body = R"(
 OpMemoryBarrier %f32_1 %acquire_release_uniform_workgroup
@@ -872,8 +954,8 @@
 OpMemoryBarrier %u32 %u32_0
 )";
 
-  CompileSuccessfully(GenerateKernelCode(body));
-  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
   EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 5[%uint] cannot be a "
                                                "type"));
 }
diff --git a/test/val/val_builtins_test.cpp b/test/val/val_builtins_test.cpp
index ec07582..797194f 100644
--- a/test/val/val_builtins_test.cpp
+++ b/test/val/val_builtins_test.cpp
@@ -24,7 +24,9 @@
 #include <vector>
 
 #include "gmock/gmock.h"
+#include "source/spirv_target_env.h"
 #include "test/unit_spirv.h"
+#include "test/val/val_code_generator.h"
 #include "test/val/val_fixtures.h"
 
 namespace spvtools {
@@ -53,180 +55,47 @@
 using ValidateVulkanCombineBuiltInExecutionModelDataTypeResult =
     spvtest::ValidateBase<std::tuple<const char*, const char*, const char*,
                                      const char*, TestResult>>;
+using ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult =
+    spvtest::ValidateBase<std::tuple<const char*, const char*, const char*,
+                                     const char*, TestResult>>;
 using ValidateVulkanCombineBuiltInArrayedVariable = spvtest::ValidateBase<
     std::tuple<const char*, const char*, const char*, const char*, TestResult>>;
+using ValidateWebGPUCombineBuiltInArrayedVariable = spvtest::ValidateBase<
+    std::tuple<const char*, const char*, const char*, const char*, TestResult>>;
 
-struct EntryPoint {
-  std::string name;
-  std::string execution_model;
-  std::string execution_modes;
-  std::string body;
-  std::string interfaces;
-};
 
-class CodeGenerator {
- public:
-  std::string Build() const;
-
-  std::vector<EntryPoint> entry_points_;
-  std::string capabilities_;
-  std::string extensions_;
-  std::string memory_model_;
-  std::string before_types_;
-  std::string types_;
-  std::string after_types_;
-  std::string add_at_the_end_;
-};
-
-std::string CodeGenerator::Build() const {
-  std::ostringstream ss;
-
-  ss << capabilities_;
-  ss << extensions_;
-  ss << memory_model_;
-
-  for (const EntryPoint& entry_point : entry_points_) {
-    ss << "OpEntryPoint " << entry_point.execution_model << " %"
-       << entry_point.name << " \"" << entry_point.name << "\" "
-       << entry_point.interfaces << "\n";
-  }
-
-  for (const EntryPoint& entry_point : entry_points_) {
-    ss << entry_point.execution_modes << "\n";
-  }
-
-  ss << before_types_;
-  ss << types_;
-  ss << after_types_;
-
-  for (const EntryPoint& entry_point : entry_points_) {
-    ss << "\n";
-    ss << "%" << entry_point.name << " = OpFunction %void None %func\n";
-    ss << "%" << entry_point.name << "_entry = OpLabel\n";
-    ss << entry_point.body;
-    ss << "\nOpReturn\nOpFunctionEnd\n";
-  }
-
-  ss << add_at_the_end_;
-
-  return ss.str();
+bool InitializerRequired(spv_target_env env, const char* const storage_class) {
+  return spvIsWebGPUEnv(env) && (strncmp(storage_class, "Output", 6) == 0 ||
+                                 strncmp(storage_class, "Private", 7) == 0 ||
+                                 strncmp(storage_class, "Function", 8) == 0);
 }
 
-std::string GetDefaultShaderCapabilities() {
-  return R"(
-OpCapability Shader
-OpCapability Geometry
-OpCapability Tessellation
-OpCapability Float64
-OpCapability Int64
-OpCapability MultiViewport
-OpCapability SampleRateShading
-)";
-}
+CodeGenerator GetInMainCodeGenerator(spv_target_env env,
+                                     const char* const built_in,
+                                     const char* const execution_model,
+                                     const char* const storage_class,
+                                     const char* const data_type) {
+  CodeGenerator generator =
+      spvIsWebGPUEnv(env) ? CodeGenerator::GetWebGPUShaderCodeGenerator()
+                          : CodeGenerator::GetDefaultShaderCodeGenerator();
 
-std::string GetDefaultShaderTypes() {
-  return R"(
-%void = OpTypeVoid
-%func = OpTypeFunction %void
-%bool = OpTypeBool
-%f32 = OpTypeFloat 32
-%f64 = OpTypeFloat 64
-%u32 = OpTypeInt 32 0
-%u64 = OpTypeInt 64 0
-%f32vec2 = OpTypeVector %f32 2
-%f32vec3 = OpTypeVector %f32 3
-%f32vec4 = OpTypeVector %f32 4
-%f64vec2 = OpTypeVector %f64 2
-%f64vec3 = OpTypeVector %f64 3
-%f64vec4 = OpTypeVector %f64 4
-%u32vec2 = OpTypeVector %u32 2
-%u32vec3 = OpTypeVector %u32 3
-%u64vec3 = OpTypeVector %u64 3
-%u32vec4 = OpTypeVector %u32 4
-%u64vec2 = OpTypeVector %u64 2
-
-%f32_0 = OpConstant %f32 0
-%f32_1 = OpConstant %f32 1
-%f32_2 = OpConstant %f32 2
-%f32_3 = OpConstant %f32 3
-%f32_4 = OpConstant %f32 4
-%f32_h = OpConstant %f32 0.5
-%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1
-%f32vec2_12 = OpConstantComposite %f32vec2 %f32_1 %f32_2
-%f32vec3_012 = OpConstantComposite %f32vec3 %f32_0 %f32_1 %f32_2
-%f32vec3_123 = OpConstantComposite %f32vec3 %f32_1 %f32_2 %f32_3
-%f32vec4_0123 = OpConstantComposite %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3
-%f32vec4_1234 = OpConstantComposite %f32vec4 %f32_1 %f32_2 %f32_3 %f32_4
-
-%f64_0 = OpConstant %f64 0
-%f64_1 = OpConstant %f64 1
-%f64_2 = OpConstant %f64 2
-%f64_3 = OpConstant %f64 3
-%f64vec2_01 = OpConstantComposite %f64vec2 %f64_0 %f64_1
-%f64vec3_012 = OpConstantComposite %f64vec3 %f64_0 %f64_1 %f64_2
-%f64vec4_0123 = OpConstantComposite %f64vec4 %f64_0 %f64_1 %f64_2 %f64_3
-
-%u32_0 = OpConstant %u32 0
-%u32_1 = OpConstant %u32 1
-%u32_2 = OpConstant %u32 2
-%u32_3 = OpConstant %u32 3
-%u32_4 = OpConstant %u32 4
-
-%u64_0 = OpConstant %u64 0
-%u64_1 = OpConstant %u64 1
-%u64_2 = OpConstant %u64 2
-%u64_3 = OpConstant %u64 3
-
-%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1
-%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2
-%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3
-%u64vec2_01 = OpConstantComposite %u64vec2 %u64_0 %u64_1
-
-%u32arr2 = OpTypeArray %u32 %u32_2
-%u32arr3 = OpTypeArray %u32 %u32_3
-%u32arr4 = OpTypeArray %u32 %u32_4
-%u64arr2 = OpTypeArray %u64 %u32_2
-%u64arr3 = OpTypeArray %u64 %u32_3
-%u64arr4 = OpTypeArray %u64 %u32_4
-%f32arr2 = OpTypeArray %f32 %u32_2
-%f32arr3 = OpTypeArray %f32 %u32_3
-%f32arr4 = OpTypeArray %f32 %u32_4
-%f64arr2 = OpTypeArray %f64 %u32_2
-%f64arr3 = OpTypeArray %f64 %u32_3
-%f64arr4 = OpTypeArray %f64 %u32_4
-
-%f32vec3arr3 = OpTypeArray %f32vec3 %u32_3
-%f32vec4arr3 = OpTypeArray %f32vec4 %u32_3
-%f64vec4arr3 = OpTypeArray %f64vec4 %u32_3
-)";
-}
-
-CodeGenerator GetDefaultShaderCodeGenerator() {
-  CodeGenerator generator;
-  generator.capabilities_ = GetDefaultShaderCapabilities();
-  generator.memory_model_ = "OpMemoryModel Logical GLSL450\n";
-  generator.types_ = GetDefaultShaderTypes();
-  return generator;
-}
-
-TEST_P(ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, InMain) {
-  const char* const built_in = std::get<0>(GetParam());
-  const char* const execution_model = std::get<1>(GetParam());
-  const char* const storage_class = std::get<2>(GetParam());
-  const char* const data_type = std::get<3>(GetParam());
-  const TestResult& test_result = std::get<4>(GetParam());
-
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
   generator.before_types_ = "OpMemberDecorate %built_in_type 0 BuiltIn ";
   generator.before_types_ += built_in;
   generator.before_types_ += "\n";
 
   std::ostringstream after_types;
+
   after_types << "%built_in_type = OpTypeStruct " << data_type << "\n";
+  if (InitializerRequired(env, storage_class)) {
+    after_types << "%built_in_null = OpConstantNull %built_in_type\n";
+  }
   after_types << "%built_in_ptr = OpTypePointer " << storage_class
               << " %built_in_type\n";
-  after_types << "%built_in_var = OpVariable %built_in_ptr " << storage_class
-              << "\n";
+  after_types << "%built_in_var = OpVariable %built_in_ptr " << storage_class;
+  if (InitializerRequired(env, storage_class)) {
+    after_types << " %built_in_null";
+  }
+  after_types << "\n";
   after_types << "%data_ptr = OpTypePointer " << storage_class << " "
               << data_type << "\n";
   generator.after_types_ = after_types.str();
@@ -265,6 +134,19 @@
 )";
   generator.entry_points_.push_back(std::move(entry_point));
 
+  return generator;
+}
+
+TEST_P(ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, InMain) {
+  const char* const built_in = std::get<0>(GetParam());
+  const char* const execution_model = std::get<1>(GetParam());
+  const char* const storage_class = std::get<2>(GetParam());
+  const char* const data_type = std::get<3>(GetParam());
+  const TestResult& test_result = std::get<4>(GetParam());
+
+  CodeGenerator generator = GetInMainCodeGenerator(
+      SPV_ENV_VULKAN_1_0, built_in, execution_model, storage_class, data_type);
+
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(test_result.validation_result,
             ValidateInstructions(SPV_ENV_VULKAN_1_0));
@@ -276,24 +158,52 @@
   }
 }
 
-TEST_P(ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, InFunction) {
+TEST_P(ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult, InMain) {
   const char* const built_in = std::get<0>(GetParam());
   const char* const execution_model = std::get<1>(GetParam());
   const char* const storage_class = std::get<2>(GetParam());
   const char* const data_type = std::get<3>(GetParam());
   const TestResult& test_result = std::get<4>(GetParam());
 
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  CodeGenerator generator = GetInMainCodeGenerator(
+      SPV_ENV_WEBGPU_0, built_in, execution_model, storage_class, data_type);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(test_result.validation_result,
+            ValidateInstructions(SPV_ENV_WEBGPU_0));
+  if (test_result.error_str) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str));
+  }
+  if (test_result.error_str2) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2));
+  }
+}
+
+CodeGenerator GetInFunctionCodeGenerator(spv_target_env env,
+                                         const char* const built_in,
+                                         const char* const execution_model,
+                                         const char* const storage_class,
+                                         const char* const data_type) {
+  CodeGenerator generator =
+      spvIsWebGPUEnv(env) ? CodeGenerator::GetWebGPUShaderCodeGenerator()
+                          : CodeGenerator::GetDefaultShaderCodeGenerator();
+
   generator.before_types_ = "OpMemberDecorate %built_in_type 0 BuiltIn ";
   generator.before_types_ += built_in;
   generator.before_types_ += "\n";
 
   std::ostringstream after_types;
   after_types << "%built_in_type = OpTypeStruct " << data_type << "\n";
+  if (InitializerRequired(env, storage_class)) {
+    after_types << "%built_in_null = OpConstantNull %built_in_type\n";
+  }
   after_types << "%built_in_ptr = OpTypePointer " << storage_class
               << " %built_in_type\n";
-  after_types << "%built_in_var = OpVariable %built_in_ptr " << storage_class
-              << "\n";
+  after_types << "%built_in_var = OpVariable %built_in_ptr " << storage_class;
+  if (InitializerRequired(env, storage_class)) {
+    after_types << " %built_in_null";
+  }
+  after_types << "\n";
   after_types << "%data_ptr = OpTypePointer " << storage_class << " "
               << data_type << "\n";
   generator.after_types_ = after_types.str();
@@ -331,15 +241,35 @@
 %val2 = OpFunctionCall %void %foo
 )";
 
-  generator.add_at_the_end_ = R"(
+  std::string function_body = R"(
 %foo = OpFunction %void None %func
 %foo_entry = OpLabel
 %ptr = OpAccessChain %data_ptr %built_in_var %u32_0
 OpReturn
 OpFunctionEnd
 )";
+
+  if (spvIsWebGPUEnv(env)) {
+    generator.after_types_ += function_body;
+  } else {
+    generator.add_at_the_end_ = function_body;
+  }
+
   generator.entry_points_.push_back(std::move(entry_point));
 
+  return generator;
+}
+
+TEST_P(ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, InFunction) {
+  const char* const built_in = std::get<0>(GetParam());
+  const char* const execution_model = std::get<1>(GetParam());
+  const char* const storage_class = std::get<2>(GetParam());
+  const char* const data_type = std::get<3>(GetParam());
+  const TestResult& test_result = std::get<4>(GetParam());
+
+  CodeGenerator generator = GetInFunctionCodeGenerator(
+      SPV_ENV_VULKAN_1_0, built_in, execution_model, storage_class, data_type);
+
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(test_result.validation_result,
             ValidateInstructions(SPV_ENV_VULKAN_1_0));
@@ -351,23 +281,51 @@
   }
 }
 
-TEST_P(ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Variable) {
+TEST_P(ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult, InFunction) {
   const char* const built_in = std::get<0>(GetParam());
   const char* const execution_model = std::get<1>(GetParam());
   const char* const storage_class = std::get<2>(GetParam());
   const char* const data_type = std::get<3>(GetParam());
   const TestResult& test_result = std::get<4>(GetParam());
 
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  CodeGenerator generator = GetInFunctionCodeGenerator(
+      SPV_ENV_WEBGPU_0, built_in, execution_model, storage_class, data_type);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(test_result.validation_result,
+            ValidateInstructions(SPV_ENV_WEBGPU_0));
+  if (test_result.error_str) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str));
+  }
+  if (test_result.error_str2) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2));
+  }
+}
+
+CodeGenerator GetVariableCodeGenerator(spv_target_env env,
+                                       const char* const built_in,
+                                       const char* const execution_model,
+                                       const char* const storage_class,
+                                       const char* const data_type) {
+  CodeGenerator generator =
+      spvIsWebGPUEnv(env) ? CodeGenerator::GetWebGPUShaderCodeGenerator()
+                          : CodeGenerator::GetDefaultShaderCodeGenerator();
+
   generator.before_types_ = "OpDecorate %built_in_var BuiltIn ";
   generator.before_types_ += built_in;
   generator.before_types_ += "\n";
 
   std::ostringstream after_types;
+  if (InitializerRequired(env, storage_class)) {
+    after_types << "%built_in_null = OpConstantNull " << data_type << "\n";
+  }
   after_types << "%built_in_ptr = OpTypePointer " << storage_class << " "
               << data_type << "\n";
-  after_types << "%built_in_var = OpVariable %built_in_ptr " << storage_class
-              << "\n";
+  after_types << "%built_in_var = OpVariable %built_in_ptr " << storage_class;
+  if (InitializerRequired(env, storage_class)) {
+    after_types << " %built_in_null";
+  }
+  after_types << "\n";
   generator.after_types_ = after_types.str();
 
   EntryPoint entry_point;
@@ -379,7 +337,7 @@
   }
   // Any kind of reference would do.
   entry_point.body = R"(
-%val = OpBitcast %u64 %built_in_var
+%val = OpBitcast %u32 %built_in_var
 )";
 
   std::ostringstream execution_modes;
@@ -405,6 +363,19 @@
 
   generator.entry_points_.push_back(std::move(entry_point));
 
+  return generator;
+}
+
+TEST_P(ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Variable) {
+  const char* const built_in = std::get<0>(GetParam());
+  const char* const execution_model = std::get<1>(GetParam());
+  const char* const storage_class = std::get<2>(GetParam());
+  const char* const data_type = std::get<3>(GetParam());
+  const TestResult& test_result = std::get<4>(GetParam());
+
+  CodeGenerator generator = GetVariableCodeGenerator(
+      SPV_ENV_VULKAN_1_0, built_in, execution_model, storage_class, data_type);
+
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(test_result.validation_result,
             ValidateInstructions(SPV_ENV_VULKAN_1_0));
@@ -416,25 +387,46 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(
+TEST_P(ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult, Variable) {
+  const char* const built_in = std::get<0>(GetParam());
+  const char* const execution_model = std::get<1>(GetParam());
+  const char* const storage_class = std::get<2>(GetParam());
+  const char* const data_type = std::get<3>(GetParam());
+  const TestResult& test_result = std::get<4>(GetParam());
+
+  CodeGenerator generator = GetVariableCodeGenerator(
+      SPV_ENV_WEBGPU_0, built_in, execution_model, storage_class, data_type);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(test_result.validation_result,
+            ValidateInstructions(SPV_ENV_WEBGPU_0));
+  if (test_result.error_str) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str));
+  }
+  if (test_result.error_str2) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
     ClipAndCullDistanceOutputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("ClipDistance", "CullDistance"),
             Values("Vertex", "Geometry", "TessellationControl",
                    "TessellationEvaluation"),
             Values("Output"), Values("%f32arr2", "%f32arr4"),
-            Values(TestResult())), );
+            Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ClipAndCullDistanceInputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("ClipDistance", "CullDistance"),
             Values("Fragment", "Geometry", "TessellationControl",
                    "TessellationEvaluation"),
             Values("Input"), Values("%f32arr2", "%f32arr4"),
-            Values(TestResult())), );
+            Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ClipAndCullDistanceFragmentOutput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("ClipDistance", "CullDistance"), Values("Fragment"),
@@ -444,9 +436,9 @@
                 "Vulkan spec doesn't allow BuiltIn ClipDistance/CullDistance "
                 "to be used for variables with Output storage class if "
                 "execution model is Fragment.",
-                "which is called with execution model Fragment."))), );
+                "which is called with execution model Fragment."))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     VertexIdAndInstanceIdVertexInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("VertexId", "InstanceId"), Values("Vertex"), Values("Input"),
@@ -454,9 +446,9 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "Vulkan spec doesn't allow BuiltIn VertexId/InstanceId to be "
-                "used."))), );
+                "used."))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ClipAndCullDistanceVertexInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("ClipDistance", "CullDistance"), Values("Vertex"),
@@ -466,9 +458,9 @@
                 "Vulkan spec doesn't allow BuiltIn ClipDistance/CullDistance "
                 "to be used for variables with Input storage class if "
                 "execution model is Vertex.",
-                "which is called with execution model Vertex."))), );
+                "which is called with execution model Vertex."))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ClipAndCullInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("ClipDistance", "CullDistance"), Values("GLCompute"),
@@ -476,41 +468,46 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be used only with Fragment, Vertex, TessellationControl, "
-                "TessellationEvaluation or Geometry execution models"))), );
+                "TessellationEvaluation or Geometry execution models"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ClipAndCullDistanceNotArray,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("ClipDistance", "CullDistance"), Values("Fragment"),
             Values("Input"), Values("%f32vec2", "%f32vec4", "%f32"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float array",
-                              "is not an array"))), );
+                              "is not an array"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ClipAndCullDistanceNotFloatArray,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("ClipDistance", "CullDistance"), Values("Fragment"),
             Values("Input"), Values("%u32arr2", "%u64arr4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float array",
-                              "components are not float scalar"))), );
+                              "components are not float scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ClipAndCullDistanceNotF32Array,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("ClipDistance", "CullDistance"), Values("Fragment"),
             Values("Input"), Values("%f64arr2", "%f64arr4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float array",
-                              "has components with bit width 64"))), );
+                              "has components with bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     FragCoordSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragCoord"), Values("Fragment"), Values("Input"),
-            Values("%f32vec4"), Values(TestResult())), );
+            Values("%f32vec4"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    FragCoordSuccess, ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("FragCoord"), Values("Fragment"), Values("Input"),
+            Values("%f32vec4"), Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
     FragCoordNotFragment,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
@@ -519,50 +516,91 @@
                "TessellationEvaluation"),
         Values("Input"), Values("%f32vec4"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
-                          "to be used only with Fragment execution model"))), );
+                          "to be used only with Fragment execution model"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    FragCoordNotFragment,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(
+        Values("FragCoord"), Values("Vertex", "GLCompute"), Values("Input"),
+        Values("%f32vec4"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "to be used only with Fragment execution model"))));
+
+INSTANTIATE_TEST_SUITE_P(
     FragCoordNotInput, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragCoord"), Values("Fragment"), Values("Output"),
             Values("%f32vec4"),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
-                "uses storage class Output"))), );
+                "uses storage class Output"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    FragCoordNotInput, ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("FragCoord"), Values("Fragment"), Values("Output"),
+            Values("%f32vec4"),
+            Values(TestResult(
+                SPV_ERROR_INVALID_DATA,
+                "to be only used for variables with Input storage class",
+                "uses storage class Output"))));
+
+INSTANTIATE_TEST_SUITE_P(
     FragCoordNotFloatVector,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragCoord"), Values("Fragment"), Values("Input"),
             Values("%f32arr4", "%u32vec4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float vector",
-                              "is not a float vector"))), );
+                              "is not a float vector"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    FragCoordNotFloatVector,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("FragCoord"), Values("Fragment"), Values("Input"),
+            Values("%f32arr4", "%u32vec4"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 4-component 32-bit float vector",
+                              "is not a float vector"))));
+
+INSTANTIATE_TEST_SUITE_P(
     FragCoordNotFloatVec4,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragCoord"), Values("Fragment"), Values("Input"),
             Values("%f32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float vector",
-                              "has 3 components"))), );
+                              "has 3 components"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    FragCoordNotFloatVec4,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("FragCoord"), Values("Fragment"), Values("Input"),
+            Values("%f32vec3"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 4-component 32-bit float vector",
+                              "has 3 components"))));
+
+INSTANTIATE_TEST_SUITE_P(
     FragCoordNotF32Vec4,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragCoord"), Values("Fragment"), Values("Input"),
             Values("%f64vec4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float vector",
-                              "has components with bit width 64"))), );
+                              "has components with bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     FragDepthSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragDepth"), Values("Fragment"), Values("Output"),
-            Values("%f32"), Values(TestResult())), );
+            Values("%f32"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    FragDepthSuccess, ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("FragDepth"), Values("Fragment"), Values("Output"),
+            Values("%f32"), Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
     FragDepthNotFragment,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
@@ -571,9 +609,18 @@
                "TessellationEvaluation"),
         Values("Output"), Values("%f32"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
-                          "to be used only with Fragment execution model"))), );
+                          "to be used only with Fragment execution model"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    FragDepthNotFragment,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(
+        Values("FragDepth"), Values("Vertex", "GLCompute"), Values("Output"),
+        Values("%f32"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "to be used only with Fragment execution model"))));
+
+INSTANTIATE_TEST_SUITE_P(
     FragDepthNotOutput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragDepth"), Values("Fragment"), Values("Input"),
@@ -581,32 +628,57 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Output storage class",
-                "uses storage class Input"))), );
+                "uses storage class Input"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    FragDepthNotOutput,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("FragDepth"), Values("Fragment"), Values("Input"),
+            Values("%f32"),
+            Values(TestResult(
+                SPV_ERROR_INVALID_DATA,
+                "to be only used for variables with Output storage class",
+                "uses storage class Input"))));
+
+INSTANTIATE_TEST_SUITE_P(
     FragDepthNotFloatScalar,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragDepth"), Values("Fragment"), Values("Output"),
             Values("%f32vec4", "%u32"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float scalar",
-                              "is not a float scalar"))), );
+                              "is not a float scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    FragDepthNotFloatScalar,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("FragDepth"), Values("Fragment"), Values("Output"),
+            Values("%f32vec4", "%u32"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 32-bit float scalar",
+                              "is not a float scalar"))));
+
+INSTANTIATE_TEST_SUITE_P(
     FragDepthNotF32, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragDepth"), Values("Fragment"), Values("Output"),
             Values("%f64"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float scalar",
-                              "has bit width 64"))), );
+                              "has bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     FrontFacingAndHelperInvocationSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FrontFacing", "HelperInvocation"), Values("Fragment"),
-            Values("Input"), Values("%bool"), Values(TestResult())), );
+            Values("Input"), Values("%bool"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    FrontFacingSuccess,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("FrontFacing"), Values("Fragment"), Values("Input"),
+            Values("%bool"), Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
     FrontFacingAndHelperInvocationNotFragment,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
@@ -615,9 +687,18 @@
                "TessellationEvaluation"),
         Values("Input"), Values("%bool"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
-                          "to be used only with Fragment execution model"))), );
+                          "to be used only with Fragment execution model"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    FrontFacingNotFragment,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(
+        Values("FrontFacing"), Values("Vertex", "GLCompute"), Values("Input"),
+        Values("%bool"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "to be used only with Fragment execution model"))));
+
+INSTANTIATE_TEST_SUITE_P(
     FrontFacingAndHelperInvocationNotInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FrontFacing", "HelperInvocation"), Values("Fragment"),
@@ -625,38 +706,73 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
-                "uses storage class Output"))), );
+                "uses storage class Output"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    FrontFacingNotInput,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("FrontFacing"), Values("Fragment"), Values("Output"),
+            Values("%bool"),
+            Values(TestResult(
+                SPV_ERROR_INVALID_DATA,
+                "to be only used for variables with Input storage class",
+                "uses storage class Output"))));
+
+INSTANTIATE_TEST_SUITE_P(
     FrontFacingAndHelperInvocationNotBool,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FrontFacing", "HelperInvocation"), Values("Fragment"),
             Values("Input"), Values("%f32", "%u32"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a bool scalar",
-                              "is not a bool scalar"))), );
+                              "is not a bool scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    FrontFacingNotBool,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("FrontFacing"), Values("Fragment"), Values("Input"),
+            Values("%f32", "%u32"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a bool scalar",
+                              "is not a bool scalar"))));
+
+INSTANTIATE_TEST_SUITE_P(
     ComputeShaderInputInt32Vec3Success,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups",
                    "WorkgroupId"),
             Values("GLCompute"), Values("Input"), Values("%u32vec3"),
-            Values(TestResult())), );
+            Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    ComputeShaderInputInt32Vec3Success,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups"),
+            Values("GLCompute"), Values("Input"), Values("%u32vec3"),
+            Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
     ComputeShaderInputInt32Vec3NotGLCompute,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
-    Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups",
-                   "WorkgroupId"),
-            Values("Vertex", "Fragment", "Geometry", "TessellationControl",
-                   "TessellationEvaluation"),
-            Values("Input"), Values("%u32vec3"),
-            Values(TestResult(
-                SPV_ERROR_INVALID_DATA,
-                "to be used only with GLCompute execution model"))), );
+    Combine(
+        Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups",
+               "WorkgroupId"),
+        Values("Vertex", "Fragment", "Geometry", "TessellationControl",
+               "TessellationEvaluation"),
+        Values("Input"), Values("%u32vec3"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "to be used only with GLCompute execution model"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    ComputeShaderInputInt32Vec3NotGLCompute,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(
+        Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups"),
+        Values("Vertex", "Fragment"), Values("Input"), Values("%u32vec3"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "to be used only with GLCompute execution model"))));
+
+INSTANTIATE_TEST_SUITE_P(
     ComputeShaderInputInt32Vec3NotInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups",
@@ -665,9 +781,19 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
-                "uses storage class Output"))), );
+                "uses storage class Output"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    ComputeShaderInputInt32Vec3NotInput,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups"),
+            Values("GLCompute"), Values("Output"), Values("%u32vec3"),
+            Values(TestResult(
+                SPV_ERROR_INVALID_DATA,
+                "to be only used for variables with Input storage class",
+                "uses storage class Output"))));
+
+INSTANTIATE_TEST_SUITE_P(
     ComputeShaderInputInt32Vec3NotIntVector,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups",
@@ -676,9 +802,19 @@
             Values("%u32arr3", "%f32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 3-component 32-bit int vector",
-                              "is not an int vector"))), );
+                              "is not an int vector"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    ComputeShaderInputInt32Vec3NotIntVector,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups"),
+            Values("GLCompute"), Values("Input"),
+            Values("%u32arr3", "%f32vec3"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 3-component 32-bit int vector",
+                              "is not an int vector"))));
+
+INSTANTIATE_TEST_SUITE_P(
     ComputeShaderInputInt32Vec3NotIntVec3,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups",
@@ -686,9 +822,18 @@
             Values("GLCompute"), Values("Input"), Values("%u32vec4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 3-component 32-bit int vector",
-                              "has 4 components"))), );
+                              "has 4 components"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    ComputeShaderInputInt32Vec3NotIntVec3,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups"),
+            Values("GLCompute"), Values("Input"), Values("%u32vec4"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 3-component 32-bit int vector",
+                              "has 4 components"))));
+
+INSTANTIATE_TEST_SUITE_P(
     ComputeShaderInputInt32Vec3NotInt32Vec,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups",
@@ -696,15 +841,15 @@
             Values("GLCompute"), Values("Input"), Values("%u64vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 3-component 32-bit int vector",
-                              "has components with bit width 64"))), );
+                              "has components with bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     InvocationIdSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("InvocationId"), Values("Geometry", "TessellationControl"),
-            Values("Input"), Values("%u32"), Values(TestResult())), );
+            Values("Input"), Values("%u32"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     InvocationIdInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("InvocationId"),
@@ -712,9 +857,9 @@
             Values("Input"), Values("%u32"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "to be used only with TessellationControl or "
-                              "Geometry execution models"))), );
+                              "Geometry execution models"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     InvocationIdNotInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("InvocationId"), Values("Geometry", "TessellationControl"),
@@ -722,44 +867,57 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
-                "uses storage class Output"))), );
+                "uses storage class Output"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     InvocationIdNotIntScalar,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("InvocationId"), Values("Geometry", "TessellationControl"),
             Values("Input"), Values("%f32", "%u32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "is not an int scalar"))), );
+                              "is not an int scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     InvocationIdNotInt32,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("InvocationId"), Values("Geometry", "TessellationControl"),
             Values("Input"), Values("%u64"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "has bit width 64"))), );
+                              "has bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     InstanceIndexSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("InstanceIndex"), Values("Vertex"), Values("Input"),
-            Values("%u32"), Values(TestResult())), );
+            Values("%u32"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    InstanceIndexSuccess,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("InstanceIndex"), Values("Vertex"), Values("Input"),
+            Values("%u32"), Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
     InstanceIndexInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
-    Combine(
-        Values("InstanceIndex"),
-        Values("Geometry", "Fragment", "GLCompute", "TessellationControl",
-               "TessellationEvaluation"),
-        Values("Input"), Values("%u32"),
-        Values(TestResult(SPV_ERROR_INVALID_DATA,
-                          "to be used only with Vertex execution model"))), );
+    Combine(Values("InstanceIndex"),
+            Values("Geometry", "Fragment", "GLCompute", "TessellationControl",
+                   "TessellationEvaluation"),
+            Values("Input"), Values("%u32"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "to be used only with Vertex execution model"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    InstanceIndexInvalidExecutionModel,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("InstanceIndex"), Values("Fragment", "GLCompute"),
+            Values("Input"), Values("%u32"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "to be used only with Vertex execution model"))));
+
+INSTANTIATE_TEST_SUITE_P(
     InstanceIndexNotInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("InstanceIndex"), Values("Vertex"), Values("Output"),
@@ -767,39 +925,58 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
-                "uses storage class Output"))), );
+                "uses storage class Output"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    InstanceIndexNotInput,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("InstanceIndex"), Values("Vertex"), Values("Output"),
+            Values("%u32"),
+            Values(TestResult(
+                SPV_ERROR_INVALID_DATA,
+                "to be only used for variables with Input storage class",
+                "uses storage class Output"))));
+
+INSTANTIATE_TEST_SUITE_P(
     InstanceIndexNotIntScalar,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("InstanceIndex"), Values("Vertex"), Values("Input"),
             Values("%f32", "%u32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "is not an int scalar"))), );
+                              "is not an int scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    InstanceIndexNotIntScalar,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("InstanceIndex"), Values("Vertex"), Values("Input"),
+            Values("%f32", "%u32vec3"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 32-bit int scalar",
+                              "is not an int scalar"))));
+
+INSTANTIATE_TEST_SUITE_P(
     InstanceIndexNotInt32,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("InstanceIndex"), Values("Vertex"), Values("Input"),
             Values("%u64"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "has bit width 64"))), );
+                              "has bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LayerAndViewportIndexInputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Layer", "ViewportIndex"), Values("Fragment"),
-            Values("Input"), Values("%u32"), Values(TestResult())), );
+            Values("Input"), Values("%u32"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LayerAndViewportIndexOutputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Layer", "ViewportIndex"), Values("Geometry"),
-            Values("Output"), Values("%u32"), Values(TestResult())), );
+            Values("Output"), Values("%u32"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LayerAndViewportIndexInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Layer", "ViewportIndex"),
@@ -808,9 +985,9 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be used only with Vertex, TessellationEvaluation, "
-                "Geometry, or Fragment execution models"))), );
+                "Geometry, or Fragment execution models"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LayerAndViewportIndexExecutionModelEnabledByCapability,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Layer", "ViewportIndex"),
@@ -818,9 +995,9 @@
             Values("%u32"),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
-                "requires the ShaderViewportIndexLayerEXT capability"))), );
+                "requires the ShaderViewportIndexLayerEXT capability"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LayerAndViewportIndexFragmentNotInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
@@ -828,9 +1005,9 @@
         Values("%u32"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
                           "Output storage class if execution model is Fragment",
-                          "which is called with execution model Fragment"))), );
+                          "which is called with execution model Fragment"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LayerAndViewportIndexGeometryNotOutput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
@@ -840,34 +1017,34 @@
         Values(TestResult(SPV_ERROR_INVALID_DATA,
                           "Input storage class if execution model is Vertex, "
                           "TessellationEvaluation, or Geometry",
-                          "which is called with execution model"))), );
+                          "which is called with execution model"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LayerAndViewportIndexNotIntScalar,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Layer", "ViewportIndex"), Values("Fragment"),
             Values("Input"), Values("%f32", "%u32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "is not an int scalar"))), );
+                              "is not an int scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LayerAndViewportIndexNotInt32,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Layer", "ViewportIndex"), Values("Fragment"),
             Values("Input"), Values("%u64"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "has bit width 64"))), );
+                              "has bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PatchVerticesSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PatchVertices"),
             Values("TessellationEvaluation", "TessellationControl"),
-            Values("Input"), Values("%u32"), Values(TestResult())), );
+            Values("Input"), Values("%u32"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PatchVerticesInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PatchVertices"),
@@ -875,9 +1052,9 @@
             Values("Input"), Values("%u32"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "to be used only with TessellationControl or "
-                              "TessellationEvaluation execution models"))), );
+                              "TessellationEvaluation execution models"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PatchVerticesNotInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PatchVertices"),
@@ -886,9 +1063,9 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
-                "uses storage class Output"))), );
+                "uses storage class Output"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PatchVerticesNotIntScalar,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PatchVertices"),
@@ -896,9 +1073,9 @@
             Values("Input"), Values("%f32", "%u32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "is not an int scalar"))), );
+                              "is not an int scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PatchVerticesNotInt32,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PatchVertices"),
@@ -906,14 +1083,14 @@
             Values("Input"), Values("%u64"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "has bit width 64"))), );
+                              "has bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PointCoordSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointCoord"), Values("Fragment"), Values("Input"),
-            Values("%f32vec2"), Values(TestResult())), );
+            Values("%f32vec2"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PointCoordNotFragment,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
@@ -922,9 +1099,9 @@
                "TessellationEvaluation"),
         Values("Input"), Values("%f32vec2"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
-                          "to be used only with Fragment execution model"))), );
+                          "to be used only with Fragment execution model"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PointCoordNotInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointCoord"), Values("Fragment"), Values("Output"),
@@ -932,51 +1109,51 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
-                "uses storage class Output"))), );
+                "uses storage class Output"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PointCoordNotFloatVector,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointCoord"), Values("Fragment"), Values("Input"),
             Values("%f32arr2", "%u32vec2"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float vector",
-                              "is not a float vector"))), );
+                              "is not a float vector"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PointCoordNotFloatVec3,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointCoord"), Values("Fragment"), Values("Input"),
             Values("%f32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float vector",
-                              "has 3 components"))), );
+                              "has 3 components"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PointCoordNotF32Vec4,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointCoord"), Values("Fragment"), Values("Input"),
             Values("%f64vec2"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float vector",
-                              "has components with bit width 64"))), );
+                              "has components with bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PointSizeOutputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointSize"),
             Values("Vertex", "Geometry", "TessellationControl",
                    "TessellationEvaluation"),
-            Values("Output"), Values("%f32"), Values(TestResult())), );
+            Values("Output"), Values("%f32"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PointSizeInputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointSize"),
             Values("Geometry", "TessellationControl", "TessellationEvaluation"),
-            Values("Input"), Values("%f32"), Values(TestResult())), );
+            Values("Input"), Values("%f32"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PointSizeVertexInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointSize"), Values("Vertex"), Values("Input"),
@@ -986,9 +1163,9 @@
                 "Vulkan spec doesn't allow BuiltIn PointSize "
                 "to be used for variables with Input storage class if "
                 "execution model is Vertex.",
-                "which is called with execution model Vertex."))), );
+                "which is called with execution model Vertex."))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PointSizeInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointSize"), Values("GLCompute", "Fragment"),
@@ -996,41 +1173,66 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be used only with Vertex, TessellationControl, "
-                "TessellationEvaluation or Geometry execution models"))), );
+                "TessellationEvaluation or Geometry execution models"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PointSizeNotFloatScalar,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointSize"), Values("Vertex"), Values("Output"),
             Values("%f32vec4", "%u32"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float scalar",
-                              "is not a float scalar"))), );
+                              "is not a float scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PointSizeNotF32, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointSize"), Values("Vertex"), Values("Output"),
             Values("%f64"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float scalar",
-                              "has bit width 64"))), );
+                              "has bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PositionOutputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Position"),
             Values("Vertex", "Geometry", "TessellationControl",
                    "TessellationEvaluation"),
-            Values("Output"), Values("%f32vec4"), Values(TestResult())), );
+            Values("Output"), Values("%f32vec4"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    PositionOutputSuccess,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("Position"), Values("Vertex"), Values("Output"),
+            Values("%f32vec4"), Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    PositionOutputFailure,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("Position"), Values("Fragment", "GLCompute"),
+            Values("Output"), Values("%f32vec4"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "WebGPU spec allows BuiltIn Position to be used "
+                              "only with the Vertex execution model."))));
+
+INSTANTIATE_TEST_SUITE_P(
     PositionInputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Position"),
             Values("Geometry", "TessellationControl", "TessellationEvaluation"),
-            Values("Input"), Values("%f32vec4"), Values(TestResult())), );
+            Values("Input"), Values("%f32vec4"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    PositionInputFailure,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(
+        Values("Position"), Values("Vertex", "Fragment", "GLCompute"),
+        Values("Input"), Values("%f32vec4"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "WebGPU spec allows BuiltIn Position to be only used "
+                          "for variables with Output storage class"))));
+
+INSTANTIATE_TEST_SUITE_P(
     PositionVertexInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Position"), Values("Vertex"), Values("Input"),
@@ -1040,9 +1242,9 @@
                 "Vulkan spec doesn't allow BuiltIn Position "
                 "to be used for variables with Input storage class if "
                 "execution model is Vertex.",
-                "which is called with execution model Vertex."))), );
+                "which is called with execution model Vertex."))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PositionInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Position"), Values("GLCompute", "Fragment"),
@@ -1050,50 +1252,68 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be used only with Vertex, TessellationControl, "
-                "TessellationEvaluation or Geometry execution models"))), );
+                "TessellationEvaluation or Geometry execution models"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PositionNotFloatVector,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Position"), Values("Geometry"), Values("Input"),
             Values("%f32arr4", "%u32vec4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float vector",
-                              "is not a float vector"))), );
+                              "is not a float vector"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    PositionNotFloatVector,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(
+        Values("Position"), Values("Vertex"), Values("Output"),
+        Values("%f32arr4", "%u32vec4"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "needs to be a 4-component 32-bit float vector"))));
+
+INSTANTIATE_TEST_SUITE_P(
     PositionNotFloatVec4,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Position"), Values("Geometry"), Values("Input"),
             Values("%f32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float vector",
-                              "has 3 components"))), );
+                              "has 3 components"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    PositionNotFloatVec4,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(
+        Values("Position"), Values("Vertex"), Values("Output"),
+        Values("%f32vec3"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "needs to be a 4-component 32-bit float vector"))));
+
+INSTANTIATE_TEST_SUITE_P(
     PositionNotF32Vec4,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Position"), Values("Geometry"), Values("Input"),
             Values("%f64vec4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float vector",
-                              "has components with bit width 64"))), );
+                              "has components with bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PrimitiveIdInputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PrimitiveId"),
             Values("Fragment", "TessellationControl", "TessellationEvaluation",
                    "Geometry"),
-            Values("Input"), Values("%u32"), Values(TestResult())), );
+            Values("Input"), Values("%u32"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PrimitiveIdOutputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PrimitiveId"), Values("Geometry"), Values("Output"),
-            Values("%u32"), Values(TestResult())), );
+            Values("%u32"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PrimitiveIdInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PrimitiveId"), Values("Vertex", "GLCompute"),
@@ -1101,9 +1321,9 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be used only with Fragment, TessellationControl, "
-                "TessellationEvaluation or Geometry execution models"))), );
+                "TessellationEvaluation or Geometry execution models"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PrimitiveIdFragmentNotInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
@@ -1111,9 +1331,9 @@
         Values("%u32"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
                           "Output storage class if execution model is Fragment",
-                          "which is called with execution model Fragment"))), );
+                          "which is called with execution model Fragment"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PrimitiveIdGeometryNotInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PrimitiveId"),
@@ -1122,32 +1342,32 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "Output storage class if execution model is Tessellation",
-                "which is called with execution model Tessellation"))), );
+                "which is called with execution model Tessellation"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PrimitiveIdNotIntScalar,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PrimitiveId"), Values("Fragment"), Values("Input"),
             Values("%f32", "%u32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "is not an int scalar"))), );
+                              "is not an int scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PrimitiveIdNotInt32,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PrimitiveId"), Values("Fragment"), Values("Input"),
             Values("%u64"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "has bit width 64"))), );
+                              "has bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SampleIdSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SampleId"), Values("Fragment"), Values("Input"),
-            Values("%u32"), Values(TestResult())), );
+            Values("%u32"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SampleIdInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
@@ -1156,40 +1376,40 @@
                "TessellationEvaluation"),
         Values("Input"), Values("%u32"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
-                          "to be used only with Fragment execution model"))), );
+                          "to be used only with Fragment execution model"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SampleIdNotInput, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
         Values("SampleId"), Values("Fragment"), Values("Output"),
         Values("%u32"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
                           "Vulkan spec allows BuiltIn SampleId to be only used "
-                          "for variables with Input storage class"))), );
+                          "for variables with Input storage class"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SampleIdNotIntScalar,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SampleId"), Values("Fragment"), Values("Input"),
             Values("%f32", "%u32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "is not an int scalar"))), );
+                              "is not an int scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SampleIdNotInt32, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SampleId"), Values("Fragment"), Values("Input"),
             Values("%u64"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "has bit width 64"))), );
+                              "has bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SampleMaskSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SampleMask"), Values("Fragment"), Values("Input", "Output"),
-            Values("%u32arr2", "%u32arr4"), Values(TestResult())), );
+            Values("%u32arr2", "%u32arr4"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SampleMaskInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
@@ -1198,9 +1418,9 @@
                "TessellationEvaluation"),
         Values("Input"), Values("%u32arr2"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
-                          "to be used only with Fragment execution model"))), );
+                          "to be used only with Fragment execution model"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SampleMaskWrongStorageClass,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SampleMask"), Values("Fragment"), Values("Workgroup"),
@@ -1208,42 +1428,42 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "Vulkan spec allows BuiltIn SampleMask to be only used for "
-                "variables with Input or Output storage class"))), );
+                "variables with Input or Output storage class"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SampleMaskNotArray,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SampleMask"), Values("Fragment"), Values("Input"),
             Values("%f32", "%u32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int array",
-                              "is not an array"))), );
+                              "is not an array"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SampleMaskNotIntArray,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SampleMask"), Values("Fragment"), Values("Input"),
             Values("%f32arr2"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int array",
-                              "components are not int scalar"))), );
+                              "components are not int scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SampleMaskNotInt32Array,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SampleMask"), Values("Fragment"), Values("Input"),
             Values("%u64arr2"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int array",
-                              "has components with bit width 64"))), );
+                              "has components with bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SamplePositionSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SamplePosition"), Values("Fragment"), Values("Input"),
-            Values("%f32vec2"), Values(TestResult())), );
+            Values("%f32vec2"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SamplePositionNotFragment,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
@@ -1252,9 +1472,9 @@
                "TessellationEvaluation"),
         Values("Input"), Values("%f32vec2"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
-                          "to be used only with Fragment execution model"))), );
+                          "to be used only with Fragment execution model"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SamplePositionNotInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SamplePosition"), Values("Fragment"), Values("Output"),
@@ -1262,41 +1482,41 @@
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
-                "uses storage class Output"))), );
+                "uses storage class Output"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SamplePositionNotFloatVector,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SamplePosition"), Values("Fragment"), Values("Input"),
             Values("%f32arr2", "%u32vec4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float vector",
-                              "is not a float vector"))), );
+                              "is not a float vector"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SamplePositionNotFloatVec2,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SamplePosition"), Values("Fragment"), Values("Input"),
             Values("%f32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float vector",
-                              "has 3 components"))), );
+                              "has 3 components"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SamplePositionNotF32Vec2,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SamplePosition"), Values("Fragment"), Values("Input"),
             Values("%f64vec2"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float vector",
-                              "has components with bit width 64"))), );
+                              "has components with bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessCoordSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessCoord"), Values("TessellationEvaluation"),
-            Values("Input"), Values("%f32vec3"), Values(TestResult())), );
+            Values("Input"), Values("%f32vec3"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessCoordNotFragment,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
@@ -1306,57 +1526,57 @@
         Values("Input"), Values("%f32vec3"),
         Values(TestResult(
             SPV_ERROR_INVALID_DATA,
-            "to be used only with TessellationEvaluation execution model"))), );
+            "to be used only with TessellationEvaluation execution model"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessCoordNotInput, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessCoord"), Values("Fragment"), Values("Output"),
             Values("%f32vec3"),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
-                "uses storage class Output"))), );
+                "uses storage class Output"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessCoordNotFloatVector,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessCoord"), Values("Fragment"), Values("Input"),
             Values("%f32arr3", "%u32vec4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 3-component 32-bit float vector",
-                              "is not a float vector"))), );
+                              "is not a float vector"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessCoordNotFloatVec3,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessCoord"), Values("Fragment"), Values("Input"),
             Values("%f32vec2"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 3-component 32-bit float vector",
-                              "has 2 components"))), );
+                              "has 2 components"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessCoordNotF32Vec3,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessCoord"), Values("Fragment"), Values("Input"),
             Values("%f64vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 3-component 32-bit float vector",
-                              "has components with bit width 64"))), );
+                              "has components with bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelOuterTeseInputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"),
-            Values("Input"), Values("%f32arr4"), Values(TestResult())), );
+            Values("Input"), Values("%f32arr4"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelOuterTescOutputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelOuter"), Values("TessellationControl"),
-            Values("Output"), Values("%f32arr4"), Values(TestResult())), );
+            Values("Output"), Values("%f32arr4"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelOuterInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelOuter"),
@@ -1364,9 +1584,9 @@
             Values("Input"), Values("%f32arr4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "to be used only with TessellationControl or "
-                              "TessellationEvaluation execution models."))), );
+                              "TessellationEvaluation execution models."))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelOuterOutputTese,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"),
@@ -1375,9 +1595,9 @@
                 SPV_ERROR_INVALID_DATA,
                 "Vulkan spec doesn't allow TessLevelOuter/TessLevelInner to be "
                 "used for variables with Output storage class if execution "
-                "model is TessellationEvaluation."))), );
+                "model is TessellationEvaluation."))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelOuterInputTesc,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelOuter"), Values("TessellationControl"),
@@ -1386,57 +1606,57 @@
                 SPV_ERROR_INVALID_DATA,
                 "Vulkan spec doesn't allow TessLevelOuter/TessLevelInner to be "
                 "used for variables with Input storage class if execution "
-                "model is TessellationControl."))), );
+                "model is TessellationControl."))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelOuterNotArray,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"),
             Values("Input"), Values("%f32vec4", "%f32"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float array",
-                              "is not an array"))), );
+                              "is not an array"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelOuterNotFloatArray,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"),
             Values("Input"), Values("%u32arr4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float array",
-                              "components are not float scalar"))), );
+                              "components are not float scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelOuterNotFloatArr4,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"),
             Values("Input"), Values("%f32arr3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float array",
-                              "has 3 components"))), );
+                              "has 3 components"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelOuterNotF32Arr4,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"),
             Values("Input"), Values("%f64arr4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float array",
-                              "has components with bit width 64"))), );
+                              "has components with bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelInnerTeseInputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelInner"), Values("TessellationEvaluation"),
-            Values("Input"), Values("%f32arr2"), Values(TestResult())), );
+            Values("Input"), Values("%f32arr2"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelInnerTescOutputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelInner"), Values("TessellationControl"),
-            Values("Output"), Values("%f32arr2"), Values(TestResult())), );
+            Values("Output"), Values("%f32arr2"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelInnerInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelInner"),
@@ -1444,9 +1664,9 @@
             Values("Input"), Values("%f32arr2"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "to be used only with TessellationControl or "
-                              "TessellationEvaluation execution models."))), );
+                              "TessellationEvaluation execution models."))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelInnerOutputTese,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelInner"), Values("TessellationEvaluation"),
@@ -1455,9 +1675,9 @@
                 SPV_ERROR_INVALID_DATA,
                 "Vulkan spec doesn't allow TessLevelOuter/TessLevelInner to be "
                 "used for variables with Output storage class if execution "
-                "model is TessellationEvaluation."))), );
+                "model is TessellationEvaluation."))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelInnerInputTesc,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelInner"), Values("TessellationControl"),
@@ -1466,62 +1686,75 @@
                 SPV_ERROR_INVALID_DATA,
                 "Vulkan spec doesn't allow TessLevelOuter/TessLevelInner to be "
                 "used for variables with Input storage class if execution "
-                "model is TessellationControl."))), );
+                "model is TessellationControl."))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelInnerNotArray,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelInner"), Values("TessellationEvaluation"),
             Values("Input"), Values("%f32vec2", "%f32"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float array",
-                              "is not an array"))), );
+                              "is not an array"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelInnerNotFloatArray,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelInner"), Values("TessellationEvaluation"),
             Values("Input"), Values("%u32arr2"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float array",
-                              "components are not float scalar"))), );
+                              "components are not float scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelInnerNotFloatArr2,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelInner"), Values("TessellationEvaluation"),
             Values("Input"), Values("%f32arr3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float array",
-                              "has 3 components"))), );
+                              "has 3 components"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TessLevelInnerNotF32Arr2,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelInner"), Values("TessellationEvaluation"),
             Values("Input"), Values("%f64arr2"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float array",
-                              "has components with bit width 64"))), );
+                              "has components with bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     VertexIndexSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("VertexIndex"), Values("Vertex"), Values("Input"),
-            Values("%u32"), Values(TestResult())), );
+            Values("%u32"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    VertexIndexSuccess,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("VertexIndex"), Values("Vertex"), Values("Input"),
+            Values("%u32"), Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
     VertexIndexInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
-    Combine(
-        Values("VertexIndex"),
-        Values("Fragment", "GLCompute", "Geometry", "TessellationControl",
-               "TessellationEvaluation"),
-        Values("Input"), Values("%u32"),
-        Values(TestResult(SPV_ERROR_INVALID_DATA,
-                          "to be used only with Vertex execution model"))), );
+    Combine(Values("VertexIndex"),
+            Values("Fragment", "GLCompute", "Geometry", "TessellationControl",
+                   "TessellationEvaluation"),
+            Values("Input"), Values("%u32"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "to be used only with Vertex execution model"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    VertexIndexInvalidExecutionModel,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("VertexIndex"), Values("Fragment", "GLCompute"),
+            Values("Input"), Values("%u32"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "to be used only with Vertex execution model"))));
+
+INSTANTIATE_TEST_SUITE_P(
     VertexIndexNotInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
@@ -1529,44 +1762,115 @@
         Values("%u32"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
                           "Vulkan spec allows BuiltIn VertexIndex to be only "
-                          "used for variables with Input storage class"))), );
+                          "used for variables with Input storage class"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    VertexIndexNotInput,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(
+        Values("VertexIndex"), Values("Vertex"), Values("Output"),
+        Values("%u32"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "WebGPU spec allows BuiltIn VertexIndex to be only "
+                          "used for variables with Input storage class"))));
+
+INSTANTIATE_TEST_SUITE_P(
     VertexIndexNotIntScalar,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("VertexIndex"), Values("Vertex"), Values("Input"),
             Values("%f32", "%u32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "is not an int scalar"))), );
+                              "is not an int scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    VertexIndexNotIntScalar,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("VertexIndex"), Values("Vertex"), Values("Input"),
+            Values("%f32", "%u32vec3"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 32-bit int scalar",
+                              "is not an int scalar"))));
+
+INSTANTIATE_TEST_SUITE_P(
     VertexIndexNotInt32,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("VertexIndex"), Values("Vertex"), Values("Input"),
             Values("%u64"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
-                              "has bit width 64"))), );
+                              "has bit width 64"))));
 
-TEST_P(ValidateVulkanCombineBuiltInArrayedVariable, Variable) {
-  const char* const built_in = std::get<0>(GetParam());
-  const char* const execution_model = std::get<1>(GetParam());
-  const char* const storage_class = std::get<2>(GetParam());
-  const char* const data_type = std::get<3>(GetParam());
-  const TestResult& test_result = std::get<4>(GetParam());
+INSTANTIATE_TEST_SUITE_P(
+    LocalInvocationIndexSuccess,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("LocalInvocationIndex"), Values("GLCompute"),
+            Values("Input"), Values("%u32"), Values(TestResult())));
 
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+INSTANTIATE_TEST_SUITE_P(
+    LocalInvocationIndexInvalidExecutionModel,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(
+        Values("LocalInvocationIndex"), Values("Fragment", "Vertex"),
+        Values("Input"), Values("%u32"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "to be used only with GLCompute execution model"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    LocalInvocationIndexNotInput,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(
+        Values("LocalInvocationIndex"), Values("GLCompute"), Values("Output"),
+        Values("%u32"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "WebGPU spec allows BuiltIn LocalInvocationIndex to "
+                          "be only used for variables with Input storage "
+                          "class"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    LocalInvocationIndexNotIntScalar,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("LocalInvocationIndex"), Values("GLCompute"),
+            Values("Input"), Values("%f32", "%u32vec3"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 32-bit int", "is not an int"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    WhitelistRejection,
+    ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("PointSize", "ClipDistance", "CullDistance", "VertexId",
+                   "InstanceId", "PointCoord", "SampleMask", "HelperInvocation",
+                   "WorkgroupId"),
+            Values("Vertex"), Values("Input"), Values("%u32"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "WebGPU does not allow BuiltIn"))));
+
+CodeGenerator GetArrayedVariableCodeGenerator(spv_target_env env,
+                                              const char* const built_in,
+                                              const char* const execution_model,
+                                              const char* const storage_class,
+                                              const char* const data_type) {
+  CodeGenerator generator =
+      spvIsWebGPUEnv(env) ? CodeGenerator::GetWebGPUShaderCodeGenerator()
+                          : CodeGenerator::GetDefaultShaderCodeGenerator();
+
   generator.before_types_ = "OpDecorate %built_in_var BuiltIn ";
   generator.before_types_ += built_in;
   generator.before_types_ += "\n";
 
   std::ostringstream after_types;
   after_types << "%built_in_array = OpTypeArray " << data_type << " %u32_3\n";
+  if (InitializerRequired(env, storage_class)) {
+    after_types << "%built_in_array_null = OpConstantNull %built_in_array\n";
+  }
+
   after_types << "%built_in_ptr = OpTypePointer " << storage_class
               << " %built_in_array\n";
-  after_types << "%built_in_var = OpVariable %built_in_ptr " << storage_class
-              << "\n";
+  after_types << "%built_in_var = OpVariable %built_in_ptr " << storage_class;
+  if (InitializerRequired(env, storage_class)) {
+    after_types << " %built_in_array_null";
+  }
+  after_types << "\n";
   generator.after_types_ = after_types.str();
 
   EntryPoint entry_point;
@@ -1575,7 +1879,7 @@
   entry_point.interfaces = "%built_in_var";
   // Any kind of reference would do.
   entry_point.body = R"(
-%val = OpBitcast %u64 %built_in_var
+%val = OpBitcast %u32 %built_in_var
 )";
 
   std::ostringstream execution_modes;
@@ -1601,6 +1905,19 @@
 
   generator.entry_points_.push_back(std::move(entry_point));
 
+  return generator;
+}
+
+TEST_P(ValidateVulkanCombineBuiltInArrayedVariable, Variable) {
+  const char* const built_in = std::get<0>(GetParam());
+  const char* const execution_model = std::get<1>(GetParam());
+  const char* const storage_class = std::get<2>(GetParam());
+  const char* const data_type = std::get<3>(GetParam());
+  const TestResult& test_result = std::get<4>(GetParam());
+
+  CodeGenerator generator = GetArrayedVariableCodeGenerator(
+      SPV_ENV_VULKAN_1_0, built_in, execution_model, storage_class, data_type);
+
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(test_result.validation_result,
             ValidateInstructions(SPV_ENV_VULKAN_1_0));
@@ -1612,78 +1929,110 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(PointSizeArrayedF32TessControl,
-                        ValidateVulkanCombineBuiltInArrayedVariable,
-                        Combine(Values("PointSize"),
-                                Values("TessellationControl"), Values("Input"),
-                                Values("%f32"), Values(TestResult())), );
+TEST_P(ValidateWebGPUCombineBuiltInArrayedVariable, Variable) {
+  const char* const built_in = std::get<0>(GetParam());
+  const char* const execution_model = std::get<1>(GetParam());
+  const char* const storage_class = std::get<2>(GetParam());
+  const char* const data_type = std::get<3>(GetParam());
+  const TestResult& test_result = std::get<4>(GetParam());
 
-INSTANTIATE_TEST_CASE_P(
+  CodeGenerator generator = GetArrayedVariableCodeGenerator(
+      SPV_ENV_WEBGPU_0, built_in, execution_model, storage_class, data_type);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(test_result.validation_result,
+            ValidateInstructions(SPV_ENV_WEBGPU_0));
+  if (test_result.error_str) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str));
+  }
+  if (test_result.error_str2) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(PointSizeArrayedF32TessControl,
+                         ValidateVulkanCombineBuiltInArrayedVariable,
+                         Combine(Values("PointSize"),
+                                 Values("TessellationControl"), Values("Input"),
+                                 Values("%f32"), Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
     PointSizeArrayedF64TessControl, ValidateVulkanCombineBuiltInArrayedVariable,
     Combine(Values("PointSize"), Values("TessellationControl"), Values("Input"),
             Values("%f64"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float scalar",
-                              "has bit width 64"))), );
+                              "has bit width 64"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PointSizeArrayedF32Vertex, ValidateVulkanCombineBuiltInArrayedVariable,
     Combine(Values("PointSize"), Values("Vertex"), Values("Output"),
             Values("%f32"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float scalar",
-                              "is not a float scalar"))), );
+                              "is not a float scalar"))));
 
-INSTANTIATE_TEST_CASE_P(PositionArrayedF32Vec4TessControl,
-                        ValidateVulkanCombineBuiltInArrayedVariable,
-                        Combine(Values("Position"),
-                                Values("TessellationControl"), Values("Input"),
-                                Values("%f32vec4"), Values(TestResult())), );
+INSTANTIATE_TEST_SUITE_P(PositionArrayedF32Vec4TessControl,
+                         ValidateVulkanCombineBuiltInArrayedVariable,
+                         Combine(Values("Position"),
+                                 Values("TessellationControl"), Values("Input"),
+                                 Values("%f32vec4"), Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PositionArrayedF32Vec3TessControl,
     ValidateVulkanCombineBuiltInArrayedVariable,
     Combine(Values("Position"), Values("TessellationControl"), Values("Input"),
             Values("%f32vec3"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float vector",
-                              "has 3 components"))), );
+                              "has 3 components"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PositionArrayedF32Vec4Vertex, ValidateVulkanCombineBuiltInArrayedVariable,
     Combine(Values("Position"), Values("Vertex"), Values("Output"),
-            Values("%f32"),
+            Values("%f32vec4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float vector",
-                              "is not a float vector"))), );
+                              "is not a float vector"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
+    PositionArrayedF32Vec4Vertex, ValidateWebGPUCombineBuiltInArrayedVariable,
+    Combine(Values("Position"), Values("Vertex"), Values("Output"),
+            Values("%f32vec4"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 4-component 32-bit float vector",
+                              "is not a float vector"))));
+
+INSTANTIATE_TEST_SUITE_P(
     ClipAndCullDistanceOutputSuccess,
     ValidateVulkanCombineBuiltInArrayedVariable,
     Combine(Values("ClipDistance", "CullDistance"),
             Values("Geometry", "TessellationControl", "TessellationEvaluation"),
             Values("Output"), Values("%f32arr2", "%f32arr4"),
-            Values(TestResult())), );
+            Values(TestResult())));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ClipAndCullDistanceVertexInput, ValidateVulkanCombineBuiltInArrayedVariable,
     Combine(Values("ClipDistance", "CullDistance"), Values("Fragment"),
             Values("Input"), Values("%f32arr4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float array",
-                              "components are not float scalar"))), );
+                              "components are not float scalar"))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ClipAndCullDistanceNotArray, ValidateVulkanCombineBuiltInArrayedVariable,
     Combine(Values("ClipDistance", "CullDistance"),
             Values("Geometry", "TessellationControl", "TessellationEvaluation"),
             Values("Input"), Values("%f32vec2", "%f32vec4"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float array",
-                              "components are not float scalar"))), );
+                              "components are not float scalar"))));
 
-TEST_F(ValidateBuiltIns, WorkgroupSizeSuccess) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+CodeGenerator GetWorkgroupSizeSuccessGenerator(spv_target_env env) {
+  CodeGenerator generator =
+      env == SPV_ENV_WEBGPU_0 ? CodeGenerator::GetWebGPUShaderCodeGenerator()
+                              : CodeGenerator::GetDefaultShaderCodeGenerator();
+
   generator.before_types_ = R"(
 OpDecorate %workgroup_size BuiltIn WorkgroupSize
 )";
@@ -1700,12 +2049,27 @@
 )";
   generator.entry_points_.push_back(std::move(entry_point));
 
+  return generator;
+}
+
+TEST_F(ValidateBuiltIns, VulkanWorkgroupSizeSuccess) {
+  CodeGenerator generator =
+      GetWorkgroupSizeSuccessGenerator(SPV_ENV_VULKAN_1_0);
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
 }
 
-TEST_F(ValidateBuiltIns, WorkgroupSizeFragment) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+TEST_F(ValidateBuiltIns, WebGPUWorkgroupSizeSuccess) {
+  CodeGenerator generator = GetWorkgroupSizeSuccessGenerator(SPV_ENV_WEBGPU_0);
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
+CodeGenerator GetWorkgroupSizeFragmentGenerator(spv_target_env env) {
+  CodeGenerator generator =
+      env == SPV_ENV_WEBGPU_0 ? CodeGenerator::GetWebGPUShaderCodeGenerator()
+                              : CodeGenerator::GetDefaultShaderCodeGenerator();
+
   generator.before_types_ = R"(
 OpDecorate %workgroup_size BuiltIn WorkgroupSize
 )";
@@ -1723,6 +2087,13 @@
 )";
   generator.entry_points_.push_back(std::move(entry_point));
 
+  return generator;
+}
+
+TEST_F(ValidateBuiltIns, VulkanWorkgroupSizeFragment) {
+  CodeGenerator generator =
+      GetWorkgroupSizeFragmentGenerator(SPV_ENV_VULKAN_1_0);
+
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
@@ -1734,8 +2105,22 @@
                         "called with execution model Fragment"));
 }
 
+TEST_F(ValidateBuiltIns, WebGPUWorkgroupSizeFragment) {
+  CodeGenerator generator = GetWorkgroupSizeFragmentGenerator(SPV_ENV_WEBGPU_0);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("WebGPU spec allows BuiltIn WorkgroupSize to be used "
+                        "only with GLCompute execution model"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("is referencing ID <2> (OpConstantComposite) which is "
+                        "decorated with BuiltIn WorkgroupSize in function <1> "
+                        "called with execution model Fragment"));
+}
+
 TEST_F(ValidateBuiltIns, WorkgroupSizeNotConstant) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
   generator.before_types_ = R"(
 OpDecorate %copy BuiltIn WorkgroupSize
 )";
@@ -1759,8 +2144,11 @@
                         "constant. ID <2> (OpCopyObject) is not a constant"));
 }
 
-TEST_F(ValidateBuiltIns, WorkgroupSizeNotVector) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+CodeGenerator GetWorkgroupSizeNotVectorGenerator(spv_target_env env) {
+  CodeGenerator generator =
+      env == SPV_ENV_WEBGPU_0 ? CodeGenerator::GetWebGPUShaderCodeGenerator()
+                              : CodeGenerator::GetDefaultShaderCodeGenerator();
+
   generator.before_types_ = R"(
 OpDecorate %workgroup_size BuiltIn WorkgroupSize
 )";
@@ -1777,6 +2165,13 @@
 )";
   generator.entry_points_.push_back(std::move(entry_point));
 
+  return generator;
+}
+
+TEST_F(ValidateBuiltIns, VulkanWorkgroupSizeNotVector) {
+  CodeGenerator generator =
+      GetWorkgroupSizeNotVectorGenerator(SPV_ENV_VULKAN_1_0);
+
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
@@ -1785,8 +2180,23 @@
                         "ID <2> (OpConstant) is not an int vector."));
 }
 
-TEST_F(ValidateBuiltIns, WorkgroupSizeNotIntVector) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+TEST_F(ValidateBuiltIns, WebGPUWorkgroupSizeNotVector) {
+  CodeGenerator generator =
+      GetWorkgroupSizeNotVectorGenerator(SPV_ENV_WEBGPU_0);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("According to the WebGPU spec BuiltIn WorkgroupSize "
+                        "variable needs to be a 3-component 32-bit int vector. "
+                        "ID <2> (OpConstant) is not an int vector."));
+}
+
+CodeGenerator GetWorkgroupSizeNotIntVectorGenerator(spv_target_env env) {
+  CodeGenerator generator =
+      env == SPV_ENV_WEBGPU_0 ? CodeGenerator::GetWebGPUShaderCodeGenerator()
+                              : CodeGenerator::GetDefaultShaderCodeGenerator();
+
   generator.before_types_ = R"(
 OpDecorate %workgroup_size BuiltIn WorkgroupSize
 )";
@@ -1803,6 +2213,13 @@
 )";
   generator.entry_points_.push_back(std::move(entry_point));
 
+  return generator;
+}
+
+TEST_F(ValidateBuiltIns, VulkanWorkgroupSizeNotIntVector) {
+  CodeGenerator generator =
+      GetWorkgroupSizeNotIntVectorGenerator(SPV_ENV_VULKAN_1_0);
+
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
@@ -1811,8 +2228,23 @@
                         "ID <2> (OpConstantComposite) is not an int vector."));
 }
 
-TEST_F(ValidateBuiltIns, WorkgroupSizeNotVec3) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+TEST_F(ValidateBuiltIns, WebGPUWorkgroupSizeNotIntVector) {
+  CodeGenerator generator =
+      GetWorkgroupSizeNotIntVectorGenerator(SPV_ENV_WEBGPU_0);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("According to the WebGPU spec BuiltIn WorkgroupSize "
+                        "variable needs to be a 3-component 32-bit int vector. "
+                        "ID <2> (OpConstantComposite) is not an int vector."));
+}
+
+CodeGenerator GetWorkgroupSizeNotVec3Generator(spv_target_env env) {
+  CodeGenerator generator =
+      env == SPV_ENV_WEBGPU_0 ? CodeGenerator::GetWebGPUShaderCodeGenerator()
+                              : CodeGenerator::GetDefaultShaderCodeGenerator();
+
   generator.before_types_ = R"(
 OpDecorate %workgroup_size BuiltIn WorkgroupSize
 )";
@@ -1829,6 +2261,13 @@
 )";
   generator.entry_points_.push_back(std::move(entry_point));
 
+  return generator;
+}
+
+TEST_F(ValidateBuiltIns, VulkanWorkgroupSizeNotVec3) {
+  CodeGenerator generator =
+      GetWorkgroupSizeNotVec3Generator(SPV_ENV_VULKAN_1_0);
+
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
@@ -1837,8 +2276,19 @@
                         "ID <2> (OpConstantComposite) has 2 components."));
 }
 
+TEST_F(ValidateBuiltIns, WebGPUWorkgroupSizeNotVec3) {
+  CodeGenerator generator = GetWorkgroupSizeNotVec3Generator(SPV_ENV_WEBGPU_0);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("According to the WebGPU spec BuiltIn WorkgroupSize "
+                        "variable needs to be a 3-component 32-bit int vector. "
+                        "ID <2> (OpConstantComposite) has 2 components."));
+}
+
 TEST_F(ValidateBuiltIns, WorkgroupSizeNotInt32Vec) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
   generator.before_types_ = R"(
 OpDecorate %workgroup_size BuiltIn WorkgroupSize
 )";
@@ -1865,7 +2315,7 @@
 }
 
 TEST_F(ValidateBuiltIns, WorkgroupSizePrivateVar) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
   generator.before_types_ = R"(
 OpDecorate %workgroup_size BuiltIn WorkgroupSize
 )";
@@ -1888,7 +2338,7 @@
 }
 
 TEST_F(ValidateBuiltIns, GeometryPositionInOutSuccess) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
 
   generator.before_types_ = R"(
 OpMemberDecorate %input_type 0 BuiltIn Position
@@ -1927,7 +2377,7 @@
 }
 
 TEST_F(ValidateBuiltIns, WorkgroupIdNotVec3) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
   generator.before_types_ = R"(
 OpDecorate %workgroup_size BuiltIn WorkgroupSize
 OpDecorate %workgroup_id BuiltIn WorkgroupId
@@ -1958,7 +2408,7 @@
 }
 
 TEST_F(ValidateBuiltIns, TwoBuiltInsFirstFails) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
 
   generator.before_types_ = R"(
 OpMemberDecorate %input_type 0 BuiltIn FragCoord
@@ -1998,7 +2448,7 @@
 }
 
 TEST_F(ValidateBuiltIns, TwoBuiltInsSecondFails) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
 
   generator.before_types_ = R"(
 OpMemberDecorate %input_type 0 BuiltIn Position
@@ -2038,7 +2488,7 @@
 }
 
 TEST_F(ValidateBuiltIns, VertexPositionVariableSuccess) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
   generator.before_types_ = R"(
 OpDecorate %position BuiltIn Position
 )";
@@ -2062,7 +2512,7 @@
 }
 
 TEST_F(ValidateBuiltIns, FragmentPositionTwoEntryPoints) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
   generator.before_types_ = R"(
 OpMemberDecorate %output_type 0 BuiltIn Position
 )";
@@ -2111,16 +2561,20 @@
               HasSubstr("called with execution model Fragment"));
 }
 
-TEST_F(ValidateBuiltIns, FragmentFragDepthNoDepthReplacing) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+CodeGenerator GetNoDepthReplacingGenerator(spv_target_env env) {
+  CodeGenerator generator =
+      spvIsWebGPUEnv(env) ? CodeGenerator::GetWebGPUShaderCodeGenerator()
+                          : CodeGenerator::GetDefaultShaderCodeGenerator();
+
   generator.before_types_ = R"(
 OpMemberDecorate %output_type 0 BuiltIn FragDepth
 )";
 
   generator.after_types_ = R"(
 %output_type = OpTypeStruct %f32
+%output_null = OpConstantNull %output_type
 %output_ptr = OpTypePointer Output %output_type
-%output = OpVariable %output_ptr Output
+%output = OpVariable %output_ptr Output %output_null
 %output_f32_ptr = OpTypePointer Output %f32
 )";
 
@@ -2134,7 +2588,7 @@
 )";
   generator.entry_points_.push_back(std::move(entry_point));
 
-  generator.add_at_the_end_ = R"(
+  const std::string function_body = R"(
 %foo = OpFunction %void None %func
 %foo_entry = OpLabel
 %frag_depth = OpAccessChain %output_f32_ptr %output %u32_0
@@ -2143,6 +2597,18 @@
 OpFunctionEnd
 )";
 
+  if (spvIsWebGPUEnv(env)) {
+    generator.after_types_ += function_body;
+  } else {
+    generator.add_at_the_end_ = function_body;
+  }
+
+  return generator;
+}
+
+TEST_F(ValidateBuiltIns, VulkanFragmentFragDepthNoDepthReplacing) {
+  CodeGenerator generator = GetNoDepthReplacingGenerator(SPV_ENV_VULKAN_1_0);
+
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
@@ -2150,16 +2616,31 @@
                         "be declared when using BuiltIn FragDepth"));
 }
 
-TEST_F(ValidateBuiltIns, FragmentFragDepthOneMainHasDepthReplacingOtherHasnt) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+TEST_F(ValidateBuiltIns, WebGPUFragmentFragDepthNoDepthReplacing) {
+  CodeGenerator generator = GetNoDepthReplacingGenerator(SPV_ENV_WEBGPU_0);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("WebGPU spec requires DepthReplacing execution mode to "
+                        "be declared when using BuiltIn FragDepth"));
+}
+
+CodeGenerator GetOneMainHasDepthReplacingOtherHasntGenerator(
+    spv_target_env env) {
+  CodeGenerator generator =
+      spvIsWebGPUEnv(env) ? CodeGenerator::GetWebGPUShaderCodeGenerator()
+                          : CodeGenerator::GetDefaultShaderCodeGenerator();
+
   generator.before_types_ = R"(
 OpMemberDecorate %output_type 0 BuiltIn FragDepth
 )";
 
   generator.after_types_ = R"(
 %output_type = OpTypeStruct %f32
+%output_null = OpConstantNull %output_type
 %output_ptr = OpTypePointer Output %output_type
-%output = OpVariable %output_ptr Output
+%output = OpVariable %output_ptr Output %output_null
 %output_f32_ptr = OpTypePointer Output %f32
 )";
 
@@ -2184,7 +2665,7 @@
 )";
   generator.entry_points_.push_back(std::move(entry_point));
 
-  generator.add_at_the_end_ = R"(
+  const std::string function_body = R"(
 %foo = OpFunction %void None %func
 %foo_entry = OpLabel
 %frag_depth = OpAccessChain %output_f32_ptr %output %u32_0
@@ -2193,6 +2674,20 @@
 OpFunctionEnd
 )";
 
+  if (spvIsWebGPUEnv(env)) {
+    generator.after_types_ += function_body;
+  } else {
+    generator.add_at_the_end_ = function_body;
+  }
+
+  return generator;
+}
+
+TEST_F(ValidateBuiltIns,
+       VulkanFragmentFragDepthOneMainHasDepthReplacingOtherHasnt) {
+  CodeGenerator generator =
+      GetOneMainHasDepthReplacingOtherHasntGenerator(SPV_ENV_VULKAN_1_0);
+
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
@@ -2200,8 +2695,20 @@
                         "be declared when using BuiltIn FragDepth"));
 }
 
+TEST_F(ValidateBuiltIns,
+       WebGPUFragmentFragDepthOneMainHasDepthReplacingOtherHasnt) {
+  CodeGenerator generator =
+      GetOneMainHasDepthReplacingOtherHasntGenerator(SPV_ENV_WEBGPU_0);
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("WebGPU spec requires DepthReplacing execution mode to "
+                        "be declared when using BuiltIn FragDepth"));
+}
+
 TEST_F(ValidateBuiltIns, AllowInstanceIdWithIntersectionShader) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
   generator.capabilities_ += R"(
 OpCapability RayTracingNV
 )";
@@ -2241,7 +2748,7 @@
 }
 
 TEST_F(ValidateBuiltIns, DisallowInstanceIdWithRayGenShader) {
-  CodeGenerator generator = GetDefaultShaderCodeGenerator();
+  CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
   generator.capabilities_ += R"(
 OpCapability RayTracingNV
 )";
diff --git a/test/val/val_capability_test.cpp b/test/val/val_capability_test.cpp
index 488e957..4fb2a7c 100644
--- a/test/val/val_capability_test.cpp
+++ b/test/val/val_capability_test.cpp
@@ -611,7 +611,7 @@
   "           OpReturn"
   "           OpFunctionEnd ";
 
-INSTANTIATE_TEST_CASE_P(ExecutionModel, ValidateCapability,
+INSTANTIATE_TEST_SUITE_P(ExecutionModel, ValidateCapability,
                         Combine(
                             ValuesIn(AllCapabilities()),
                             Values(
@@ -639,9 +639,9 @@
 std::make_pair(std::string(kGLSL450MemoryModel) +
           " OpEntryPoint Kernel %func \"shader\"" +
           std::string(kVoidFVoid), KernelDependencies())
-)),);
+)));
 
-INSTANTIATE_TEST_CASE_P(AddressingAndMemoryModel, ValidateCapability,
+INSTANTIATE_TEST_SUITE_P(AddressingAndMemoryModel, ValidateCapability,
                         Combine(
                             ValuesIn(AllCapabilities()),
                             Values(
@@ -681,9 +681,9 @@
           " OpMemoryModel Physical64 OpenCL"
           " OpEntryPoint Kernel %func \"compute\"" +
           std::string(kVoidFVoid),  AddressesDependencies())
-)),);
+)));
 
-INSTANTIATE_TEST_CASE_P(ExecutionMode, ValidateCapability,
+INSTANTIATE_TEST_SUITE_P(ExecutionMode, ValidateCapability,
                         Combine(
                             ValuesIn(AllCapabilities()),
                             Values(
@@ -836,11 +836,11 @@
 std::make_pair(std::string(kGLSL450MemoryModel) +
           "OpEntryPoint Kernel %func \"shader\" "
           "OpExecutionMode %func ContractionOff" +
-          std::string(kVoidFVoid), KernelDependencies()))),);
+          std::string(kVoidFVoid), KernelDependencies()))));
 
 // clang-format on
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ExecutionModeV11, ValidateCapabilityV11,
     Combine(ValuesIn(AllCapabilities()),
             Values(std::make_pair(std::string(kOpenCLMemoryModel) +
@@ -853,10 +853,10 @@
                            "OpEntryPoint Kernel %func \"shader\" "
                            "OpExecutionMode %func SubgroupsPerWorkgroup 65535" +
                            std::string(kVoidFVoid),
-                       std::vector<std::string>{"SubgroupDispatch"}))), );
+                       std::vector<std::string>{"SubgroupDispatch"}))));
 // clang-format off
 
-INSTANTIATE_TEST_CASE_P(StorageClass, ValidateCapability,
+INSTANTIATE_TEST_SUITE_P(StorageClass, ValidateCapability,
                         Combine(
                             ValuesIn(AllCapabilities()),
                             Values(
@@ -920,9 +920,9 @@
           " %ptrt = OpTypePointer Image %intt\n"
           " %var = OpVariable %ptrt Image\n" + std::string(kVoidFVoid),
           AllCapabilities())
-)),);
+)));
 
-INSTANTIATE_TEST_CASE_P(Dim, ValidateCapability,
+INSTANTIATE_TEST_SUITE_P(Dim, ValidateCapability,
                         Combine(
                             ValuesIn(AllCapabilities()),
                             Values(
@@ -968,11 +968,11 @@
           " %voidt = OpTypeVoid"
           " %imgt = OpTypeImage %voidt SubpassData 0 0 0 2 Unknown" + std::string(kVoidFVoid2),
           std::vector<std::string>{"InputAttachment"})
-)),);
+)));
 
 // NOTE: All Sampler Address Modes require kernel capabilities but the
 // OpConstantSampler requires LiteralSampler which depends on Kernel
-INSTANTIATE_TEST_CASE_P(SamplerAddressingMode, ValidateCapability,
+INSTANTIATE_TEST_SUITE_P(SamplerAddressingMode, ValidateCapability,
                         Combine(
                             ValuesIn(AllCapabilities()),
                             Values(
@@ -1006,7 +1006,7 @@
           " %sampler = OpConstantSampler %samplert RepeatMirrored 1 Nearest" +
           std::string(kVoidFVoid),
           std::vector<std::string>{"LiteralSampler"})
-)),);
+)));
 
 // TODO(umar): Sampler Filter Mode
 // TODO(umar): Image Format
@@ -1019,7 +1019,7 @@
 // TODO(umar): Access Qualifier
 // TODO(umar): Function Parameter Attribute
 
-INSTANTIATE_TEST_CASE_P(Decoration, ValidateCapability,
+INSTANTIATE_TEST_SUITE_P(Decoration, ValidateCapability,
                         Combine(
                             ValuesIn(AllCapabilities()),
                             Values(
@@ -1129,9 +1129,14 @@
           "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
           AllCapabilities()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
+          // NonWritable must target something valid, such as a storage image.
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt NonWritable\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %var NonWritable "
+          "%float = OpTypeFloat 32 "
+          "%imstor = OpTypeImage %float 2D 0 0 0 2 Unknown "
+          "%ptr = OpTypePointer UniformConstant %imstor "
+          "%var = OpVariable %ptr UniformConstant "
+          + std::string(kVoidFVoid),
           AllCapabilities()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
@@ -1226,10 +1231,10 @@
           "OpDecorate %intt Alignment 4\n"
           "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
           KernelDependencies())
-)),);
+)));
 
 // clang-format on
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DecorationSpecId, ValidateCapability,
     Combine(
         ValuesIn(AllSpirV10Capabilities()),
@@ -1239,9 +1244,9 @@
                                   "%intt = OpTypeInt 32 0\n"
                                   "%1 = OpSpecConstant %intt 0\n" +
                                   std::string(kVoidFVoid),
-                              ShaderDependencies()))), );
+                              ShaderDependencies()))));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DecorationV11, ValidateCapabilityV11,
     Combine(ValuesIn(AllCapabilities()),
             Values(std::make_pair(std::string(kOpenCLMemoryModel) +
@@ -1270,10 +1275,10 @@
                                    "%intt = OpTypeInt 32 0 "
                                    "%1 = OpSpecConstant %intt 0") +
                            std::string(kVoidFVoid),
-                       ShaderDependencies()))), );
+                       ShaderDependencies()))));
 // clang-format off
 
-INSTANTIATE_TEST_CASE_P(BuiltIn, ValidateCapability,
+INSTANTIATE_TEST_SUITE_P(BuiltIn, ValidateCapability,
                         Combine(
                             ValuesIn(AllCapabilities()),
                             Values(
@@ -1495,13 +1500,13 @@
           "OpDecorate %intt BuiltIn InstanceIndex\n"
           "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
           ShaderDependencies())
-)),);
+)));
 
 // Ensure that mere mention of PointSize, ClipDistance, or CullDistance as
 // BuiltIns does not trigger the requirement for the associated
 // capability.
 // See https://github.com/KhronosGroup/SPIRV-Tools/issues/365
-INSTANTIATE_TEST_CASE_P(BuiltIn, ValidateCapabilityVulkan10,
+INSTANTIATE_TEST_SUITE_P(BuiltIn, ValidateCapabilityVulkan10,
                         Combine(
                             // All capabilities to try.
                             ValuesIn(AllSpirV10Capabilities()),
@@ -1532,9 +1537,9 @@
           "%f32arr4 = OpTypeArray %f32 %intt_4\n"
           "%block = OpTypeStruct %f32arr4\n" + std::string(kVoidFVoid),
           AllVulkan10Capabilities())
-)),);
+)));
 
-INSTANTIATE_TEST_CASE_P(BuiltIn, ValidateCapabilityOpenGL40,
+INSTANTIATE_TEST_SUITE_P(BuiltIn, ValidateCapabilityOpenGL40,
                         Combine(
                             // OpenGL 4.0 is based on SPIR-V 1.0
                             ValuesIn(AllSpirV10Capabilities()),
@@ -1554,9 +1559,9 @@
           "OpDecorate %intt BuiltIn CullDistance\n"
           "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
           AllSpirV10Capabilities())
-)),);
+)));
 
-INSTANTIATE_TEST_CASE_P(Capabilities, ValidateCapabilityWebGPU,
+INSTANTIATE_TEST_SUITE_P(Capabilities, ValidateCapabilityWebGPU,
                         Combine(
                             // All capabilities to try.
                             ValuesIn(AllCapabilities()),
@@ -1564,9 +1569,9 @@
 std::make_pair(std::string(kVulkanMemoryModel) +
           "OpEntryPoint Vertex %func \"shader\" \n" + std::string(kVoidFVoid),
           AllWebGPUCapabilities())
-)),);
+)));
 
-INSTANTIATE_TEST_CASE_P(Capabilities, ValidateCapabilityVulkan11,
+INSTANTIATE_TEST_SUITE_P(Capabilities, ValidateCapabilityVulkan11,
                         Combine(
                             // All capabilities to try.
                             ValuesIn(AllCapabilities()),
@@ -1581,7 +1586,7 @@
           "OpDecorate %intt BuiltIn CullDistance\n"
           "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
           AllVulkan11Capabilities())
-)),);
+)));
 
 // TODO(umar): Selection Control
 // TODO(umar): Loop Control
@@ -1593,7 +1598,7 @@
 // TODO(umar): Kernel Enqueue Flags
 // TODO(umar): Kernel Profiling Flags
 
-INSTANTIATE_TEST_CASE_P(MatrixOp, ValidateCapability,
+INSTANTIATE_TEST_SUITE_P(MatrixOp, ValidateCapability,
                         Combine(
                             ValuesIn(AllCapabilities()),
                             Values(
@@ -1602,7 +1607,7 @@
           "%f32      = OpTypeFloat 32\n"
           "%vec3     = OpTypeVector %f32 3\n"
           "%mat33    = OpTypeMatrix %vec3 3\n" + std::string(kVoidFVoid),
-          MatrixDependencies()))),);
+          MatrixDependencies()))));
 // clang-format on
 
 #if 0
@@ -1643,7 +1648,7 @@
   return ss.str();
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TwoImageOperandsMask, ValidateCapability,
     Combine(
         ValuesIn(AllCapabilities()),
@@ -2260,8 +2265,8 @@
   EXPECT_EQ(SPV_ERROR_MISSING_EXTENSION,
             ValidateInstructions(SPV_ENV_UNIVERSAL_1_0));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("operand 5255 requires one of these extensions: "
-                        "SPV_NV_viewport_array2"));
+              HasSubstr("operand ShaderViewportMaskNV(5255) requires one of "
+                        "these extensions: SPV_NV_viewport_array2"));
 }
 
 TEST_F(ValidateCapability,
@@ -2342,8 +2347,8 @@
   EXPECT_EQ(SPV_ERROR_MISSING_EXTENSION,
             ValidateInstructions(SPV_ENV_UNIVERSAL_1_0));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("operand 5568 requires one of these extensions: "
-                        "SPV_INTEL_subgroups"));
+              HasSubstr("operand SubgroupShuffleINTEL(5568) requires one of "
+                        "these extensions: SPV_INTEL_subgroups"));
 }
 
 TEST_F(ValidateCapability,
@@ -2402,6 +2407,33 @@
                         "specified if the VulkanKHR memory model is used"));
 }
 
+// In the grammar, SubgroupEqMask and SubgroupMaskKHR have different enabling
+// lists of extensions.
+TEST_F(ValidateCapability, SubgroupEqMaskEnabledByExtension) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability SubgroupBallotKHR
+OpExtension "SPV_KHR_shader_ballot"
+OpMemoryModel Logical Simple
+OpEntryPoint GLCompute %main "main"
+OpDecorate %var BuiltIn SubgroupEqMask
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%ptr_uint = OpTypePointer Private %uint
+%var = OpVariable %ptr_uint Private
+%fn = OpTypeFunction %void
+%main = OpFunction %void None %fn
+%entry = OpLabel
+%val = OpLoad %uint %var
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_0);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_0))
+      << getDiagnosticString();
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_cfg_test.cpp b/test/val/val_cfg_test.cpp
index aed0a57..72bfa42 100644
--- a/test/val/val_cfg_test.cpp
+++ b/test/val/val_cfg_test.cpp
@@ -25,6 +25,7 @@
 #include "gmock/gmock.h"
 
 #include "source/diagnostic.h"
+#include "source/spirv_target_env.h"
 #include "source/val/validate.h"
 #include "test/test_fixture.h"
 #include "test/unit_spirv.h"
@@ -103,6 +104,12 @@
         }
         out << ss.str();
       } break;
+      case SpvOpLoopMerge: {
+        assert(successors_.size() == 2);
+        out << "OpLoopMerge %" + successors_[0].label_ + " %" +
+                   successors_[0].label_ + "None";
+      } break;
+
       case SpvOpReturn:
         assert(successors_.size() == 0);
         out << "OpReturn\n";
@@ -115,6 +122,10 @@
         assert(successors_.size() == 1);
         out << "OpBranch %" + successors_.front().label_;
         break;
+      case SpvOpKill:
+        assert(successors_.size() == 0);
+        out << "OpKill\n";
+        break;
       default:
         assert(1 == 0 && "Unhandled");
     }
@@ -144,13 +155,13 @@
   return lhs;
 }
 
-const char* header(SpvCapability cap) {
-  static const char* shader_header =
+const std::string& GetDefaultHeader(SpvCapability cap) {
+  static const std::string shader_header =
       "OpCapability Shader\n"
       "OpCapability Linkage\n"
       "OpMemoryModel Logical GLSL450\n";
 
-  static const char* kernel_header =
+  static const std::string kernel_header =
       "OpCapability Kernel\n"
       "OpCapability Linkage\n"
       "OpMemoryModel Logical OpenCL\n";
@@ -158,8 +169,17 @@
   return (cap == SpvCapabilityShader) ? shader_header : kernel_header;
 }
 
-const char* types_consts() {
-  static const char* types =
+const std::string& GetWebGPUHeader() {
+  static const std::string header =
+      "OpCapability Shader\n"
+      "OpCapability VulkanMemoryModelKHR\n"
+      "OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
+      "OpMemoryModel Logical VulkanKHR\n";
+  return header;
+}
+
+const std::string& types_consts() {
+  static const std::string types =
       "%voidt   = OpTypeVoid\n"
       "%boolt   = OpTypeBool\n"
       "%intt    = OpTypeInt 32 0\n"
@@ -167,13 +187,12 @@
       "%two     = OpConstant %intt 2\n"
       "%ptrt    = OpTypePointer Function %intt\n"
       "%funct   = OpTypeFunction %voidt\n";
-
   return types;
 }
 
-INSTANTIATE_TEST_CASE_P(StructuredControlFlow, ValidateCFG,
-                        ::testing::Values(SpvCapabilityShader,
-                                          SpvCapabilityKernel));
+INSTANTIATE_TEST_SUITE_P(StructuredControlFlow, ValidateCFG,
+                         ::testing::Values(SpvCapabilityShader,
+                                           SpvCapabilityKernel));
 
 TEST_P(ValidateCFG, LoopReachableFromEntryButNeverLeadingToReturn) {
   // In this case, the loop is reachable from a node without a predecessor,
@@ -270,7 +289,7 @@
     loop.SetBody("OpLoopMerge %merge %cont None\n");
   }
 
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps("loop", "entry", "cont", "merge",
                             std::make_pair("func", "Main")) +
                     types_consts() +
@@ -293,7 +312,7 @@
 
   entry.SetBody("%var = OpVariable %ptrt Function\n");
 
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps(std::make_pair("func", "Main")) + types_consts() +
                     " %func    = OpFunction %voidt None %funct\n";
   str += entry >> cont;
@@ -313,7 +332,7 @@
   // This operation should only be performed in the entry block
   cont.SetBody("%var = OpVariable %ptrt Function\n");
 
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps(std::make_pair("func", "Main")) + types_consts() +
                     " %func    = OpFunction %voidt None %funct\n";
 
@@ -339,7 +358,7 @@
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) loop.SetBody("OpLoopMerge %merge %loop None\n");
 
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps("loop", "merge", std::make_pair("func", "Main")) +
                     types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
@@ -364,7 +383,7 @@
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) branch.SetBody("OpSelectionMerge %merge None\n");
 
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps("cont", "branch", std::make_pair("func", "Main")) +
                     types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
@@ -396,9 +415,10 @@
   // cannot share the same merge
   if (is_shader) selection.SetBody("OpSelectionMerge %merge None\n");
 
-  std::string str =
-      header(GetParam()) + nameOps("merge", std::make_pair("func", "Main")) +
-      types_consts() + "%func    = OpFunction %voidt None %funct\n";
+  std::string str = GetDefaultHeader(GetParam()) +
+                    nameOps("merge", std::make_pair("func", "Main")) +
+                    types_consts() +
+                    "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> loop;
   str += loop >> selection;
@@ -431,9 +451,10 @@
   // cannot share the same merge
   if (is_shader) loop.SetBody(" OpLoopMerge %merge %loop None\n");
 
-  std::string str =
-      header(GetParam()) + nameOps("merge", std::make_pair("func", "Main")) +
-      types_consts() + "%func    = OpFunction %voidt None %funct\n";
+  std::string str = GetDefaultHeader(GetParam()) +
+                    nameOps("merge", std::make_pair("func", "Main")) +
+                    types_consts() +
+                    "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> selection;
   str += selection >> std::vector<Block>({merge, loop});
@@ -457,7 +478,7 @@
   Block entry("entry");
   Block bad("bad");
   Block end("end", SpvOpReturn);
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps("entry", "bad", std::make_pair("func", "Main")) +
                     types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
@@ -481,7 +502,7 @@
   Block bad("bad");
   Block end("end", SpvOpReturn);
   Block badvalue("undef");  // This referenes the OpUndef.
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps("entry", "bad", std::make_pair("func", "Main")) +
                     types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
@@ -493,13 +514,10 @@
   str += "OpFunctionEnd\n";
 
   CompileSuccessfully(str);
-  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      MatchesRegex("Block\\(s\\) \\{11\\[%11\\]\\} are referenced but not "
-                   "defined in function .\\[%Main\\]\n  %Main = OpFunction "
-                   "%void None %10\n"))
-      << str;
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("'Target Label' operands for OpBranch must "
+                        "be the ID of an OpLabel instruction"));
 }
 
 TEST_P(ValidateCFG, BranchConditionalTrueTargetFirstBlockBad) {
@@ -510,7 +528,7 @@
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   bad.SetBody(" OpLoopMerge %entry %exit None\n");
 
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps("entry", "bad", std::make_pair("func", "Main")) +
                     types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
@@ -538,7 +556,7 @@
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   bad.SetBody("OpLoopMerge %merge %cont None\n");
 
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps("entry", "bad", std::make_pair("func", "Main")) +
                     types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
@@ -570,7 +588,7 @@
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   bad.SetBody("OpSelectionMerge %merge None\n");
 
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps("entry", "bad", std::make_pair("func", "Main")) +
                     types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
@@ -605,9 +623,10 @@
   Block middle2("middle2");
   Block end2("end2", SpvOpReturn);
 
-  std::string str =
-      header(GetParam()) + nameOps("middle2", std::make_pair("func", "Main")) +
-      types_consts() + "%func    = OpFunction %voidt None %funct\n";
+  std::string str = GetDefaultHeader(GetParam()) +
+                    nameOps("middle2", std::make_pair("func", "Main")) +
+                    types_consts() +
+                    "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> middle;
   str += middle >> std::vector<Block>({end, middle2});
@@ -640,7 +659,7 @@
 
   if (is_shader) head.AppendBody("OpSelectionMerge %merge None\n");
 
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps("head", "merge", std::make_pair("func", "Main")) +
                     types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
@@ -675,7 +694,7 @@
 
   if (is_shader) head.AppendBody("OpSelectionMerge %head None\n");
 
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps("head", "exit", std::make_pair("func", "Main")) +
                     types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
@@ -697,8 +716,10 @@
   }
 }
 
-TEST_P(ValidateCFG, UnreachableMerge) {
-  bool is_shader = GetParam() == SpvCapabilityShader;
+std::string GetUnreachableMergeNoMergeInst(SpvCapability cap,
+                                           spv_target_env env) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
   Block entry("entry");
   Block branch("branch", SpvOpBranchConditional);
   Block t("t", SpvOpReturn);
@@ -706,13 +727,18 @@
   Block merge("merge", SpvOpReturn);
 
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
-  if (is_shader) branch.AppendBody("OpSelectionMerge %merge None\n");
+  if (!spvIsWebGPUEnv(env) && cap == SpvCapabilityShader)
+    branch.AppendBody("OpSelectionMerge %merge None\n");
 
-  std::string str = header(GetParam()) +
-                    nameOps("branch", "merge", std::make_pair("func", "Main")) +
-                    types_consts() +
-                    "%func    = OpFunction %voidt None %funct\n";
-
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("branch", "merge", std::make_pair("func", "Main"));
+  str += types_consts() + "%func    = OpFunction %voidt None %funct\n";
   str += entry >> branch;
   str += branch >> std::vector<Block>({t, f});
   str += t;
@@ -720,12 +746,209 @@
   str += merge;
   str += "OpFunctionEnd\n";
 
-  CompileSuccessfully(str);
+  return str;
+}
+
+TEST_P(ValidateCFG, UnreachableMergeNoMergeInst) {
+  CompileSuccessfully(
+      GetUnreachableMergeNoMergeInst(GetParam(), SPV_ENV_UNIVERSAL_1_0));
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_P(ValidateCFG, UnreachableMergeDefinedByOpUnreachable) {
-  bool is_shader = GetParam() == SpvCapabilityShader;
+TEST_F(ValidateCFG, WebGPUUnreachableMergeNoMergeInst) {
+  CompileSuccessfully(
+      GetUnreachableMergeNoMergeInst(SpvCapabilityShader, SPV_ENV_WEBGPU_0));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("For WebGPU, all blocks must be reachable"));
+}
+
+std::string GetUnreachableMergeTerminatedBy(SpvCapability cap,
+                                            spv_target_env env, SpvOp op) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
+
+  Block entry("entry");
+  Block branch("branch", SpvOpBranchConditional);
+  Block t("t", SpvOpReturn);
+  Block f("f", SpvOpReturn);
+  Block merge("merge", op);
+
+  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (cap == SpvCapabilityShader)
+    branch.AppendBody("OpSelectionMerge %merge None\n");
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("branch", "merge", std::make_pair("func", "Main"));
+
+  str += types_consts();
+  str += "%func    = OpFunction %voidt None %funct\n";
+  str += entry >> branch;
+  str += branch >> std::vector<Block>({t, f});
+  str += t;
+  str += f;
+  str += merge;
+  str += "OpFunctionEnd\n";
+
+  return str;
+}
+
+TEST_P(ValidateCFG, UnreachableMergeTerminatedByOpUnreachable) {
+  CompileSuccessfully(GetUnreachableMergeTerminatedBy(
+      GetParam(), SPV_ENV_UNIVERSAL_1_0, SpvOpUnreachable));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, UnreachableMergeTerminatedByOpKill) {
+  CompileSuccessfully(GetUnreachableMergeTerminatedBy(
+      SpvCapabilityShader, SPV_ENV_UNIVERSAL_1_0, SpvOpKill));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateCFG, UnreachableMergeTerminatedByOpReturn) {
+  CompileSuccessfully(GetUnreachableMergeTerminatedBy(
+      GetParam(), SPV_ENV_UNIVERSAL_1_0, SpvOpReturn));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, WebGPUUnreachableMergeTerminatedByOpUnreachable) {
+  CompileSuccessfully(GetUnreachableMergeTerminatedBy(
+      SpvCapabilityShader, SPV_ENV_WEBGPU_0, SpvOpUnreachable));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
+TEST_F(ValidateCFG, WebGPUUnreachableMergeTerminatedByOpKill) {
+  CompileSuccessfully(GetUnreachableMergeTerminatedBy(
+      SpvCapabilityShader, SPV_ENV_WEBGPU_0, SpvOpKill));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("must terminate with OpUnreachable"));
+}
+
+TEST_P(ValidateCFG, WebGPUUnreachableMergeTerminatedByOpReturn) {
+  CompileSuccessfully(GetUnreachableMergeTerminatedBy(
+      SpvCapabilityShader, SPV_ENV_WEBGPU_0, SpvOpReturn));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("must terminate with OpUnreachable"));
+}
+
+std::string GetUnreachableContinueTerminatedBy(SpvCapability cap,
+                                               spv_target_env env, SpvOp op) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
+
+  Block entry("entry");
+  Block branch("branch", SpvOpBranch);
+  Block merge("merge", SpvOpReturn);
+  Block target("target", op);
+
+  if (op == SpvOpBranch) target >> branch;
+
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (cap == SpvCapabilityShader)
+    branch.AppendBody("OpLoopMerge %merge %target None\n");
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("branch", "merge", "target", std::make_pair("func", "Main"));
+
+  str += types_consts();
+  str += "%func    = OpFunction %voidt None %funct\n";
+  str += entry >> branch;
+  str += branch >> std::vector<Block>({merge});
+  str += merge;
+  str += target;
+  str += "OpFunctionEnd\n";
+
+  return str;
+}
+
+TEST_P(ValidateCFG, UnreachableContinueTerminatedBySpvOpUnreachable) {
+  CompileSuccessfully(GetUnreachableContinueTerminatedBy(
+      GetParam(), SPV_ENV_UNIVERSAL_1_0, SpvOpUnreachable));
+  if (GetParam() == SpvCapabilityShader) {
+    ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("targeted by 0 back-edge blocks"));
+  } else {
+    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+  }
+}
+
+TEST_F(ValidateCFG, UnreachableContinueTerminatedBySpvOpKill) {
+  CompileSuccessfully(GetUnreachableContinueTerminatedBy(
+      SpvCapabilityShader, SPV_ENV_UNIVERSAL_1_0, SpvOpKill));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("targeted by 0 back-edge blocks"));
+}
+
+TEST_P(ValidateCFG, UnreachableContinueTerminatedBySpvOpReturn) {
+  CompileSuccessfully(GetUnreachableContinueTerminatedBy(
+      GetParam(), SPV_ENV_UNIVERSAL_1_0, SpvOpReturn));
+  if (GetParam() == SpvCapabilityShader) {
+    ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("targeted by 0 back-edge blocks"));
+  } else {
+    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+  }
+}
+
+TEST_P(ValidateCFG, UnreachableContinueTerminatedBySpvOpBranch) {
+  CompileSuccessfully(GetUnreachableContinueTerminatedBy(
+      GetParam(), SPV_ENV_UNIVERSAL_1_0, SpvOpBranch));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, WebGPUUnreachableContinueTerminatedBySpvOpUnreachable) {
+  CompileSuccessfully(GetUnreachableContinueTerminatedBy(
+      SpvCapabilityShader, SPV_ENV_WEBGPU_0, SpvOpUnreachable));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("For WebGPU, unreachable continue-target must "
+                        "terminate with OpBranch.\n  %12 = OpLabel\n"));
+}
+
+TEST_F(ValidateCFG, WebGPUUnreachableContinueTerminatedBySpvOpKill) {
+  CompileSuccessfully(GetUnreachableContinueTerminatedBy(
+      SpvCapabilityShader, SPV_ENV_WEBGPU_0, SpvOpKill));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("For WebGPU, unreachable continue-target must "
+                        "terminate with OpBranch.\n  %12 = OpLabel\n"));
+}
+
+TEST_F(ValidateCFG, WebGPUUnreachableContinueTerminatedBySpvOpReturn) {
+  CompileSuccessfully(GetUnreachableContinueTerminatedBy(
+      SpvCapabilityShader, SPV_ENV_WEBGPU_0, SpvOpReturn));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("For WebGPU, unreachable continue-target must "
+                        "terminate with OpBranch.\n  %12 = OpLabel\n"));
+}
+
+TEST_F(ValidateCFG, WebGPUUnreachableContinueTerminatedBySpvOpBranch) {
+  CompileSuccessfully(GetUnreachableContinueTerminatedBy(
+      SpvCapabilityShader, SPV_ENV_WEBGPU_0, SpvOpBranch));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
+std::string GetUnreachableMergeUnreachableMergeInst(SpvCapability cap,
+                                                    spv_target_env env) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
+
+  Block body("body", SpvOpReturn);
   Block entry("entry");
   Block branch("branch", SpvOpBranchConditional);
   Block t("t", SpvOpReturn);
@@ -733,13 +956,134 @@
   Block merge("merge", SpvOpUnreachable);
 
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
-  if (is_shader) branch.AppendBody("OpSelectionMerge %merge None\n");
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (cap == SpvCapabilityShader)
+    branch.AppendBody("OpSelectionMerge %merge None\n");
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("branch", "merge", std::make_pair("func", "Main"));
 
-  std::string str = header(GetParam()) +
-                    nameOps("branch", "merge", std::make_pair("func", "Main")) +
-                    types_consts() +
-                    "%func    = OpFunction %voidt None %funct\n";
+  str += types_consts();
+  str += "%func    = OpFunction %voidt None %funct\n";
+  str += body;
+  str += merge;
+  str += entry >> branch;
+  str += branch >> std::vector<Block>({t, f});
+  str += t;
+  str += f;
+  str += "OpFunctionEnd\n";
 
+  return str;
+}
+
+TEST_P(ValidateCFG, UnreachableMergeUnreachableMergeInst) {
+  CompileSuccessfully(GetUnreachableMergeUnreachableMergeInst(
+      GetParam(), SPV_ENV_UNIVERSAL_1_0));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, WebGPUUnreachableMergeUnreachableMergeInst) {
+  CompileSuccessfully(GetUnreachableMergeUnreachableMergeInst(
+      SpvCapabilityShader, SPV_ENV_WEBGPU_0));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("must be referenced by a reachable merge instruction"));
+}
+
+std::string GetUnreachableContinueUnreachableLoopInst(SpvCapability cap,
+                                                      spv_target_env env) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
+
+  Block body("body", SpvOpReturn);
+  Block entry("entry");
+  Block branch("branch", SpvOpBranch);
+  Block merge("merge", SpvOpReturn);
+  Block target("target", SpvOpBranch);
+
+  target >> branch;
+
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (cap == SpvCapabilityShader)
+    branch.AppendBody("OpLoopMerge %merge %target None\n");
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("branch", "merge", "target", std::make_pair("func", "Main"));
+
+  str += types_consts();
+  str += "%func    = OpFunction %voidt None %funct\n";
+  str += body;
+  str += target;
+  str += merge;
+  str += entry >> branch;
+  str += branch >> std::vector<Block>({merge});
+  str += "OpFunctionEnd\n";
+
+  return str;
+}
+
+TEST_P(ValidateCFG, UnreachableContinueUnreachableLoopInst) {
+  CompileSuccessfully(GetUnreachableContinueUnreachableLoopInst(
+      GetParam(), SPV_ENV_UNIVERSAL_1_0));
+  if (GetParam() == SpvCapabilityShader) {
+    // Shader causes additional structured CFG checks that cause a failure.
+    ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("Back-edges (1[%branch] -> 3[%target]) can only be "
+                          "formed between a block and a loop header."));
+
+  } else {
+    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+  }
+}
+
+TEST_F(ValidateCFG, WebGPUUnreachableContinueUnreachableLoopInst) {
+  CompileSuccessfully(GetUnreachableContinueUnreachableLoopInst(
+      SpvCapabilityShader, SPV_ENV_WEBGPU_0));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("must be referenced by a reachable loop instruction"));
+}
+
+std::string GetUnreachableMergeWithComplexBody(SpvCapability cap,
+                                               spv_target_env env) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
+
+  Block entry("entry");
+  Block branch("branch", SpvOpBranchConditional);
+  Block t("t", SpvOpReturn);
+  Block f("f", SpvOpReturn);
+  Block merge("merge", SpvOpUnreachable);
+
+  entry.AppendBody(spvIsWebGPUEnv(env)
+                       ? "%dummy   = OpVariable %intptrt Function %two\n"
+                       : "%dummy   = OpVariable %intptrt Function\n");
+  entry.AppendBody("%cond    = OpSLessThan %boolt %one %two\n");
+  merge.AppendBody("OpStore %dummy %one\n");
+
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (cap == SpvCapabilityShader)
+    branch.AppendBody("OpSelectionMerge %merge None\n");
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("branch", "merge", std::make_pair("func", "Main"));
+
+  str += types_consts();
+  str += "%intptrt = OpTypePointer Function %intt\n";
+  str += "%func    = OpFunction %voidt None %funct\n";
   str += entry >> branch;
   str += branch >> std::vector<Block>({t, f});
   str += t;
@@ -747,31 +1091,406 @@
   str += merge;
   str += "OpFunctionEnd\n";
 
-  CompileSuccessfully(str);
+  return str;
+}
+
+TEST_P(ValidateCFG, UnreachableMergeWithComplexBody) {
+  CompileSuccessfully(
+      GetUnreachableMergeWithComplexBody(GetParam(), SPV_ENV_UNIVERSAL_1_0));
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_P(ValidateCFG, UnreachableBlock) {
+TEST_F(ValidateCFG, WebGPUUnreachableMergeWithComplexBody) {
+  CompileSuccessfully(GetUnreachableMergeWithComplexBody(SpvCapabilityShader,
+                                                         SPV_ENV_WEBGPU_0));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("must only contain an OpLabel and OpUnreachable instruction"));
+}
+
+std::string GetUnreachableContinueWithComplexBody(SpvCapability cap,
+                                                  spv_target_env env) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
+
+  Block entry("entry");
+  Block branch("branch", SpvOpBranch);
+  Block merge("merge", SpvOpReturn);
+  Block target("target", SpvOpBranch);
+
+  target >> branch;
+
+  entry.AppendBody(spvIsWebGPUEnv(env)
+                       ? "%dummy   = OpVariable %intptrt Function %two\n"
+                       : "%dummy   = OpVariable %intptrt Function\n");
+  target.AppendBody("OpStore %dummy %one\n");
+
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (cap == SpvCapabilityShader)
+    branch.AppendBody("OpLoopMerge %merge %target None\n");
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("branch", "merge", "target", std::make_pair("func", "Main"));
+
+  str += types_consts();
+  str += "%intptrt = OpTypePointer Function %intt\n";
+  str += "%func    = OpFunction %voidt None %funct\n";
+  str += entry >> branch;
+  str += branch >> std::vector<Block>({merge});
+  str += merge;
+  str += target;
+  str += "OpFunctionEnd\n";
+
+  return str;
+}
+
+TEST_P(ValidateCFG, UnreachableContinueWithComplexBody) {
+  CompileSuccessfully(
+      GetUnreachableContinueWithComplexBody(GetParam(), SPV_ENV_UNIVERSAL_1_0));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, WebGPUUnreachableContinueWithComplexBody) {
+  CompileSuccessfully(GetUnreachableContinueWithComplexBody(SpvCapabilityShader,
+                                                            SPV_ENV_WEBGPU_0));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("must only contain an OpLabel and an OpBranch instruction"));
+}
+
+std::string GetUnreachableMergeWithBranchUse(SpvCapability cap,
+                                             spv_target_env env) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
+
+  Block entry("entry");
+  Block branch("branch", SpvOpBranchConditional);
+  Block t("t", SpvOpBranch);
+  Block f("f", SpvOpReturn);
+  Block merge("merge", SpvOpUnreachable);
+
+  entry.AppendBody("%cond    = OpSLessThan %boolt %one %two\n");
+
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (cap == SpvCapabilityShader)
+    branch.AppendBody("OpSelectionMerge %merge None\n");
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("branch", "merge", std::make_pair("func", "Main"));
+
+  str += types_consts();
+  str += "%func    = OpFunction %voidt None %funct\n";
+  str += entry >> branch;
+  str += branch >> std::vector<Block>({t, f});
+  str += t >> merge;
+  str += f;
+  str += merge;
+  str += "OpFunctionEnd\n";
+
+  return str;
+}
+
+TEST_P(ValidateCFG, UnreachableMergeWithBranchUse) {
+  CompileSuccessfully(
+      GetUnreachableMergeWithBranchUse(GetParam(), SPV_ENV_UNIVERSAL_1_0));
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, WebGPUUnreachableMergeWithBranchUse) {
+  CompileSuccessfully(
+      GetUnreachableMergeWithBranchUse(SpvCapabilityShader, SPV_ENV_WEBGPU_0));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("cannot be the target of a branch."));
+}
+
+std::string GetUnreachableMergeWithMultipleUses(SpvCapability cap,
+                                                spv_target_env env) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
+
+  Block entry("entry");
+  Block branch("branch", SpvOpBranchConditional);
+  Block t("t", SpvOpReturn);
+  Block f("f", SpvOpReturn);
+  Block merge("merge", SpvOpUnreachable);
+  Block duplicate("duplicate", SpvOpBranchConditional);
+
+  entry.AppendBody("%cond    = OpSLessThan %boolt %one %two\n");
+
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (cap == SpvCapabilityShader) {
+    branch.AppendBody("OpSelectionMerge %merge None\n");
+    duplicate.AppendBody("OpSelectionMerge %merge None\n");
+  }
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("branch", "merge", std::make_pair("func", "Main"));
+
+  str += types_consts();
+  str += "%func    = OpFunction %voidt None %funct\n";
+  str += entry >> branch;
+  str += branch >> std::vector<Block>({t, f});
+  str += duplicate >> std::vector<Block>({t, f});
+  str += t;
+  str += f;
+  str += merge;
+  str += "OpFunctionEnd\n";
+
+  return str;
+}
+
+TEST_P(ValidateCFG, UnreachableMergeWithMultipleUses) {
+  CompileSuccessfully(
+      GetUnreachableMergeWithMultipleUses(GetParam(), SPV_ENV_UNIVERSAL_1_0));
+  if (GetParam() == SpvCapabilityShader) {
+    ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("is already a merge block for another header"));
+  } else {
+    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+  }
+}
+
+TEST_F(ValidateCFG, WebGPUUnreachableMergeWithMultipleUses) {
+  CompileSuccessfully(GetUnreachableMergeWithMultipleUses(SpvCapabilityShader,
+                                                          SPV_ENV_WEBGPU_0));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("is already a merge block for another header"));
+}
+
+std::string GetUnreachableContinueWithBranchUse(SpvCapability cap,
+                                                spv_target_env env) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
+
+  Block entry("entry");
+  Block foo("foo", SpvOpBranch);
+  Block branch("branch", SpvOpBranch);
+  Block merge("merge", SpvOpReturn);
+  Block target("target", SpvOpBranch);
+
+  foo >> target;
+  target >> branch;
+
+  entry.AppendBody(spvIsWebGPUEnv(env)
+                       ? "%dummy   = OpVariable %intptrt Function %two\n"
+                       : "%dummy   = OpVariable %intptrt Function\n");
+
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (cap == SpvCapabilityShader)
+    branch.AppendBody("OpLoopMerge %merge %target None\n");
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("branch", "merge", "target", std::make_pair("func", "Main"));
+
+  str += types_consts();
+  str += "%intptrt = OpTypePointer Function %intt\n";
+  str += "%func    = OpFunction %voidt None %funct\n";
+  str += entry >> branch;
+  str += branch >> std::vector<Block>({merge});
+  str += merge;
+  str += target;
+  str += foo;
+  str += "OpFunctionEnd\n";
+
+  return str;
+}
+
+TEST_P(ValidateCFG, UnreachableContinueWithBranchUse) {
+  CompileSuccessfully(
+      GetUnreachableContinueWithBranchUse(GetParam(), SPV_ENV_UNIVERSAL_1_0));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, WebGPUUnreachableContinueWithBranchUse) {
+  CompileSuccessfully(GetUnreachableContinueWithBranchUse(SpvCapabilityShader,
+                                                          SPV_ENV_WEBGPU_0));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("cannot be the target of a branch."));
+}
+
+std::string GetReachableMergeAndContinue(SpvCapability cap,
+                                         spv_target_env env) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
+
+  Block entry("entry");
+  Block branch("branch", SpvOpBranch);
+  Block merge("merge", SpvOpReturn);
+  Block target("target", SpvOpBranch);
+  Block body("body", SpvOpBranchConditional);
+  Block t("t", SpvOpBranch);
+  Block f("f", SpvOpBranch);
+
+  target >> branch;
+  body.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
+  t >> merge;
+  f >> target;
+
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (cap == SpvCapabilityShader) {
+    branch.AppendBody("OpLoopMerge %merge %target None\n");
+    body.AppendBody("OpSelectionMerge %target None\n");
+  }
+
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("branch", "merge", "target", "body", "t", "f",
+                   std::make_pair("func", "Main"));
+
+  str += types_consts();
+  str += "%func    = OpFunction %voidt None %funct\n";
+  str += entry >> branch;
+  str += branch >> std::vector<Block>({body});
+  str += body >> std::vector<Block>({t, f});
+  str += t;
+  str += f;
+  str += merge;
+  str += target;
+  str += "OpFunctionEnd\n";
+
+  return str;
+}
+
+TEST_P(ValidateCFG, ReachableMergeAndContinue) {
+  CompileSuccessfully(
+      GetReachableMergeAndContinue(GetParam(), SPV_ENV_UNIVERSAL_1_0));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, WebGPUReachableMergeAndContinue) {
+  CompileSuccessfully(
+      GetReachableMergeAndContinue(SpvCapabilityShader, SPV_ENV_WEBGPU_0));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
+std::string GetUnreachableMergeAndContinue(SpvCapability cap,
+                                           spv_target_env env) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
+
+  Block entry("entry");
+  Block branch("branch", SpvOpBranch);
+  Block merge("merge", SpvOpReturn);
+  Block target("target", SpvOpBranch);
+  Block body("body", SpvOpBranchConditional);
+  Block t("t", SpvOpReturn);
+  Block f("f", SpvOpReturn);
+
+  target >> branch;
+  body.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
+
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (cap == SpvCapabilityShader) {
+    branch.AppendBody("OpLoopMerge %merge %target None\n");
+    body.AppendBody("OpSelectionMerge %target None\n");
+  }
+
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("branch", "merge", "target", "body", "t", "f",
+                   std::make_pair("func", "Main"));
+
+  str += types_consts();
+  str += "%func    = OpFunction %voidt None %funct\n";
+  str += entry >> branch;
+  str += branch >> std::vector<Block>({body});
+  str += body >> std::vector<Block>({t, f});
+  str += t;
+  str += f;
+  str += merge;
+  str += target;
+  str += "OpFunctionEnd\n";
+
+  return str;
+}
+
+TEST_P(ValidateCFG, UnreachableMergeAndContinue) {
+  CompileSuccessfully(
+      GetUnreachableMergeAndContinue(GetParam(), SPV_ENV_UNIVERSAL_1_0));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, WebGPUUnreachableMergeAndContinue) {
+  CompileSuccessfully(
+      GetUnreachableMergeAndContinue(SpvCapabilityShader, SPV_ENV_WEBGPU_0));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("unreachable merge-blocks must terminate with OpUnreachable"));
+}
+
+std::string GetUnreachableBlock(SpvCapability cap, spv_target_env env) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
+
   Block entry("entry");
   Block unreachable("unreachable");
   Block exit("exit", SpvOpReturn);
 
-  std::string str =
-      header(GetParam()) +
-      nameOps("unreachable", "exit", std::make_pair("func", "Main")) +
-      types_consts() + "%func    = OpFunction %voidt None %funct\n";
-
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("unreachable", "exit", std::make_pair("func", "Main"));
+  str += types_consts();
+  str += "%func    = OpFunction %voidt None %funct\n";
   str += entry >> exit;
   str += unreachable >> exit;
   str += exit;
   str += "OpFunctionEnd\n";
 
-  CompileSuccessfully(str);
+  return str;
+}
+
+TEST_P(ValidateCFG, UnreachableBlock) {
+  CompileSuccessfully(GetUnreachableBlock(GetParam(), SPV_ENV_UNIVERSAL_1_0));
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_P(ValidateCFG, UnreachableBranch) {
-  bool is_shader = GetParam() == SpvCapabilityShader;
+TEST_F(ValidateCFG, WebGPUUnreachableBlock) {
+  CompileSuccessfully(
+      GetUnreachableBlock(SpvCapabilityShader, SPV_ENV_WEBGPU_0));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("all blocks must be reachable"));
+}
+
+std::string GetUnreachableBranch(SpvCapability cap, spv_target_env env) {
+  std::string header =
+      spvIsWebGPUEnv(env) ? GetWebGPUHeader() : GetDefaultHeader(cap);
+
   Block entry("entry");
   Block unreachable("unreachable", SpvOpBranchConditional);
   Block unreachablechildt("unreachablechildt");
@@ -780,11 +1499,19 @@
   Block exit("exit", SpvOpReturn);
 
   unreachable.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
-  if (is_shader) unreachable.AppendBody("OpSelectionMerge %merge None\n");
-  std::string str =
-      header(GetParam()) +
-      nameOps("unreachable", "exit", std::make_pair("func", "Main")) +
-      types_consts() + "%func    = OpFunction %voidt None %funct\n";
+  if (cap == SpvCapabilityShader)
+    unreachable.AppendBody("OpSelectionMerge %merge None\n");
+
+  std::string str = header;
+  if (spvIsWebGPUEnv(env)) {
+    str +=
+        "OpEntryPoint Fragment %func \"func\"\n"
+        "OpExecutionMode %func OriginUpperLeft\n";
+  }
+  if (!spvIsWebGPUEnv(env))
+    str += nameOps("unreachable", "exit", std::make_pair("func", "Main"));
+  str += types_consts();
+  str += "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> exit;
   str +=
@@ -795,12 +1522,23 @@
   str += exit;
   str += "OpFunctionEnd\n";
 
-  CompileSuccessfully(str);
+  return str;
+}
+
+TEST_P(ValidateCFG, UnreachableBranch) {
+  CompileSuccessfully(GetUnreachableBranch(GetParam(), SPV_ENV_UNIVERSAL_1_0));
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateCFG, WebGPUUnreachableBranch) {
+  CompileSuccessfully(
+      GetUnreachableBranch(SpvCapabilityShader, SPV_ENV_WEBGPU_0));
+  ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("all blocks must be reachable"));
+}
+
 TEST_P(ValidateCFG, EmptyFunction) {
-  std::string str = header(GetParam()) + std::string(types_consts()) +
+  std::string str = GetDefaultHeader(GetParam()) + std::string(types_consts()) +
                     R"(%func    = OpFunction %voidt None %funct
                   %l = OpLabel
                   OpReturn
@@ -819,7 +1557,7 @@
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) loop.AppendBody("OpLoopMerge %exit %loop None\n");
 
-  std::string str = header(GetParam()) + std::string(types_consts()) +
+  std::string str = GetDefaultHeader(GetParam()) + std::string(types_consts()) +
                     "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> loop;
@@ -848,8 +1586,8 @@
     loop2.SetBody("OpLoopMerge %loop2_merge %loop2 None\n");
   }
 
-  std::string str = header(GetParam()) + nameOps("loop2", "loop2_merge") +
-                    types_consts() +
+  std::string str = GetDefaultHeader(GetParam()) +
+                    nameOps("loop2", "loop2_merge") + types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> loop1;
@@ -888,7 +1626,7 @@
       if_blocks[i].SetBody("OpSelectionMerge %if_merge" + ss.str() + " None\n");
     merge_blocks.emplace_back("if_merge" + ss.str(), SpvOpBranch);
   }
-  std::string str = header(GetParam()) + std::string(types_consts()) +
+  std::string str = GetDefaultHeader(GetParam()) + std::string(types_consts()) +
                     "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> if_blocks[0];
@@ -923,7 +1661,7 @@
     loop2.SetBody("OpLoopMerge %loop2_merge %loop2 None\n");
   }
 
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps("loop1", "loop2", "be_block", "loop2_merge") +
                     types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
@@ -960,7 +1698,7 @@
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) split.SetBody("OpSelectionMerge %exit None\n");
 
-  std::string str = header(GetParam()) + nameOps("split", "f") +
+  std::string str = GetDefaultHeader(GetParam()) + nameOps("split", "f") +
                     types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
 
@@ -993,7 +1731,8 @@
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) split.SetBody("OpSelectionMerge %exit None\n");
 
-  std::string str = header(GetParam()) + nameOps("split") + types_consts() +
+  std::string str = GetDefaultHeader(GetParam()) + nameOps("split") +
+                    types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> split;
@@ -1025,8 +1764,8 @@
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) loop.SetBody("OpLoopMerge %merge %back0 None\n");
 
-  std::string str = header(GetParam()) + nameOps("loop", "back0", "back1") +
-                    types_consts() +
+  std::string str = GetDefaultHeader(GetParam()) +
+                    nameOps("loop", "back0", "back1") + types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> loop;
@@ -1062,8 +1801,8 @@
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) loop.SetBody("OpLoopMerge %merge %cheader None\n");
 
-  std::string str = header(GetParam()) + nameOps("cheader", "be_block") +
-                    types_consts() +
+  std::string str = GetDefaultHeader(GetParam()) +
+                    nameOps("cheader", "be_block") + types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> loop;
@@ -1097,7 +1836,7 @@
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) loop.SetBody("OpLoopMerge %merge %loop None\n");
 
-  std::string str = header(GetParam()) + nameOps("cont", "loop") +
+  std::string str = GetDefaultHeader(GetParam()) + nameOps("cont", "loop") +
                     types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
 
@@ -1132,7 +1871,7 @@
   entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
   if (is_shader) loop.SetBody("OpLoopMerge %merge %loop None\n");
 
-  std::string str = header(GetParam()) + nameOps("cont", "loop") +
+  std::string str = GetDefaultHeader(GetParam()) + nameOps("cont", "loop") +
                     types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
 
@@ -1283,7 +2022,7 @@
     inner_head.SetBody("OpSelectionMerge %inner_merge None\n");
   }
 
-  std::string str = header(GetParam()) +
+  std::string str = GetDefaultHeader(GetParam()) +
                     nameOps("entry", "inner_merge", "exit") + types_consts() +
                     "%func    = OpFunction %voidt None %funct\n";
 
@@ -1321,7 +2060,7 @@
   }
 
   std::string str =
-      header(GetParam()) +
+      GetDefaultHeader(GetParam()) +
       nameOps("entry", "loop", "if_head", "if_true", "if_merge", "merge") +
       types_consts() + "%func    = OpFunction %voidt None %funct\n";
 
@@ -1351,9 +2090,10 @@
     loop.SetBody("OpLoopMerge %merge %latch None\n");
   }
 
-  std::string str =
-      header(GetParam()) + nameOps("entry", "loop", "latch", "merge") +
-      types_consts() + "%func    = OpFunction %voidt None %funct\n";
+  std::string str = GetDefaultHeader(GetParam()) +
+                    nameOps("entry", "loop", "latch", "merge") +
+                    types_consts() +
+                    "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> loop;
   str += loop >> std::vector<Block>({latch, merge});
@@ -1384,9 +2124,10 @@
     loop.SetBody("OpLoopMerge %merge %loop None\n");
   }
 
-  std::string str =
-      header(GetParam()) + nameOps("entry", "loop", "latch", "merge") +
-      types_consts() + "%func    = OpFunction %voidt None %funct\n";
+  std::string str = GetDefaultHeader(GetParam()) +
+                    nameOps("entry", "loop", "latch", "merge") +
+                    types_consts() +
+                    "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> loop;
   str += loop >> latch;
@@ -2060,6 +2801,233 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateCFG, SwitchTargetMustBeLabel) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "foo"
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+       %void = OpTypeVoid
+          %5 = OpTypeFunction %void
+          %1 = OpFunction %void None %5
+          %6 = OpLabel
+          %7 = OpCopyObject %uint %uint_0
+               OpSelectionMerge %8 None
+               OpSwitch %uint_0 %8 0 %7
+          %8 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("'Target Label' operands for OpSwitch must "
+                        "be IDs of an OpLabel instruction"));
+}
+
+TEST_F(ValidateCFG, BranchTargetMustBeLabel) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "foo"
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+       %void = OpTypeVoid
+          %5 = OpTypeFunction %void
+          %1 = OpFunction %void None %5
+          %2 = OpLabel
+          %7 = OpCopyObject %uint %uint_0
+               OpBranch %7
+          %8 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("'Target Label' operands for OpBranch must "
+                        "be the ID of an OpLabel instruction"));
+}
+
+TEST_F(ValidateCFG, ReachableOpUnreachableOneBlock) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpUnreachable
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, ReachableOpUnreachableOpBranch) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpBranch %block
+%block = OpLabel
+OpUnreachable
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, ReachableOpUnreachableOpBranchConditional) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%bool = OpTypeBool
+%undef = OpUndef %bool
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpBranchConditional %undef %block %unreachable
+%block = OpLabel
+OpReturn
+%unreachable = OpLabel
+OpUnreachable
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, ReachableOpUnreachableOpSwitch) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%int = OpTypeInt 32 0
+%undef = OpUndef %int
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpSwitch %undef %block1 0 %unreachable 1 %block2
+%block1 = OpLabel
+OpReturn
+%unreachable = OpLabel
+OpUnreachable
+%block2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, ReachableOpUnreachableLoop) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%bool = OpTypeBool
+%undef = OpUndef %bool
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpBranch %loop
+%loop = OpLabel
+OpLoopMerge %unreachable %loop None
+OpBranchConditional %undef %loop %unreachable
+%unreachable = OpLabel
+OpUnreachable
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, UnreachableLoopBadBackedge) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+%4 = OpTypeVoid
+%5 = OpTypeFunction %4
+%8 = OpTypeBool
+%13 = OpConstantTrue %8
+%2 = OpFunction %4 None %5
+%14 = OpLabel
+OpSelectionMerge %15 None
+OpBranchConditional %13 %15 %15
+%16 = OpLabel
+OpLoopMerge %17 %18 None
+OpBranch %17
+%18 = OpLabel
+OpBranch %17
+%17 = OpLabel
+OpBranch %15
+%15 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  // The back-edge in this test is bad, but the validator fails to identify it
+  // because it is in an entirely unreachable section of code. Prior to #2488
+  // this code failed an assert in Construct::blocks().
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, OneContinueTwoBackedges) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "main"
+OpExecutionMode %1 LocalSize 1 1 1
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%5 = OpTypeFunction %void
+%1 = OpFunction %void None %5
+%6 = OpLabel
+OpBranch %7
+%7 = OpLabel
+OpLoopMerge %8 %9 None
+OpBranch %10
+%10 = OpLabel
+OpLoopMerge %11 %9 None
+OpBranchConditional %true %11 %9
+%9 = OpLabel
+OpBranchConditional %true %10 %7
+%11 = OpLabel
+OpBranch %8
+%8 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("block <ID> 9 branches to the loop construct, but not "
+                        "to the loop header <ID> 7"));
+}
+
 /// TODO(umar): Nested CFG constructs
 
 }  // namespace
diff --git a/test/val/val_code_generator.cpp b/test/val/val_code_generator.cpp
new file mode 100644
index 0000000..1c83517
--- /dev/null
+++ b/test/val/val_code_generator.cpp
@@ -0,0 +1,222 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "test/val/val_code_generator.h"
+
+#include <sstream>
+
+namespace spvtools {
+namespace val {
+namespace {
+
+std::string GetDefaultShaderCapabilities() {
+  return R"(
+OpCapability Shader
+OpCapability Geometry
+OpCapability Tessellation
+OpCapability Float64
+OpCapability Int64
+OpCapability MultiViewport
+OpCapability SampleRateShading
+)";
+}
+
+std::string GetWebGPUShaderCapabilities() {
+  return R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+)";
+}
+
+std::string GetDefaultShaderTypes() {
+  return R"(
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f32 = OpTypeFloat 32
+%f64 = OpTypeFloat 64
+%u32 = OpTypeInt 32 0
+%u64 = OpTypeInt 64 0
+%f32vec2 = OpTypeVector %f32 2
+%f32vec3 = OpTypeVector %f32 3
+%f32vec4 = OpTypeVector %f32 4
+%f64vec2 = OpTypeVector %f64 2
+%f64vec3 = OpTypeVector %f64 3
+%f64vec4 = OpTypeVector %f64 4
+%u32vec2 = OpTypeVector %u32 2
+%u32vec3 = OpTypeVector %u32 3
+%u64vec3 = OpTypeVector %u64 3
+%u32vec4 = OpTypeVector %u32 4
+%u64vec2 = OpTypeVector %u64 2
+
+%f32_0 = OpConstant %f32 0
+%f32_1 = OpConstant %f32 1
+%f32_2 = OpConstant %f32 2
+%f32_3 = OpConstant %f32 3
+%f32_4 = OpConstant %f32 4
+%f32_h = OpConstant %f32 0.5
+%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1
+%f32vec2_12 = OpConstantComposite %f32vec2 %f32_1 %f32_2
+%f32vec3_012 = OpConstantComposite %f32vec3 %f32_0 %f32_1 %f32_2
+%f32vec3_123 = OpConstantComposite %f32vec3 %f32_1 %f32_2 %f32_3
+%f32vec4_0123 = OpConstantComposite %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3
+%f32vec4_1234 = OpConstantComposite %f32vec4 %f32_1 %f32_2 %f32_3 %f32_4
+
+%f64_0 = OpConstant %f64 0
+%f64_1 = OpConstant %f64 1
+%f64_2 = OpConstant %f64 2
+%f64_3 = OpConstant %f64 3
+%f64vec2_01 = OpConstantComposite %f64vec2 %f64_0 %f64_1
+%f64vec3_012 = OpConstantComposite %f64vec3 %f64_0 %f64_1 %f64_2
+%f64vec4_0123 = OpConstantComposite %f64vec4 %f64_0 %f64_1 %f64_2 %f64_3
+
+%u32_0 = OpConstant %u32 0
+%u32_1 = OpConstant %u32 1
+%u32_2 = OpConstant %u32 2
+%u32_3 = OpConstant %u32 3
+%u32_4 = OpConstant %u32 4
+
+%u64_0 = OpConstant %u64 0
+%u64_1 = OpConstant %u64 1
+%u64_2 = OpConstant %u64 2
+%u64_3 = OpConstant %u64 3
+
+%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1
+%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2
+%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3
+%u64vec2_01 = OpConstantComposite %u64vec2 %u64_0 %u64_1
+
+%u32arr2 = OpTypeArray %u32 %u32_2
+%u32arr3 = OpTypeArray %u32 %u32_3
+%u32arr4 = OpTypeArray %u32 %u32_4
+%u64arr2 = OpTypeArray %u64 %u32_2
+%u64arr3 = OpTypeArray %u64 %u32_3
+%u64arr4 = OpTypeArray %u64 %u32_4
+%f32arr2 = OpTypeArray %f32 %u32_2
+%f32arr3 = OpTypeArray %f32 %u32_3
+%f32arr4 = OpTypeArray %f32 %u32_4
+%f64arr2 = OpTypeArray %f64 %u32_2
+%f64arr3 = OpTypeArray %f64 %u32_3
+%f64arr4 = OpTypeArray %f64 %u32_4
+
+%f32vec3arr3 = OpTypeArray %f32vec3 %u32_3
+%f32vec4arr3 = OpTypeArray %f32vec4 %u32_3
+%f64vec4arr3 = OpTypeArray %f64vec4 %u32_3
+)";
+}
+
+std::string GetWebGPUShaderTypes() {
+  return R"(
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f32 = OpTypeFloat 32
+%u32 = OpTypeInt 32 0
+%f32vec2 = OpTypeVector %f32 2
+%f32vec3 = OpTypeVector %f32 3
+%f32vec4 = OpTypeVector %f32 4
+%u32vec2 = OpTypeVector %u32 2
+%u32vec3 = OpTypeVector %u32 3
+%u32vec4 = OpTypeVector %u32 4
+
+%f32_0 = OpConstant %f32 0
+%f32_1 = OpConstant %f32 1
+%f32_2 = OpConstant %f32 2
+%f32_3 = OpConstant %f32 3
+%f32_4 = OpConstant %f32 4
+%f32_h = OpConstant %f32 0.5
+%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1
+%f32vec2_12 = OpConstantComposite %f32vec2 %f32_1 %f32_2
+%f32vec3_012 = OpConstantComposite %f32vec3 %f32_0 %f32_1 %f32_2
+%f32vec3_123 = OpConstantComposite %f32vec3 %f32_1 %f32_2 %f32_3
+%f32vec4_0123 = OpConstantComposite %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3
+%f32vec4_1234 = OpConstantComposite %f32vec4 %f32_1 %f32_2 %f32_3 %f32_4
+
+%u32_0 = OpConstant %u32 0
+%u32_1 = OpConstant %u32 1
+%u32_2 = OpConstant %u32 2
+%u32_3 = OpConstant %u32 3
+%u32_4 = OpConstant %u32 4
+
+%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1
+%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2
+%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3
+
+%u32arr2 = OpTypeArray %u32 %u32_2
+%u32arr3 = OpTypeArray %u32 %u32_3
+%u32arr4 = OpTypeArray %u32 %u32_4
+%f32arr2 = OpTypeArray %f32 %u32_2
+%f32arr3 = OpTypeArray %f32 %u32_3
+%f32arr4 = OpTypeArray %f32 %u32_4
+
+%f32vec3arr3 = OpTypeArray %f32vec3 %u32_3
+%f32vec4arr3 = OpTypeArray %f32vec4 %u32_3
+)";
+}
+
+}  // namespace
+
+CodeGenerator CodeGenerator::GetDefaultShaderCodeGenerator() {
+  CodeGenerator generator;
+  generator.capabilities_ = GetDefaultShaderCapabilities();
+  generator.memory_model_ = "OpMemoryModel Logical GLSL450\n";
+  generator.types_ = GetDefaultShaderTypes();
+  return generator;
+}
+
+CodeGenerator CodeGenerator::GetWebGPUShaderCodeGenerator() {
+  CodeGenerator generator;
+  generator.capabilities_ = GetWebGPUShaderCapabilities();
+  generator.memory_model_ = "OpMemoryModel Logical VulkanKHR\n";
+  generator.extensions_ = "OpExtension \"SPV_KHR_vulkan_memory_model\"\n";
+  generator.types_ = GetWebGPUShaderTypes();
+  return generator;
+}
+
+std::string CodeGenerator::Build() const {
+  std::ostringstream ss;
+
+  ss << capabilities_;
+  ss << extensions_;
+  ss << memory_model_;
+
+  for (const EntryPoint& entry_point : entry_points_) {
+    ss << "OpEntryPoint " << entry_point.execution_model << " %"
+       << entry_point.name << " \"" << entry_point.name << "\" "
+       << entry_point.interfaces << "\n";
+  }
+
+  for (const EntryPoint& entry_point : entry_points_) {
+    ss << entry_point.execution_modes << "\n";
+  }
+
+  ss << before_types_;
+  ss << types_;
+  ss << after_types_;
+
+  for (const EntryPoint& entry_point : entry_points_) {
+    ss << "\n";
+    ss << "%" << entry_point.name << " = OpFunction %void None %func\n";
+    ss << "%" << entry_point.name << "_entry = OpLabel\n";
+    ss << entry_point.body;
+    ss << "\nOpReturn\nOpFunctionEnd\n";
+  }
+
+  ss << add_at_the_end_;
+
+  return ss.str();
+}
+
+}  // namespace val
+}  // namespace spvtools
diff --git a/test/val/val_code_generator.h b/test/val/val_code_generator.h
new file mode 100644
index 0000000..e580ddf
--- /dev/null
+++ b/test/val/val_code_generator.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Utility class used to generate SPIR-V code strings for tests
+
+#include <string>
+#include <vector>
+
+namespace spvtools {
+namespace val {
+
+struct EntryPoint {
+  std::string name;
+  std::string execution_model;
+  std::string execution_modes;
+  std::string body;
+  std::string interfaces;
+};
+
+class CodeGenerator {
+ public:
+  static CodeGenerator GetDefaultShaderCodeGenerator();
+  static CodeGenerator GetWebGPUShaderCodeGenerator();
+
+  std::string Build() const;
+
+  std::vector<EntryPoint> entry_points_;
+  std::string capabilities_;
+  std::string extensions_;
+  std::string memory_model_;
+  std::string before_types_;
+  std::string types_;
+  std::string after_types_;
+  std::string add_at_the_end_;
+};
+
+}  // namespace val
+}  // namespace spvtools
diff --git a/test/val/val_composites_test.cpp b/test/val/val_composites_test.cpp
index 92c4cc3..309c194 100644
--- a/test/val/val_composites_test.cpp
+++ b/test/val/val_composites_test.cpp
@@ -1467,6 +1467,83 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateComposites, CoopMatConstantCompositeMismatchFail) {
+  const std::string body =
+      R"(
+OpCapability Shader
+OpCapability Float16
+OpCapability CooperativeMatrixNV
+OpExtension "SPV_NV_cooperative_matrix"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f16 = OpTypeFloat 16
+%f32 = OpTypeFloat 32
+%u32 = OpTypeInt 32 0
+
+%u32_8 = OpConstant %u32 8
+%subgroup = OpConstant %u32 3
+
+%f16mat = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_8 %u32_8
+
+%f32_1 = OpConstant %f32 1
+
+%f16mat_1 = OpConstantComposite %f16mat %f32_1
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+
+OpReturn
+OpFunctionEnd)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpConstantComposite Constituent <id> '11[%float_1]' type does "
+                "not match the Result Type <id> '10[%10]'s component type."));
+}
+
+TEST_F(ValidateComposites, CoopMatCompositeConstructMismatchFail) {
+  const std::string body =
+      R"(
+OpCapability Shader
+OpCapability Float16
+OpCapability CooperativeMatrixNV
+OpExtension "SPV_NV_cooperative_matrix"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f16 = OpTypeFloat 16
+%f32 = OpTypeFloat 32
+%u32 = OpTypeInt 32 0
+
+%u32_8 = OpConstant %u32 8
+%subgroup = OpConstant %u32 3
+
+%f16mat = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_8 %u32_8
+
+%f32_1 = OpConstant %f32 1
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+
+%f16mat_1 = OpCompositeConstruct %f16mat %f32_1
+
+OpReturn
+OpFunctionEnd)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Expected Constituent type to be equal to the component type"));
+}
+
 TEST_F(ValidateComposites, ExtractDynamicLabelIndex) {
   const std::string spirv = R"(
 OpCapability Shader
@@ -1480,7 +1557,7 @@
 %v4float_0 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
 %func = OpFunction %void None %void_fn
 %1 = OpLabel
-%ex = OpVectorExtractDynamic %float %v4float_0 %1
+%ex = OpVectorExtractDynamic %float %v4float_0 %v4float_0
 OpReturn
 OpFunctionEnd
 )";
@@ -1491,6 +1568,209 @@
               HasSubstr("Expected Index to be int scalar"));
 }
 
+TEST_F(ValidateComposites, CopyLogicalSameType) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%struct = OpTypeStruct
+%const_struct = OpConstantComposite %struct
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+%copy = OpCopyLogical %struct %const_struct
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Result Type must not equal the Operand type"));
+}
+
+TEST_F(ValidateComposites, CopyLogicalSameStructDifferentId) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%struct1 = OpTypeStruct
+%struct2 = OpTypeStruct
+%const_struct = OpConstantComposite %struct1
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+%copy = OpCopyLogical %struct2 %const_struct
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+}
+
+TEST_F(ValidateComposites, CopyLogicalArrayDifferentLength) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_4 = OpConstant %int 4
+%int_5 = OpConstant %int 5
+%array1 = OpTypeArray %int %int_4
+%array2 = OpTypeArray %int %int_5
+%const_array = OpConstantComposite %array1 %int_4 %int_4 %int_4 %int_4
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+%copy = OpCopyLogical %array2 %const_array
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Result Type does not logically match the Operand type"));
+}
+
+TEST_F(ValidateComposites, CopyLogicalArrayDifferentElement) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%int = OpTypeInt 32 0
+%int_4 = OpConstant %int 4
+%array1 = OpTypeArray %int %int_4
+%array2 = OpTypeArray %float %int_4
+%const_array = OpConstantComposite %array1 %int_4 %int_4 %int_4 %int_4
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+%copy = OpCopyLogical %array2 %const_array
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Result Type does not logically match the Operand type"));
+}
+
+TEST_F(ValidateComposites, CopyLogicalArrayLogicallyMatchedElement) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%int = OpTypeInt 32 0
+%int_1 = OpConstant %int 1
+%inner1 = OpTypeArray %int %int_1
+%inner2 = OpTypeArray %int %int_1
+%array1 = OpTypeArray %inner1 %int_1
+%array2 = OpTypeArray %inner2 %int_1
+%const_inner = OpConstantComposite %inner1 %int_1
+%const_array = OpConstantComposite %array1 %const_inner
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+%copy = OpCopyLogical %array2 %const_array
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+}
+
+TEST_F(ValidateComposites, CopyLogicalStructDifferentNumberElements) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%struct1 = OpTypeStruct
+%struct2 = OpTypeStruct %int
+%const_struct = OpConstantComposite %struct1
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+%copy = OpCopyLogical %struct2 %const_struct
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Result Type does not logically match the Operand type"));
+}
+
+TEST_F(ValidateComposites, CopyLogicalStructDifferentElement) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%uint_0 = OpConstant %uint 0
+%struct1 = OpTypeStruct %int %uint
+%struct2 = OpTypeStruct %int %int
+%const_struct = OpConstantComposite %struct1 %int_0 %uint_0
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+%copy = OpCopyLogical %struct2 %const_struct
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Result Type does not logically match the Operand type"));
+}
+
+TEST_F(ValidateComposites, CopyLogicalStructLogicallyMatch) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_1 = OpConstant %int 1
+%array1 = OpTypeArray %int %int_1
+%array2 = OpTypeArray %int %int_1
+%struct1 = OpTypeStruct %int %array1
+%struct2 = OpTypeStruct %int %array2
+%const_array = OpConstantComposite %array1 %int_1
+%const_struct = OpConstantComposite %struct1 %int_1 %const_array
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+%copy = OpCopyLogical %struct2 %const_struct
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_constants_test.cpp b/test/val/val_constants_test.cpp
new file mode 100644
index 0000000..0c26d14
--- /dev/null
+++ b/test/val/val_constants_test.cpp
@@ -0,0 +1,372 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Test validation of constants.
+//
+// This file contains newer tests.  Older tests may be in other files such as
+// val_id_test.cpp.
+
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "test/unit_spirv.h"
+#include "test/val/val_fixtures.h"
+
+namespace spvtools {
+namespace val {
+namespace {
+
+using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::ValuesIn;
+
+using ValidateConstant = spvtest::ValidateBase<bool>;
+
+#define kBasicTypes                             \
+  "%bool = OpTypeBool "                         \
+  "%uint = OpTypeInt 32 0 "                     \
+  "%uint2 = OpTypeVector %uint 2 "              \
+  "%float = OpTypeFloat 32 "                    \
+  "%_ptr_uint = OpTypePointer Workgroup %uint " \
+  "%uint_0 = OpConstantNull %uint "             \
+  "%uint2_0 = OpConstantNull %uint "            \
+  "%float_0 = OpConstantNull %float "           \
+  "%false = OpConstantFalse %bool "             \
+  "%true = OpConstantTrue %bool "               \
+  "%null = OpConstantNull %_ptr_uint "
+
+#define kShaderPreamble    \
+  "OpCapability Shader\n"  \
+  "OpCapability Linkage\n" \
+  "OpMemoryModel Logical Simple\n"
+
+#define kKernelPreamble      \
+  "OpCapability Kernel\n"    \
+  "OpCapability Linkage\n"   \
+  "OpCapability Addresses\n" \
+  "OpMemoryModel Physical32 OpenCL\n"
+
+struct ConstantOpCase {
+  spv_target_env env;
+  std::string assembly;
+  bool expect_success;
+  std::string expect_err;
+};
+
+using ValidateConstantOp = spvtest::ValidateBase<ConstantOpCase>;
+
+TEST_P(ValidateConstantOp, Samples) {
+  const auto env = GetParam().env;
+  CompileSuccessfully(GetParam().assembly, env);
+  const auto result = ValidateInstructions(env);
+  if (GetParam().expect_success) {
+    EXPECT_EQ(SPV_SUCCESS, result);
+    EXPECT_THAT(getDiagnosticString(), Eq(""));
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, result);
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(GetParam().expect_err));
+  }
+}
+
+#define GOOD_SHADER_10(STR) \
+  { SPV_ENV_UNIVERSAL_1_0, kShaderPreamble kBasicTypes STR, true, "" }
+#define GOOD_KERNEL_10(STR) \
+  { SPV_ENV_UNIVERSAL_1_0, kKernelPreamble kBasicTypes STR, true, "" }
+INSTANTIATE_TEST_SUITE_P(
+    UniversalInShader, ValidateConstantOp,
+    ValuesIn(std::vector<ConstantOpCase>{
+        // TODO(dneto): Conversions must change width.
+        GOOD_SHADER_10("%v = OpSpecConstantOp %uint SConvert %uint_0"),
+        GOOD_SHADER_10("%v = OpSpecConstantOp %float FConvert %float_0"),
+        GOOD_SHADER_10("%v = OpSpecConstantOp %uint SNegate %uint_0"),
+        GOOD_SHADER_10("%v = OpSpecConstantOp %uint Not %uint_0"),
+        GOOD_SHADER_10("%v = OpSpecConstantOp %uint IAdd %uint_0 %uint_0"),
+        GOOD_SHADER_10("%v = OpSpecConstantOp %uint ISub %uint_0 %uint_0"),
+        GOOD_SHADER_10("%v = OpSpecConstantOp %uint IMul %uint_0 %uint_0"),
+        GOOD_SHADER_10("%v = OpSpecConstantOp %uint UDiv %uint_0 %uint_0"),
+        GOOD_SHADER_10("%v = OpSpecConstantOp %uint SDiv %uint_0 %uint_0"),
+        GOOD_SHADER_10("%v = OpSpecConstantOp %uint UMod %uint_0 %uint_0"),
+        GOOD_SHADER_10("%v = OpSpecConstantOp %uint SRem %uint_0 %uint_0"),
+        GOOD_SHADER_10("%v = OpSpecConstantOp %uint SMod %uint_0 %uint_0"),
+        GOOD_SHADER_10(
+            "%v = OpSpecConstantOp %uint ShiftRightLogical %uint_0 %uint_0"),
+        GOOD_SHADER_10(
+            "%v = OpSpecConstantOp %uint ShiftRightArithmetic %uint_0 %uint_0"),
+        GOOD_SHADER_10(
+            "%v = OpSpecConstantOp %uint ShiftLeftLogical %uint_0 %uint_0"),
+        GOOD_SHADER_10("%v = OpSpecConstantOp %uint BitwiseOr %uint_0 %uint_0"),
+        GOOD_SHADER_10(
+            "%v = OpSpecConstantOp %uint BitwiseXor %uint_0 %uint_0"),
+        GOOD_SHADER_10(
+            "%v = OpSpecConstantOp %uint2 VectorShuffle %uint2_0 %uint2_0 1 3"),
+        GOOD_SHADER_10(
+            "%v = OpSpecConstantOp %uint CompositeExtract %uint2_0 1"),
+        GOOD_SHADER_10(
+            "%v = OpSpecConstantOp %uint2 CompositeInsert %uint_0 %uint2_0 1"),
+        GOOD_SHADER_10("%v = OpSpecConstantOp %bool LogicalOr %true %false"),
+        GOOD_SHADER_10("%v = OpSpecConstantOp %bool LogicalNot %true"),
+        GOOD_SHADER_10("%v = OpSpecConstantOp %bool LogicalAnd %true %false"),
+        GOOD_SHADER_10("%v = OpSpecConstantOp %bool LogicalEqual %true %false"),
+        GOOD_SHADER_10(
+            "%v = OpSpecConstantOp %bool LogicalNotEqual %true %false"),
+        GOOD_SHADER_10(
+            "%v = OpSpecConstantOp %uint Select %true %uint_0 %uint_0"),
+        GOOD_SHADER_10("%v = OpSpecConstantOp %bool IEqual %uint_0 %uint_0"),
+        GOOD_SHADER_10("%v = OpSpecConstantOp %bool INotEqual %uint_0 %uint_0"),
+        GOOD_SHADER_10("%v = OpSpecConstantOp %bool ULessThan %uint_0 %uint_0"),
+        GOOD_SHADER_10("%v = OpSpecConstantOp %bool SLessThan %uint_0 %uint_0"),
+        GOOD_SHADER_10(
+            "%v = OpSpecConstantOp %bool ULessThanEqual %uint_0 %uint_0"),
+        GOOD_SHADER_10(
+            "%v = OpSpecConstantOp %bool SLessThanEqual %uint_0 %uint_0"),
+        GOOD_SHADER_10(
+            "%v = OpSpecConstantOp %bool UGreaterThan %uint_0 %uint_0"),
+        GOOD_SHADER_10(
+            "%v = OpSpecConstantOp %bool UGreaterThanEqual %uint_0 %uint_0"),
+        GOOD_SHADER_10(
+            "%v = OpSpecConstantOp %bool SGreaterThan %uint_0 %uint_0"),
+        GOOD_SHADER_10(
+            "%v = OpSpecConstantOp %bool SGreaterThanEqual %uint_0 %uint_0"),
+    }));
+
+INSTANTIATE_TEST_SUITE_P(
+    UniversalInKernel, ValidateConstantOp,
+    ValuesIn(std::vector<ConstantOpCase>{
+        // TODO(dneto): Conversions must change width.
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %uint SConvert %uint_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %float FConvert %float_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %uint SNegate %uint_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %uint Not %uint_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %uint IAdd %uint_0 %uint_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %uint ISub %uint_0 %uint_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %uint IMul %uint_0 %uint_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %uint UDiv %uint_0 %uint_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %uint SDiv %uint_0 %uint_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %uint UMod %uint_0 %uint_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %uint SRem %uint_0 %uint_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %uint SMod %uint_0 %uint_0"),
+        GOOD_KERNEL_10(
+            "%v = OpSpecConstantOp %uint ShiftRightLogical %uint_0 %uint_0"),
+        GOOD_KERNEL_10(
+            "%v = OpSpecConstantOp %uint ShiftRightArithmetic %uint_0 %uint_0"),
+        GOOD_KERNEL_10(
+            "%v = OpSpecConstantOp %uint ShiftLeftLogical %uint_0 %uint_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %uint BitwiseOr %uint_0 %uint_0"),
+        GOOD_KERNEL_10(
+            "%v = OpSpecConstantOp %uint BitwiseXor %uint_0 %uint_0"),
+        GOOD_KERNEL_10(
+            "%v = OpSpecConstantOp %uint2 VectorShuffle %uint2_0 %uint2_0 1 3"),
+        GOOD_KERNEL_10(
+            "%v = OpSpecConstantOp %uint CompositeExtract %uint2_0 1"),
+        GOOD_KERNEL_10(
+            "%v = OpSpecConstantOp %uint2 CompositeInsert %uint_0 %uint2_0 1"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %bool LogicalOr %true %false"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %bool LogicalNot %true"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %bool LogicalAnd %true %false"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %bool LogicalEqual %true %false"),
+        GOOD_KERNEL_10(
+            "%v = OpSpecConstantOp %bool LogicalNotEqual %true %false"),
+        GOOD_KERNEL_10(
+            "%v = OpSpecConstantOp %uint Select %true %uint_0 %uint_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %bool IEqual %uint_0 %uint_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %bool INotEqual %uint_0 %uint_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %bool ULessThan %uint_0 %uint_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %bool SLessThan %uint_0 %uint_0"),
+        GOOD_KERNEL_10(
+            "%v = OpSpecConstantOp %bool ULessThanEqual %uint_0 %uint_0"),
+        GOOD_KERNEL_10(
+            "%v = OpSpecConstantOp %bool SLessThanEqual %uint_0 %uint_0"),
+        GOOD_KERNEL_10(
+            "%v = OpSpecConstantOp %bool UGreaterThan %uint_0 %uint_0"),
+        GOOD_KERNEL_10(
+            "%v = OpSpecConstantOp %bool UGreaterThanEqual %uint_0 %uint_0"),
+        GOOD_KERNEL_10(
+            "%v = OpSpecConstantOp %bool SGreaterThan %uint_0 %uint_0"),
+        GOOD_KERNEL_10(
+            "%v = OpSpecConstantOp %bool SGreaterThanEqual %uint_0 %uint_0"),
+    }));
+
+INSTANTIATE_TEST_SUITE_P(
+    UConvert, ValidateConstantOp,
+    ValuesIn(std::vector<ConstantOpCase>{
+        // TODO(dneto): Conversions must change width.
+        {SPV_ENV_UNIVERSAL_1_0,
+         kKernelPreamble kBasicTypes
+         "%v = OpSpecConstantOp %uint UConvert %uint_0",
+         true, ""},
+        {SPV_ENV_UNIVERSAL_1_1,
+         kKernelPreamble kBasicTypes
+         "%v = OpSpecConstantOp %uint UConvert %uint_0",
+         true, ""},
+        {SPV_ENV_UNIVERSAL_1_3,
+         kKernelPreamble kBasicTypes
+         "%v = OpSpecConstantOp %uint UConvert %uint_0",
+         true, ""},
+        {SPV_ENV_UNIVERSAL_1_3,
+         kKernelPreamble kBasicTypes
+         "%v = OpSpecConstantOp %uint UConvert %uint_0",
+         true, ""},
+        {SPV_ENV_UNIVERSAL_1_4,
+         kKernelPreamble kBasicTypes
+         "%v = OpSpecConstantOp %uint UConvert %uint_0",
+         true, ""},
+        {SPV_ENV_UNIVERSAL_1_0,
+         kShaderPreamble kBasicTypes
+         "%v = OpSpecConstantOp %uint UConvert %uint_0",
+         false,
+         "Prior to SPIR-V 1.4, specialization constant operation "
+         "UConvert requires Kernel capability"},
+        {SPV_ENV_UNIVERSAL_1_1,
+         kShaderPreamble kBasicTypes
+         "%v = OpSpecConstantOp %uint UConvert %uint_0",
+         false,
+         "Prior to SPIR-V 1.4, specialization constant operation "
+         "UConvert requires Kernel capability"},
+        {SPV_ENV_UNIVERSAL_1_3,
+         kShaderPreamble kBasicTypes
+         "%v = OpSpecConstantOp %uint UConvert %uint_0",
+         false,
+         "Prior to SPIR-V 1.4, specialization constant operation "
+         "UConvert requires Kernel capability"},
+        {SPV_ENV_UNIVERSAL_1_3,
+         kShaderPreamble kBasicTypes
+         "%v = OpSpecConstantOp %uint UConvert %uint_0",
+         false,
+         "Prior to SPIR-V 1.4, specialization constant operation "
+         "UConvert requires Kernel capability"},
+        {SPV_ENV_UNIVERSAL_1_4,
+         kShaderPreamble kBasicTypes
+         "%v = OpSpecConstantOp %uint UConvert %uint_0",
+         true, ""},
+    }));
+
+INSTANTIATE_TEST_SUITE_P(
+    KernelInKernel, ValidateConstantOp,
+    ValuesIn(std::vector<ConstantOpCase>{
+        // TODO(dneto): Conversions must change width.
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %uint ConvertFToS %float_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %float ConvertSToF %uint_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %uint ConvertFToU %float_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %float ConvertUToF %uint_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %uint UConvert %uint_0"),
+        GOOD_KERNEL_10(
+            "%v = OpSpecConstantOp %_ptr_uint GenericCastToPtr %null"),
+        GOOD_KERNEL_10(
+            "%v = OpSpecConstantOp %_ptr_uint PtrCastToGeneric %null"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %uint Bitcast %uint_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %float FNegate %float_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %float FAdd %float_0 %float_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %float FSub %float_0 %float_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %float FMul %float_0 %float_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %float FDiv %float_0 %float_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %float FRem %float_0 %float_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %float FMod %float_0 %float_0"),
+        GOOD_KERNEL_10(
+            "%v = OpSpecConstantOp %_ptr_uint AccessChain %null %uint_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %_ptr_uint InBoundsAccessChain "
+                       "%null %uint_0"),
+        GOOD_KERNEL_10(
+            "%v = OpSpecConstantOp %_ptr_uint PtrAccessChain %null %uint_0"),
+        GOOD_KERNEL_10("%v = OpSpecConstantOp %_ptr_uint "
+                       "InBoundsPtrAccessChain %null %uint_0"),
+    }));
+
+#define BAD_SHADER_10(STR, NAME)                                   \
+  {                                                                \
+    SPV_ENV_UNIVERSAL_1_0, kShaderPreamble kBasicTypes STR, false, \
+        "Specialization constant operation " NAME                  \
+        " requires Kernel capability"                              \
+  }
+INSTANTIATE_TEST_SUITE_P(
+    KernelInShader, ValidateConstantOp,
+    ValuesIn(std::vector<ConstantOpCase>{
+        // TODO(dneto): Conversions must change width.
+        BAD_SHADER_10("%v = OpSpecConstantOp %uint ConvertFToS %float_0",
+                      "ConvertFToS"),
+        BAD_SHADER_10("%v = OpSpecConstantOp %float ConvertSToF %uint_0",
+                      "ConvertSToF"),
+        BAD_SHADER_10("%v = OpSpecConstantOp %uint ConvertFToU %float_0",
+                      "ConvertFToU"),
+        BAD_SHADER_10("%v = OpSpecConstantOp %float ConvertUToF %uint_0",
+                      "ConvertUToF"),
+        BAD_SHADER_10("%v = OpSpecConstantOp %_ptr_uint GenericCastToPtr %null",
+                      "GenericCastToPtr"),
+        BAD_SHADER_10("%v = OpSpecConstantOp %_ptr_uint PtrCastToGeneric %null",
+                      "PtrCastToGeneric"),
+        BAD_SHADER_10("%v = OpSpecConstantOp %uint Bitcast %uint_0", "Bitcast"),
+        BAD_SHADER_10("%v = OpSpecConstantOp %float FNegate %float_0",
+                      "FNegate"),
+        BAD_SHADER_10("%v = OpSpecConstantOp %float FAdd %float_0 %float_0",
+                      "FAdd"),
+        BAD_SHADER_10("%v = OpSpecConstantOp %float FSub %float_0 %float_0",
+                      "FSub"),
+        BAD_SHADER_10("%v = OpSpecConstantOp %float FMul %float_0 %float_0",
+                      "FMul"),
+        BAD_SHADER_10("%v = OpSpecConstantOp %float FDiv %float_0 %float_0",
+                      "FDiv"),
+        BAD_SHADER_10("%v = OpSpecConstantOp %float FRem %float_0 %float_0",
+                      "FRem"),
+        BAD_SHADER_10("%v = OpSpecConstantOp %float FMod %float_0 %float_0",
+                      "FMod"),
+        BAD_SHADER_10(
+            "%v = OpSpecConstantOp %_ptr_uint AccessChain %null %uint_0",
+            "AccessChain"),
+        BAD_SHADER_10("%v = OpSpecConstantOp %_ptr_uint InBoundsAccessChain "
+                      "%null %uint_0",
+                      "InBoundsAccessChain"),
+        BAD_SHADER_10(
+            "%v = OpSpecConstantOp %_ptr_uint PtrAccessChain %null %uint_0",
+            "PtrAccessChain"),
+        BAD_SHADER_10("%v = OpSpecConstantOp %_ptr_uint "
+                      "InBoundsPtrAccessChain %null %uint_0",
+                      "InBoundsPtrAccessChain"),
+    }));
+
+INSTANTIATE_TEST_SUITE_P(
+    UConvertInAMD_gpu_shader_int16, ValidateConstantOp,
+    ValuesIn(std::vector<ConstantOpCase>{
+        // SPV_AMD_gpu_shader_int16 should enable UConvert for OpSpecConstantOp
+        // https://github.com/KhronosGroup/glslang/issues/848
+        {SPV_ENV_UNIVERSAL_1_0,
+         "OpCapability Shader "
+         "OpCapability Linkage ; So we don't need to define a function\n"
+         "OpExtension \"SPV_AMD_gpu_shader_int16\" "
+         "OpMemoryModel Logical Simple " kBasicTypes
+         "%v = OpSpecConstantOp %uint UConvert %uint_0",
+         true, ""},
+    }));
+
+TEST_F(ValidateConstant, SpecConstantUConvert1p3Binary1p4EnvBad) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%int = OpTypeInt 32 0
+%int0 = OpConstant %int 0
+%const = OpSpecConstantOp %int UConvert %int0
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Prior to SPIR-V 1.4, specialization constant operation UConvert "
+          "requires Kernel capability or extension SPV_AMD_gpu_shader_int16"));
+}
+
+}  // namespace
+}  // namespace val
+}  // namespace spvtools
diff --git a/test/val/val_conversion_test.cpp b/test/val/val_conversion_test.cpp
index 4161c74..f905657 100644
--- a/test/val/val_conversion_test.cpp
+++ b/test/val/val_conversion_test.cpp
@@ -1184,6 +1184,172 @@
                 "GenericCastToPtrExplicit"));
 }
 
+TEST_F(ValidateConversion, CoopMatConversionSuccess) {
+  const std::string body =
+      R"(
+OpCapability Shader
+OpCapability Float16
+OpCapability Int16
+OpCapability CooperativeMatrixNV
+OpExtension "SPV_NV_cooperative_matrix"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f16 = OpTypeFloat 16
+%f32 = OpTypeFloat 32
+%u16 = OpTypeInt 16 0
+%u32 = OpTypeInt 32 0
+%s16 = OpTypeInt 16 1
+%s32 = OpTypeInt 32 1
+
+%u32_8 = OpConstant %u32 8
+%subgroup = OpConstant %u32 3
+
+%f16mat = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_8 %u32_8
+%f32mat = OpTypeCooperativeMatrixNV %f32 %subgroup %u32_8 %u32_8
+%u16mat = OpTypeCooperativeMatrixNV %u16 %subgroup %u32_8 %u32_8
+%u32mat = OpTypeCooperativeMatrixNV %u32 %subgroup %u32_8 %u32_8
+%s16mat = OpTypeCooperativeMatrixNV %s16 %subgroup %u32_8 %u32_8
+%s32mat = OpTypeCooperativeMatrixNV %s32 %subgroup %u32_8 %u32_8
+
+%f16_1 = OpConstant %f16 1
+%f32_1 = OpConstant %f32 1
+%u16_1 = OpConstant %u16 1
+%u32_1 = OpConstant %u32 1
+%s16_1 = OpConstant %s16 1
+%s32_1 = OpConstant %s32 1
+
+%f16mat_1 = OpConstantComposite %f16mat %f16_1
+%f32mat_1 = OpConstantComposite %f32mat %f32_1
+%u16mat_1 = OpConstantComposite %u16mat %u16_1
+%u32mat_1 = OpConstantComposite %u32mat %u32_1
+%s16mat_1 = OpConstantComposite %s16mat %s16_1
+%s32mat_1 = OpConstantComposite %s32mat %s32_1
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+
+%val11 = OpConvertFToU %u16mat %f16mat_1
+%val12 = OpConvertFToU %u32mat %f16mat_1
+%val13 = OpConvertFToS %s16mat %f16mat_1
+%val14 = OpConvertFToS %s32mat %f16mat_1
+%val15 = OpFConvert %f32mat %f16mat_1
+
+%val21 = OpConvertFToU %u16mat %f32mat_1
+%val22 = OpConvertFToU %u32mat %f32mat_1
+%val23 = OpConvertFToS %s16mat %f32mat_1
+%val24 = OpConvertFToS %s32mat %f32mat_1
+%val25 = OpFConvert %f16mat %f32mat_1
+
+%val31 = OpConvertUToF %f16mat %u16mat_1
+%val32 = OpConvertUToF %f32mat %u16mat_1
+%val33 = OpUConvert %u32mat %u16mat_1
+%val34 = OpSConvert %s32mat %u16mat_1
+
+%val41 = OpConvertSToF %f16mat %s16mat_1
+%val42 = OpConvertSToF %f32mat %s16mat_1
+%val43 = OpUConvert %u32mat %s16mat_1
+%val44 = OpSConvert %s32mat %s16mat_1
+
+OpReturn
+OpFunctionEnd)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateConversion, CoopMatConversionShapesMismatchFail) {
+  const std::string body =
+      R"(
+OpCapability Shader
+OpCapability Float16
+OpCapability Int16
+OpCapability CooperativeMatrixNV
+OpExtension "SPV_NV_cooperative_matrix"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f16 = OpTypeFloat 16
+%f32 = OpTypeFloat 32
+%u16 = OpTypeInt 16 0
+%u32 = OpTypeInt 32 0
+%s16 = OpTypeInt 16 1
+%s32 = OpTypeInt 32 1
+
+%u32_8 = OpConstant %u32 8
+%u32_4 = OpConstant %u32 4
+%subgroup = OpConstant %u32 3
+
+%f16mat = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_8 %u32_8
+%f32mat = OpTypeCooperativeMatrixNV %f32 %subgroup %u32_4 %u32_4
+
+%f16_1 = OpConstant %f16 1
+
+%f16mat_1 = OpConstantComposite %f16mat %f16_1
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+
+%val15 = OpFConvert %f32mat %f16mat_1
+
+OpReturn
+OpFunctionEnd)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Expected rows of Matrix type and Result Type to be identical"));
+}
+
+TEST_F(ValidateConversion, CoopMatConversionShapesMismatchPass) {
+  const std::string body =
+      R"(
+OpCapability Shader
+OpCapability Float16
+OpCapability Int16
+OpCapability CooperativeMatrixNV
+OpExtension "SPV_NV_cooperative_matrix"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f16 = OpTypeFloat 16
+%f32 = OpTypeFloat 32
+%u16 = OpTypeInt 16 0
+%u32 = OpTypeInt 32 0
+%s16 = OpTypeInt 16 1
+%s32 = OpTypeInt 32 1
+
+%u32_8 = OpConstant %u32 8
+%u32_4 = OpSpecConstant %u32 4
+%subgroup = OpConstant %u32 3
+
+%f16mat = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_8 %u32_8
+%f32mat = OpTypeCooperativeMatrixNV %f32 %subgroup %u32_4 %u32_4
+
+%f16_1 = OpConstant %f16 1
+
+%f16mat_1 = OpConstantComposite %f16mat %f16_1
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+
+%val15 = OpFConvert %f32mat %f16mat_1
+
+OpReturn
+OpFunctionEnd)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
 TEST_F(ValidateConversion, BitcastSuccess) {
   const std::string body = R"(
 %ptr = OpVariable %f32ptr_func Function
@@ -1302,6 +1468,118 @@
                                                "type"));
 }
 
+TEST_F(ValidateConversion, ConvertUToPtrPSBSuccess) {
+  const std::string body = R"(
+OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability Int64
+OpCapability Shader
+OpExtension "SPV_EXT_physical_storage_buffer"
+OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%uint64 = OpTypeInt 64 0
+%u64_1 = OpConstant %uint64 1
+%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%void = OpTypeVoid
+%voidfn = OpTypeFunction %void
+%main = OpFunction %void None %voidfn
+%entry = OpLabel
+%val1 = OpConvertUToPtr %ptr %u64_1
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateConversion, ConvertUToPtrPSBStorageClass) {
+  const std::string body = R"(
+OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability Int64
+OpCapability Shader
+OpExtension "SPV_EXT_physical_storage_buffer"
+OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%uint64 = OpTypeInt 64 0
+%u64_1 = OpConstant %uint64 1
+%ptr = OpTypePointer Function %uint64
+%void = OpTypeVoid
+%voidfn = OpTypeFunction %void
+%main = OpFunction %void None %voidfn
+%entry = OpLabel
+%val1 = OpConvertUToPtr %ptr %u64_1
+%val2 = OpConvertPtrToU %uint64 %val1
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Pointer storage class must be "
+                        "PhysicalStorageBufferEXT: ConvertUToPtr"));
+}
+
+TEST_F(ValidateConversion, ConvertPtrToUPSBSuccess) {
+  const std::string body = R"(
+OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability Int64
+OpCapability Shader
+OpExtension "SPV_EXT_physical_storage_buffer"
+OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %val1 RestrictPointerEXT
+%uint64 = OpTypeInt 64 0
+%u64_1 = OpConstant %uint64 1
+%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%pptr_f = OpTypePointer Function %ptr
+%void = OpTypeVoid
+%voidfn = OpTypeFunction %void
+%main = OpFunction %void None %voidfn
+%entry = OpLabel
+%val1 = OpVariable %pptr_f Function
+%val2 = OpLoad %ptr %val1
+%val3 = OpConvertPtrToU %uint64 %val2
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateConversion, ConvertPtrToUPSBStorageClass) {
+  const std::string body = R"(
+OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability Int64
+OpCapability Shader
+OpExtension "SPV_EXT_physical_storage_buffer"
+OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%uint64 = OpTypeInt 64 0
+%u64_1 = OpConstant %uint64 1
+%ptr = OpTypePointer Function %uint64
+%void = OpTypeVoid
+%voidfn = OpTypeFunction %void
+%main = OpFunction %void None %voidfn
+%entry = OpLabel
+%val1 = OpVariable %ptr Function
+%val2 = OpConvertPtrToU %uint64 %val1
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Pointer storage class must be "
+                        "PhysicalStorageBufferEXT: ConvertPtrToU"));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_data_test.cpp b/test/val/val_data_test.cpp
index fcf447a..7dd0269 100644
--- a/test/val/val_data_test.cpp
+++ b/test/val/val_data_test.cpp
@@ -36,6 +36,24 @@
          cap + " OpMemoryModel Logical GLSL450 ";
 }
 
+std::string WebGPUHeaderWith(std::string cap) {
+  return R"(
+OpCapability Shader
+OpCapability )" +
+         cap + R"(
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+)";
+}
+
+std::string webgpu_header = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+)";
+
 std::string header = R"(
      OpCapability Shader
      OpCapability Linkage
@@ -249,6 +267,18 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
 }
 
+TEST_F(ValidateData, webgpu_int8_bad) {
+  std::string str = WebGPUHeaderWith("Int8") + "%2 = OpTypeInt 8 0";
+  CompileSuccessfully(str.c_str(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_CAPABILITY,
+            ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Capability Int8 is not allowed by WebGPU specification (or "
+                "requires extension)\n"
+                "  OpCapability Int8\n"));
+}
+
 TEST_F(ValidateData, int16_good) {
   std::string str = header_with_int16 + "%2 = OpTypeInt 16 1";
   CompileSuccessfully(str.c_str());
@@ -308,6 +338,34 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr(missing_int16_cap_error));
 }
 
+TEST_F(ValidateData, webgpu_int16_bad) {
+  std::string str = WebGPUHeaderWith("Int16") + "%2 = OpTypeInt 16 1";
+  CompileSuccessfully(str.c_str(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_CAPABILITY,
+            ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Capability Int16 is not allowed by WebGPU specification (or "
+                "requires extension)\n"
+                "  OpCapability Int16\n"));
+}
+
+TEST_F(ValidateData, webgpu_int32_good) {
+  std::string str = webgpu_header + R"(
+          OpEntryPoint Fragment %func "func"
+          OpExecutionMode %func OriginUpperLeft
+%uint_t = OpTypeInt 32 0
+  %void = OpTypeVoid
+%func_t = OpTypeFunction %void
+  %func = OpFunction %void None %func_t
+     %1 = OpLabel
+          OpReturn
+          OpFunctionEnd
+)";
+  CompileSuccessfully(str.c_str(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
 TEST_F(ValidateData, int64_good) {
   std::string str = header_with_int64 + "%2 = OpTypeInt 64 1";
   CompileSuccessfully(str.c_str());
@@ -321,6 +379,18 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr(missing_int64_cap_error));
 }
 
+TEST_F(ValidateData, webgpu_int64_bad) {
+  std::string str = WebGPUHeaderWith("Int64") + "%2 = OpTypeInt 64 1";
+  CompileSuccessfully(str.c_str(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_CAPABILITY,
+            ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Capability Int64 is not allowed by WebGPU specification (or "
+                "requires extension)\n"
+                "  OpCapability Int64\n"));
+}
+
 // Number of bits in an integer may be only one of: {8,16,32,64}
 TEST_F(ValidateData, int_invalid_num_bits) {
   std::string str = header + "%2 = OpTypeInt 48 1";
@@ -348,6 +418,34 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr(missing_float16_cap_error));
 }
 
+TEST_F(ValidateData, webgpu_float16_bad) {
+  std::string str = WebGPUHeaderWith("Float16") + "%2 = OpTypeFloat 16";
+  CompileSuccessfully(str.c_str(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_CAPABILITY,
+            ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Capability Float16 is not allowed by WebGPU specification (or "
+                "requires extension)\n"
+                "  OpCapability Float16\n"));
+}
+
+TEST_F(ValidateData, webgpu_float32_good) {
+  std::string str = webgpu_header + R"(
+           OpEntryPoint Fragment %func "func"
+           OpExecutionMode %func OriginUpperLeft
+%float_t = OpTypeFloat 32
+   %void = OpTypeVoid
+ %func_t = OpTypeFunction %void
+   %func = OpFunction %void None %func_t
+      %1 = OpLabel
+           OpReturn
+           OpFunctionEnd
+)";
+  CompileSuccessfully(str.c_str(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
 TEST_F(ValidateData, float64_good) {
   std::string str = header_with_float64 + "%2 = OpTypeFloat 64";
   CompileSuccessfully(str.c_str());
@@ -361,6 +459,18 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr(missing_float64_cap_error));
 }
 
+TEST_F(ValidateData, webgpu_float64_bad) {
+  std::string str = WebGPUHeaderWith("Float64") + "%2 = OpTypeFloat 64";
+  CompileSuccessfully(str.c_str(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_CAPABILITY,
+            ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Capability Float64 is not allowed by WebGPU specification (or "
+                "requires extension)\n"
+                "  OpCapability Float64\n"));
+}
+
 // Number of bits in a float may be only one of: {16,32,64}
 TEST_F(ValidateData, float_invalid_num_bits) {
   std::string str = header + "%2 = OpTypeFloat 48";
@@ -668,9 +778,10 @@
       ASSERT_EQ(SPV_ERROR_INVALID_CAPABILITY, ValidateInstructions(env));
       EXPECT_THAT(
           getDiagnosticString(),
-          HasSubstr("Operand 2 of Decorate requires one of these capabilities: "
-                    "StorageBuffer16BitAccess StorageUniform16 "
-                    "StoragePushConstant16 StorageInputOutput16"));
+          HasSubstr(
+              "Operand 2 of Decorate requires one of these capabilities: "
+              "StorageBuffer16BitAccess UniformAndStorageBuffer16BitAccess "
+              "StoragePushConstant16 StorageInputOutput16"));
     }
   }
 }
@@ -703,6 +814,144 @@
       HasSubstr(
           "OpTypeRuntimeArray Element Type <id> '1[%void]' is a void type."));
 }
+
+TEST_F(ValidateData, vulkan_RTA_array_at_end_of_struct) {
+  std::string str = R"(
+              OpCapability Shader
+              OpMemoryModel Logical GLSL450
+              OpEntryPoint Fragment %func "func"
+              OpExecutionMode %func OriginUpperLeft
+              OpDecorate %array_t ArrayStride 4
+              OpMemberDecorate %struct_t 0 Offset 0
+              OpMemberDecorate %struct_t 1 Offset 4
+              OpDecorate %struct_t Block
+     %uint_t = OpTypeInt 32 0
+   %array_t = OpTypeRuntimeArray %uint_t
+  %struct_t = OpTypeStruct %uint_t %array_t
+%struct_ptr = OpTypePointer StorageBuffer %struct_t
+         %2 = OpVariable %struct_ptr StorageBuffer
+      %void = OpTypeVoid
+    %func_t = OpTypeFunction %void
+      %func = OpFunction %void None %func_t
+         %1 = OpLabel
+              OpReturn
+              OpFunctionEnd
+)";
+
+  CompileSuccessfully(str.c_str(), SPV_ENV_VULKAN_1_1);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+}
+
+TEST_F(ValidateData, vulkan_RTA_not_at_end_of_struct) {
+  std::string str = R"(
+              OpCapability Shader
+              OpMemoryModel Logical GLSL450
+              OpEntryPoint Fragment %func "func"
+              OpExecutionMode %func OriginUpperLeft
+              OpDecorate %array_t ArrayStride 4
+              OpMemberDecorate %struct_t 0 Offset 0
+              OpMemberDecorate %struct_t 1 Offset 4
+              OpDecorate %struct_t Block
+     %uint_t = OpTypeInt 32 0
+   %array_t = OpTypeRuntimeArray %uint_t
+  %struct_t = OpTypeStruct %array_t %uint_t
+%struct_ptr = OpTypePointer StorageBuffer %struct_t
+         %2 = OpVariable %struct_ptr StorageBuffer
+      %void = OpTypeVoid
+    %func_t = OpTypeFunction %void
+      %func = OpFunction %void None %func_t
+         %1 = OpLabel
+              OpReturn
+              OpFunctionEnd
+)";
+
+  CompileSuccessfully(str.c_str(), SPV_ENV_VULKAN_1_1);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("In Vulkan, OpTypeRuntimeArray must only be used for "
+                        "the last member of an OpTypeStruct\n  %_struct_3 = "
+                        "OpTypeStruct %_runtimearr_uint %uint\n"));
+}
+
+TEST_F(ValidateData, webgpu_RTA_array_at_end_of_struct) {
+  std::string str = R"(
+              OpCapability Shader
+              OpCapability VulkanMemoryModelKHR
+              OpExtension "SPV_KHR_vulkan_memory_model"
+              OpMemoryModel Logical VulkanKHR
+              OpEntryPoint Fragment %func "func"
+              OpExecutionMode %func OriginUpperLeft
+              OpDecorate %array_t ArrayStride 4
+              OpMemberDecorate %struct_t 0 Offset 0
+              OpMemberDecorate %struct_t 1 Offset 4
+              OpDecorate %struct_t Block
+     %uint_t = OpTypeInt 32 0
+   %array_t = OpTypeRuntimeArray %uint_t
+  %struct_t = OpTypeStruct %uint_t %array_t
+%struct_ptr = OpTypePointer StorageBuffer %struct_t
+         %2 = OpVariable %struct_ptr StorageBuffer
+      %void = OpTypeVoid
+    %func_t = OpTypeFunction %void
+      %func = OpFunction %void None %func_t
+         %1 = OpLabel
+              OpReturn
+              OpFunctionEnd
+)";
+
+  CompileSuccessfully(str.c_str(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
+TEST_F(ValidateData, webgpu_RTA_not_at_end_of_struct) {
+  std::string str = R"(
+              OpCapability Shader
+              OpCapability VulkanMemoryModelKHR
+              OpExtension "SPV_KHR_vulkan_memory_model"
+              OpMemoryModel Logical VulkanKHR
+              OpEntryPoint Fragment %func "func"
+              OpExecutionMode %func OriginUpperLeft
+              OpDecorate %array_t ArrayStride 4
+              OpMemberDecorate %struct_t 0 Offset 0
+              OpMemberDecorate %struct_t 1 Offset 4
+              OpDecorate %struct_t Block
+     %uint_t = OpTypeInt 32 0
+   %array_t = OpTypeRuntimeArray %uint_t
+  %struct_t = OpTypeStruct %array_t %uint_t
+%struct_ptr = OpTypePointer StorageBuffer %struct_t
+         %2 = OpVariable %struct_ptr StorageBuffer
+      %void = OpTypeVoid
+    %func_t = OpTypeFunction %void
+      %func = OpFunction %void None %func_t
+         %1 = OpLabel
+              OpReturn
+              OpFunctionEnd
+)";
+
+  CompileSuccessfully(str.c_str(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("In WebGPU, OpTypeRuntimeArray must only be used for "
+                        "the last member of an OpTypeStruct\n  %_struct_3 = "
+                        "OpTypeStruct %_runtimearr_uint %uint\n"));
+}
+
+TEST_F(ValidateData, invalid_forward_reference_in_array) {
+  std::string str = R"(
+               OpCapability Shader
+               OpCapability Linkage
+               OpMemoryModel Logical GLSL450
+       %uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%_arr_3_uint_1 = OpTypeArray %_arr_3_uint_1 %uint_1
+)";
+
+  CompileSuccessfully(str.c_str(), SPV_ENV_UNIVERSAL_1_3);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Forward reference operands in an OpTypeArray must "
+                        "first be declared using OpTypeForwardPointer."));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_decoration_test.cpp b/test/val/val_decoration_test.cpp
index 3a4320d..d3e9d0a 100644
--- a/test/val/val_decoration_test.cpp
+++ b/test/val/val_decoration_test.cpp
@@ -19,17 +19,31 @@
 
 #include "gmock/gmock.h"
 #include "source/val/decoration.h"
+#include "test/test_fixture.h"
 #include "test/unit_spirv.h"
+#include "test/val/val_code_generator.h"
 #include "test/val/val_fixtures.h"
 
 namespace spvtools {
 namespace val {
 namespace {
 
+using ::testing::Combine;
 using ::testing::Eq;
 using ::testing::HasSubstr;
+using ::testing::Values;
+
+struct TestResult {
+  TestResult(spv_result_t in_validation_result = SPV_SUCCESS,
+             const std::string& in_error_str = "")
+      : validation_result(in_validation_result), error_str(in_error_str) {}
+  spv_result_t validation_result;
+  const std::string error_str;
+};
 
 using ValidateDecorations = spvtest::ValidateBase<bool>;
+using ValidateWebGPUCombineDecorationResult =
+    spvtest::ValidateBase<std::tuple<const char*, TestResult>>;
 
 TEST_F(ValidateDecorations, ValidateOpDecorateRegistration) {
   std::string spirv = R"(
@@ -115,7 +129,7 @@
                OpCapability Linkage
                OpMemoryModel Logical GLSL450
                OpDecorate %1 DescriptorSet 0
-               OpDecorate %1 NonWritable
+               OpDecorate %1 RelaxedPrecision
                OpDecorate %1 Restrict
           %1 = OpDecorationGroup
                OpGroupDecorate %1 %2 %3
@@ -136,9 +150,10 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
 
   // Decoration group has 3 decorations.
-  auto expected_decorations = std::vector<Decoration>{
-      Decoration(SpvDecorationDescriptorSet, {0}),
-      Decoration(SpvDecorationNonWritable), Decoration(SpvDecorationRestrict)};
+  auto expected_decorations =
+      std::vector<Decoration>{Decoration(SpvDecorationDescriptorSet, {0}),
+                              Decoration(SpvDecorationRelaxedPrecision),
+                              Decoration(SpvDecorationRestrict)};
 
   // Decoration group is applied to id 1, 2, 3, and 4. Note that id 1 (which is
   // the decoration group id) also has all the decorations.
@@ -1496,6 +1511,158 @@
       << getDiagnosticString();
 }
 
+TEST_F(ValidateDecorations, BlockCantAppearWithinABlockBad) {
+  // See https://github.com/KhronosGroup/SPIRV-Tools/issues/1587
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource GLSL 450
+               OpMemberDecorate %S 0 Offset 0
+               OpMemberDecorate %S 1 Offset 16
+               OpMemberDecorate %S2 0 Offset 0
+               OpMemberDecorate %S2 1 Offset 12
+               OpDecorate %S Block
+               OpDecorate %S2 Block
+               OpDecorate %B DescriptorSet 0
+               OpDecorate %B Binding 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+         %S2 = OpTypeStruct %float %float
+          %S = OpTypeStruct %float %S2
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+          %B = OpVariable %_ptr_Uniform_S Uniform
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("rules: A Block or BufferBlock cannot be nested within "
+                        "another Block or BufferBlock."));
+}
+
+TEST_F(ValidateDecorations, BufferblockCantAppearWithinABufferblockBad) {
+  // See https://github.com/KhronosGroup/SPIRV-Tools/issues/1587
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource GLSL 450
+               OpMemberDecorate %S 0 Offset 0
+               OpMemberDecorate %S 1 Offset 16
+              OpMemberDecorate %S2 0 Offset 0
+               OpMemberDecorate %S2 1 Offset 16
+               OpMemberDecorate %S3 0 Offset 0
+               OpMemberDecorate %S3 1 Offset 12
+               OpDecorate %S BufferBlock
+               OpDecorate %S3 BufferBlock
+               OpDecorate %B DescriptorSet 0
+               OpDecorate %B Binding 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+         %S3 = OpTypeStruct %float %float
+         %S2 = OpTypeStruct %float %S3
+          %S = OpTypeStruct %float %S2
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+          %B = OpVariable %_ptr_Uniform_S Uniform
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("rules: A Block or BufferBlock cannot be nested within "
+                        "another Block or BufferBlock."));
+}
+
+TEST_F(ValidateDecorations, BufferblockCantAppearWithinABlockBad) {
+  // See https://github.com/KhronosGroup/SPIRV-Tools/issues/1587
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource GLSL 450
+               OpMemberDecorate %S 0 Offset 0
+               OpMemberDecorate %S 1 Offset 16
+              OpMemberDecorate %S2 0 Offset 0
+               OpMemberDecorate %S2 1 Offset 16
+               OpMemberDecorate %S3 0 Offset 0
+               OpMemberDecorate %S3 1 Offset 12
+               OpDecorate %S Block
+               OpDecorate %S3 BufferBlock
+               OpDecorate %B DescriptorSet 0
+               OpDecorate %B Binding 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+         %S3 = OpTypeStruct %float %float
+         %S2 = OpTypeStruct %float %S3
+          %S = OpTypeStruct %float %S2
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+          %B = OpVariable %_ptr_Uniform_S Uniform
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("rules: A Block or BufferBlock cannot be nested within "
+                        "another Block or BufferBlock."));
+}
+
+TEST_F(ValidateDecorations, BlockCantAppearWithinABufferblockBad) {
+  // See https://github.com/KhronosGroup/SPIRV-Tools/issues/1587
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource GLSL 450
+               OpMemberDecorate %S 0 Offset 0
+               OpMemberDecorate %S 1 Offset 16
+              OpMemberDecorate %S2 0 Offset 0
+               OpMemberDecorate %S2 1 Offset 16
+              OpMemberDecorate %S3 0 Offset 0
+               OpMemberDecorate %S3 1 Offset 16
+               OpMemberDecorate %S4 0 Offset 0
+               OpMemberDecorate %S4 1 Offset 12
+               OpDecorate %S BufferBlock
+               OpDecorate %S4 Block
+               OpDecorate %B DescriptorSet 0
+               OpDecorate %B Binding 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+         %S4 = OpTypeStruct %float %float
+         %S3 = OpTypeStruct %float %S4
+         %S2 = OpTypeStruct %float %S3
+          %S = OpTypeStruct %float %S2
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+          %B = OpVariable %_ptr_Uniform_S Uniform
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("rules: A Block or BufferBlock cannot be nested within "
+                        "another Block or BufferBlock."));
+}
+
 TEST_F(ValidateDecorations, BlockLayoutForbidsTightScalarVec3PackingBad) {
   // See https://github.com/KhronosGroup/SPIRV-Tools/issues/1666
   std::string spirv = R"(
@@ -1933,7 +2100,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
 }
 
-TEST_F(ValidateDecorations, BlockArrayBaseAlignmentGood) {
+TEST_F(ValidateDecorations, BlockArrayExtendedAlignmentGood) {
   // For uniform buffer, Array base alignment is 16, and ArrayStride
   // must be a multiple of 16.
   std::string spirv = R"(
@@ -1966,7 +2133,7 @@
       << getDiagnosticString();
 }
 
-TEST_F(ValidateDecorations, BlockArrayBadAlignmentBad) {
+TEST_F(ValidateDecorations, BlockArrayBaseAlignmentBad) {
   // For uniform buffer, Array base alignment is 16.
   std::string spirv = R"(
                OpCapability Shader
@@ -2003,7 +2170,7 @@
           "member 1 at offset 8 is not aligned to 16"));
 }
 
-TEST_F(ValidateDecorations, BlockArrayBadAlignmentWithRelaxedLayoutStillBad) {
+TEST_F(ValidateDecorations, BlockArrayBaseAlignmentWithRelaxedLayoutStillBad) {
   // For uniform buffer, Array base alignment is 16, and ArrayStride
   // must be a multiple of 16.  This case uses relaxed block layout.  Relaxed
   // layout only relaxes rules for vector alignment, not array alignment.
@@ -2046,7 +2213,7 @@
           "member 1 at offset 8 is not aligned to 16"));
 }
 
-TEST_F(ValidateDecorations, BlockArrayBadAlignmentWithVulkan1_1StillBad) {
+TEST_F(ValidateDecorations, BlockArrayBaseAlignmentWithVulkan1_1StillBad) {
   // Same as previous test, but with Vulkan 1.1, which includes
   // VK_KHR_relaxed_block_layout in core.
   std::string spirv = R"(
@@ -2087,6 +2254,75 @@
           "member 1 at offset 8 is not aligned to 16"));
 }
 
+TEST_F(ValidateDecorations,
+       BlockArrayBaseAlignmentWithBlockStandardLayoutGood) {
+  // Same as previous test, but with VK_KHR_uniform_buffer_standard_layout
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpSource GLSL 450
+               OpDecorate %_arr_float_uint_2 ArrayStride 16
+               OpDecorate %u DescriptorSet 0
+               OpDecorate %u Binding 0
+               OpMemberDecorate %S 0 Offset 0
+               OpMemberDecorate %S 1 Offset 8
+               OpDecorate %S Block
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v2float = OpTypeVector %float 2
+       %uint = OpTypeInt 32 0
+     %uint_2 = OpConstant %uint 2
+%_arr_float_uint_2 = OpTypeArray %float %uint_2
+          %S = OpTypeStruct %v2float %_arr_float_uint_2
+%_ptr_Uniform_S = OpTypePointer Uniform %S
+          %u = OpVariable %_ptr_Uniform_S Uniform
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  spvValidatorOptionsSetUniformBufferStandardLayout(getValidatorOptions(),
+                                                    true);
+  EXPECT_EQ(SPV_SUCCESS,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, VulkanBufferBlockOnStorageBufferBad) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpExtension "SPV_KHR_storage_buffer_storage_class"
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Fragment %1 "main"
+            OpExecutionMode %1 OriginUpperLeft
+
+            OpDecorate %struct BufferBlock
+
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+   %float = OpTypeFloat 32
+  %struct = OpTypeStruct %float
+     %ptr = OpTypePointer StorageBuffer %struct
+     %var = OpVariable %ptr StorageBuffer
+
+       %1 = OpFunction %void None %voidfn
+   %label = OpLabel
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("In Vulkan, BufferBlock is disallowed on variables in "
+                        "the StorageBuffer storage class"));
+}
+
 TEST_F(ValidateDecorations, PushConstantArrayBaseAlignmentGood) {
   // Tests https://github.com/KhronosGroup/SPIRV-Tools/issues/1664
   // From GLSL vertex shader:
@@ -2289,10 +2525,10 @@
                 OpMemoryModel Logical GLSL450
                 OpEntryPoint Fragment %1 "main"
                 OpExecutionMode %1 OriginUpperLeft
-    
+
                 OpDecorate %struct Block
                 OpMemberDecorate %struct 0 Offset 0
-    
+
         %void = OpTypeVoid
       %voidfn = OpTypeFunction %void
        %float = OpTypeFloat 32
@@ -2300,7 +2536,7 @@
        %int_0 = OpConstant %int 0
       %struct = OpTypeStruct %float
          %ptr = OpTypePointer PushConstant %struct
-   %ptr_float = OpTypePointer PushConstant %float 
+   %ptr_float = OpTypePointer PushConstant %float
          %pc1 = OpVariable %ptr PushConstant
          %pc2 = OpVariable %ptr PushConstant
 
@@ -2327,10 +2563,10 @@
                 OpEntryPoint Vertex %1 "func1"
                 OpEntryPoint Fragment %2 "func2"
                 OpExecutionMode %2 OriginUpperLeft
-    
+
                 OpDecorate %struct Block
                 OpMemberDecorate %struct 0 Offset 0
-    
+
         %void = OpTypeVoid
       %voidfn = OpTypeFunction %void
        %float = OpTypeFloat 32
@@ -2338,7 +2574,7 @@
        %int_0 = OpConstant %int 0
       %struct = OpTypeStruct %float
          %ptr = OpTypePointer PushConstant %struct
-   %ptr_float = OpTypePointer PushConstant %float 
+   %ptr_float = OpTypePointer PushConstant %float
          %pc1 = OpVariable %ptr PushConstant
          %pc2 = OpVariable %ptr PushConstant
 
@@ -2369,10 +2605,10 @@
                 OpMemoryModel Logical GLSL450
                 OpEntryPoint Fragment %1 "main"
                 OpExecutionMode %1 OriginUpperLeft
-    
+
                 OpDecorate %struct Block
                 OpMemberDecorate %struct 0 Offset 0
-    
+
         %void = OpTypeVoid
       %voidfn = OpTypeFunction %void
        %float = OpTypeFloat 32
@@ -2380,7 +2616,7 @@
        %int_0 = OpConstant %int 0
       %struct = OpTypeStruct %float
          %ptr = OpTypePointer PushConstant %struct
-   %ptr_float = OpTypePointer PushConstant %float 
+   %ptr_float = OpTypePointer PushConstant %float
          %pc1 = OpVariable %ptr PushConstant
          %pc2 = OpVariable %ptr PushConstant
 
@@ -2401,10 +2637,10 @@
                 OpMemoryModel Logical GLSL450
                 OpEntryPoint Fragment %1 "main"
                 OpExecutionMode %1 OriginUpperLeft
-    
+
                 OpDecorate %struct Block
                 OpMemberDecorate %struct 0 Offset 0
-    
+
         %void = OpTypeVoid
       %voidfn = OpTypeFunction %void
        %float = OpTypeFloat 32
@@ -2412,7 +2648,7 @@
        %int_0 = OpConstant %int 0
       %struct = OpTypeStruct %float
          %ptr = OpTypePointer PushConstant %struct
-   %ptr_float = OpTypePointer PushConstant %float 
+   %ptr_float = OpTypePointer PushConstant %float
          %pc1 = OpVariable %ptr PushConstant
          %pc2 = OpVariable %ptr PushConstant
 
@@ -2446,10 +2682,10 @@
                 OpEntryPoint Vertex %1 "func1"
                 OpEntryPoint Fragment %2 "func2"
                 OpExecutionMode %2 OriginUpperLeft
-    
+
                 OpDecorate %struct Block
                 OpMemberDecorate %struct 0 Offset 0
-    
+
         %void = OpTypeVoid
       %voidfn = OpTypeFunction %void
        %float = OpTypeFloat 32
@@ -2457,10 +2693,10 @@
        %int_0 = OpConstant %int 0
       %struct = OpTypeStruct %float
          %ptr = OpTypePointer PushConstant %struct
-   %ptr_float = OpTypePointer PushConstant %float 
+   %ptr_float = OpTypePointer PushConstant %float
          %pc1 = OpVariable %ptr PushConstant
          %pc2 = OpVariable %ptr PushConstant
- 
+
         %sub1 = OpFunction %void None %voidfn
   %label_sub1 = OpLabel
            %3 = OpAccessChain %ptr_float %pc1 %int_0
@@ -2500,10 +2736,10 @@
                 OpMemoryModel Logical GLSL450
                 OpEntryPoint Fragment %1 "main"
                 OpExecutionMode %1 OriginUpperLeft
-    
+
                 OpDecorate %struct Block
                 OpMemberDecorate %struct 0 Offset 0
-    
+
         %void = OpTypeVoid
       %voidfn = OpTypeFunction %void
        %float = OpTypeFloat 32
@@ -2511,7 +2747,7 @@
        %int_0 = OpConstant %int 0
       %struct = OpTypeStruct %float
          %ptr = OpTypePointer PushConstant %struct
-   %ptr_float = OpTypePointer PushConstant %float 
+   %ptr_float = OpTypePointer PushConstant %float
          %pc1 = OpVariable %ptr PushConstant
          %pc2 = OpVariable %ptr PushConstant
 
@@ -2826,8 +3062,8 @@
             OpMemoryModel Logical GLSL450
             OpEntryPoint Fragment %1 "main"
             OpExecutionMode %1 OriginUpperLeft
-
-            OpDecorate %struct BufferBlock
+            OpDecorate %struct Block
+            OpMemberDecorate %struct 0 Offset 0
 
     %void = OpTypeVoid
   %voidfn = OpTypeFunction %void
@@ -3285,7 +3521,7 @@
       getDiagnosticString(),
       HasSubstr("Structure id 6 decorated as Block for variable in Uniform "
                 "storage class must follow standard uniform buffer layout "
-                "rules: member 2 at offset 24 is not aligned to 16"));
+                "rules: member 2 at offset 152 is not aligned to 16"));
 }
 
 TEST_F(ValidateDecorations,
@@ -3418,7 +3654,8 @@
       getDiagnosticString(),
       HasSubstr(
           "Structure id 6 decorated as Block for variable in Uniform storage "
-          "class must follow standard uniform buffer layout rules: member 4 is "
+          "class must follow standard uniform buffer layout rules: member 4 "
+          "contains "
           "an array with stride 49 not satisfying alignment to 16"));
 }
 
@@ -4345,11 +4582,11 @@
 
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("FPRoundingMode decoration can be applied only to the "
-                "Object operand of an OpStore in the StorageBuffer, Uniform, "
-                "PushConstant, Input, or Output Storage Classes."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("FPRoundingMode decoration can be applied only to the "
+                        "Object operand of an OpStore in the StorageBuffer, "
+                        "PhysicalStorageBufferEXT, Uniform, "
+                        "PushConstant, Input, or Output Storage Classes."));
 }
 
 TEST_F(ValidateDecorations, FPRoundingModeMultipleOpStoreGood) {
@@ -4510,7 +4747,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-// Uniform decoration
+// Uniform and UniformId decorations
 
 TEST_F(ValidateDecorations, UniformDecorationGood) {
   const std::string spirv = R"(
@@ -4539,49 +4776,96 @@
   EXPECT_THAT(getDiagnosticString(), Eq(""));
 }
 
-TEST_F(ValidateDecorations, UniformDecorationTargetsTypeBad) {
-  const std::string spirv = R"(
+// Returns SPIR-V assembly for a shader that uses a given decoration
+// instruction.
+std::string ShaderWithUniformLikeDecoration(const std::string& inst) {
+  return std::string(R"(
 OpCapability Shader
 OpMemoryModel Logical Simple
 OpEntryPoint GLCompute %main "main"
 OpExecutionMode %main LocalSize 1 1 1
-OpDecorate %fn Uniform
-%void = OpTypeVoid
-%fn = OpTypeFunction %void
-%main = OpFunction %void None %fn
-%entry = OpLabel
-OpReturn
-OpFunctionEnd
-)";
-
-  CompileSuccessfully(spirv);
-  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Uniform decoration applied to a non-object"));
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("%2 = OpTypeFunction %void"));
-}
-
-TEST_F(ValidateDecorations, UniformDecorationTargetsVoidValueBad) {
-  const std::string spirv = R"(
-OpCapability Shader
-OpMemoryModel Logical Simple
-OpEntryPoint GLCompute %main "main"
-OpExecutionMode %main LocalSize 1 1 1
+OpName %subgroupscope "subgroupscope"
 OpName %call "call"
 OpName %myfunc "myfunc"
-OpDecorate %call Uniform
+OpName %int0 "int0"
+OpName %float0 "float0"
+OpName %fn "fn"
+)") + inst +
+         R"(
 %void = OpTypeVoid
-%fnty = OpTypeFunction %void
-%myfunc = OpFunction %void None %fnty
+%float = OpTypeFloat 32
+%int = OpTypeInt 32 1
+%int0 = OpConstantNull %int
+%int_99 = OpConstant %int 99
+%subgroupscope = OpConstant %int 3
+%float0 = OpConstantNull %float
+%fn = OpTypeFunction %void
+%myfunc = OpFunction %void None %fn
 %myfuncentry = OpLabel
 OpReturn
 OpFunctionEnd
-%main = OpFunction %void None %fnty
+%main = OpFunction %void None %fn
 %entry = OpLabel
 %call = OpFunctionCall %void %myfunc
 OpReturn
 OpFunctionEnd
 )";
+}
+
+TEST_F(ValidateDecorations, UniformIdDecorationWithScopeIdV13Bad) {
+  const std::string spirv = ShaderWithUniformLikeDecoration(
+      "OpDecorateId %int0 UniformId %subgroupscope");
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_WRONG_VERSION,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("requires SPIR-V version 1.4 or later\n"
+                        "  OpDecorateId %int0 UniformId %subgroupscope"));
+}
+
+TEST_F(ValidateDecorations, UniformIdDecorationWithScopeIdV13BadTargetV14) {
+  const std::string spirv = ShaderWithUniformLikeDecoration(
+      "OpDecorateId %int0 UniformId %subgroupscope");
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_WRONG_VERSION,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("requires SPIR-V version 1.4 or later"));
+}
+
+TEST_F(ValidateDecorations, UniformIdDecorationWithScopeIdV14Good) {
+  const std::string spirv = ShaderWithUniformLikeDecoration(
+      "OpDecorateId %int0 UniformId %subgroupscope");
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, UniformDecorationTargetsTypeBad) {
+  const std::string spirv =
+      ShaderWithUniformLikeDecoration("OpDecorate %fn Uniform");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Uniform decoration applied to a non-object"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("%fn = OpTypeFunction %void"));
+}
+
+TEST_F(ValidateDecorations, UniformIdDecorationTargetsTypeBad) {
+  const std::string spirv = ShaderWithUniformLikeDecoration(
+      "OpDecorateId %fn UniformId %subgroupscope");
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("UniformId decoration applied to a non-object"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("%fn = OpTypeFunction %void"));
+}
+
+TEST_F(ValidateDecorations, UniformDecorationTargetsVoidValueBad) {
+  const std::string spirv =
+      ShaderWithUniformLikeDecoration("OpDecorate %call Uniform");
 
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
@@ -4590,6 +4874,82 @@
                         "  %call = OpFunctionCall %void %myfunc"));
 }
 
+TEST_F(ValidateDecorations, UniformIdDecorationTargetsVoidValueBad) {
+  const std::string spirv = ShaderWithUniformLikeDecoration(
+      "OpDecorateId %call UniformId %subgroupscope");
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4))
+      << spirv;
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("UniformId decoration applied to a value with void type\n"
+                "  %call = OpFunctionCall %void %myfunc"));
+}
+
+TEST_F(ValidateDecorations,
+       UniformDecorationWithScopeIdV14IdIsFloatValueIsBad) {
+  const std::string spirv =
+      ShaderWithUniformLikeDecoration("OpDecorateId %int0 UniformId %float0");
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("ConstantNull: expected Execution Scope to be a 32-bit int"));
+}
+
+TEST_F(ValidateDecorations,
+       UniformDecorationWithScopeIdV14IdIsInvalidIntValueBad) {
+  const std::string spirv =
+      ShaderWithUniformLikeDecoration("OpDecorateId %int0 UniformId %int_99");
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Invalid scope value:\n %int_99 = OpConstant %int 99\n"));
+}
+
+TEST_F(ValidateDecorations, UniformDecorationWithScopeIdV14VulkanEnv) {
+  const std::string spirv =
+      ShaderWithUniformLikeDecoration("OpDecorateId %int0 UniformId %int0");
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1_SPIRV_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_VULKAN_1_1_SPIRV_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(": in Vulkan environment Execution Scope is limited to "
+                        "Workgroup and Subgroup"));
+}
+
+TEST_F(ValidateDecorations, UniformDecorationWithWrongInstructionBad) {
+  const std::string spirv =
+      ShaderWithUniformLikeDecoration("OpDecorateId %int0 Uniform");
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_2);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_2));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Decorations that don't take ID parameters may not be "
+                        "used with OpDecorateId\n"
+                        "  OpDecorateId %int0 Uniform"));
+}
+
+TEST_F(ValidateDecorations, UniformIdDecorationWithWrongInstructionBad) {
+  const std::string spirv = ShaderWithUniformLikeDecoration(
+      "OpDecorate %int0 UniformId %subgroupscope");
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Decorations taking ID parameters may not be used with OpDecorateId\n"
+          "  OpDecorate %int0 UniformId %subgroupscope"));
+}
+
 TEST_F(ValidateDecorations, MultipleOffsetDecorationsOnSameID) {
   std::string spirv = R"(
             OpCapability Shader
@@ -4810,6 +5170,1194 @@
           "ID '2' decorated with both BufferBlock and Block is not allowed."));
 }
 
+std::string MakeIntegerShader(
+    const std::string& decoration, const std::string& inst,
+    const std::string& extension =
+        "OpExtension \"SPV_KHR_no_integer_wrap_decoration\"") {
+  return R"(
+OpCapability Shader
+OpCapability Linkage
+)" + extension +
+         R"(
+%glsl = OpExtInstImport "GLSL.std.450"
+%opencl = OpExtInstImport "OpenCL.std"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpName %entry "entry"
+)" + decoration +
+         R"(
+    %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+     %int = OpTypeInt 32 1
+    %zero = OpConstantNull %int
+   %float = OpTypeFloat 32
+  %float0 = OpConstantNull %float
+    %main = OpFunction %void None %voidfn
+   %entry = OpLabel
+)" + inst +
+         R"(
+OpReturn
+OpFunctionEnd)";
+}
+
+// NoSignedWrap
+
+TEST_F(ValidateDecorations, NoSignedWrapOnTypeBad) {
+  std::string spirv = MakeIntegerShader("OpDecorate %void NoSignedWrap", "");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("NoSignedWrap decoration may not be applied to TypeVoid"));
+}
+
+TEST_F(ValidateDecorations, NoSignedWrapOnLabelBad) {
+  std::string spirv = MakeIntegerShader("OpDecorate %entry NoSignedWrap", "");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("NoSignedWrap decoration may not be applied to Label"));
+}
+
+TEST_F(ValidateDecorations, NoSignedWrapRequiresExtensionBad) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoSignedWrap",
+                                        "%val = OpIAdd %int %zero %zero", "");
+
+  CompileSuccessfully(spirv);
+  EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("requires one of these extensions: "
+                        "SPV_KHR_no_integer_wrap_decoration"));
+}
+
+TEST_F(ValidateDecorations, NoSignedWrapRequiresExtensionV13Bad) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoSignedWrap",
+                                        "%val = OpIAdd %int %zero %zero", "");
+
+  CompileSuccessfully(spirv);
+  EXPECT_NE(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("requires one of these extensions: "
+                        "SPV_KHR_no_integer_wrap_decoration"));
+}
+
+TEST_F(ValidateDecorations, NoSignedWrapOkInSPV14Good) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoSignedWrap",
+                                        "%val = OpIAdd %int %zero %zero", "");
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NoSignedWrapIAddGood) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoSignedWrap",
+                                        "%val = OpIAdd %int %zero %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NoSignedWrapISubGood) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoSignedWrap",
+                                        "%val = OpISub %int %zero %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NoSignedWrapIMulGood) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoSignedWrap",
+                                        "%val = OpIMul %int %zero %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NoSignedWrapShiftLeftLogicalGood) {
+  std::string spirv =
+      MakeIntegerShader("OpDecorate %val NoSignedWrap",
+                        "%val = OpShiftLeftLogical %int %zero %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NoSignedWrapSNegateGood) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoSignedWrap",
+                                        "%val = OpSNegate %int %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NoSignedWrapSRemBad) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoSignedWrap",
+                                        "%val = OpSRem %int %zero %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("NoSignedWrap decoration may not be applied to SRem"));
+}
+
+TEST_F(ValidateDecorations, NoSignedWrapFAddBad) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoSignedWrap",
+                                        "%val = OpFAdd %float %float0 %float0");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("NoSignedWrap decoration may not be applied to FAdd"));
+}
+
+TEST_F(ValidateDecorations, NoSignedWrapExtInstOpenCLGood) {
+  std::string spirv =
+      MakeIntegerShader("OpDecorate %val NoSignedWrap",
+                        "%val = OpExtInst %int %opencl s_abs %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NoSignedWrapExtInstGLSLGood) {
+  std::string spirv = MakeIntegerShader(
+      "OpDecorate %val NoSignedWrap", "%val = OpExtInst %int %glsl SAbs %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+// TODO(dneto): For NoSignedWrap and NoUnsignedWrap, permit
+// "OpExtInst for instruction numbers specified in the extended
+// instruction-set specifications as accepting this decoration."
+
+// NoUnignedWrap
+
+TEST_F(ValidateDecorations, NoUnsignedWrapOnTypeBad) {
+  std::string spirv = MakeIntegerShader("OpDecorate %void NoUnsignedWrap", "");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("NoUnsignedWrap decoration may not be applied to TypeVoid"));
+}
+
+TEST_F(ValidateDecorations, NoUnsignedWrapOnLabelBad) {
+  std::string spirv = MakeIntegerShader("OpDecorate %entry NoUnsignedWrap", "");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("NoUnsignedWrap decoration may not be applied to Label"));
+}
+
+TEST_F(ValidateDecorations, NoUnsignedWrapRequiresExtensionBad) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
+                                        "%val = OpIAdd %int %zero %zero", "");
+
+  CompileSuccessfully(spirv);
+  EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("requires one of these extensions: "
+                        "SPV_KHR_no_integer_wrap_decoration"));
+}
+
+TEST_F(ValidateDecorations, NoUnsignedWrapRequiresExtensionV13Bad) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
+                                        "%val = OpIAdd %int %zero %zero", "");
+
+  CompileSuccessfully(spirv);
+  EXPECT_NE(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("requires one of these extensions: "
+                        "SPV_KHR_no_integer_wrap_decoration"));
+}
+
+TEST_F(ValidateDecorations, NoUnsignedWrapOkInSPV14Good) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
+                                        "%val = OpIAdd %int %zero %zero", "");
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NoUnsignedWrapIAddGood) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
+                                        "%val = OpIAdd %int %zero %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NoUnsignedWrapISubGood) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
+                                        "%val = OpISub %int %zero %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NoUnsignedWrapIMulGood) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
+                                        "%val = OpIMul %int %zero %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NoUnsignedWrapShiftLeftLogicalGood) {
+  std::string spirv =
+      MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
+                        "%val = OpShiftLeftLogical %int %zero %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NoUnsignedWrapSNegateGood) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
+                                        "%val = OpSNegate %int %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NoUnsignedWrapSRemBad) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
+                                        "%val = OpSRem %int %zero %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("NoUnsignedWrap decoration may not be applied to SRem"));
+}
+
+TEST_F(ValidateDecorations, NoUnsignedWrapFAddBad) {
+  std::string spirv = MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
+                                        "%val = OpFAdd %float %float0 %float0");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("NoUnsignedWrap decoration may not be applied to FAdd"));
+}
+
+TEST_F(ValidateDecorations, NoUnsignedWrapExtInstOpenCLGood) {
+  std::string spirv =
+      MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
+                        "%val = OpExtInst %int %opencl s_abs %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NoUnsignedWrapExtInstGLSLGood) {
+  std::string spirv =
+      MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
+                        "%val = OpExtInst %int %glsl SAbs %zero");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, AliasedandRestrictBad) {
+  const std::string body = R"(
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpSource GLSL 430
+OpMemberDecorate %Output 0 Offset 0
+OpDecorate %Output BufferBlock
+OpDecorate %dataOutput Restrict
+OpDecorate %dataOutput Aliased
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%Output = OpTypeStruct %float
+%_ptr_Uniform_Output = OpTypePointer Uniform %Output
+%dataOutput = OpVariable %_ptr_Uniform_Output Uniform
+%main = OpFunction %void None %3
+%5 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("decorated with both Aliased and Restrict is not allowed"));
+}
+
+// TODO(dneto): For NoUnsignedWrap and NoUnsignedWrap, permit
+// "OpExtInst for instruction numbers specified in the extended
+// instruction-set specifications as accepting this decoration."
+
+TEST_F(ValidateDecorations, PSBAliasedRestrictPointerSuccess) {
+  const std::string body = R"(
+OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability Int64
+OpCapability Shader
+OpExtension "SPV_EXT_physical_storage_buffer"
+OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %val1 RestrictPointerEXT
+%uint64 = OpTypeInt 64 0
+%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%pptr_f = OpTypePointer Function %ptr
+%void = OpTypeVoid
+%voidfn = OpTypeFunction %void
+%main = OpFunction %void None %voidfn
+%entry = OpLabel
+%val1 = OpVariable %pptr_f Function
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateDecorations, PSBAliasedRestrictPointerMissing) {
+  const std::string body = R"(
+OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability Int64
+OpCapability Shader
+OpExtension "SPV_EXT_physical_storage_buffer"
+OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%uint64 = OpTypeInt 64 0
+%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%pptr_f = OpTypePointer Function %ptr
+%void = OpTypeVoid
+%voidfn = OpTypeFunction %void
+%main = OpFunction %void None %voidfn
+%entry = OpLabel
+%val1 = OpVariable %pptr_f Function
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected AliasedPointerEXT or RestrictPointerEXT for "
+                        "PhysicalStorageBufferEXT pointer"));
+}
+
+TEST_F(ValidateDecorations, PSBAliasedRestrictPointerBoth) {
+  const std::string body = R"(
+OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability Int64
+OpCapability Shader
+OpExtension "SPV_EXT_physical_storage_buffer"
+OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %val1 RestrictPointerEXT
+OpDecorate %val1 AliasedPointerEXT
+%uint64 = OpTypeInt 64 0
+%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%pptr_f = OpTypePointer Function %ptr
+%void = OpTypeVoid
+%voidfn = OpTypeFunction %void
+%main = OpFunction %void None %voidfn
+%entry = OpLabel
+%val1 = OpVariable %pptr_f Function
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("can't specify both AliasedPointerEXT and RestrictPointerEXT "
+                "for PhysicalStorageBufferEXT pointer"));
+}
+
+TEST_F(ValidateDecorations, PSBAliasedRestrictFunctionParamSuccess) {
+  const std::string body = R"(
+OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability Int64
+OpCapability Shader
+OpExtension "SPV_EXT_physical_storage_buffer"
+OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %fparam Restrict
+%uint64 = OpTypeInt 64 0
+%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%void = OpTypeVoid
+%voidfn = OpTypeFunction %void
+%fnptr = OpTypeFunction %void %ptr
+%main = OpFunction %void None %voidfn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+%fn = OpFunction %void None %fnptr
+%fparam = OpFunctionParameter %ptr
+%lab = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateDecorations, PSBAliasedRestrictFunctionParamMissing) {
+  const std::string body = R"(
+OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability Int64
+OpCapability Shader
+OpExtension "SPV_EXT_physical_storage_buffer"
+OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%uint64 = OpTypeInt 64 0
+%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%void = OpTypeVoid
+%voidfn = OpTypeFunction %void
+%fnptr = OpTypeFunction %void %ptr
+%main = OpFunction %void None %voidfn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+%fn = OpFunction %void None %fnptr
+%fparam = OpFunctionParameter %ptr
+%lab = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected Aliased or Restrict for "
+                        "PhysicalStorageBufferEXT pointer"));
+}
+
+TEST_F(ValidateDecorations, PSBAliasedRestrictFunctionParamBoth) {
+  const std::string body = R"(
+OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability Int64
+OpCapability Shader
+OpExtension "SPV_EXT_physical_storage_buffer"
+OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %fparam Restrict
+OpDecorate %fparam Aliased
+%uint64 = OpTypeInt 64 0
+%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%void = OpTypeVoid
+%voidfn = OpTypeFunction %void
+%fnptr = OpTypeFunction %void %ptr
+%main = OpFunction %void None %voidfn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+%fn = OpFunction %void None %fnptr
+%fparam = OpFunctionParameter %ptr
+%lab = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("can't specify both Aliased and Restrict for "
+                        "PhysicalStorageBufferEXT pointer"));
+}
+
+TEST_F(ValidateDecorations, PSBFPRoundingModeSuccess) {
+  std::string spirv = R"(
+OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability Shader
+OpCapability Linkage
+OpCapability StorageBuffer16BitAccess
+OpExtension "SPV_EXT_physical_storage_buffer"
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpExtension "SPV_KHR_variable_pointers"
+OpExtension "SPV_KHR_16bit_storage"
+OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpEntryPoint GLCompute %main "main"
+OpDecorate %_ FPRoundingMode RTE
+OpDecorate %half_ptr_var AliasedPointerEXT
+%half = OpTypeFloat 16
+%float = OpTypeFloat 32
+%float_1_25 = OpConstant %float 1.25
+%half_ptr = OpTypePointer PhysicalStorageBufferEXT %half
+%half_pptr_f = OpTypePointer Function %half_ptr
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+%half_ptr_var = OpVariable %half_pptr_f Function
+%val1 = OpLoad %half_ptr %half_ptr_var
+%_ = OpFConvert %half %float_1_25
+OpStore %val1 %_ Aligned 2
+OpReturn
+OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
+}
+
+TEST_F(ValidateDecorations, InvalidStraddle) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpMemberDecorate %inner_struct 0 Offset 0
+OpMemberDecorate %inner_struct 1 Offset 4
+OpDecorate %outer_struct Block
+OpMemberDecorate %outer_struct 0 Offset 0
+OpMemberDecorate %outer_struct 1 Offset 8
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%float2 = OpTypeVector %float 2
+%inner_struct = OpTypeStruct %float %float2
+%outer_struct = OpTypeStruct %float2 %inner_struct
+%ptr_ssbo_outer = OpTypePointer StorageBuffer %outer_struct
+%var = OpVariable %ptr_ssbo_outer StorageBuffer
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Structure id 2 decorated as Block for variable in "
+                        "StorageBuffer storage class must follow relaxed "
+                        "storage buffer layout rules: member 1 is an "
+                        "improperly straddling vector at offset 12"));
+}
+
+TEST_F(ValidateDecorations, DescriptorArray) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Offset 0
+OpMemberDecorate %struct 1 Offset 1
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%int = OpTypeInt 32 0
+%int_2 = OpConstant %int 2
+%float2 = OpTypeVector %float 2
+%struct = OpTypeStruct %float %float2
+%struct_array = OpTypeArray %struct %int_2
+%ptr_ssbo_array = OpTypePointer StorageBuffer %struct_array
+%var = OpVariable %ptr_ssbo_array StorageBuffer
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Structure id 2 decorated as Block for variable in "
+                        "StorageBuffer storage class must follow standard "
+                        "storage buffer layout rules: member 1 at offset 1 is "
+                        "not aligned to 8"));
+}
+
+TEST_F(ValidateDecorations, DescriptorRuntimeArray) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability RuntimeDescriptorArrayEXT
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpExtension "SPV_EXT_descriptor_indexing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Offset 0
+OpMemberDecorate %struct 1 Offset 1
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%int = OpTypeInt 32 0
+%float2 = OpTypeVector %float 2
+%struct = OpTypeStruct %float %float2
+%struct_array = OpTypeRuntimeArray %struct
+%ptr_ssbo_array = OpTypePointer StorageBuffer %struct_array
+%var = OpVariable %ptr_ssbo_array StorageBuffer
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Structure id 2 decorated as Block for variable in "
+                        "StorageBuffer storage class must follow standard "
+                        "storage buffer layout rules: member 1 at offset 1 is "
+                        "not aligned to 8"));
+}
+
+TEST_F(ValidateDecorations, MultiDimensionalArray) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Offset 0
+OpDecorate %array_4 ArrayStride 4
+OpDecorate %array_3 ArrayStride 48
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_3 = OpConstant %int 3
+%int_4 = OpConstant %int 4
+%array_4 = OpTypeArray %int %int_4
+%array_3 = OpTypeArray %array_4 %int_3
+%struct = OpTypeStruct %array_3
+%ptr_struct = OpTypePointer Uniform %struct
+%var = OpVariable %ptr_struct Uniform
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Structure id 2 decorated as Block for variable in "
+                        "Uniform storage class must follow standard uniform "
+                        "buffer layout rules: member 0 contains an array with "
+                        "stride 4 not satisfying alignment to 16"));
+}
+
+TEST_F(ValidateDecorations, ImproperStraddleInArray) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Offset 0
+OpDecorate %array ArrayStride 24
+OpMemberDecorate %inner 0 Offset 0
+OpMemberDecorate %inner 1 Offset 4
+OpMemberDecorate %inner 2 Offset 12
+OpMemberDecorate %inner 3 Offset 16
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_2 = OpConstant %int 2
+%int2 = OpTypeVector %int 2
+%inner = OpTypeStruct %int %int2 %int %int
+%array = OpTypeArray %inner %int_2
+%struct = OpTypeStruct %array
+%ptr_struct = OpTypePointer StorageBuffer %struct
+%var = OpVariable %ptr_struct StorageBuffer
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Structure id 4 decorated as Block for variable in "
+                        "StorageBuffer storage class must follow relaxed "
+                        "storage buffer layout rules: member 1 is an "
+                        "improperly straddling vector at offset 28"));
+}
+
+TEST_F(ValidateDecorations, LargeArray) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Offset 0
+OpDecorate %array ArrayStride 24
+OpMemberDecorate %inner 0 Offset 0
+OpMemberDecorate %inner 1 Offset 8
+OpMemberDecorate %inner 2 Offset 16
+OpMemberDecorate %inner 3 Offset 20
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_2000000 = OpConstant %int 2000000
+%int2 = OpTypeVector %int 2
+%inner = OpTypeStruct %int %int2 %int %int
+%array = OpTypeArray %inner %int_2000000
+%struct = OpTypeStruct %array
+%ptr_struct = OpTypePointer StorageBuffer %struct
+%var = OpVariable %ptr_struct StorageBuffer
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+}
+
+// NonWritable
+
+// Returns a SPIR-V shader module with variables in various storage classes,
+// parameterizable by which ID should be decorated as NonWritable.
+std::string ShaderWithNonWritableTarget(const std::string& target,
+                                        bool member_decorate = false) {
+  const std::string decoration_inst =
+      std::string(member_decorate ? "OpMemberDecorate " : "OpDecorate ") +
+      target + (member_decorate ? " 0" : "");
+
+  return std::string(R"(
+            OpCapability Shader
+            OpCapability RuntimeDescriptorArrayEXT
+            OpExtension "SPV_EXT_descriptor_indexing"
+            OpExtension "SPV_KHR_storage_buffer_storage_class"
+            OpMemoryModel Logical GLSL450
+            OpEntryPoint Vertex %main "main"
+            OpName %label "label"
+            OpName %param_f "param_f"
+            OpName %param_p "param_p"
+            OpName %_ptr_imstor "_ptr_imstor"
+            OpName %_ptr_imsam "_ptr_imsam"
+            OpName %var_wg "var_wg"
+            OpName %var_imsam "var_imsam"
+            OpName %var_priv "var_priv"
+            OpName %var_func "var_func"
+            OpName %simple_struct "simple_struct"
+
+            OpDecorate %struct_b Block
+            OpDecorate %struct_bb BufferBlock
+            OpDecorate %struct_b_rtarr Block
+            OpMemberDecorate %struct_b 0 Offset 0
+            OpMemberDecorate %struct_bb 0 Offset 0
+            OpMemberDecorate %struct_b_rtarr 0 Offset 0
+            OpDecorate %rtarr ArrayStride 4
+)") + decoration_inst +
+
+         R"( NonWritable
+
+      %void = OpTypeVoid
+   %void_fn = OpTypeFunction %void
+     %float = OpTypeFloat 32
+   %float_0 = OpConstant %float 0
+   %int     = OpTypeInt 32 0
+   %int_2   = OpConstant %int 2
+  %struct_b = OpTypeStruct %float
+ %struct_bb = OpTypeStruct %float
+ %rtarr = OpTypeRuntimeArray %float
+%struct_b_rtarr = OpTypeStruct %rtarr
+%simple_struct = OpTypeStruct %float
+ ; storage image
+ %imstor = OpTypeImage %float 2D 0 0 0 2 R32f
+ ; sampled image
+ %imsam = OpTypeImage %float 2D 0 0 0 1 R32f
+%array_imstor = OpTypeArray %imstor %int_2
+%rta_imstor = OpTypeRuntimeArray %imstor
+
+%_ptr_Uniform_stb        = OpTypePointer Uniform %struct_b
+%_ptr_Uniform_stbb       = OpTypePointer Uniform %struct_bb
+%_ptr_StorageBuffer_stb  = OpTypePointer StorageBuffer %struct_b
+%_ptr_StorageBuffer_stb_rtarr  = OpTypePointer StorageBuffer %struct_b_rtarr
+%_ptr_Workgroup          = OpTypePointer Workgroup %float
+%_ptr_Private            = OpTypePointer Private %float
+%_ptr_Function           = OpTypePointer Function %float
+%_ptr_imstor             = OpTypePointer UniformConstant %imstor
+%_ptr_imsam              = OpTypePointer UniformConstant %imsam
+%_ptr_array_imstor       = OpTypePointer UniformConstant %array_imstor
+%_ptr_rta_imstor         = OpTypePointer UniformConstant %rta_imstor
+
+%extra_fn = OpTypeFunction %void %float %_ptr_Private %_ptr_imstor
+
+%var_ubo = OpVariable %_ptr_Uniform_stb Uniform
+%var_ssbo_u = OpVariable %_ptr_Uniform_stbb Uniform
+%var_ssbo_sb = OpVariable %_ptr_StorageBuffer_stb StorageBuffer
+%var_ssbo_sb_rtarr = OpVariable %_ptr_StorageBuffer_stb_rtarr StorageBuffer
+%var_wg = OpVariable %_ptr_Workgroup Workgroup
+%var_priv = OpVariable %_ptr_Private Private
+%var_imstor = OpVariable %_ptr_imstor UniformConstant
+%var_imsam = OpVariable %_ptr_imsam UniformConstant
+%var_array_imstor = OpVariable %_ptr_array_imstor UniformConstant
+%var_rta_imstor = OpVariable %_ptr_rta_imstor UniformConstant
+
+  %helper = OpFunction %void None %extra_fn
+ %param_f = OpFunctionParameter %float
+ %param_p = OpFunctionParameter %_ptr_Private
+ %param_pimstor = OpFunctionParameter %_ptr_imstor
+%helper_label = OpLabel
+%helper_func_var = OpVariable %_ptr_Function Function
+            OpReturn
+            OpFunctionEnd
+
+    %main = OpFunction %void None %void_fn
+   %label = OpLabel
+%var_func = OpVariable %_ptr_Function Function
+            OpReturn
+            OpFunctionEnd
+)";
+}
+
+TEST_F(ValidateDecorations, NonWritableLabelTargetBad) {
+  std::string spirv = ShaderWithNonWritableTarget("%label");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of NonWritable decoration must be a "
+                        "memory object declaration (a variable or a function "
+                        "parameter)\n  %label = OpLabel"));
+}
+
+TEST_F(ValidateDecorations, NonWritableTypeTargetBad) {
+  std::string spirv = ShaderWithNonWritableTarget("%void");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of NonWritable decoration must be a "
+                        "memory object declaration (a variable or a function "
+                        "parameter)\n  %void = OpTypeVoid"));
+}
+
+TEST_F(ValidateDecorations, NonWritableValueTargetBad) {
+  std::string spirv = ShaderWithNonWritableTarget("%float_0");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of NonWritable decoration must be a "
+                        "memory object declaration (a variable or a function "
+                        "parameter)\n  %float_0 = OpConstant %float 0"));
+}
+
+TEST_F(ValidateDecorations, NonWritableValueParamBad) {
+  std::string spirv = ShaderWithNonWritableTarget("%param_f");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of NonWritable decoration is invalid: must "
+                        "point to a storage image, uniform block, or storage "
+                        "buffer\n  %param_f = OpFunctionParameter %float"));
+}
+
+TEST_F(ValidateDecorations, NonWritablePointerParamButWrongTypeBad) {
+  std::string spirv = ShaderWithNonWritableTarget("%param_p");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Target of NonWritable decoration is invalid: must "
+          "point to a storage image, uniform block, or storage "
+          "buffer\n  %param_p = OpFunctionParameter %_ptr_Private_float"));
+}
+
+TEST_F(ValidateDecorations, NonWritablePointerParamStorageImageGood) {
+  std::string spirv = ShaderWithNonWritableTarget("%param_pimstor");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarStorageImageGood) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_imstor");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarSampledImageBad) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_imsam");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of NonWritable decoration is invalid: must "
+                        "point to a storage image, uniform block, or storage "
+                        "buffer\n  %var_imsam"));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarUboGood) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_ubo");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarSsboInUniformGood) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_ssbo_u");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarSsboInStorageBufferGood) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_ssbo_sb");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NonWritableMemberOfSsboInStorageBufferGood) {
+  std::string spirv = ShaderWithNonWritableTarget("%struct_b_rtarr", true);
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NonWritableMemberOfStructGood) {
+  std::string spirv = ShaderWithNonWritableTarget("%simple_struct", true);
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateDecorations, NonWritableVarWorkgroupBad) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_wg");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of NonWritable decoration is invalid: must "
+                        "point to a storage image, uniform block, or storage "
+                        "buffer\n  %var_wg"));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarWorkgroupV14Bad) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_wg");
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of NonWritable decoration is invalid: must "
+                        "point to a storage image, uniform block, storage "
+                        "buffer, or variable in Private or Function storage "
+                        "class\n  %var_wg"));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarPrivateBad) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_priv");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of NonWritable decoration is invalid: must "
+                        "point to a storage image, uniform block, or storage "
+                        "buffer\n  %var_priv"));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarPrivateV13Bad) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_priv");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of NonWritable decoration is invalid: must "
+                        "point to a storage image, uniform block, or storage "
+                        "buffer\n  %var_priv"));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarPrivateV14Good) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_priv");
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarPrivateV13TargetV14Bad) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_priv");
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of NonWritable decoration is invalid: must "
+                        "point to a storage image, uniform block, or storage "
+                        "buffer\n  %var_priv"));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarFunctionBad) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_func");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of NonWritable decoration is invalid: must "
+                        "point to a storage image, uniform block, or storage "
+                        "buffer\n  %var_func"));
+}
+
+TEST_F(ValidateDecorations, NonWritableArrayGood) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_array_imstor");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateDecorations, NonWritableRuntimeArrayGood) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_rta_imstor");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateWebGPUCombineDecorationResult, Decorate) {
+  const char* const decoration = std::get<0>(GetParam());
+  const TestResult& test_result = std::get<1>(GetParam());
+
+  CodeGenerator generator = CodeGenerator::GetWebGPUShaderCodeGenerator();
+  generator.before_types_ = "OpDecorate %u32 ";
+  generator.before_types_ += decoration;
+  generator.before_types_ += "\n";
+
+  EntryPoint entry_point;
+  entry_point.name = "main";
+  entry_point.execution_model = "Vertex";
+  generator.entry_points_.push_back(std::move(entry_point));
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(test_result.validation_result,
+            ValidateInstructions(SPV_ENV_WEBGPU_0));
+  if (test_result.error_str != "") {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str));
+  }
+}
+
+TEST_P(ValidateWebGPUCombineDecorationResult, DecorateMember) {
+  const char* const decoration = std::get<0>(GetParam());
+  const TestResult& test_result = std::get<1>(GetParam());
+
+  CodeGenerator generator = CodeGenerator::GetWebGPUShaderCodeGenerator();
+  generator.before_types_ = "OpMemberDecorate %struct_type 0 ";
+  generator.before_types_ += decoration;
+  generator.before_types_ += "\n";
+
+  generator.after_types_ = "%struct_type = OpTypeStruct %u32\n";
+
+  EntryPoint entry_point;
+  entry_point.name = "main";
+  entry_point.execution_model = "Vertex";
+  generator.entry_points_.push_back(std::move(entry_point));
+
+  CompileSuccessfully(generator.Build(), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(test_result.validation_result,
+            ValidateInstructions(SPV_ENV_WEBGPU_0));
+  if (!test_result.error_str.empty()) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    DecorationCapabilityFailure, ValidateWebGPUCombineDecorationResult,
+    Combine(Values("CPacked", "Patch", "Sample", "Constant",
+                   "SaturatedConversion", "NonUniformEXT"),
+            Values(TestResult(SPV_ERROR_INVALID_CAPABILITY,
+                              "requires one of these capabilities"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    DecorationWhitelistFailure, ValidateWebGPUCombineDecorationResult,
+    Combine(Values("RelaxedPrecision", "BufferBlock", "GLSLShared",
+                   "GLSLPacked", "Invariant", "Volatile", "Coherent"),
+            Values(TestResult(
+                SPV_ERROR_INVALID_ID,
+                "is not valid for the WebGPU execution environment."))));
+
+TEST_F(ValidateDecorations, NonWritableVarFunctionV13Bad) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_func");
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of NonWritable decoration is invalid: must "
+                        "point to a storage image, uniform block, or storage "
+                        "buffer\n  %var_func"));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarFunctionV14Good) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_func");
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateDecorations, NonWritableVarFunctionV13TargetV14Bad) {
+  std::string spirv = ShaderWithNonWritableTarget("%var_func");
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Target of NonWritable decoration is invalid: must "
+                        "point to a storage image, uniform block, or storage "
+                        "buffer\n  %var_func"));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_ext_inst_test.cpp b/test/val/val_ext_inst_test.cpp
index d1505da..e5ff392 100644
--- a/test/val/val_ext_inst_test.cpp
+++ b/test/val/val_ext_inst_test.cpp
@@ -493,20 +493,20 @@
                         "Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllSqrtLike, ValidateGlslStd450SqrtLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "Round",
-                            "RoundEven",
-                            "FAbs",
-                            "Trunc",
-                            "FSign",
-                            "Floor",
-                            "Ceil",
-                            "Fract",
-                            "Sqrt",
-                            "InverseSqrt",
-                            "Normalize",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllSqrtLike, ValidateGlslStd450SqrtLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "Round",
+                             "RoundEven",
+                             "FAbs",
+                             "Trunc",
+                             "FSign",
+                             "Floor",
+                             "Ceil",
+                             "Fract",
+                             "Sqrt",
+                             "InverseSqrt",
+                             "Normalize",
+                         }));
 
 TEST_P(ValidateGlslStd450FMinLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -560,15 +560,15 @@
                         "Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllFMinLike, ValidateGlslStd450FMinLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "FMin",
-                            "FMax",
-                            "Step",
-                            "Reflect",
-                            "NMin",
-                            "NMax",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllFMinLike, ValidateGlslStd450FMinLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "FMin",
+                             "FMax",
+                             "Step",
+                             "Reflect",
+                             "NMin",
+                             "NMax",
+                         }));
 
 TEST_P(ValidateGlslStd450FClampLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -635,15 +635,15 @@
                         "Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllFClampLike, ValidateGlslStd450FClampLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "FClamp",
-                            "FMix",
-                            "SmoothStep",
-                            "Fma",
-                            "FaceForward",
-                            "NClamp",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllFClampLike, ValidateGlslStd450FClampLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "FClamp",
+                             "FMix",
+                             "SmoothStep",
+                             "Fma",
+                             "FaceForward",
+                             "NClamp",
+                         }));
 
 TEST_P(ValidateGlslStd450SAbsLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -716,14 +716,14 @@
                         "Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllSAbsLike, ValidateGlslStd450SAbsLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "SAbs",
-                            "SSign",
-                            "FindILsb",
-                            "FindUMsb",
-                            "FindSMsb",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllSAbsLike, ValidateGlslStd450SAbsLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "SAbs",
+                             "SSign",
+                             "FindILsb",
+                             "FindUMsb",
+                             "FindSMsb",
+                         }));
 
 TEST_F(ValidateExtInst, FindUMsbNot32Bit) {
   const std::string body = R"(
@@ -865,13 +865,13 @@
                         "Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllUMinLike, ValidateGlslStd450UMinLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "UMin",
-                            "SMin",
-                            "UMax",
-                            "SMax",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllUMinLike, ValidateGlslStd450UMinLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "UMin",
+                             "SMin",
+                             "UMax",
+                             "SMax",
+                         }));
 
 TEST_P(ValidateGlslStd450UClampLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -1028,11 +1028,11 @@
                         "Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllUClampLike, ValidateGlslStd450UClampLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "UClamp",
-                            "SClamp",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllUClampLike, ValidateGlslStd450UClampLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "UClamp",
+                             "SClamp",
+                         }));
 
 TEST_P(ValidateGlslStd450SinLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -1083,27 +1083,27 @@
                         "Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllSinLike, ValidateGlslStd450SinLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "Radians",
-                            "Degrees",
-                            "Sin",
-                            "Cos",
-                            "Tan",
-                            "Asin",
-                            "Acos",
-                            "Atan",
-                            "Sinh",
-                            "Cosh",
-                            "Tanh",
-                            "Asinh",
-                            "Acosh",
-                            "Atanh",
-                            "Exp",
-                            "Exp2",
-                            "Log",
-                            "Log2",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllSinLike, ValidateGlslStd450SinLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "Radians",
+                             "Degrees",
+                             "Sin",
+                             "Cos",
+                             "Tan",
+                             "Asin",
+                             "Acos",
+                             "Atan",
+                             "Sinh",
+                             "Cosh",
+                             "Tanh",
+                             "Asinh",
+                             "Acosh",
+                             "Atanh",
+                             "Exp",
+                             "Exp2",
+                             "Log",
+                             "Log2",
+                         }));
 
 TEST_P(ValidateGlslStd450PowLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -1168,11 +1168,11 @@
                         "Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllPowLike, ValidateGlslStd450PowLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "Atan2",
-                            "Pow",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllPowLike, ValidateGlslStd450PowLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "Atan2",
+                             "Pow",
+                         }));
 
 TEST_F(ValidateExtInst, GlslStd450DeterminantSuccess) {
   const std::string body = R"(
@@ -1795,14 +1795,14 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr(expected.str()));
 }
 
-INSTANTIATE_TEST_CASE_P(AllPack, ValidateGlslStd450Pack,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "PackSnorm4x8",
-                            "PackUnorm4x8",
-                            "PackSnorm2x16",
-                            "PackUnorm2x16",
-                            "PackHalf2x16",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllPack, ValidateGlslStd450Pack,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "PackSnorm4x8",
+                             "PackUnorm4x8",
+                             "PackSnorm2x16",
+                             "PackUnorm2x16",
+                             "PackHalf2x16",
+                         }));
 
 TEST_F(ValidateExtInst, PackDouble2x32Success) {
   const std::string body = R"(
@@ -2034,14 +2034,14 @@
   EXPECT_THAT(getDiagnosticString(), HasSubstr(expected.str()));
 }
 
-INSTANTIATE_TEST_CASE_P(AllUnpack, ValidateGlslStd450Unpack,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "UnpackSnorm4x8",
-                            "UnpackUnorm4x8",
-                            "UnpackSnorm2x16",
-                            "UnpackUnorm2x16",
-                            "UnpackHalf2x16",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllUnpack, ValidateGlslStd450Unpack,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "UnpackSnorm4x8",
+                             "UnpackUnorm4x8",
+                             "UnpackSnorm2x16",
+                             "UnpackUnorm2x16",
+                             "UnpackHalf2x16",
+                         }));
 
 TEST_F(ValidateExtInst, UnpackDouble2x32Success) {
   const std::string body = R"(
@@ -2831,7 +2831,7 @@
                         "Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     AllSqrtLike, ValidateOpenCLStdSqrtLike,
     ::testing::ValuesIn(std::vector<std::string>{
         "acos",         "acosh",       "acospi",       "asin",
@@ -2851,7 +2851,7 @@
         "native_log",   "native_log2", "native_log10", "native_recip",
         "native_rsqrt", "native_sin",  "native_sqrt",  "native_tan",
         "degrees",      "radians",     "sign",
-    }), );
+    }));
 
 TEST_P(ValidateOpenCLStdFMinLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -2905,16 +2905,16 @@
                         "Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllFMinLike, ValidateOpenCLStdFMinLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "atan2",     "atan2pi",       "copysign",
-                            "fdim",      "fmax",          "fmin",
-                            "fmod",      "maxmag",        "minmag",
-                            "hypot",     "nextafter",     "pow",
-                            "powr",      "remainder",     "half_divide",
-                            "half_powr", "native_divide", "native_powr",
-                            "step",      "fmax_common",   "fmin_common",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllFMinLike, ValidateOpenCLStdFMinLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "atan2",     "atan2pi",       "copysign",
+                             "fdim",      "fmax",          "fmin",
+                             "fmod",      "maxmag",        "minmag",
+                             "hypot",     "nextafter",     "pow",
+                             "powr",      "remainder",     "half_divide",
+                             "half_powr", "native_divide", "native_powr",
+                             "step",      "fmax_common",   "fmin_common",
+                         }));
 
 TEST_P(ValidateOpenCLStdFClampLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -2981,14 +2981,14 @@
                         "Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllFClampLike, ValidateOpenCLStdFClampLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "fma",
-                            "mad",
-                            "fclamp",
-                            "mix",
-                            "smoothstep",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllFClampLike, ValidateOpenCLStdFClampLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "fma",
+                             "mad",
+                             "fclamp",
+                             "mix",
+                             "smoothstep",
+                         }));
 
 TEST_P(ValidateOpenCLStdSAbsLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -3048,14 +3048,14 @@
                 ": expected types of all operands to be equal to Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllSAbsLike, ValidateOpenCLStdSAbsLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "s_abs",
-                            "clz",
-                            "ctz",
-                            "popcount",
-                            "u_abs",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllSAbsLike, ValidateOpenCLStdSAbsLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "s_abs",
+                             "clz",
+                             "ctz",
+                             "popcount",
+                             "u_abs",
+                         }));
 
 TEST_P(ValidateOpenCLStdUMinLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -3147,26 +3147,26 @@
                 ": expected types of all operands to be equal to Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllUMinLike, ValidateOpenCLStdUMinLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "s_max",
-                            "u_max",
-                            "s_min",
-                            "u_min",
-                            "s_abs_diff",
-                            "s_add_sat",
-                            "u_add_sat",
-                            "s_mul_hi",
-                            "rotate",
-                            "s_sub_sat",
-                            "u_sub_sat",
-                            "s_hadd",
-                            "u_hadd",
-                            "s_rhadd",
-                            "u_rhadd",
-                            "u_abs_diff",
-                            "u_mul_hi",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllUMinLike, ValidateOpenCLStdUMinLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "s_max",
+                             "u_max",
+                             "s_min",
+                             "u_min",
+                             "s_abs_diff",
+                             "s_add_sat",
+                             "u_add_sat",
+                             "s_mul_hi",
+                             "rotate",
+                             "s_sub_sat",
+                             "u_sub_sat",
+                             "s_hadd",
+                             "u_hadd",
+                             "s_rhadd",
+                             "u_rhadd",
+                             "u_abs_diff",
+                             "u_mul_hi",
+                         }));
 
 TEST_P(ValidateOpenCLStdUClampLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -3284,15 +3284,15 @@
                 ": expected types of all operands to be equal to Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllUClampLike, ValidateOpenCLStdUClampLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "s_clamp",
-                            "u_clamp",
-                            "s_mad_hi",
-                            "u_mad_sat",
-                            "s_mad_sat",
-                            "u_mad_hi",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllUClampLike, ValidateOpenCLStdUClampLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "s_clamp",
+                             "u_clamp",
+                             "s_mad_hi",
+                             "u_mad_sat",
+                             "s_mad_sat",
+                             "u_mad_hi",
+                         }));
 
 // -------------------------------------------------------------
 TEST_P(ValidateOpenCLStdUMul24Like, Success) {
@@ -3398,11 +3398,11 @@
                 ": expected types of all operands to be equal to Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllUMul24Like, ValidateOpenCLStdUMul24Like,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "s_mul24",
-                            "u_mul24",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllUMul24Like, ValidateOpenCLStdUMul24Like,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "s_mul24",
+                             "u_mul24",
+                         }));
 
 TEST_P(ValidateOpenCLStdUMad24Like, Success) {
   const std::string ext_inst_name = GetParam();
@@ -3533,11 +3533,11 @@
                 ": expected types of all operands to be equal to Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllUMad24Like, ValidateOpenCLStdUMad24Like,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "s_mad24",
-                            "u_mad24",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllUMad24Like, ValidateOpenCLStdUMad24Like,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "s_mad24",
+                             "u_mad24",
+                         }));
 
 TEST_F(ValidateExtInst, OpenCLStdCrossSuccess) {
   const std::string body = R"(
@@ -3662,11 +3662,11 @@
                         "Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllLengthLike, ValidateOpenCLStdLengthLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "length",
-                            "fast_length",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllLengthLike, ValidateOpenCLStdLengthLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "length",
+                             "fast_length",
+                         }));
 
 TEST_P(ValidateOpenCLStdDistanceLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -3751,11 +3751,11 @@
                         "expected operands P0 and P1 to be of the same type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllDistanceLike, ValidateOpenCLStdDistanceLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "distance",
-                            "fast_distance",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllDistanceLike, ValidateOpenCLStdDistanceLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "distance",
+                             "fast_distance",
+                         }));
 
 TEST_P(ValidateOpenCLStdNormalizeLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -3811,11 +3811,11 @@
                         "expected operand P type to be equal to Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllNormalizeLike, ValidateOpenCLStdNormalizeLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "normalize",
-                            "fast_normalize",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllNormalizeLike, ValidateOpenCLStdNormalizeLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "normalize",
+                             "fast_normalize",
+                         }));
 
 TEST_F(ValidateExtInst, OpenCLStdBitselectSuccess) {
   const std::string body = R"(
@@ -4208,15 +4208,15 @@
                 ": expected operand P data type to be 16-bit float scalar"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllVStoreHalfLike, ValidateOpenCLStdVStoreHalfLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "vstore_half",
-                            "vstore_half_r",
-                            "vstore_halfn",
-                            "vstore_halfn_r",
-                            "vstorea_halfn",
-                            "vstorea_halfn_r",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllVStoreHalfLike, ValidateOpenCLStdVStoreHalfLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "vstore_half",
+                             "vstore_half_r",
+                             "vstore_halfn",
+                             "vstore_halfn_r",
+                             "vstorea_halfn",
+                             "vstorea_halfn_r",
+                         }));
 
 TEST_P(ValidateOpenCLStdVLoadHalfLike, SuccessPhysical32) {
   const std::string ext_inst_name = GetParam();
@@ -4375,11 +4375,11 @@
                         "components of Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllVLoadHalfLike, ValidateOpenCLStdVLoadHalfLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "vload_halfn",
-                            "vloada_halfn",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllVLoadHalfLike, ValidateOpenCLStdVLoadHalfLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "vload_halfn",
+                             "vloada_halfn",
+                         }));
 
 TEST_F(ValidateExtInst, VLoadNSuccessFloatPhysical32) {
   std::ostringstream ss;
@@ -5299,12 +5299,12 @@
           ": expected data type of the pointer to be equal to Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllFractLike, ValidateOpenCLStdFractLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "fract",
-                            "modf",
-                            "sincos",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllFractLike, ValidateOpenCLStdFractLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "fract",
+                             "modf",
+                             "sincos",
+                         }));
 
 TEST_F(ValidateExtInst, OpenCLStdRemquoSuccess) {
   const std::string body = R"(
@@ -5518,11 +5518,11 @@
                         "number of components as Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllFrexpLike, ValidateOpenCLStdFrexpLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "frexp",
-                            "lgamma_r",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllFrexpLike, ValidateOpenCLStdFrexpLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "frexp",
+                             "lgamma_r",
+                         }));
 
 TEST_F(ValidateExtInst, OpenCLStdIlogbSuccess) {
   const std::string body = R"(
@@ -5716,12 +5716,12 @@
                         "components as Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllLdexpLike, ValidateOpenCLStdLdexpLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "ldexp",
-                            "pown",
-                            "rootn",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllLdexpLike, ValidateOpenCLStdLdexpLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "ldexp",
+                             "pown",
+                             "rootn",
+                         }));
 
 TEST_P(ValidateOpenCLStdUpsampleLike, Success) {
   const std::string ext_inst_name = GetParam();
@@ -5808,11 +5808,11 @@
                 "be half of the bit width of components of Result Type"));
 }
 
-INSTANTIATE_TEST_CASE_P(AllUpsampleLike, ValidateOpenCLStdUpsampleLike,
-                        ::testing::ValuesIn(std::vector<std::string>{
-                            "u_upsample",
-                            "s_upsample",
-                        }), );
+INSTANTIATE_TEST_SUITE_P(AllUpsampleLike, ValidateOpenCLStdUpsampleLike,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             "u_upsample",
+                             "s_upsample",
+                         }));
 
 }  // namespace
 }  // namespace val
diff --git a/test/val/val_extensions_test.cpp b/test/val/val_extensions_test.cpp
index 3d6466d..682c321 100644
--- a/test/val/val_extensions_test.cpp
+++ b/test/val/val_extensions_test.cpp
@@ -43,7 +43,7 @@
   return "Found unrecognized extension " + extension;
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ExpectSuccess, ValidateKnownExtensions,
     Values(
         // Match the order as published on the SPIR-V Registry.
@@ -64,9 +64,9 @@
         "SPV_GOOGLE_decorate_string", "SPV_GOOGLE_hlsl_functionality1",
         "SPV_NV_shader_subgroup_partitioned", "SPV_EXT_descriptor_indexing"));
 
-INSTANTIATE_TEST_CASE_P(FailSilently, ValidateUnknownExtensions,
-                        Values("ERROR_unknown_extension", "SPV_KHR_",
-                               "SPV_KHR_shader_ballot_ERROR"));
+INSTANTIATE_TEST_SUITE_P(FailSilently, ValidateUnknownExtensions,
+                         Values("ERROR_unknown_extension", "SPV_KHR_",
+                                "SPV_KHR_shader_ballot_ERROR"));
 
 TEST_P(ValidateKnownExtensions, ExpectSuccess) {
   const std::string extension = GetParam();
@@ -227,8 +227,8 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
 }
 
-INSTANTIATE_TEST_CASE_P(ExpectSuccess, ValidateAMDShaderBallotCapabilities,
-                        ValuesIn(AMDShaderBallotGroupInstructions()));
+INSTANTIATE_TEST_SUITE_P(ExpectSuccess, ValidateAMDShaderBallotCapabilities,
+                         ValuesIn(AMDShaderBallotGroupInstructions()));
 
 TEST_P(ValidateAMDShaderBallotCapabilities, ExpectFailure) {
   // Fail because the module does not specify the SPV_AMD_shader_ballot
@@ -251,8 +251,8 @@
                             " requires one of these capabilities: Groups")));
 }
 
-INSTANTIATE_TEST_CASE_P(ExpectFailure, ValidateAMDShaderBallotCapabilities,
-                        ValuesIn(AMDShaderBallotGroupInstructions()));
+INSTANTIATE_TEST_SUITE_P(ExpectFailure, ValidateAMDShaderBallotCapabilities,
+                         ValuesIn(AMDShaderBallotGroupInstructions()));
 
 struct ExtIntoCoreCase {
   const char* ext;
@@ -288,9 +288,12 @@
 
   CompileSuccessfully(code.c_str(), GetParam().env);
   if (GetParam().success) {
-    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(GetParam().env));
+    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(GetParam().env))
+        << getDiagnosticString();
   } else {
-    ASSERT_NE(SPV_SUCCESS, ValidateInstructions(GetParam().env));
+    ASSERT_NE(SPV_SUCCESS, ValidateInstructions(GetParam().env))
+        << " in " << spvTargetEnvDescription(GetParam().env) << ":\n"
+        << code;
     const std::string message = getDiagnosticString();
     if (spvIsVulkanEnv(GetParam().env)) {
       EXPECT_THAT(message, HasSubstr(std::string(GetParam().cap) +
@@ -305,7 +308,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     KHR_extensions, ValidateExtIntoCore,
     ValuesIn(std::vector<ExtIntoCoreCase>{
         // SPV_KHR_shader_draw_parameters became core SPIR-V 1.3
diff --git a/test/val/val_fixtures.h b/test/val/val_fixtures.h
index 73a0cc6..5635c78 100644
--- a/test/val/val_fixtures.h
+++ b/test/val/val_fixtures.h
@@ -21,6 +21,7 @@
 #include <string>
 
 #include "source/val/validation_state.h"
+#include "spirv-tools/libspirv.h"
 #include "test/test_fixture.h"
 #include "test/unit_spirv.h"
 
@@ -37,6 +38,11 @@
   // Returns the a spv_const_binary struct
   spv_const_binary get_const_binary();
 
+  // Assembles the given SPIR-V text, checks that it fails to assemble,
+  // and returns resulting diagnostic.  No internal state is updated.
+  std::string CompileFailure(std::string code,
+                             spv_target_env env = SPV_ENV_UNIVERSAL_1_0);
+
   // Checks that 'code' is valid SPIR-V text representation and stores the
   // binary version for further method calls.
   void CompileSuccessfully(std::string code,
@@ -56,6 +62,18 @@
   spv_result_t ValidateAndRetrieveValidationState(
       spv_target_env env = SPV_ENV_UNIVERSAL_1_0);
 
+  // Destroys the stored binary.
+  void DestroyBinary() {
+    spvBinaryDestroy(binary_);
+    binary_ = nullptr;
+  }
+
+  // Destroys the stored diagnostic.
+  void DestroyDiagnostic() {
+    spvDiagnosticDestroy(diagnostic_);
+    diagnostic_ = nullptr;
+  }
+
   std::string getDiagnosticString();
   spv_position_t getErrorPosition();
   spv_validator_options getValidatorOptions();
@@ -67,7 +85,7 @@
 };
 
 template <typename T>
-ValidateBase<T>::ValidateBase() : binary_(), diagnostic_() {
+ValidateBase<T>::ValidateBase() : binary_(nullptr), diagnostic_(nullptr) {
   // Initialize to default command line options. Different tests can then
   // specialize specific options as necessary.
   options_ = spvValidatorOptionsCreate();
@@ -83,21 +101,37 @@
   if (diagnostic_) {
     spvDiagnosticPrint(diagnostic_);
   }
-  spvDiagnosticDestroy(diagnostic_);
-  spvBinaryDestroy(binary_);
+  DestroyBinary();
+  DestroyDiagnostic();
   spvValidatorOptionsDestroy(options_);
 }
 
 template <typename T>
+std::string ValidateBase<T>::CompileFailure(std::string code,
+                                            spv_target_env env) {
+  spv_diagnostic diagnostic = nullptr;
+  EXPECT_NE(SPV_SUCCESS,
+            spvTextToBinary(ScopedContext(env).context, code.c_str(),
+                            code.size(), &binary_, &diagnostic));
+  std::string result(diagnostic->error);
+  spvDiagnosticDestroy(diagnostic);
+  return result;
+}
+
+template <typename T>
 void ValidateBase<T>::CompileSuccessfully(std::string code,
                                           spv_target_env env) {
+  DestroyBinary();
   spv_diagnostic diagnostic = nullptr;
-  ASSERT_EQ(SPV_SUCCESS,
-            spvTextToBinary(ScopedContext(env).context, code.c_str(),
-                            code.size(), &binary_, &diagnostic))
+  ScopedContext context(env);
+  auto status = spvTextToBinary(context.context, code.c_str(), code.size(),
+                                &binary_, &diagnostic);
+  EXPECT_EQ(SPV_SUCCESS, status)
       << "ERROR: " << diagnostic->error
       << "\nSPIR-V could not be compiled into binary:\n"
       << code;
+  ASSERT_EQ(SPV_SUCCESS, status);
+  spvDiagnosticDestroy(diagnostic);
 }
 
 template <typename T>
@@ -110,6 +144,14 @@
 
 template <typename T>
 spv_result_t ValidateBase<T>::ValidateInstructions(spv_target_env env) {
+  DestroyDiagnostic();
+  if (binary_ == nullptr) {
+    fprintf(stderr,
+            "ERROR: Attempting to validate a null binary, did you forget to "
+            "call CompileSuccessfully?");
+    fflush(stderr);
+  }
+  assert(binary_ != nullptr);
   return spvValidateWithOptions(ScopedContext(env).context, options_,
                                 get_const_binary(), &diagnostic_);
 }
@@ -117,6 +159,7 @@
 template <typename T>
 spv_result_t ValidateBase<T>::ValidateAndRetrieveValidationState(
     spv_target_env env) {
+  DestroyDiagnostic();
   return spvtools::val::ValidateBinaryAndKeepValidationState(
       ScopedContext(env).context, options_, get_const_binary()->code,
       get_const_binary()->wordCount, &diagnostic_, &vstate_);
diff --git a/test/val/val_function_test.cpp b/test/val/val_function_test.cpp
new file mode 100644
index 0000000..6c0e8a1
--- /dev/null
+++ b/test/val/val_function_test.cpp
@@ -0,0 +1,426 @@
+// Copyright (c) 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <sstream>
+#include <string>
+#include <tuple>
+
+#include "gmock/gmock.h"
+#include "test/test_fixture.h"
+#include "test/unit_spirv.h"
+#include "test/val/val_fixtures.h"
+
+namespace spvtools {
+namespace val {
+namespace {
+
+using ::testing::Combine;
+using ::testing::HasSubstr;
+using ::testing::Values;
+
+using ValidateFunctionCall = spvtest::ValidateBase<std::string>;
+
+std::string GenerateShader(const std::string& storage_class,
+                           const std::string& capabilities,
+                           const std::string& extensions) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability AtomicStorage
+)" + capabilities + R"(
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+)" +
+                      extensions + R"(
+OpMemoryModel Logical GLSL450
+OpName %var "var"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr = OpTypePointer )" + storage_class + R"( %int
+%caller_ty = OpTypeFunction %void
+%callee_ty = OpTypeFunction %void %ptr
+)";
+
+  if (storage_class != "Function") {
+    spirv += "%var = OpVariable %ptr " + storage_class;
+  }
+
+  spirv += R"(
+%caller = OpFunction %void None %caller_ty
+%1 = OpLabel
+)";
+
+  if (storage_class == "Function") {
+    spirv += "%var = OpVariable %ptr Function";
+  }
+
+  spirv += R"(
+%call = OpFunctionCall %void %callee %var
+OpReturn
+OpFunctionEnd
+%callee = OpFunction %void None %callee_ty
+%param = OpFunctionParameter %ptr
+%2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  return spirv;
+}
+
+std::string GenerateShaderParameter(const std::string& storage_class,
+                                    const std::string& capabilities,
+                                    const std::string& extensions) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability AtomicStorage
+)" + capabilities + R"(
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+)" +
+                      extensions + R"(
+OpMemoryModel Logical GLSL450
+OpName %p "p"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr = OpTypePointer )" + storage_class + R"( %int
+%func_ty = OpTypeFunction %void %ptr
+%caller = OpFunction %void None %func_ty
+%p = OpFunctionParameter %ptr
+%1 = OpLabel
+%call = OpFunctionCall %void %callee %p
+OpReturn
+OpFunctionEnd
+%callee = OpFunction %void None %func_ty
+%param = OpFunctionParameter %ptr
+%2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  return spirv;
+}
+
+std::string GenerateShaderAccessChain(const std::string& storage_class,
+                                      const std::string& capabilities,
+                                      const std::string& extensions) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability AtomicStorage
+)" + capabilities + R"(
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+)" +
+                      extensions + R"(
+OpMemoryModel Logical GLSL450
+OpName %var "var"
+OpName %gep "gep"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int2 = OpTypeVector %int 2
+%int_0 = OpConstant %int 0
+%ptr = OpTypePointer )" + storage_class + R"( %int2
+%ptr2 = OpTypePointer )" +
+                      storage_class + R"( %int
+%caller_ty = OpTypeFunction %void
+%callee_ty = OpTypeFunction %void %ptr2
+)";
+
+  if (storage_class != "Function") {
+    spirv += "%var = OpVariable %ptr " + storage_class;
+  }
+
+  spirv += R"(
+%caller = OpFunction %void None %caller_ty
+%1 = OpLabel
+)";
+
+  if (storage_class == "Function") {
+    spirv += "%var = OpVariable %ptr Function";
+  }
+
+  spirv += R"(
+%gep = OpAccessChain %ptr2 %var %int_0
+%call = OpFunctionCall %void %callee %gep
+OpReturn
+OpFunctionEnd
+%callee = OpFunction %void None %callee_ty
+%param = OpFunctionParameter %ptr2
+%2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  return spirv;
+}
+
+TEST_P(ValidateFunctionCall, VariableNoVariablePointers) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv = GenerateShader(storage_class, "", "");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "UniformConstant", "Function", "Private", "Workgroup", "AtomicCounter"};
+  bool valid =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+
+  CompileSuccessfully(spirv);
+  if (valid) {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    if (storage_class == "StorageBuffer") {
+      EXPECT_THAT(getDiagnosticString(),
+                  HasSubstr("StorageBuffer pointer operand 1[%var] requires a "
+                            "variable pointers capability"));
+    } else {
+      EXPECT_THAT(
+          getDiagnosticString(),
+          HasSubstr("Invalid storage class for pointer operand 1[%var]"));
+    }
+  }
+}
+
+TEST_P(ValidateFunctionCall, VariableVariablePointersStorageClass) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv = GenerateShader(
+      storage_class, "OpCapability VariablePointersStorageBuffer",
+      "OpExtension \"SPV_KHR_variable_pointers\"");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "UniformConstant", "Function",      "Private",
+      "Workgroup",       "StorageBuffer", "AtomicCounter"};
+  bool valid =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+
+  CompileSuccessfully(spirv);
+  if (valid) {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("Invalid storage class for pointer operand 1[%var]"));
+  }
+}
+
+TEST_P(ValidateFunctionCall, VariableVariablePointers) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv =
+      GenerateShader(storage_class, "OpCapability VariablePointers",
+                     "OpExtension \"SPV_KHR_variable_pointers\"");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "UniformConstant", "Function",      "Private",
+      "Workgroup",       "StorageBuffer", "AtomicCounter"};
+  bool valid =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+
+  CompileSuccessfully(spirv);
+  if (valid) {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("Invalid storage class for pointer operand 1[%var]"));
+  }
+}
+
+TEST_P(ValidateFunctionCall, ParameterNoVariablePointers) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv = GenerateShaderParameter(storage_class, "", "");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "UniformConstant", "Function", "Private", "Workgroup", "AtomicCounter"};
+  bool valid =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+
+  CompileSuccessfully(spirv);
+  if (valid) {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    if (storage_class == "StorageBuffer") {
+      EXPECT_THAT(getDiagnosticString(),
+                  HasSubstr("StorageBuffer pointer operand 1[%p] requires a "
+                            "variable pointers capability"));
+    } else {
+      EXPECT_THAT(getDiagnosticString(),
+                  HasSubstr("Invalid storage class for pointer operand 1[%p]"));
+    }
+  }
+}
+
+TEST_P(ValidateFunctionCall, ParameterVariablePointersStorageBuffer) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv = GenerateShaderParameter(
+      storage_class, "OpCapability VariablePointersStorageBuffer",
+      "OpExtension \"SPV_KHR_variable_pointers\"");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "UniformConstant", "Function",      "Private",
+      "Workgroup",       "StorageBuffer", "AtomicCounter"};
+  bool valid =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+
+  CompileSuccessfully(spirv);
+  if (valid) {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("Invalid storage class for pointer operand 1[%p]"));
+  }
+}
+
+TEST_P(ValidateFunctionCall, ParameterVariablePointers) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv =
+      GenerateShaderParameter(storage_class, "OpCapability VariablePointers",
+                              "OpExtension \"SPV_KHR_variable_pointers\"");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "UniformConstant", "Function",      "Private",
+      "Workgroup",       "StorageBuffer", "AtomicCounter"};
+  bool valid =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+
+  CompileSuccessfully(spirv);
+  if (valid) {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("Invalid storage class for pointer operand 1[%p]"));
+  }
+}
+
+TEST_P(ValidateFunctionCall, NonMemoryObjectDeclarationNoVariablePointers) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv = GenerateShaderAccessChain(storage_class, "", "");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "Function", "Private", "Workgroup", "AtomicCounter"};
+  bool valid_sc =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+
+  CompileSuccessfully(spirv);
+  spv_result_t expected_result =
+      storage_class == "UniformConstant" ? SPV_SUCCESS : SPV_ERROR_INVALID_ID;
+  EXPECT_EQ(expected_result, ValidateInstructions());
+  if (valid_sc) {
+    EXPECT_THAT(
+        getDiagnosticString(),
+        HasSubstr(
+            "Pointer operand 2[%gep] must be a memory object declaration"));
+  } else {
+    if (storage_class == "StorageBuffer") {
+      EXPECT_THAT(getDiagnosticString(),
+                  HasSubstr("StorageBuffer pointer operand 2[%gep] requires a "
+                            "variable pointers capability"));
+    } else if (storage_class != "UniformConstant") {
+      EXPECT_THAT(
+          getDiagnosticString(),
+          HasSubstr("Invalid storage class for pointer operand 2[%gep]"));
+    }
+  }
+}
+
+TEST_P(ValidateFunctionCall,
+       NonMemoryObjectDeclarationVariablePointersStorageBuffer) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv = GenerateShaderAccessChain(
+      storage_class, "OpCapability VariablePointersStorageBuffer",
+      "OpExtension \"SPV_KHR_variable_pointers\"");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "Function", "Private", "Workgroup", "StorageBuffer", "AtomicCounter"};
+  bool valid_sc =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+  bool validate =
+      storage_class == "StorageBuffer" || storage_class == "UniformConstant";
+
+  CompileSuccessfully(spirv);
+  if (validate) {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    if (valid_sc) {
+      EXPECT_THAT(
+          getDiagnosticString(),
+          HasSubstr(
+              "Pointer operand 2[%gep] must be a memory object declaration"));
+    } else {
+      EXPECT_THAT(
+          getDiagnosticString(),
+          HasSubstr("Invalid storage class for pointer operand 2[%gep]"));
+    }
+  }
+}
+
+TEST_P(ValidateFunctionCall, NonMemoryObjectDeclarationVariablePointers) {
+  const std::string storage_class = GetParam();
+
+  std::string spirv =
+      GenerateShaderAccessChain(storage_class, "OpCapability VariablePointers",
+                                "OpExtension \"SPV_KHR_variable_pointers\"");
+
+  const std::vector<std::string> valid_storage_classes = {
+      "Function", "Private", "Workgroup", "StorageBuffer", "AtomicCounter"};
+  bool valid_sc =
+      std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
+                storage_class) != valid_storage_classes.end();
+  bool validate = storage_class == "StorageBuffer" ||
+                  storage_class == "Workgroup" ||
+                  storage_class == "UniformConstant";
+
+  CompileSuccessfully(spirv);
+  if (validate) {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    if (valid_sc) {
+      EXPECT_THAT(
+          getDiagnosticString(),
+          HasSubstr(
+              "Pointer operand 2[%gep] must be a memory object declaration"));
+    } else {
+      EXPECT_THAT(
+          getDiagnosticString(),
+          HasSubstr("Invalid storage class for pointer operand 2[%gep]"));
+    }
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(StorageClass, ValidateFunctionCall,
+                         Values("UniformConstant", "Input", "Uniform", "Output",
+                                "Workgroup", "Private", "Function",
+                                "PushConstant", "Image", "StorageBuffer",
+                                "AtomicCounter"));
+}  // namespace
+}  // namespace val
+}  // namespace spvtools
diff --git a/test/val/val_id_test.cpp b/test/val/val_id_test.cpp
index a162648..73e2ce1 100644
--- a/test/val/val_id_test.cpp
+++ b/test/val/val_id_test.cpp
@@ -780,6 +780,8 @@
   // Runs spvValidate() on v, printing any errors via spvDiagnosticPrint().
   spv_result_t Val(const SpirvVector& v, const std::string& expected_err = "") {
     spv_const_binary_t cbinary{v.data(), v.size()};
+    spvDiagnosticDestroy(diagnostic_);
+    diagnostic_ = nullptr;
     const auto status =
         spvValidate(ScopedContext().context, &cbinary, &diagnostic_);
     if (status != SPV_SUCCESS) {
@@ -855,8 +857,8 @@
 // capability prohibits usage of signed integers, we can skip 8-bit integers
 // here since the purpose of these tests is to check the validity of
 // OpTypeArray, not OpTypeInt.
-INSTANTIATE_TEST_CASE_P(Widths, OpTypeArrayLengthTest,
-                        ValuesIn(std::vector<int>{16, 32, 64}));
+INSTANTIATE_TEST_SUITE_P(Widths, OpTypeArrayLengthTest,
+                         ValuesIn(std::vector<int>{16, 32, 64}));
 
 TEST_F(ValidateIdWithMessage, OpTypeArrayLengthNull) {
   std::string spirv = kGLSL450MemoryModel + R"(
@@ -2104,6 +2106,29 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateIdWithMessage, OpVariableContainsRayPayloadBoolGood) {
+  std::string spirv = R"(
+OpCapability RayTracingNV
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_NV_ray_tracing"
+OpMemoryModel Logical GLSL450
+%bool = OpTypeBool
+%PerRayData = OpTypeStruct %bool
+%_ptr_PerRayData = OpTypePointer RayPayloadNV %PerRayData
+%var = OpVariable %_ptr_PerRayData RayPayloadNV
+%void = OpTypeVoid
+%fnty = OpTypeFunction %void
+%main = OpFunction %void None %fnty
+%entry = OpLabel
+%load = OpLoad %PerRayData %var
+OpReturn
+OpFunctionEnd
+)";
+  CompileSuccessfully(spirv.c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
 TEST_F(ValidateIdWithMessage, OpVariablePointerNoVariablePointersBad) {
   const std::string spirv = R"(
 OpCapability Shader
@@ -2153,6 +2178,47 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateIdWithMessage, OpFunctionWithNonMemoryObject) {
+  // DXC generates code that looks like when given something like:
+  //   T t;
+  //   t.s.fn_1();
+  // This needs to be accepted before legalization takes place, so we
+  // will include it with the relaxed logical pointer.
+
+  const std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %1 "main"
+               OpSource HLSL 600
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %void = OpTypeVoid
+          %9 = OpTypeFunction %void
+  %_struct_5 = OpTypeStruct
+  %_struct_6 = OpTypeStruct %_struct_5
+%_ptr_Function__struct_6 = OpTypePointer Function %_struct_6
+%_ptr_Function__struct_5 = OpTypePointer Function %_struct_5
+         %23 = OpTypeFunction %void %_ptr_Function__struct_5
+          %1 = OpFunction %void None %9
+         %10 = OpLabel
+         %11 = OpVariable %_ptr_Function__struct_6 Function
+         %20 = OpAccessChain %_ptr_Function__struct_5 %11 %int_0
+         %21 = OpFunctionCall %void %12 %20
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %void None %23
+         %13 = OpFunctionParameter %_ptr_Function__struct_5
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto options = getValidatorOptions();
+  options->relax_logical_pointer = true;
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
 TEST_F(ValidateIdWithMessage,
        OpVariablePointerVariablePointersStorageBufferGood) {
   const std::string spirv = R"(
@@ -2249,6 +2315,7 @@
     *spirv << "OpCapability VariablePointers ";
     *spirv << "OpExtension \"SPV_KHR_variable_pointers\" ";
   }
+  *spirv << "OpExtension \"SPV_KHR_storage_buffer_storage_class\" ";
   *spirv << R"(
     OpMemoryModel Logical GLSL450
     OpEntryPoint GLCompute %main "main"
@@ -2257,12 +2324,12 @@
     %bool      = OpTypeBool
     %i32       = OpTypeInt 32 1
     %f32       = OpTypeFloat 32
-    %f32ptr    = OpTypePointer Uniform %f32
+    %f32ptr    = OpTypePointer StorageBuffer %f32
     %i         = OpConstant %i32 1
     %zero      = OpConstant %i32 0
     %float_1   = OpConstant %f32 1.0
-    %ptr1      = OpVariable %f32ptr Uniform
-    %ptr2      = OpVariable %f32ptr Uniform
+    %ptr1      = OpVariable %f32ptr StorageBuffer
+    %ptr2      = OpVariable %f32ptr StorageBuffer
   )";
   if (add_helper_function) {
     *spirv << R"(
@@ -2881,7 +2948,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpStore Object <id> '7[%7]' is not an object."));
+              HasSubstr("Operand 7[%7] requires a type"));
 }
 
 // TODO: enable when this bug is fixed:
@@ -3101,13 +3168,13 @@
 %7 = OpTypeFunction %1
 %8 = OpFunction %1 None %7
 %9 = OpLabel
-     OpCopyMemorySized %9 %6 %5 None
+     OpCopyMemorySized %5 %5 %5 None
      OpReturn
      OpFunctionEnd)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Target operand <id> '9[%9]' is not a pointer."));
+              HasSubstr("Target operand <id> '5[%uint_4]' is not a pointer."));
 }
 TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSourceBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
@@ -3893,7 +3960,7 @@
 }
 
 // Run tests for Access Chain Instructions.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     CheckAccessChainInstructions, AccessChainInstructionTest,
     ::testing::Values("OpAccessChain", "OpInBoundsAccessChain",
                       "OpPtrAccessChain", "OpInBoundsPtrAccessChain"));
@@ -4979,8 +5046,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpReturnValue Value <id> '5[%5]' does not represent a "
-                        "value."));
+              HasSubstr("Operand 5[%5] requires a type"));
 }
 
 TEST_F(ValidateIdWithMessage, OpReturnValueIsVoid) {
@@ -6137,20 +6203,22 @@
 %4 = OpTypeFunction %3
 %5 = OpFunction %1 None %2
 %6 = OpLabel
-%7 = OpFunctionCall %3 %8
+OpReturn
+%7 = OpLabel
+%8 = OpFunctionCall %3 %9
 OpUnreachable
 OpFunctionEnd
-%8 = OpFunction %3 None %4
-%9 = OpLabel
-OpReturnValue %7
+%9 = OpFunction %3 None %4
+%10 = OpLabel
+OpReturnValue %8
 OpFunctionEnd
 )";
 
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("ID 7[%7] defined in block 6[%6] does not dominate its "
-                        "use in block 9[%9]\n  %9 = OpLabel"));
+              HasSubstr("ID 8[%8] defined in block 7[%7] does not dominate its "
+                        "use in block 10[%10]\n  %10 = OpLabel"));
 }
 
 TEST_F(ValidateIdWithMessage, IdDefInUnreachableBlock2) {
diff --git a/test/val/val_image_test.cpp b/test/val/val_image_test.cpp
index 79aecb2..c2fcb4f 100644
--- a/test/val/val_image_test.cpp
+++ b/test/val/val_image_test.cpp
@@ -25,6 +25,7 @@
 namespace val {
 namespace {
 
+using ::testing::Eq;
 using ::testing::HasSubstr;
 using ::testing::Not;
 
@@ -54,9 +55,33 @@
     ss << "OpCapability SampledRect\n";
   }
 
+  // In 1.4, the entry point must list all module-scope variables used.  Just
+  // list all of them.
+  std::string interface_vars = (env != SPV_ENV_UNIVERSAL_1_4) ? "" :
+                                                              R"(
+%uniform_image_f32_1d_0001
+%uniform_image_f32_1d_0002_rgba32f
+%uniform_image_f32_2d_0001
+%uniform_image_f32_2d_0010
+%uniform_image_u32_2d_0001
+%uniform_image_u32_2d_0000
+%uniform_image_s32_3d_0001
+%uniform_image_f32_2d_0002
+%uniform_image_s32_2d_0002
+%uniform_image_f32_spd_0002
+%uniform_image_f32_3d_0111
+%uniform_image_f32_cube_0101
+%uniform_image_f32_cube_0102_rgba32f
+%uniform_sampler
+%private_image_u32_buffer_0002_r32ui
+%private_image_u32_spd_0002
+%private_image_f32_buffer_0002_r32ui
+)";
+
   ss << capabilities_and_extensions;
   ss << "OpMemoryModel Logical " << memory_model << "\n";
-  ss << "OpEntryPoint " << execution_model << " %main \"main\"\n";
+  ss << "OpEntryPoint " << execution_model
+     << " %main \"main\" " + interface_vars + "\n";
   if (execution_model == "Fragment") {
     ss << "OpExecutionMode %main OriginUpperLeft\n";
   }
@@ -79,6 +104,8 @@
 OpDecorate %uniform_image_s32_3d_0001 Binding 2
 OpDecorate %uniform_image_f32_2d_0002 DescriptorSet 1
 OpDecorate %uniform_image_f32_2d_0002 Binding 3
+OpDecorate %uniform_image_s32_2d_0002 DescriptorSet 1
+OpDecorate %uniform_image_s32_2d_0002 Binding 4
 OpDecorate %uniform_image_f32_spd_0002 DescriptorSet 2
 OpDecorate %uniform_image_f32_spd_0002 Binding 0
 OpDecorate %uniform_image_f32_3d_0111 DescriptorSet 2
@@ -222,6 +249,11 @@
 %uniform_image_f32_2d_0002 = OpVariable %ptr_image_f32_2d_0002 UniformConstant
 %type_sampled_image_f32_2d_0002 = OpTypeSampledImage %type_image_f32_2d_0002
 
+%type_image_s32_2d_0002 = OpTypeImage %s32 2D 0 0 0 2 Unknown
+%ptr_image_s32_2d_0002 = OpTypePointer UniformConstant %type_image_s32_2d_0002
+%uniform_image_s32_2d_0002 = OpVariable %ptr_image_s32_2d_0002 UniformConstant
+%type_sampled_image_s32_2d_0002 = OpTypeSampledImage %type_image_s32_2d_0002
+
 %type_image_f32_spd_0002 = OpTypeImage %f32 SubpassData 0 0 0 2 Unknown
 %ptr_image_f32_spd_0002 = OpTypePointer UniformConstant %type_image_f32_spd_0002
 %uniform_image_f32_spd_0002 = OpVariable %ptr_image_f32_spd_0002 UniformConstant
@@ -703,7 +735,7 @@
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 136[%136] cannot be a "
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 140[%140] cannot be a "
                                                "type"));
 }
 
@@ -2283,7 +2315,7 @@
 %img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
 %sampler = OpLoad %type_sampler %uniform_sampler
 %simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
-%res1 = OpImageFetch %f32vec4 %simg %u32vec2_01
+%res1 = OpImageFetch %f32vec4 %sampler %u32vec2_01
 )";
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
@@ -2292,6 +2324,21 @@
               HasSubstr("Expected Image to be of type OpTypeImage"));
 }
 
+TEST_F(ValidateImage, FetchSampledImageDirectly) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageFetch %f32vec4 %simg %u32vec2_01
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpSampledImage instruction must not appear as operand "
+                        "for OpImageFetch"));
+}
+
 TEST_F(ValidateImage, FetchNotSampled) {
   const std::string body = R"(
 %img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
@@ -2733,7 +2780,19 @@
 )";
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateImage, ReadNeedCapabilityStorageImageReadWithoutFormatVulkan) {
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
+%res1 = OpImageRead %u32vec4 %img %u32vec2_01
+)";
+
+  spv_target_env env = SPV_ENV_VULKAN_1_0;
+  CompileSuccessfully(GenerateShaderCode(body, "", "Fragment", env).c_str(),
+                      env);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(env));
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("Capability StorageImageReadWithoutFormat is required "
                         "to read storage image"));
@@ -2939,7 +2998,19 @@
 )";
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateImage, WriteNeedCapabilityStorageImageWriteWithoutFormatVulkan) {
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
+%res1 = OpImageWrite %img %u32vec2_01 %u32vec4_0123
+)";
+
+  spv_target_env env = SPV_ENV_VULKAN_1_0;
+  CompileSuccessfully(GenerateShaderCode(body, "", "Fragment", env).c_str(),
+                      env);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(env));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
@@ -3191,7 +3262,7 @@
 %img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
 %sampler = OpLoad %type_sampler %uniform_sampler
 %simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
-%res1 = OpImageQueryFormat %u32 %simg
+%res1 = OpImageQueryFormat %u32 %sampler
 )";
 
   CompileSuccessfully(GenerateKernelCode(body).c_str());
@@ -3227,7 +3298,7 @@
 %img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
 %sampler = OpLoad %type_sampler %uniform_sampler
 %simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
-%res1 = OpImageQueryOrder %u32 %simg
+%res1 = OpImageQueryOrder %u32 %sampler
 )";
 
   CompileSuccessfully(GenerateKernelCode(body).c_str());
@@ -3276,7 +3347,7 @@
 %img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
 %sampler = OpLoad %type_sampler %uniform_sampler
 %simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
-%res1 = OpImageQuerySizeLod %u32vec2 %simg %u32_1
+%res1 = OpImageQuerySizeLod %u32vec2 %sampler %u32_1
 )";
 
   CompileSuccessfully(GenerateKernelCode(body).c_str());
@@ -3285,6 +3356,21 @@
               HasSubstr("Expected Image to be of type OpTypeImage"));
 }
 
+TEST_F(ValidateImage, QuerySizeLodSampledImageDirectly) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageQuerySizeLod %u32vec2 %simg %u32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpSampledImage instruction must not appear as operand "
+                        "for OpImageQuerySizeLod"));
+}
+
 TEST_F(ValidateImage, QuerySizeLodWrongImageDim) {
   const std::string body = R"(
 %img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001
@@ -3348,7 +3434,7 @@
 %img = OpLoad %type_image_f32_2d_0010 %uniform_image_f32_2d_0010
 %sampler = OpLoad %type_sampler %uniform_sampler
 %simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
-%res1 = OpImageQuerySize %u32vec2 %simg
+%res1 = OpImageQuerySize %u32vec2 %sampler
 )";
 
   CompileSuccessfully(GenerateKernelCode(body).c_str());
@@ -3357,6 +3443,21 @@
               HasSubstr("Expected Image to be of type OpTypeImage"));
 }
 
+TEST_F(ValidateImage, QuerySizeSampledImageDirectly) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0010 %uniform_image_f32_2d_0010
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageQuerySize %u32vec2 %simg
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpSampledImage instruction must not appear as operand "
+                        "for OpImageQuerySize"));
+}
+
 TEST_F(ValidateImage, QuerySizeDimSubpassDataBad) {
   const std::string body = R"(
 %img = OpLoad %type_image_f32_spd_0002 %uniform_image_f32_spd_0002
@@ -3531,7 +3632,7 @@
 %img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
 %sampler = OpLoad %type_sampler %uniform_sampler
 %simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
-%res1 = OpImageQueryLevels %u32 %simg
+%res1 = OpImageQueryLevels %u32 %sampler
 )";
 
   CompileSuccessfully(GenerateKernelCode(body).c_str());
@@ -3540,6 +3641,21 @@
               HasSubstr("Expected Image to be of type OpTypeImage"));
 }
 
+TEST_F(ValidateImage, QueryLevelsSampledImageDirectly) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageQueryLevels %u32 %simg
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpSampledImage instruction must not appear as operand "
+                        "for OpImageQueryLevels"));
+}
+
 TEST_F(ValidateImage, QueryLevelsWrongDim) {
   const std::string body = R"(
 %img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001
@@ -4452,6 +4568,185 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
 }
 
+// This example used to cause a seg fault on OpReturnValue, verifying it doesn't
+// anymore.
+TEST_F(ValidateImage, Issue2463NoSegFault) {
+  const std::string spirv = R"(
+               OpCapability Linkage
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+          %8 = OpTypeImage %float 3D 0 0 0 1 Unknown
+%_ptr_UniformConstant_8 = OpTypePointer UniformConstant %8
+         %10 = OpTypeSampler
+%_ptr_UniformConstant_10 = OpTypePointer UniformConstant %10
+         %12 = OpTypeSampledImage %8
+         %13 = OpTypeFunction %12 %_ptr_UniformConstant_8 %_ptr_UniformConstant_10
+         %23 = OpFunction %12 None %13
+         %24 = OpFunctionParameter %_ptr_UniformConstant_8
+         %25 = OpFunctionParameter %_ptr_UniformConstant_10
+         %26 = OpLabel
+         %27 = OpLoad %8 %24
+         %28 = OpLoad %10 %25
+         %29 = OpSampledImage %12 %27 %28
+               OpReturnValue %29
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpSampledImage instruction must not appear as operand "
+                        "for OpReturnValue"));
+}
+
+TEST_F(ValidateImage, SignExtendV13Bad) {
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
+%res1 = OpImageRead %u32vec4 %img %u32vec2_01 SignExtend
+)";
+
+  EXPECT_THAT(CompileFailure(GenerateShaderCode(body, "", "Fragment",
+                                                SPV_ENV_UNIVERSAL_1_3)),
+              HasSubstr("Invalid image operand 'SignExtend'"));
+}
+
+TEST_F(ValidateImage, ZeroExtendV13Bad) {
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
+%res1 = OpImageRead %u32vec4 %img %u32vec2_01 ZeroExtend
+)";
+
+  EXPECT_THAT(CompileFailure(GenerateShaderCode(body, "", "Fragment",
+                                                SPV_ENV_UNIVERSAL_1_3)),
+              HasSubstr("Invalid image operand 'ZeroExtend'"));
+}
+
+TEST_F(ValidateImage, SignExtendScalarUIntTexelV14Good) {
+  // Unsigned int sampled type
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
+%res1 = OpImageRead %u32 %img %u32vec2_01 SignExtend
+)";
+  const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "Fragment", SPV_ENV_UNIVERSAL_1_4),
+      SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateImage, SignExtendScalarSIntTexelV14Good) {
+  // Signed int sampled type
+  const std::string body = R"(
+%img = OpLoad %type_image_s32_2d_0002 %uniform_image_s32_2d_0002
+%res1 = OpImageRead %s32 %img %u32vec2_01 SignExtend
+)";
+  const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "Fragment", SPV_ENV_UNIVERSAL_1_4),
+      SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateImage, SignExtendScalarVectorUIntTexelV14Good) {
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
+%res1 = OpImageRead %u32vec4 %img %u32vec2_01 SignExtend
+)";
+  const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "Fragment", SPV_ENV_UNIVERSAL_1_4),
+      SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateImage, SignExtendVectorSIntTexelV14Good) {
+  const std::string body = R"(
+%img = OpLoad %type_image_s32_2d_0002 %uniform_image_s32_2d_0002
+%res1 = OpImageRead %s32vec4 %img %u32vec2_01 SignExtend
+)";
+  const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "Fragment", SPV_ENV_UNIVERSAL_1_4),
+      SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+// No negative tests for SignExtend since we don't truly know the
+// texel format.
+
+TEST_F(ValidateImage, ZeroExtendScalarUIntTexelV14Good) {
+  // Unsigned int sampled type
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
+%res1 = OpImageRead %u32 %img %u32vec2_01 ZeroExtend
+)";
+  const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "Fragment", SPV_ENV_UNIVERSAL_1_4),
+      SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateImage, ZeroExtendScalarSIntTexelV14Good) {
+  // Zeroed int sampled type
+  const std::string body = R"(
+%img = OpLoad %type_image_s32_2d_0002 %uniform_image_s32_2d_0002
+%res1 = OpImageRead %s32 %img %u32vec2_01 ZeroExtend
+)";
+  const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "Fragment", SPV_ENV_UNIVERSAL_1_4),
+      SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateImage, ZeroExtendScalarVectorUIntTexelV14Good) {
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
+%res1 = OpImageRead %u32vec4 %img %u32vec2_01 ZeroExtend
+)";
+  const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "Fragment", SPV_ENV_UNIVERSAL_1_4),
+      SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateImage, ZeroExtendVectorSIntTexelV14Good) {
+  const std::string body = R"(
+%img = OpLoad %type_image_s32_2d_0002 %uniform_image_s32_2d_0002
+%res1 = OpImageRead %s32vec4 %img %u32vec2_01 ZeroExtend
+)";
+  const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, extra, "Fragment", SPV_ENV_UNIVERSAL_1_4),
+      SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+// No negative tests for ZeroExtend since we don't truly know the
+// texel format.
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_interfaces_test.cpp b/test/val/val_interfaces_test.cpp
index ce430f6..3410616 100644
--- a/test/val/val_interfaces_test.cpp
+++ b/test/val/val_interfaces_test.cpp
@@ -48,8 +48,9 @@
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Input variable id <5> is used by entry point 'func' id <1>, "
-                "but is not listed as an interface"));
+      HasSubstr(
+          "Interface variable id <5> is used by entry point 'func' id <1>, "
+          "but is not listed as an interface"));
 }
 
 TEST_F(ValidateInterfacesTest, EntryPointMissingOutput) {
@@ -74,8 +75,9 @@
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Output variable id <5> is used by entry point 'func' id <1>, "
-                "but is not listed as an interface"));
+      HasSubstr(
+          "Interface variable id <5> is used by entry point 'func' id <1>, "
+          "but is not listed as an interface"));
 }
 
 TEST_F(ValidateInterfacesTest, InterfaceMissingUseInSubfunction) {
@@ -105,8 +107,9 @@
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Input variable id <5> is used by entry point 'func' id <1>, "
-                "but is not listed as an interface"));
+      HasSubstr(
+          "Interface variable id <5> is used by entry point 'func' id <1>, "
+          "but is not listed as an interface"));
 }
 
 TEST_F(ValidateInterfacesTest, TwoEntryPointsOneFunction) {
@@ -132,8 +135,9 @@
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Input variable id <2> is used by entry point 'func2' id <1>, "
-                "but is not listed as an interface"));
+      HasSubstr(
+          "Interface variable id <2> is used by entry point 'func2' id <1>, "
+          "but is not listed as an interface"));
 }
 
 TEST_F(ValidateInterfacesTest, MissingInterfaceThroughInitializer) {
@@ -160,8 +164,239 @@
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Input variable id <6> is used by entry point 'func' id <1>, "
-                "but is not listed as an interface"));
+      HasSubstr(
+          "Interface variable id <6> is used by entry point 'func' id <1>, "
+          "but is not listed as an interface"));
+}
+
+TEST_F(ValidateInterfacesTest, NonUniqueInterfacesSPV1p3) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %var %var
+OpExecutionMode %main LocalSize 1 1 1
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint3 = OpTypeVector %uint 3
+%struct = OpTypeStruct %uint3
+%ptr_struct = OpTypePointer Input %struct
+%var = OpVariable %ptr_struct Input
+%func_ty = OpTypeFunction %void
+%main = OpFunction %void None %func_ty
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateInterfacesTest, NonUniqueInterfacesSPV1p4) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %var %var
+OpExecutionMode %main LocalSize 1 1 1
+OpName %main "main"
+OpName %var "var"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint3 = OpTypeVector %uint 3
+%struct = OpTypeStruct %uint3
+%ptr_struct = OpTypePointer Input %struct
+%var = OpVariable %ptr_struct Input
+%func_ty = OpTypeFunction %void
+%main = OpFunction %void None %func_ty
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Non-unique OpEntryPoint interface 2[%var] is disallowed"));
+}
+
+TEST_F(ValidateInterfacesTest, MissingGlobalVarSPV1p3) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint3 = OpTypeVector %uint 3
+%struct = OpTypeStruct %uint3
+%ptr_struct = OpTypePointer StorageBuffer %struct
+%var = OpVariable %ptr_struct StorageBuffer
+%func_ty = OpTypeFunction %void
+%main = OpFunction %void None %func_ty
+%1 = OpLabel
+%ld = OpLoad %struct %var
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateInterfacesTest, MissingGlobalVarSPV1p4) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpName %var "var"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint3 = OpTypeVector %uint 3
+%struct = OpTypeStruct %uint3
+%ptr_struct = OpTypePointer StorageBuffer %struct
+%var = OpVariable %ptr_struct StorageBuffer
+%func_ty = OpTypeFunction %void
+%main = OpFunction %void None %func_ty
+%1 = OpLabel
+%ld = OpLoad %struct %var
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Interface variable id <2> is used by entry point "
+                        "'main' id <1>, but is not listed as an interface"));
+}
+
+TEST_F(ValidateInterfacesTest, FunctionInterfaceVarSPV1p3) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %var
+OpExecutionMode %main LocalSize 1 1 1
+OpName %var "var"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint3 = OpTypeVector %uint 3
+%struct = OpTypeStruct %uint3
+%ptr_struct = OpTypePointer Function %struct
+%func_ty = OpTypeFunction %void
+%main = OpFunction %void None %func_ty
+%1 = OpLabel
+%var = OpVariable %ptr_struct Function
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpEntryPoint interfaces must be OpVariables with "
+                        "Storage Class of Input(1) or Output(3). Found Storage "
+                        "Class 7 for Entry Point id 1."));
+}
+
+TEST_F(ValidateInterfacesTest, FunctionInterfaceVarSPV1p4) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %var
+OpExecutionMode %main LocalSize 1 1 1
+OpName %var "var"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint3 = OpTypeVector %uint 3
+%struct = OpTypeStruct %uint3
+%ptr_struct = OpTypePointer Function %struct
+%func_ty = OpTypeFunction %void
+%main = OpFunction %void None %func_ty
+%1 = OpLabel
+%var = OpVariable %ptr_struct Function
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpEntryPoint interfaces should only list global variables"));
+}
+
+TEST_F(ValidateInterfacesTest, ModuleSPV1p3ValidateSPV1p4_NotAllUsedGlobals) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpName %var "var"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint3 = OpTypeVector %uint 3
+%struct = OpTypeStruct %uint3
+%ptr_struct = OpTypePointer StorageBuffer %struct
+%var = OpVariable %ptr_struct StorageBuffer
+%func_ty = OpTypeFunction %void
+%main = OpFunction %void None %func_ty
+%1 = OpLabel
+%ld = OpLoad %struct %var
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+}
+
+TEST_F(ValidateInterfacesTest, ModuleSPV1p3ValidateSPV1p4_DuplicateInterface) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %gid %gid
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %gid BuiltIn GlobalInvocationId
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int3 = OpTypeVector %int 3
+%ptr_input_int3 = OpTypePointer Input %int3
+%gid = OpVariable %ptr_input_int3 Input
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+}
+
+TEST_F(ValidateInterfacesTest, SPV14MultipleEntryPointsSameFunction) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main1" %gid
+OpEntryPoint GLCompute %main "main2" %gid
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %gid BuiltIn GlobalInvocationId
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int3 = OpTypeVector %int 3
+%ptr_input_int3 = OpTypePointer Input %int3
+%gid = OpVariable %ptr_input_int3 Input
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
 }
 
 }  // namespace
diff --git a/test/val/val_layout_test.cpp b/test/val/val_layout_test.cpp
index e502d8c..9d4491c 100644
--- a/test/val/val_layout_test.cpp
+++ b/test/val/val_layout_test.cpp
@@ -120,7 +120,7 @@
 static const int kRangeEnd = 1000;
 pred_type All = Range<0, kRangeEnd>();
 
-INSTANTIATE_TEST_CASE_P(InstructionsOrder,
+INSTANTIATE_TEST_SUITE_P(InstructionsOrder,
     ValidateLayout,
     ::testing::Combine(::testing::Range((int)0, (int)getInstructions().size()),
     // Note: Because of ID dependencies between instructions, some instructions
@@ -160,7 +160,7 @@
                     , std::make_tuple(std::string("%fLabel   = OpLabel")       , Equals<39>             , All)
                     , std::make_tuple(std::string("OpNop")                     , Equals<40>             , Range<40,kRangeEnd>())
                     , std::make_tuple(std::string("OpReturn ; %func2 return")  , Equals<41>             , All)
-    )),);
+    )));
 // clang-format on
 
 // Creates a new vector which removes the string if the substr is found in the
@@ -181,7 +181,7 @@
 }
 
 // This test will check the logical layout of a binary by removing each
-// instruction in the pair of the INSTANTIATE_TEST_CASE_P call and moving it in
+// instruction in the pair of the INSTANTIATE_TEST_SUITE_P call and moving it in
 // the SPIRV source formed by combining the vector "instructions".
 TEST_P(ValidateLayout, Layout) {
   int order;
diff --git a/test/val/val_literals_test.cpp b/test/val/val_literals_test.cpp
index cbdbdd1..6eadf32 100644
--- a/test/val/val_literals_test.cpp
+++ b/test/val/val_literals_test.cpp
@@ -85,6 +85,16 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateLiterals, InvalidInt) {
+  std::string str = GenerateShaderCode() + R"(
+%11 = OpTypeInt 32 90
+  )";
+  CompileSuccessfully(str);
+  EXPECT_EQ(SPV_ERROR_INVALID_VALUE, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpTypeInt has invalid signedness:"));
+}
+
 TEST_P(ValidateLiteralsShader, LiteralsShaderBad) {
   std::string str = GenerateShaderCode() + GetParam();
   std::string inst_id = "11";
@@ -99,7 +109,7 @@
                 "or sign extended when Signedness is 1"));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LiteralsShaderCases, ValidateLiteralsShader,
     ::testing::Values("%11 = OpConstant %int16  !0xFFFF0000",  // Sign bit is 0
                       "%11 = OpConstant %int16  !0x00008000",  // Sign bit is 1
@@ -132,7 +142,7 @@
                 "or sign extended when Signedness is 1"));
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     LiteralsKernelCases, ValidateLiteralsKernel,
     ::testing::Values("%2 = OpConstant %uint8  !0xABCDEF00",
                       "%2 = OpConstant %uint8  !0xABCDEFFF"));
diff --git a/test/val/val_logicals_test.cpp b/test/val/val_logicals_test.cpp
index f457887..b57c743 100644
--- a/test/val/val_logicals_test.cpp
+++ b/test/val/val_logicals_test.cpp
@@ -24,6 +24,7 @@
 namespace val {
 namespace {
 
+using ::testing::Eq;
 using ::testing::HasSubstr;
 using ::testing::Not;
 
@@ -144,6 +145,18 @@
 %boolvec3_tft = OpConstantComposite %boolvec3 %true %false %true
 %boolvec4_tftf = OpConstantComposite %boolvec4 %true %false %true %false
 
+%arr_u32_2 = OpTypeArray %u32 %u32_2
+%st_u32_u32 = OpTypeStruct %u32 %u32
+%mat_f32_2_2 = OpTypeMatrix %f32vec2 2
+
+%nul_arr_u32_2 = OpConstantNull %arr_u32_2
+%nul_st_u32_u32 = OpConstantNull %st_u32_u32
+%nul_mat_f32_2_2 = OpConstantNull %mat_f32_2_2
+
+%arr_u32_2_1_2 = OpConstantComposite %arr_u32_2 %u32_1 %u32_2
+%st_u32_u32_1_2 = OpConstantComposite %st_u32_u32 %u32_1 %u32_2
+%mat_f32_2_2_01_12 = OpConstantComposite %mat_f32_2_2 %f32vec2_01 %f32vec2_12
+
 %f32vec4ptr = OpTypePointer Function %f32vec4
 
 %main = OpFunction %void None %func
@@ -585,6 +598,20 @@
       HasSubstr("Expected scalar or vector type as Result Type: Select"));
 }
 
+TEST_F(ValidateLogicals, OpSelectWrongTypeIdV14) {
+  // In 1.4, the message changes to allow composites.
+  const std::string body = R"(
+%val1 = OpSelect %void %true %u32_0 %u32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str(), SPV_ENV_UNIVERSAL_1_4);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Expected scalar or composite type as Result Type: Select"));
+}
+
 TEST_F(ValidateLogicals, OpSelectPointerNoCapability) {
   const std::string body = R"(
 %x = OpVariable %f32vec4ptr Function
@@ -687,6 +714,111 @@
               HasSubstr("Expected both objects to be of Result Type: Select"));
 }
 
+TEST_F(ValidateLogicals, OpSelectArrayV13Bad) {
+  const std::string body = R"(
+%val1 = OpSelect %arr_u32_2 %true %nul_arr_u32_2 %arr_u32_2_1_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Expected scalar or vector type as Result Type: Select"));
+}
+
+TEST_F(ValidateLogicals, OpSelectArrayV13TargetV14Bad) {
+  const std::string body = R"(
+%val1 = OpSelect %arr_u32_2 %true %nul_arr_u32_2 %arr_u32_2_1_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected scalar or vector type as Result Type"));
+}
+
+TEST_F(ValidateLogicals, OpSelectArrayV14Good) {
+  const std::string body = R"(
+%val1 = OpSelect %arr_u32_2 %true %nul_arr_u32_2 %arr_u32_2_1_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str(), SPV_ENV_UNIVERSAL_1_4);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateLogicals, OpSelectStructV13Bad) {
+  const std::string body = R"(
+%val1 = OpSelect %st_u32_u32 %true %nul_st_u32_u32 %st_u32_u32_1_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Expected scalar or vector type as Result Type: Select"));
+}
+
+TEST_F(ValidateLogicals, OpSelectStructV13TargetV14Bad) {
+  const std::string body = R"(
+%val1 = OpSelect %st_u32_u32 %true %nul_st_u32_u32 %st_u32_u32_1_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected scalar or vector type as Result Type"));
+}
+
+TEST_F(ValidateLogicals, OpSelectStructV14Good) {
+  const std::string body = R"(
+%val1 = OpSelect %st_u32_u32 %true %nul_st_u32_u32 %st_u32_u32_1_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str(), SPV_ENV_UNIVERSAL_1_4);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateLogicals, OpSelectMatrixV13Bad) {
+  const std::string body = R"(
+%val1 = OpSelect %mat_f32_2_2 %true %nul_mat_f32_2_2 %mat_f32_2_2_01_12
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Expected scalar or vector type as Result Type: Select"));
+}
+
+TEST_F(ValidateLogicals, OpSelectMatrixV13TargetV14Bad) {
+  const std::string body = R"(
+%val1 = OpSelect %mat_f32_2_2 %true %nul_mat_f32_2_2 %mat_f32_2_2_01_12
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected scalar or vector type as Result Type"));
+}
+
+TEST_F(ValidateLogicals, OpSelectMatrixV14Good) {
+  const std::string body = R"(
+%val1 = OpSelect %mat_f32_2_2 %true %nul_mat_f32_2_2 %mat_f32_2_2_01_12
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str(), SPV_ENV_UNIVERSAL_1_4);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
 TEST_F(ValidateLogicals, OpIEqualSuccess) {
   const std::string body = R"(
 %val1 = OpIEqual %bool %u32_0 %s32_1
@@ -919,6 +1051,114 @@
                         "width: SGreaterThan"));
 }
 
+TEST_F(ValidateLogicals, PSBSelectSuccess) {
+  const std::string body = R"(
+OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability Int64
+OpCapability Shader
+OpExtension "SPV_EXT_physical_storage_buffer"
+OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %val1 AliasedPointerEXT
+%uint64 = OpTypeInt 64 0
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%pptr_f = OpTypePointer Function %ptr
+%void = OpTypeVoid
+%voidfn = OpTypeFunction %void
+%main = OpFunction %void None %voidfn
+%entry = OpLabel
+%val1 = OpVariable %pptr_f Function
+%val2 = OpLoad %ptr %val1
+%val3 = OpSelect %ptr %true %val2 %val2
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateLogicals, SelectVectorsScalarCondition) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%int4 = OpTypeVector %int 4
+%int4_0 = OpConstantNull %int4
+%true = OpConstantTrue %bool
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+%select = OpSelect %int4 %true %int4_0 %int4_0
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected vector sizes of Result Type and the "
+                        "condition to be equal: Select"));
+}
+
+TEST_F(ValidateLogicals, SelectVectorsScalarCondition1p4) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%int4 = OpTypeVector %int 4
+%int4_0 = OpConstantNull %int4
+%true = OpConstantTrue %bool
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+%select = OpSelect %int4 %true %int4_0 %int4_0
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+}
+
+TEST_F(ValidateLogicals, SelectVectorsVectorConditionMismatchedDimensions1p4) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%bool3 = OpTypeVector %bool 3
+%int = OpTypeInt 32 0
+%int4 = OpTypeVector %int 4
+%int4_0 = OpConstantNull %int4
+%bool3_null = OpConstantNull %bool3
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+%select = OpSelect %int4 %bool3_null %int4_0 %int4_0
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected vector sizes of Result Type and the "
+                        "condition to be equal: Select"));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_memory_test.cpp b/test/val/val_memory_test.cpp
index 296ea43..490ffa8 100644
--- a/test/val/val_memory_test.cpp
+++ b/test/val/val_memory_test.cpp
@@ -27,6 +27,7 @@
 
 using ::testing::Eq;
 using ::testing::HasSubstr;
+using ::testing::Values;
 
 using ValidateMemory = spvtest::ValidateBase<bool>;
 
@@ -138,7 +139,9 @@
 
 TEST_F(ValidateMemory, VulkanUniformConstantOnOpaqueResourceRuntimeArrayGood) {
   std::string spirv = R"(
+OpCapability RuntimeDescriptorArrayEXT
 OpCapability Shader
+OpExtension "SPV_EXT_descriptor_indexing"
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %func "func"
 OpExecutionMode %func OriginUpperLeft
@@ -447,12 +450,98 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr(
-          "OpVariable, <id> '5[%5]', has a disallowed initializer & storage "
-          "class combination.\nFrom WebGPU execution environment spec:\n"
-          "Variable declarations that include initializers must have one of "
-          "the following storage classes: Output, Private, or Function\n"
-          "  %5 = OpVariable %_ptr_Uniform_float Uniform %float_1\n"));
+      HasSubstr("OpVariable, <id> '5[%5]', has a disallowed initializer & "
+                "storage class combination.\nFrom WebGPU spec:\nVariable "
+                "declarations that include initializers must have one of the "
+                "following storage classes: Output, Private, or Function\n  %5 "
+                "= OpVariable %_ptr_Uniform_float Uniform %float_1\n"));
+}
+
+TEST_F(ValidateMemory, WebGPUOutputStorageClassWithoutInitializerBad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%float = OpTypeFloat 32
+%float_ptr = OpTypePointer Output %float
+%1 = OpVariable %float_ptr Output
+%void = OpTypeVoid
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpVariable, <id> '4[%4]', must have an initializer.\n"
+                "From WebGPU execution environment spec:\n"
+                "All variables in the following storage classes must have an "
+                "initializer: Output, Private, or Function\n"
+                "  %4 = OpVariable %_ptr_Output_float Output\n"));
+}
+
+TEST_F(ValidateMemory, WebGPUFunctionStorageClassWithoutInitializerBad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%float = OpTypeFloat 32
+%float_ptr = OpTypePointer Function %float
+%void = OpTypeVoid
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%1 = OpLabel
+%2 = OpVariable %float_ptr Function
+OpReturn
+OpFunctionEnd
+)";
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpVariable, <id> '7[%7]', must have an initializer.\n"
+                "From WebGPU execution environment spec:\n"
+                "All variables in the following storage classes must have an "
+                "initializer: Output, Private, or Function\n"
+                "  %7 = OpVariable %_ptr_Function_float Function\n"));
+}
+
+TEST_F(ValidateMemory, WebGPUPrivateStorageClassWithoutInitializerBad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%float = OpTypeFloat 32
+%float_ptr = OpTypePointer Private %float
+%1 = OpVariable %float_ptr Private
+%void = OpTypeVoid
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpVariable, <id> '4[%4]', must have an initializer.\n"
+                "From WebGPU execution environment spec:\n"
+                "All variables in the following storage classes must have an "
+                "initializer: Output, Private, or Function\n"
+                "  %4 = OpVariable %_ptr_Private_float Private\n"));
 }
 
 TEST_F(ValidateMemory, VulkanInitializerWithOutputStorageClassesGood) {
@@ -539,12 +628,11 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr(
-          "OpVariable, <id> '5[%5]', has a disallowed initializer & storage "
-          "class combination.\nFrom Vulkan spec, Appendix A:\n"
-          "Variable declarations that include initializers must have one of "
-          "the following storage classes: Output, Private, or Function\n  "
-          "%5 = OpVariable %_ptr_Input_float Input %float_1\n"));
+      HasSubstr("OpVariable, <id> '5[%5]', has a disallowed initializer & "
+                "storage class combination.\nFrom Vulkan spec:\nVariable "
+                "declarations that include initializers must have one of the "
+                "following storage classes: Output, Private, or Function\n  %5 "
+                "= OpVariable %_ptr_Input_float Input %float_1\n"));
 }
 
 TEST_F(ValidateMemory, ArrayLenCorrectResultType) {
@@ -1255,32 +1343,6 @@
                 "VulkanMemoryModelDeviceScopeKHR capability"));
 }
 
-TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemoryGood1) {
-  const std::string spirv = R"(
-OpCapability Shader
-OpCapability VulkanMemoryModelKHR
-OpCapability VulkanMemoryModelDeviceScopeKHR
-OpCapability Linkage
-OpExtension "SPV_KHR_vulkan_memory_model"
-OpMemoryModel Logical VulkanKHR
-%void = OpTypeVoid
-%int = OpTypeInt 32 0
-%device = OpConstant %int 1
-%int_ptr_ssbo = OpTypePointer StorageBuffer %int
-%var1 = OpVariable %int_ptr_ssbo StorageBuffer
-%var2 = OpVariable %int_ptr_ssbo StorageBuffer
-%voidfn = OpTypeFunction %void
-%func = OpFunction %void None %voidfn
-%entry = OpLabel
-OpCopyMemory %var1 %var2 MakePointerAvailableKHR|NonPrivatePointerKHR %device
-OpReturn
-OpFunctionEnd
-)";
-
-  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
-  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
-}
-
 TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemoryGood2) {
   const std::string spirv = R"(
 OpCapability Shader
@@ -1335,6 +1397,138 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
 }
 
+TEST_F(ValidateMemory, VulkanMemoryModelCopyMemoryTwoAccessAvVisBadBinaryV13) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2
+  MakePointerAvailableKHR|NonPrivatePointerKHR %device
+  MakePointerVisibleKHR|NonPrivatePointerKHR %device
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "with two memory access operands requires SPIR-V 1.4 or later"));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelCopyMemoryTwoAccessAvVisGood) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2
+  MakePointerAvailableKHR|NonPrivatePointerKHR %device
+  MakePointerVisibleKHR|NonPrivatePointerKHR %device
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelCopyMemoryTwoAccessFirstWithAvBad) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2
+  MakePointerAvailableKHR|NonPrivatePointerKHR %device
+  MakePointerAvailableKHR|NonPrivatePointerKHR %device
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Source memory access must not include MakePointerAvailableKHR\n"
+          "  OpCopyMemory %5 %6 MakePointerAvailableKHR|NonPrivatePointerKHR"
+          " %uint_1 MakePointerAvailableKHR|NonPrivatePointerKHR %uint_1"));
+}
+
+TEST_F(ValidateMemory, VulkanMemoryModelCopyMemoryTwoAccessSecondWithVisBad) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpCapability Linkage
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%device = OpConstant %int 1
+%int_ptr_ssbo = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %int_ptr_ssbo StorageBuffer
+%var2 = OpVariable %int_ptr_ssbo StorageBuffer
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2
+  MakePointerVisibleKHR|NonPrivatePointerKHR %device
+  MakePointerVisibleKHR|NonPrivatePointerKHR %device
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Target memory access must not include MakePointerVisibleKHR\n"
+          "  OpCopyMemory %5 %6 MakePointerVisibleKHR|NonPrivatePointerKHR"
+          " %uint_1 MakePointerVisibleKHR|NonPrivatePointerKHR %uint_1"));
+}
+
 TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemorySizedBad1) {
   const std::string spirv = R"(
 OpCapability Shader
@@ -1513,6 +1707,1842 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
 }
 
+TEST_F(ValidateMemory, ArrayLengthStructIsLabel) {
+  const std::string spirv = R"(
+OpCapability Tessellation
+OpMemoryModel Logical GLSL450
+OpName %20 "incorrect"
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%uint = OpTypeInt 32 0
+%4 = OpFunction %void None %3
+%20 = OpLabel
+%24 = OpArrayLength %uint %20 0
+%25 = OpLoad %v4float %24
+OpReturnValue %25
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Operand 1[%incorrect] requires a type"));
+}
+
+TEST_F(ValidateMemory, PSBLoadAlignedSuccess) {
+  const std::string body = R"(
+OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability Int64
+OpCapability Shader
+OpExtension "SPV_EXT_physical_storage_buffer"
+OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %val1 AliasedPointerEXT
+%uint64 = OpTypeInt 64 0
+%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%pptr_f = OpTypePointer Function %ptr
+%void = OpTypeVoid
+%voidfn = OpTypeFunction %void
+%main = OpFunction %void None %voidfn
+%entry = OpLabel
+%val1 = OpVariable %pptr_f Function
+%val2 = OpLoad %ptr %val1
+%val3 = OpLoad %uint64 %val2 Aligned 8
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateMemory, PSBLoadAlignedMissing) {
+  const std::string body = R"(
+OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability Int64
+OpCapability Shader
+OpExtension "SPV_EXT_physical_storage_buffer"
+OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %val1 AliasedPointerEXT
+%uint64 = OpTypeInt 64 0
+%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%pptr_f = OpTypePointer Function %ptr
+%void = OpTypeVoid
+%voidfn = OpTypeFunction %void
+%main = OpFunction %void None %voidfn
+%entry = OpLabel
+%val1 = OpVariable %pptr_f Function
+%val2 = OpLoad %ptr %val1
+%val3 = OpLoad %uint64 %val2
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Memory accesses with PhysicalStorageBufferEXT must use Aligned"));
+}
+
+TEST_F(ValidateMemory, PSBStoreAlignedSuccess) {
+  const std::string body = R"(
+OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability Int64
+OpCapability Shader
+OpExtension "SPV_EXT_physical_storage_buffer"
+OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %val1 AliasedPointerEXT
+%uint64 = OpTypeInt 64 0
+%u64_1 = OpConstant %uint64 1
+%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%pptr_f = OpTypePointer Function %ptr
+%void = OpTypeVoid
+%voidfn = OpTypeFunction %void
+%main = OpFunction %void None %voidfn
+%entry = OpLabel
+%val1 = OpVariable %pptr_f Function
+%val2 = OpLoad %ptr %val1
+OpStore %val2 %u64_1 Aligned 8
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateMemory, PSBStoreAlignedMissing) {
+  const std::string body = R"(
+OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability Int64
+OpCapability Shader
+OpExtension "SPV_EXT_physical_storage_buffer"
+OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %val1 AliasedPointerEXT
+%uint64 = OpTypeInt 64 0
+%u64_1 = OpConstant %uint64 1
+%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%pptr_f = OpTypePointer Function %ptr
+%void = OpTypeVoid
+%voidfn = OpTypeFunction %void
+%main = OpFunction %void None %voidfn
+%entry = OpLabel
+%val1 = OpVariable %pptr_f Function
+%val2 = OpLoad %ptr %val1
+OpStore %val2 %u64_1 None
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Memory accesses with PhysicalStorageBufferEXT must use Aligned"));
+}
+
+TEST_F(ValidateMemory, PSBVariable) {
+  const std::string body = R"(
+OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability Int64
+OpCapability Shader
+OpExtension "SPV_EXT_physical_storage_buffer"
+OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %val1 AliasedPointerEXT
+%uint64 = OpTypeInt 64 0
+%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%val1 = OpVariable %ptr PhysicalStorageBufferEXT
+%void = OpTypeVoid
+%voidfn = OpTypeFunction %void
+%main = OpFunction %void None %voidfn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("PhysicalStorageBufferEXT must not be used with OpVariable"));
+}
+
+std::string GenCoopMatLoadStoreShader(const std::string& storeMemoryAccess,
+                                      const std::string& loadMemoryAccess) {
+  std::string s = R"(
+OpCapability Shader
+OpCapability GroupNonUniform
+OpCapability VulkanMemoryModelKHR
+OpCapability CooperativeMatrixNV
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpExtension "SPV_NV_cooperative_matrix"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint GLCompute %4 "main" %11 %21
+OpExecutionMode %4 LocalSize 1 1 1
+OpDecorate %11 BuiltIn SubgroupId
+OpDecorate %21 BuiltIn WorkgroupId
+OpDecorate %74 ArrayStride 4
+OpMemberDecorate %75 0 Offset 0
+OpDecorate %75 Block
+OpDecorate %77 DescriptorSet 0
+OpDecorate %77 Binding 0
+OpDecorate %92 ArrayStride 4
+OpMemberDecorate %93 0 Offset 0
+OpDecorate %93 Block
+OpDecorate %95 DescriptorSet 0
+OpDecorate %95 Binding 1
+OpDecorate %102 ArrayStride 4
+OpMemberDecorate %103 0 Offset 0
+OpDecorate %103 Block
+OpDecorate %105 DescriptorSet 0
+OpDecorate %105 Binding 2
+OpDecorate %117 ArrayStride 4
+OpMemberDecorate %118 0 Offset 0
+OpDecorate %118 Block
+OpDecorate %120 DescriptorSet 0
+OpDecorate %120 Binding 3
+OpDecorate %123 SpecId 2
+OpDecorate %124 SpecId 3
+OpDecorate %125 SpecId 4
+OpDecorate %126 SpecId 5
+OpDecorate %127 SpecId 0
+OpDecorate %128 SpecId 1
+OpDecorate %129 BuiltIn WorkgroupSize
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%6 = OpTypeInt 32 0
+%7 = OpTypeVector %6 2
+%8 = OpTypePointer Function %7
+%10 = OpTypePointer Input %6
+%11 = OpVariable %10 Input
+%13 = OpConstant %6 2
+%19 = OpTypeVector %6 3
+%20 = OpTypePointer Input %19
+%21 = OpVariable %20 Input
+%27 = OpConstantComposite %7 %13 %13
+%31 = OpTypePointer Function %6
+%33 = OpConstant %6 1024
+%34 = OpConstant %6 1
+%38 = OpConstant %6 8
+%39 = OpConstant %6 0
+%68 = OpTypeFloat 32
+%69 = OpConstant %6 16
+%70 = OpConstant %6 3
+%71 = OpTypeCooperativeMatrixNV %68 %70 %69 %38
+%72 = OpTypePointer Function %71
+%74 = OpTypeRuntimeArray %68
+%75 = OpTypeStruct %74
+%76 = OpTypePointer StorageBuffer %75
+%77 = OpVariable %76 StorageBuffer
+%78 = OpTypeInt 32 1
+%79 = OpConstant %78 0
+%81 = OpConstant %6 5
+%82 = OpTypePointer StorageBuffer %68
+%84 = OpConstant %6 64
+%85 = OpTypeBool
+%86 = OpConstantFalse %85
+%88 = OpTypePointer Private %71
+%89 = OpVariable %88 Private
+%92 = OpTypeRuntimeArray %68
+%93 = OpTypeStruct %92
+%94 = OpTypePointer StorageBuffer %93
+%95 = OpVariable %94 StorageBuffer
+%99 = OpVariable %88 Private
+%102 = OpTypeRuntimeArray %68
+%103 = OpTypeStruct %102
+%104 = OpTypePointer StorageBuffer %103
+%105 = OpVariable %104 StorageBuffer
+%109 = OpVariable %88 Private
+%111 = OpVariable %88 Private
+%112 = OpSpecConstantOp %6 CooperativeMatrixLengthNV %71
+%113 = OpSpecConstantOp %78 IAdd %112 %79
+%117 = OpTypeRuntimeArray %68
+%118 = OpTypeStruct %117
+%119 = OpTypePointer StorageBuffer %118
+%120 = OpVariable %119 StorageBuffer
+%123 = OpSpecConstant %78 1
+%124 = OpSpecConstant %78 1
+%125 = OpSpecConstant %78 1
+%126 = OpSpecConstant %78 1
+%127 = OpSpecConstant %6 1
+%128 = OpSpecConstant %6 1
+%129 = OpSpecConstantComposite %19 %127 %128 %34
+%4 = OpFunction %2 None %3
+%5 = OpLabel
+%9 = OpVariable %8 Function
+%18 = OpVariable %8 Function
+%32 = OpVariable %31 Function
+%44 = OpVariable %31 Function
+%52 = OpVariable %31 Function
+%60 = OpVariable %31 Function
+%73 = OpVariable %72 Function
+%91 = OpVariable %72 Function
+%101 = OpVariable %72 Function
+%12 = OpLoad %6 %11
+%14 = OpUMod %6 %12 %13
+%15 = OpLoad %6 %11
+%16 = OpUDiv %6 %15 %13
+%17 = OpCompositeConstruct %7 %14 %16
+OpStore %9 %17
+%22 = OpLoad %19 %21
+%23 = OpVectorShuffle %7 %22 %22 0 1
+%24 = OpCompositeExtract %6 %23 0
+%25 = OpCompositeExtract %6 %23 1
+%26 = OpCompositeConstruct %7 %24 %25
+%28 = OpIMul %7 %26 %27
+%29 = OpLoad %7 %9
+%30 = OpIAdd %7 %28 %29
+OpStore %18 %30
+%35 = OpAccessChain %31 %18 %34
+%36 = OpLoad %6 %35
+%37 = OpIMul %6 %33 %36
+%40 = OpAccessChain %31 %18 %39
+%41 = OpLoad %6 %40
+%42 = OpIMul %6 %38 %41
+%43 = OpIAdd %6 %37 %42
+OpStore %32 %43
+%45 = OpAccessChain %31 %18 %34
+%46 = OpLoad %6 %45
+%47 = OpIMul %6 %33 %46
+%48 = OpAccessChain %31 %18 %39
+%49 = OpLoad %6 %48
+%50 = OpIMul %6 %38 %49
+%51 = OpIAdd %6 %47 %50
+OpStore %44 %51
+%53 = OpAccessChain %31 %18 %34
+%54 = OpLoad %6 %53
+%55 = OpIMul %6 %33 %54
+%56 = OpAccessChain %31 %18 %39
+%57 = OpLoad %6 %56
+%58 = OpIMul %6 %38 %57
+%59 = OpIAdd %6 %55 %58
+OpStore %52 %59
+%61 = OpAccessChain %31 %18 %34
+%62 = OpLoad %6 %61
+%63 = OpIMul %6 %33 %62
+%64 = OpAccessChain %31 %18 %39
+%65 = OpLoad %6 %64
+%66 = OpIMul %6 %38 %65
+%67 = OpIAdd %6 %63 %66
+OpStore %60 %67
+%80 = OpLoad %6 %32
+%83 = OpAccessChain %82 %77 %79 %80
+%87 = OpCooperativeMatrixLoadNV %71 %83 %84 %86 )" +
+                  loadMemoryAccess + R"( %81
+OpStore %73 %87
+%90 = OpLoad %71 %73
+OpStore %89 %90
+%96 = OpLoad %6 %44
+%97 = OpAccessChain %82 %95 %79 %96
+%98 = OpCooperativeMatrixLoadNV %71 %97 %84 %86 MakePointerVisibleKHR|NonPrivatePointerKHR %81
+OpStore %91 %98
+%100 = OpLoad %71 %91
+OpStore %99 %100
+%106 = OpLoad %6 %52
+%107 = OpAccessChain %82 %105 %79 %106
+%108 = OpCooperativeMatrixLoadNV %71 %107 %84 %86 MakePointerVisibleKHR|NonPrivatePointerKHR %81
+OpStore %101 %108
+%110 = OpLoad %71 %101
+OpStore %109 %110
+%114 = OpConvertSToF %68 %113
+%115 = OpCompositeConstruct %71 %114
+OpStore %111 %115
+%116 = OpLoad %71 %111
+%121 = OpLoad %6 %60
+%122 = OpAccessChain %82 %120 %79 %121
+OpCooperativeMatrixStoreNV %122 %116 %84 %86 )" + storeMemoryAccess + R"( %81
+OpReturn
+OpFunctionEnd
+)";
+
+  return s;
+}
+
+TEST_F(ValidateMemory, CoopMatLoadStoreSuccess) {
+  std::string spirv =
+      GenCoopMatLoadStoreShader("MakePointerAvailableKHR|NonPrivatePointerKHR",
+                                "MakePointerVisibleKHR|NonPrivatePointerKHR");
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+}
+
+TEST_F(ValidateMemory, CoopMatStoreMemoryAccessFail) {
+  std::string spirv =
+      GenCoopMatLoadStoreShader("MakePointerVisibleKHR|NonPrivatePointerKHR",
+                                "MakePointerVisibleKHR|NonPrivatePointerKHR");
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("MakePointerVisibleKHR cannot be used with OpStore"));
+}
+
+TEST_F(ValidateMemory, CoopMatLoadMemoryAccessFail) {
+  std::string spirv =
+      GenCoopMatLoadStoreShader("MakePointerAvailableKHR|NonPrivatePointerKHR",
+                                "MakePointerAvailableKHR|NonPrivatePointerKHR");
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("MakePointerAvailableKHR cannot be used with OpLoad"));
+}
+
+TEST_F(ValidateMemory, CoopMatInvalidStorageClassFail) {
+  const std::string body =
+      R"(
+OpCapability Shader
+OpCapability Float16
+OpCapability CooperativeMatrixNV
+OpExtension "SPV_NV_cooperative_matrix"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%f16 = OpTypeFloat 16
+%u32 = OpTypeInt 32 0
+
+%u32_8 = OpConstant %u32 8
+%subgroup = OpConstant %u32 3
+
+%f16mat = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_8 %u32_8
+
+%str = OpTypeStruct %f16mat
+%str_ptr = OpTypePointer Workgroup %str
+%sh = OpVariable %str_ptr Workgroup
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+
+OpReturn
+OpFunctionEnd)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Cooperative matrix types (or types containing them) can only be "
+          "allocated in Function or Private storage classes or as function "
+          "parameters"));
+}
+
+TEST_F(ValidateMemory, CoopMatMatrixLengthResultTypeBad) {
+  const std::string body =
+      R"(
+OpCapability Shader
+OpCapability Float16
+OpCapability CooperativeMatrixNV
+OpExtension "SPV_NV_cooperative_matrix"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%f16 = OpTypeFloat 16
+%u32 = OpTypeInt 32 0
+%i32 = OpTypeInt 32 1
+
+%u32_8 = OpConstant %u32 8
+%subgroup = OpConstant %u32 3
+
+%f16mat = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_8 %u32_8
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+
+%1 = OpCooperativeMatrixLengthNV %i32 %f16mat
+
+OpReturn
+OpFunctionEnd)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("The Result Type of OpCooperativeMatrixLengthNV <id> "
+                "'11[%11]' must be OpTypeInt with width 32 and signedness 0"));
+}
+
+TEST_F(ValidateMemory, CoopMatMatrixLengthOperandTypeBad) {
+  const std::string body =
+      R"(
+OpCapability Shader
+OpCapability Float16
+OpCapability CooperativeMatrixNV
+OpExtension "SPV_NV_cooperative_matrix"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%f16 = OpTypeFloat 16
+%u32 = OpTypeInt 32 0
+%i32 = OpTypeInt 32 1
+
+%u32_8 = OpConstant %u32 8
+%subgroup = OpConstant %u32 3
+
+%f16mat = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_8 %u32_8
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+
+%1 = OpCooperativeMatrixLengthNV %u32 %u32
+
+OpReturn
+OpFunctionEnd)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("The type in OpCooperativeMatrixLengthNV <id> '5[%uint]' "
+                "must be OpTypeCooperativeMatrixNV"));
+}
+
+TEST_F(ValidateMemory, CoopMatMatrixLengthGood) {
+  const std::string body =
+      R"(
+OpCapability Shader
+OpCapability Float16
+OpCapability CooperativeMatrixNV
+OpExtension "SPV_NV_cooperative_matrix"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%f16 = OpTypeFloat 16
+%u32 = OpTypeInt 32 0
+%i32 = OpTypeInt 32 1
+
+%u32_8 = OpConstant %u32 8
+%subgroup = OpConstant %u32 3
+
+%f16mat = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_8 %u32_8
+
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+
+%1 = OpCooperativeMatrixLengthNV %u32 %f16mat
+
+OpReturn
+OpFunctionEnd)";
+
+  CompileSuccessfully(body.c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateMemory, VulkanRTAOutsideOfStructBad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%sampler_t = OpTypeSampler
+%array_t = OpTypeRuntimeArray %sampler_t
+%array_ptr = OpTypePointer UniformConstant %array_t
+%2 = OpVariable %array_ptr UniformConstant
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpVariable, <id> '5[%5]', is attempting to create memory for an "
+          "illegal type, OpTypeRuntimeArray.\nFor Vulkan OpTypeRuntimeArray "
+          "can only appear as the final member of an OpTypeStruct, thus cannot "
+          "be instantiated via OpVariable\n  %5 = OpVariable "
+          "%_ptr_UniformConstant__runtimearr_2 UniformConstant\n"));
+}
+
+TEST_F(ValidateMemory, WebGPURTAOutsideOfStructBad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%sampler_t = OpTypeSampler
+%array_t = OpTypeRuntimeArray %sampler_t
+%array_ptr = OpTypePointer UniformConstant %array_t
+%2 = OpVariable %array_ptr UniformConstant
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpVariable, <id> '5[%5]', is attempting to create memory for an "
+          "illegal type, OpTypeRuntimeArray.\nFor WebGPU OpTypeRuntimeArray "
+          "can only appear as the final member of an OpTypeStruct, thus cannot "
+          "be instantiated via OpVariable\n  %5 = OpVariable "
+          "%_ptr_UniformConstant__runtimearr_2 UniformConstant\n"));
+}
+
+TEST_F(ValidateMemory, VulkanRTAOutsideOfStructWithRuntimeDescriptorArrayGood) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability RuntimeDescriptorArrayEXT
+OpExtension "SPV_EXT_descriptor_indexing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%sampler_t = OpTypeSampler
+%array_t = OpTypeRuntimeArray %sampler_t
+%array_sb_ptr = OpTypePointer StorageBuffer %array_t
+%2 = OpVariable %array_sb_ptr StorageBuffer
+%array_uc_ptr = OpTypePointer UniformConstant %array_t
+%3 = OpVariable %array_uc_ptr UniformConstant
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+}
+
+TEST_F(
+    ValidateMemory,
+    VulkanRTAOutsideOfStructWithRuntimeDescriptorArrayAndWrongStorageClassBad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability RuntimeDescriptorArrayEXT
+OpExtension "SPV_EXT_descriptor_indexing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%uint_t = OpTypeInt 32 0
+%array_t = OpTypeRuntimeArray %uint_t
+%array_ptr = OpTypePointer Workgroup %array_t
+%2 = OpVariable %array_ptr Workgroup
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("For Vulkan with RuntimeDescriptorArrayEXT, a variable "
+                "containing OpTypeRuntimeArray must have storage class of "
+                "StorageBuffer, Uniform, or UniformConstant.\n  %5 = "
+                "OpVariable %_ptr_Workgroup__runtimearr_uint Workgroup\n"));
+}
+
+TEST_F(ValidateMemory, VulkanRTAInsideStorageBufferStructGood) {
+  std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+OpDecorate %array_t ArrayStride 4
+OpMemberDecorate %struct_t 0 Offset 0
+OpDecorate %struct_t Block
+%uint_t = OpTypeInt 32 0
+%array_t = OpTypeRuntimeArray %uint_t
+%struct_t = OpTypeStruct %array_t
+%struct_ptr = OpTypePointer StorageBuffer %struct_t
+%2 = OpVariable %struct_ptr StorageBuffer
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+}
+
+TEST_F(ValidateMemory, WebGPURTAInsideStorageBufferStructGood) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+OpDecorate %array_t ArrayStride 4
+OpMemberDecorate %struct_t 0 Offset 0
+OpDecorate %struct_t Block
+%uint_t = OpTypeInt 32 0
+%array_t = OpTypeRuntimeArray %uint_t
+%struct_t = OpTypeStruct %array_t
+%struct_ptr = OpTypePointer StorageBuffer %struct_t
+%2 = OpVariable %struct_ptr StorageBuffer
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
+TEST_F(ValidateMemory, VulkanRTAInsideWrongStorageClassStructBad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%uint_t = OpTypeInt 32 0
+%array_t = OpTypeRuntimeArray %uint_t
+%struct_t = OpTypeStruct %array_t
+%struct_ptr = OpTypePointer Workgroup %struct_t
+%2 = OpVariable %struct_ptr Workgroup
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "For Vulkan, OpTypeStruct variables containing OpTypeRuntimeArray "
+          "must have storage class of StorageBuffer or Uniform.\n  %6 = "
+          "OpVariable %_ptr_Workgroup__struct_4 Workgroup\n"));
+}
+
+TEST_F(ValidateMemory, WebGPURTAInsideWrongStorageClassStructBad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%uint_t = OpTypeInt 32 0
+%array_t = OpTypeRuntimeArray %uint_t
+%struct_t = OpTypeStruct %array_t
+%struct_ptr = OpTypePointer Workgroup %struct_t
+%2 = OpVariable %struct_ptr Workgroup
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("For WebGPU, OpTypeStruct variables containing "
+                "OpTypeRuntimeArray must have storage class of StorageBuffer\n "
+                " %6 = OpVariable %_ptr_Workgroup__struct_4 Workgroup\n"));
+}
+
+TEST_F(ValidateMemory, VulkanRTAInsideStorageBufferStructWithoutBlockBad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%uint_t = OpTypeInt 32 0
+%array_t = OpTypeRuntimeArray %uint_t
+%struct_t = OpTypeStruct %array_t
+%struct_ptr = OpTypePointer StorageBuffer %struct_t
+%2 = OpVariable %struct_ptr StorageBuffer
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("For Vulkan, an OpTypeStruct variable containing an "
+                        "OpTypeRuntimeArray must be decorated with Block if it "
+                        "has storage class StorageBuffer.\n  %6 = OpVariable "
+                        "%_ptr_StorageBuffer__struct_4 StorageBuffer\n"));
+}
+
+TEST_F(ValidateMemory, WebGPURTAInsideStorageBufferStructWithoutBlockBad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%uint_t = OpTypeInt 32 0
+%array_t = OpTypeRuntimeArray %uint_t
+%struct_t = OpTypeStruct %array_t
+%struct_ptr = OpTypePointer StorageBuffer %struct_t
+%2 = OpVariable %struct_ptr StorageBuffer
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("For WebGPU, an OpTypeStruct variable containing an "
+                        "OpTypeRuntimeArray must be decorated with Block if it "
+                        "has storage class StorageBuffer.\n  %6 = OpVariable "
+                        "%_ptr_StorageBuffer__struct_4 StorageBuffer\n"));
+}
+
+TEST_F(ValidateMemory, VulkanRTAInsideUniformStructGood) {
+  std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+OpDecorate %array_t ArrayStride 4
+OpMemberDecorate %struct_t 0 Offset 0
+OpDecorate %struct_t BufferBlock
+%uint_t = OpTypeInt 32 0
+%array_t = OpTypeRuntimeArray %uint_t
+%struct_t = OpTypeStruct %array_t
+%struct_ptr = OpTypePointer Uniform %struct_t
+%2 = OpVariable %struct_ptr Uniform
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+}
+
+TEST_F(ValidateMemory, WebGPURTAInsideUniformStructBad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+OpDecorate %array_t ArrayStride 4
+OpMemberDecorate %struct_t 0 Offset 0
+OpDecorate %struct_t Block
+%uint_t = OpTypeInt 32 0
+%array_t = OpTypeRuntimeArray %uint_t
+%struct_t = OpTypeStruct %array_t
+%struct_ptr = OpTypePointer Uniform %struct_t
+%2 = OpVariable %struct_ptr Uniform
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("For WebGPU, OpTypeStruct variables containing "
+                "OpTypeRuntimeArray must have storage class of StorageBuffer\n "
+                " %6 = OpVariable %_ptr_Uniform__struct_3 Uniform\n"));
+}
+
+TEST_F(ValidateMemory, VulkanRTAInsideUniformStructWithoutBufferBlockBad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%uint_t = OpTypeInt 32 0
+%array_t = OpTypeRuntimeArray %uint_t
+%struct_t = OpTypeStruct %array_t
+%struct_ptr = OpTypePointer Uniform %struct_t
+%2 = OpVariable %struct_ptr Uniform
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("For Vulkan, an OpTypeStruct variable containing an "
+                        "OpTypeRuntimeArray must be decorated with BufferBlock "
+                        "if it has storage class Uniform.\n  %6 = OpVariable "
+                        "%_ptr_Uniform__struct_4 Uniform\n"));
+}
+
+TEST_F(ValidateMemory, VulkanRTAInsideRTABad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%sampler_t = OpTypeSampler
+%inner_array_t = OpTypeRuntimeArray %sampler_t
+%array_t = OpTypeRuntimeArray %inner_array_t
+%array_ptr = OpTypePointer UniformConstant %array_t
+%2 = OpVariable %array_ptr UniformConstant
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpTypeRuntimeArray Element Type <id> '3[%_runtimearr_2]' is not "
+          "valid in Vulkan environments.\n  %_runtimearr__runtimearr_2 = "
+          "OpTypeRuntimeArray %_runtimearr_2\n"));
+}
+
+TEST_F(ValidateMemory, WebGPURTAInsideRTABad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%sampler_t = OpTypeSampler
+%inner_array_t = OpTypeRuntimeArray %sampler_t
+%array_t = OpTypeRuntimeArray %inner_array_t
+%array_ptr = OpTypePointer UniformConstant %array_t
+%2 = OpVariable %array_ptr UniformConstant
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpTypeRuntimeArray Element Type <id> '3[%_runtimearr_2]' is not "
+          "valid in WebGPU environments.\n  %_runtimearr__runtimearr_2 = "
+          "OpTypeRuntimeArray %_runtimearr_2\n"));
+}
+
+TEST_F(ValidateMemory, VulkanRTAInsideRTAWithRuntimeDescriptorArrayBad) {
+  std::string spirv = R"(
+OpCapability RuntimeDescriptorArrayEXT
+OpCapability Shader
+OpExtension "SPV_EXT_descriptor_indexing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+OpDecorate %array_t Block
+%uint_t = OpTypeInt 32 0
+%inner_array_t = OpTypeRuntimeArray %uint_t
+%array_t = OpTypeRuntimeArray %inner_array_t
+%array_ptr = OpTypePointer StorageBuffer %array_t
+%2 = OpVariable %array_ptr StorageBuffer
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpTypeRuntimeArray Element Type <id> '4[%_runtimearr_uint]' is not "
+          "valid in Vulkan environments.\n  %_runtimearr__runtimearr_uint = "
+          "OpTypeRuntimeArray %_runtimearr_uint\n"));
+}
+
+TEST_F(ValidateMemory,
+       VulkanUniformStructInsideRTAWithRuntimeDescriptorArrayGood) {
+  std::string spirv = R"(
+OpCapability RuntimeDescriptorArrayEXT
+OpCapability Shader
+OpExtension "SPV_EXT_descriptor_indexing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+OpDecorate %array_t ArrayStride 4
+OpMemberDecorate %struct_t 0 Offset 0
+OpDecorate %struct_t Block
+%uint_t = OpTypeInt 32 0
+%struct_t = OpTypeStruct %uint_t
+%array_t = OpTypeRuntimeArray %struct_t
+%array_ptr = OpTypePointer Uniform %array_t
+%2 = OpVariable %array_ptr Uniform
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+}
+
+TEST_F(ValidateMemory, VulkanRTAInsideRTAInsideStructBad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+OpDecorate %array_t ArrayStride 4
+OpMemberDecorate %struct_t 0 Offset 0
+OpDecorate %struct_t Block
+%uint_t = OpTypeInt 32 0
+%inner_array_t = OpTypeRuntimeArray %uint_t
+%array_t = OpTypeRuntimeArray %inner_array_t
+%struct_t = OpTypeStruct %array_t
+%struct_ptr = OpTypePointer StorageBuffer %struct_t
+%2 = OpVariable %struct_ptr StorageBuffer
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpTypeRuntimeArray Element Type <id> '5[%_runtimearr_uint]' is not "
+          "valid in Vulkan environments.\n  %_runtimearr__runtimearr_uint = "
+          "OpTypeRuntimeArray %_runtimearr_uint\n"));
+}
+
+TEST_F(ValidateMemory,
+       VulkanRTAInsideRTAInsideStructWithRuntimeDescriptorArrayBad) {
+  std::string spirv = R"(
+OpCapability RuntimeDescriptorArrayEXT
+OpCapability Shader
+OpExtension "SPV_EXT_descriptor_indexing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+OpDecorate %array_t ArrayStride 4
+OpMemberDecorate %struct_t 0 Offset 0
+OpDecorate %struct_t Block
+%uint_t = OpTypeInt 32 0
+%inner_array_t = OpTypeRuntimeArray %uint_t
+%array_t = OpTypeRuntimeArray %inner_array_t
+%struct_t = OpTypeStruct %array_t
+%struct_ptr = OpTypePointer StorageBuffer %struct_t
+%2 = OpVariable %struct_ptr StorageBuffer
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpTypeRuntimeArray Element Type <id> '5[%_runtimearr_uint]' is not "
+          "valid in Vulkan environments.\n  %_runtimearr__runtimearr_uint = "
+          "OpTypeRuntimeArray %_runtimearr_uint\n"));
+}
+
+TEST_F(ValidateMemory, VulkanRTAInsideArrayBad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%uint_t = OpTypeInt 32 0
+%dim = OpConstant %uint_t 1
+%sampler_t = OpTypeSampler
+%inner_array_t = OpTypeRuntimeArray %sampler_t
+%array_t = OpTypeArray %inner_array_t %dim
+%array_ptr = OpTypePointer UniformConstant %array_t
+%2 = OpVariable %array_ptr UniformConstant
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpTypeArray Element Type <id> '5[%_runtimearr_4]' is not "
+                "valid in Vulkan environments.\n  %_arr__runtimearr_4_uint_1 = "
+                "OpTypeArray %_runtimearr_4 %uint_1\n"));
+}
+
+TEST_F(ValidateMemory, WebGPURTAInsideArrayBad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%uint_t = OpTypeInt 32 0
+%dim = OpConstant %uint_t 1
+%sampler_t = OpTypeSampler
+%inner_array_t = OpTypeRuntimeArray %sampler_t
+%array_t = OpTypeArray %inner_array_t %dim
+%array_ptr = OpTypePointer UniformConstant %array_t
+%2 = OpVariable %array_ptr UniformConstant
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpTypeArray Element Type <id> '5[%_runtimearr_4]' is not "
+                "valid in WebGPU environments.\n  %_arr__runtimearr_4_uint_1 = "
+                "OpTypeArray %_runtimearr_4 %uint_1\n"));
+}
+
+TEST_F(ValidateMemory, VulkanRTAInsideArrayWithRuntimeDescriptorArrayBad) {
+  std::string spirv = R"(
+OpCapability RuntimeDescriptorArrayEXT
+OpCapability Shader
+OpExtension "SPV_EXT_descriptor_indexing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+OpDecorate %array_t Block
+%uint_t = OpTypeInt 32 0
+%dim = OpConstant %uint_t 1
+%sampler_t = OpTypeSampler
+%inner_array_t = OpTypeRuntimeArray %uint_t
+%array_t = OpTypeRuntimeArray %inner_array_t
+%array_ptr = OpTypePointer StorageBuffer %array_t
+%2 = OpVariable %array_ptr StorageBuffer
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpTypeRuntimeArray Element Type <id> '6[%_runtimearr_uint]' is not "
+          "valid in Vulkan environments.\n  %_runtimearr__runtimearr_uint = "
+          "OpTypeRuntimeArray %_runtimearr_uint\n"));
+}
+
+TEST_F(ValidateMemory, VulkanRTAInsideArrayInsideStructBad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+OpDecorate %array_t ArrayStride 4
+OpMemberDecorate %struct_t 0 Offset 0
+OpDecorate %struct_t Block
+%uint_t = OpTypeInt 32 0
+%dim = OpConstant %uint_t 1
+%inner_array_t = OpTypeRuntimeArray %uint_t
+%array_t = OpTypeArray %inner_array_t %dim
+%struct_t = OpTypeStruct %array_t
+%struct_ptr = OpTypePointer StorageBuffer %struct_t
+%2 = OpVariable %struct_ptr StorageBuffer
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpTypeArray Element Type <id> '6[%_runtimearr_uint]' is not "
+          "valid in Vulkan environments.\n  %_arr__runtimearr_uint_uint_1 "
+          "= OpTypeArray %_runtimearr_uint %uint_1\n"));
+}
+
+TEST_F(ValidateMemory,
+       VulkanRTAInsideArrayInsideStructWithRuntimeDescriptorArrayBad) {
+  std::string spirv = R"(
+OpCapability RuntimeDescriptorArrayEXT
+OpCapability Shader
+OpExtension "SPV_EXT_descriptor_indexing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+OpDecorate %array_t ArrayStride 4
+OpMemberDecorate %struct_t 0 Offset 0
+OpDecorate %struct_t Block
+%uint_t = OpTypeInt 32 0
+%dim = OpConstant %uint_t 1
+%inner_array_t = OpTypeRuntimeArray %uint_t
+%array_t = OpTypeArray %inner_array_t %dim
+%struct_t = OpTypeStruct %array_t
+%struct_ptr = OpTypePointer StorageBuffer %struct_t
+%2 = OpVariable %struct_ptr StorageBuffer
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpTypeArray Element Type <id> '6[%_runtimearr_uint]' is not "
+          "valid in Vulkan environments.\n  %_arr__runtimearr_uint_uint_1 "
+          "= OpTypeArray %_runtimearr_uint %uint_1\n"));
+}
+
+TEST_F(ValidateMemory, VulkanRTAStructInsideRTAWithRuntimeDescriptorArrayGood) {
+  std::string spirv = R"(
+OpCapability RuntimeDescriptorArrayEXT
+OpCapability Shader
+OpExtension "SPV_EXT_descriptor_indexing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+OpDecorate %array_t ArrayStride 4
+OpMemberDecorate %struct_t 0 Offset 0
+OpDecorate %struct_t Block
+%uint_t = OpTypeInt 32 0
+%inner_array_t = OpTypeRuntimeArray %uint_t
+%struct_t = OpTypeStruct %inner_array_t
+%array_t = OpTypeRuntimeArray %struct_t
+%array_ptr = OpTypePointer StorageBuffer %array_t
+%2 = OpVariable %array_ptr StorageBuffer
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+}
+
+TEST_F(ValidateMemory, VulkanRTAStructInsideArrayGood) {
+  std::string spirv = R"(
+OpCapability RuntimeDescriptorArrayEXT
+OpCapability Shader
+OpExtension "SPV_EXT_descriptor_indexing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+OpDecorate %array_t ArrayStride 4
+OpMemberDecorate %struct_t 0 Offset 0
+OpDecorate %struct_t Block
+%uint_t = OpTypeInt 32 0
+%inner_array_t = OpTypeRuntimeArray %uint_t
+%struct_t = OpTypeStruct %inner_array_t
+%array_size = OpConstant %uint_t 5
+%array_t = OpTypeArray %struct_t %array_size
+%array_ptr = OpTypePointer StorageBuffer %array_t
+%2 = OpVariable %array_ptr StorageBuffer
+%void = OpTypeVoid
+%func_t = OpTypeFunction %void
+%func = OpFunction %void None %func_t
+%1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+}
+
+TEST_F(ValidateMemory, CopyMemoryNoAccessGood) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_ptr_priv = OpTypePointer Private %int
+%var1 = OpVariable %int_ptr_priv Private
+%var2 = OpVariable %int_ptr_priv Private
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateMemory, CopyMemorySimpleMixedAccessGood) {
+  // Test one memory access operand using features that don't require the
+  // Vulkan memory model.
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_ptr_priv = OpTypePointer Private %int
+%var1 = OpVariable %int_ptr_priv Private
+%var2 = OpVariable %int_ptr_priv Private
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2 Volatile|Aligned|Nontemporal 4
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateMemory, CopyMemorySimpleTwoMixedAccessV13Bad) {
+  // Two memory access operands is invalid up to SPIR-V 1.3
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_ptr_priv = OpTypePointer Private %int
+%var1 = OpVariable %int_ptr_priv Private
+%var2 = OpVariable %int_ptr_priv Private
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2 Volatile Volatile
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("CopyMemory with two memory access operands requires "
+                        "SPIR-V 1.4 or later"));
+}
+
+TEST_F(ValidateMemory, CopyMemorySimpleTwoMixedAccessV14Good) {
+  // Two memory access operands is valid in SPIR-V 1.4
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_ptr_priv = OpTypePointer Private %int
+%var1 = OpVariable %int_ptr_priv Private
+%var2 = OpVariable %int_ptr_priv Private
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemory %var1 %var2 Volatile Volatile
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateMemory, CopyMemorySizedNoAccessGood) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability Addresses
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_16 = OpConstant %int 16
+%int_ptr_priv = OpTypePointer Private %int
+%var1 = OpVariable %int_ptr_priv Private
+%var2 = OpVariable %int_ptr_priv Private
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemorySized %var1 %var2 %int_16
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateMemory, CopyMemorySizedSimpleMixedAccessGood) {
+  // Test one memory access operand using features that don't require the
+  // Vulkan memory model.
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability Addresses
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_16 = OpConstant %int 16
+%int_ptr_priv = OpTypePointer Private %int
+%var1 = OpVariable %int_ptr_priv Private
+%var2 = OpVariable %int_ptr_priv Private
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemorySized %var1 %var2 %int_16 Volatile|Aligned|Nontemporal 4
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateMemory, CopyMemorySizedSimpleTwoMixedAccessV13Bad) {
+  // Two memory access operands is invalid up to SPIR-V 1.3
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability Addresses
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_16 = OpConstant %int 16
+%int_ptr_priv = OpTypePointer Private %int
+%var1 = OpVariable %int_ptr_priv Private
+%var2 = OpVariable %int_ptr_priv Private
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemorySized %var1 %var2 %int_16 Volatile Volatile
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("CopyMemorySized with two memory access operands requires "
+                "SPIR-V 1.4 or later"));
+}
+
+TEST_F(ValidateMemory, CopyMemorySizedSimpleTwoMixedAccessV14Good) {
+  // Two memory access operands is valid in SPIR-V 1.4
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability Addresses
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_16 = OpConstant %int 16
+%int_ptr_priv = OpTypePointer Private %int
+%var1 = OpVariable %int_ptr_priv Private
+%var2 = OpVariable %int_ptr_priv Private
+%voidfn = OpTypeFunction %void
+%func = OpFunction %void None %voidfn
+%entry = OpLabel
+OpCopyMemorySized %var1 %var2 %int_16 Volatile Volatile
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+using ValidatePointerComparisons = spvtest::ValidateBase<std::string>;
+
+TEST_P(ValidatePointerComparisons, Good) {
+  const std::string operation = GetParam();
+
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability VariablePointersStorageBuffer
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%ptr_int = OpTypePointer StorageBuffer %int
+%var = OpVariable %ptr_int StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%equal = )" + operation;
+
+  if (operation == "OpPtrDiff") {
+    spirv += " %int ";
+  } else {
+    spirv += " %bool ";
+  }
+
+  spirv += R"(%var %var
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+}
+
+TEST_P(ValidatePointerComparisons, GoodWorkgroup) {
+  const std::string operation = GetParam();
+
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability VariablePointers
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%ptr_int = OpTypePointer Workgroup %int
+%var = OpVariable %ptr_int Workgroup
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%equal = )" + operation;
+
+  if (operation == "OpPtrDiff") {
+    spirv += " %int ";
+  } else {
+    spirv += " %bool ";
+  }
+
+  spirv += R"(%var %var
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+}
+
+TEST_P(ValidatePointerComparisons, BadResultType) {
+  const std::string operation = GetParam();
+
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability VariablePointersStorageBuffer
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%ptr_int = OpTypePointer StorageBuffer %int
+%var = OpVariable %ptr_int StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%equal = )" + operation;
+
+  if (operation == "OpPtrDiff") {
+    spirv += " %bool ";
+  } else {
+    spirv += " %int ";
+  }
+
+  spirv += R"(%var %var
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  if (operation == "OpPtrDiff") {
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("Result Type must be an integer scalar"));
+  } else {
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("Result Type must be OpTypeBool"));
+  }
+}
+
+TEST_P(ValidatePointerComparisons, BadCapabilities) {
+  const std::string operation = GetParam();
+
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%ptr_int = OpTypePointer StorageBuffer %int
+%var = OpVariable %ptr_int StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%equal = )" + operation;
+
+  if (operation == "OpPtrDiff") {
+    spirv += " %int ";
+  } else {
+    spirv += " %bool ";
+  }
+
+  spirv += R"(%var %var
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  if (operation == "OpPtrDiff") {
+    // Gets caught by the grammar.
+    EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY,
+              ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID,
+              ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("Instruction cannot be used without a variable "
+                          "pointers capability"));
+  }
+}
+
+TEST_P(ValidatePointerComparisons, BadOperandType) {
+  const std::string operation = GetParam();
+
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability VariablePointersStorageBuffer
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%ptr_int = OpTypePointer StorageBuffer %int
+%var = OpVariable %ptr_int StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%ld = OpLoad %int %var
+%equal = )" + operation;
+
+  if (operation == "OpPtrDiff") {
+    spirv += " %int ";
+  } else {
+    spirv += " %bool ";
+  }
+
+  spirv += R"(%ld %ld
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Operand type must be a pointer"));
+}
+
+TEST_P(ValidatePointerComparisons, BadStorageClassWorkgroup) {
+  const std::string operation = GetParam();
+
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability VariablePointersStorageBuffer
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%ptr_int = OpTypePointer Workgroup %int
+%var = OpVariable %ptr_int Workgroup
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%equal = )" + operation;
+
+  if (operation == "OpPtrDiff") {
+    spirv += " %int ";
+  } else {
+    spirv += " %bool ";
+  }
+
+  spirv += R"(%var %var
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Workgroup storage class pointer requires "
+                        "VariablePointers capability to be specified"));
+}
+
+TEST_P(ValidatePointerComparisons, BadStorageClass) {
+  const std::string operation = GetParam();
+
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability VariablePointersStorageBuffer
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%ptr_int = OpTypePointer Private %int
+%var = OpVariable %ptr_int Private
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%equal = )" + operation;
+
+  if (operation == "OpPtrDiff") {
+    spirv += " %int ";
+  } else {
+    spirv += " %bool ";
+  }
+
+  spirv += R"(%var %var
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Invalid pointer storage class"));
+}
+
+TEST_P(ValidatePointerComparisons, BadDiffOperandTypes) {
+  const std::string operation = GetParam();
+
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability VariablePointersStorageBuffer
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%ptr_int = OpTypePointer Private %int
+%var = OpVariable %ptr_int Private
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%ld = OpLoad %int %var
+%equal = )" + operation;
+
+  if (operation == "OpPtrDiff") {
+    spirv += " %int ";
+  } else {
+    spirv += " %bool ";
+  }
+
+  spirv += R"(%var %ld
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("The types of Operand 1 and Operand 2 must match"));
+}
+
+INSTANTIATE_TEST_SUITE_P(PointerComparisons, ValidatePointerComparisons,
+                         Values("OpPtrEqual", "OpPtrNotEqual", "OpPtrDiff"));
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_modes_test.cpp b/test/val/val_modes_test.cpp
index 7f1ef09..bc157a4 100644
--- a/test/val/val_modes_test.cpp
+++ b/test/val/val_modes_test.cpp
@@ -510,7 +510,7 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     GeometryRequiredModes, ValidateModeGeometry,
     Combine(Combine(Values("InputPoints", ""), Values("InputLines", ""),
                     Values("InputLinesAdjacency", ""), Values("Triangles", ""),
@@ -531,16 +531,24 @@
 
   std::ostringstream sstr;
   sstr << "OpCapability Shader\n";
-  sstr << "OpCapability Geometry\n";
-  sstr << "OpCapability Tessellation\n";
-  sstr << "OpCapability TransformFeedback\n";
-  if (!spvIsVulkanEnv(env)) {
+  if (!spvIsWebGPUEnv(env)) {
+    sstr << "OpCapability Geometry\n";
+    sstr << "OpCapability Tessellation\n";
+    sstr << "OpCapability TransformFeedback\n";
+  }
+  if (!spvIsVulkanOrWebGPUEnv(env)) {
     sstr << "OpCapability Kernel\n";
     if (env == SPV_ENV_UNIVERSAL_1_3) {
       sstr << "OpCapability SubgroupDispatch\n";
     }
   }
-  sstr << "OpMemoryModel Logical GLSL450\n";
+  if (spvIsWebGPUEnv(env)) {
+    sstr << "OpCapability VulkanMemoryModelKHR\n";
+    sstr << "OpExtension \"SPV_KHR_vulkan_memory_model\"\n";
+    sstr << "OpMemoryModel Logical VulkanKHR\n";
+  } else {
+    sstr << "OpMemoryModel Logical GLSL450\n";
+  }
   sstr << "OpEntryPoint " << model << " %main \"main\"\n";
   if (mode.find("LocalSizeId") == 0 || mode.find("LocalSizeHintId") == 0 ||
       mode.find("SubgroupsPerWorkgroupId") == 0) {
@@ -584,7 +592,7 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidateModeGeometryOnlyGoodSpv10, ValidateModeExecution,
     Combine(Values(SPV_SUCCESS), Values(""), Values("Geometry"),
             Values("Invocations 3", "InputPoints", "InputLines",
@@ -592,7 +600,7 @@
                    "OutputPoints", "OutputLineStrip", "OutputTriangleStrip"),
             Values(SPV_ENV_UNIVERSAL_1_0)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidateModeGeometryOnlyBadSpv10, ValidateModeExecution,
     Combine(Values(SPV_ERROR_INVALID_DATA),
             Values("Execution mode can only be used with the Geometry "
@@ -604,7 +612,7 @@
                    "OutputPoints", "OutputLineStrip", "OutputTriangleStrip"),
             Values(SPV_ENV_UNIVERSAL_1_0)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidateModeTessellationOnlyGoodSpv10, ValidateModeExecution,
     Combine(Values(SPV_SUCCESS), Values(""),
             Values("TessellationControl", "TessellationEvaluation"),
@@ -613,7 +621,7 @@
                    "PointMode", "Quads", "Isolines"),
             Values(SPV_ENV_UNIVERSAL_1_0)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidateModeTessellationOnlyBadSpv10, ValidateModeExecution,
     Combine(Values(SPV_ERROR_INVALID_DATA),
             Values("Execution mode can only be used with a tessellation "
@@ -624,15 +632,15 @@
                    "PointMode", "Quads", "Isolines"),
             Values(SPV_ENV_UNIVERSAL_1_0)));
 
-INSTANTIATE_TEST_CASE_P(ValidateModeGeometryAndTessellationGoodSpv10,
-                        ValidateModeExecution,
-                        Combine(Values(SPV_SUCCESS), Values(""),
-                                Values("TessellationControl",
-                                       "TessellationEvaluation", "Geometry"),
-                                Values("Triangles", "OutputVertices 3"),
-                                Values(SPV_ENV_UNIVERSAL_1_0)));
+INSTANTIATE_TEST_SUITE_P(ValidateModeGeometryAndTessellationGoodSpv10,
+                         ValidateModeExecution,
+                         Combine(Values(SPV_SUCCESS), Values(""),
+                                 Values("TessellationControl",
+                                        "TessellationEvaluation", "Geometry"),
+                                 Values("Triangles", "OutputVertices 3"),
+                                 Values(SPV_ENV_UNIVERSAL_1_0)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidateModeGeometryAndTessellationBadSpv10, ValidateModeExecution,
     Combine(Values(SPV_ERROR_INVALID_DATA),
             Values("Execution mode can only be used with a Geometry or "
@@ -641,7 +649,7 @@
             Values("Triangles", "OutputVertices 3"),
             Values(SPV_ENV_UNIVERSAL_1_0)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidateModeFragmentOnlyGoodSpv10, ValidateModeExecution,
     Combine(Values(SPV_SUCCESS), Values(""), Values("Fragment"),
             Values("PixelCenterInteger", "OriginUpperLeft", "OriginLowerLeft",
@@ -649,7 +657,7 @@
                    "DepthUnchanged"),
             Values(SPV_ENV_UNIVERSAL_1_0)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidateModeFragmentOnlyBadSpv10, ValidateModeExecution,
     Combine(Values(SPV_ERROR_INVALID_DATA),
             Values("Execution mode can only be used with the Fragment "
@@ -657,19 +665,19 @@
             Values("Geometry", "TessellationControl", "TessellationEvaluation",
                    "GLCompute", "Vertex", "Kernel"),
             Values("PixelCenterInteger", "OriginUpperLeft", "OriginLowerLeft",
-                   "EarlyFragmentTests", "DepthReplacing", "DepthLess",
-                   "DepthUnchanged"),
+                   "EarlyFragmentTests", "DepthReplacing", "DepthGreater",
+                   "DepthLess", "DepthUnchanged"),
             Values(SPV_ENV_UNIVERSAL_1_0)));
 
-INSTANTIATE_TEST_CASE_P(ValidateModeKernelOnlyGoodSpv13, ValidateModeExecution,
-                        Combine(Values(SPV_SUCCESS), Values(""),
-                                Values("Kernel"),
-                                Values("LocalSizeHint 1 1 1", "VecTypeHint 4",
-                                       "ContractionOff",
-                                       "LocalSizeHintId %int1"),
-                                Values(SPV_ENV_UNIVERSAL_1_3)));
+INSTANTIATE_TEST_SUITE_P(ValidateModeKernelOnlyGoodSpv13, ValidateModeExecution,
+                         Combine(Values(SPV_SUCCESS), Values(""),
+                                 Values("Kernel"),
+                                 Values("LocalSizeHint 1 1 1", "VecTypeHint 4",
+                                        "ContractionOff",
+                                        "LocalSizeHintId %int1"),
+                                 Values(SPV_ENV_UNIVERSAL_1_3)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidateModeKernelOnlyBadSpv13, ValidateModeExecution,
     Combine(
         Values(SPV_ERROR_INVALID_DATA),
@@ -681,13 +689,13 @@
                "LocalSizeHintId %int1"),
         Values(SPV_ENV_UNIVERSAL_1_3)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidateModeGLComputeAndKernelGoodSpv13, ValidateModeExecution,
     Combine(Values(SPV_SUCCESS), Values(""), Values("Kernel", "GLCompute"),
             Values("LocalSize 1 1 1", "LocalSizeId %int1 %int1 %int1"),
             Values(SPV_ENV_UNIVERSAL_1_3)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidateModeGLComputeAndKernelBadSpv13, ValidateModeExecution,
     Combine(Values(SPV_ERROR_INVALID_DATA),
             Values("Execution mode can only be used with a Kernel or GLCompute "
@@ -697,7 +705,7 @@
             Values("LocalSize 1 1 1", "LocalSizeId %int1 %int1 %int1"),
             Values(SPV_ENV_UNIVERSAL_1_3)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ValidateModeAllGoodSpv13, ValidateModeExecution,
     Combine(Values(SPV_SUCCESS), Values(""),
             Values("Kernel", "GLCompute", "Geometry", "TessellationControl",
@@ -706,6 +714,39 @@
                    "SubgroupsPerWorkgroup 1", "SubgroupsPerWorkgroupId %int1"),
             Values(SPV_ENV_UNIVERSAL_1_3)));
 
+INSTANTIATE_TEST_SUITE_P(ValidateModeGLComputeWebGPUWhitelistGood,
+                         ValidateModeExecution,
+                         Combine(Values(SPV_SUCCESS), Values(""),
+                                 Values("GLCompute"), Values("LocalSize 1 1 1"),
+                                 Values(SPV_ENV_WEBGPU_0)));
+
+INSTANTIATE_TEST_SUITE_P(
+    ValidateModeGLComputeWebGPUWhitelistBad, ValidateModeExecution,
+    Combine(Values(SPV_ERROR_INVALID_DATA),
+            Values("Execution mode must be one of OriginUpperLeft, "
+                   "DepthReplacing, DepthGreater, DepthLess, DepthUnchanged, "
+                   "LocalSize, or LocalSizeHint for WebGPU environment"),
+            Values("GLCompute"), Values("LocalSizeId %int1 %int1 %int1"),
+            Values(SPV_ENV_WEBGPU_0)));
+
+INSTANTIATE_TEST_SUITE_P(
+    ValidateModeFragmentWebGPUWhitelistGood, ValidateModeExecution,
+    Combine(Values(SPV_SUCCESS), Values(""), Values("Fragment"),
+            Values("OriginUpperLeft", "DepthReplacing", "DepthGreater",
+                   "DepthLess", "DepthUnchanged"),
+            Values(SPV_ENV_WEBGPU_0)));
+
+INSTANTIATE_TEST_SUITE_P(
+    ValidateModeFragmentWebGPUWhitelistBad, ValidateModeExecution,
+    Combine(Values(SPV_ERROR_INVALID_DATA),
+            Values("Execution mode must be one of OriginUpperLeft, "
+                   "DepthReplacing, DepthGreater, DepthLess, DepthUnchanged, "
+                   "LocalSize, or LocalSizeHint for WebGPU environment"),
+            Values("Fragment"),
+            Values("PixelCenterInteger", "OriginLowerLeft",
+                   "EarlyFragmentTests"),
+            Values(SPV_ENV_WEBGPU_0)));
+
 TEST_F(ValidateModeExecution, MeshNVLocalSize) {
   const std::string spirv = R"(
 OpCapability Shader
@@ -796,6 +837,171 @@
   EXPECT_THAT(SPV_SUCCESS, ValidateInstructions(env));
 }
 
+TEST_F(ValidateModeExecution, ExecModeSubgroupsPerWorkgroupIdBad) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability SubgroupDispatch
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpExecutionMode %main SubgroupsPerWorkgroupId %int_1
+%int = OpTypeInt 32 0
+%int_1 = OpConstant %int 1
+)" + kVoidFunction;
+
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  CompileSuccessfully(spirv, env);
+  EXPECT_THAT(SPV_ERROR_INVALID_DATA, ValidateInstructions(env));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpExecutionMode is only valid when the Mode operand "
+                        "is an execution mode that takes no Extra Operands"));
+}
+
+TEST_F(ValidateModeExecution, ExecModeIdSubgroupsPerWorkgroupIdGood) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability SubgroupDispatch
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpExecutionModeId %main SubgroupsPerWorkgroupId %int_1
+%int = OpTypeInt 32 0
+%int_1 = OpConstant %int 1
+)" + kVoidFunction;
+
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  CompileSuccessfully(spirv, env);
+  EXPECT_THAT(SPV_SUCCESS, ValidateInstructions(env));
+}
+
+TEST_F(ValidateModeExecution, ExecModeIdSubgroupsPerWorkgroupIdNonConstantBad) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability SubgroupDispatch
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpExecutionModeId %main SubgroupsPerWorkgroupId %int_1
+%int = OpTypeInt 32 0
+%int_ptr = OpTypePointer Private %int
+%int_1 = OpVariable %int_ptr Private
+)" + kVoidFunction;
+
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  CompileSuccessfully(spirv, env);
+  EXPECT_THAT(SPV_ERROR_INVALID_ID, ValidateInstructions(env));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("For OpExecutionModeId all Extra Operand ids must be "
+                        "constant instructions."));
+}
+
+TEST_F(ValidateModeExecution, ExecModeLocalSizeHintIdBad) {
+  const std::string spirv = R"(
+OpCapability Kernel
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Kernel %main "main"
+OpExecutionMode %main LocalSizeHintId %int_1
+%int = OpTypeInt 32 0
+%int_1 = OpConstant %int 1
+)" + kVoidFunction;
+
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  CompileSuccessfully(spirv, env);
+  EXPECT_THAT(SPV_ERROR_INVALID_DATA, ValidateInstructions(env));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpExecutionMode is only valid when the Mode operand "
+                        "is an execution mode that takes no Extra Operands"));
+}
+
+TEST_F(ValidateModeExecution, ExecModeIdLocalSizeHintIdGood) {
+  const std::string spirv = R"(
+OpCapability Kernel
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Kernel %main "main"
+OpExecutionModeId %main LocalSizeHintId %int_1
+%int = OpTypeInt 32 0
+%int_1 = OpConstant %int 1
+)" + kVoidFunction;
+
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  CompileSuccessfully(spirv, env);
+  EXPECT_THAT(SPV_SUCCESS, ValidateInstructions(env));
+}
+
+TEST_F(ValidateModeExecution, ExecModeIdLocalSizeHintIdNonConstantBad) {
+  const std::string spirv = R"(
+OpCapability Kernel
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpExecutionModeId %main LocalSizeHintId %int_1
+%int = OpTypeInt 32 0
+%int_ptr = OpTypePointer Private %int
+%int_1 = OpVariable %int_ptr Private
+)" + kVoidFunction;
+
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  CompileSuccessfully(spirv, env);
+  EXPECT_THAT(SPV_ERROR_INVALID_ID, ValidateInstructions(env));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("For OpExecutionModeId all Extra Operand ids must be "
+                        "constant instructions."));
+}
+
+TEST_F(ValidateModeExecution, ExecModeLocalSizeIdBad) {
+  const std::string spirv = R"(
+OpCapability Kernel
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Kernel %main "main"
+OpExecutionMode %main LocalSizeId %int_1 %int_1 %int_1
+%int = OpTypeInt 32 0
+%int_1 = OpConstant %int 1
+)" + kVoidFunction;
+
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  CompileSuccessfully(spirv, env);
+  EXPECT_THAT(SPV_ERROR_INVALID_DATA, ValidateInstructions(env));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpExecutionMode is only valid when the Mode operand "
+                        "is an execution mode that takes no Extra Operands"));
+}
+
+TEST_F(ValidateModeExecution, ExecModeIdLocalSizeIdGood) {
+  const std::string spirv = R"(
+OpCapability Kernel
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Kernel %main "main"
+OpExecutionModeId %main LocalSizeId %int_1 %int_1 %int_1
+%int = OpTypeInt 32 0
+%int_1 = OpConstant %int 1
+)" + kVoidFunction;
+
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  CompileSuccessfully(spirv, env);
+  EXPECT_THAT(SPV_SUCCESS, ValidateInstructions(env));
+}
+
+TEST_F(ValidateModeExecution, ExecModeIdLocalSizeIdNonConstantBad) {
+  const std::string spirv = R"(
+OpCapability Kernel
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpExecutionModeId %main LocalSizeId %int_1 %int_1 %int_1
+%int = OpTypeInt 32 0
+%int_ptr = OpTypePointer Private %int
+%int_1 = OpVariable %int_ptr Private
+)" + kVoidFunction;
+
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  CompileSuccessfully(spirv, env);
+  EXPECT_THAT(SPV_ERROR_INVALID_ID, ValidateInstructions(env));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("For OpExecutionModeId all Extra Operand ids must be "
+                        "constant instructions."));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_non_uniform_test.cpp b/test/val/val_non_uniform_test.cpp
index a185d87..fbd11a9 100644
--- a/test/val/val_non_uniform_test.cpp
+++ b/test/val/val_non_uniform_test.cpp
@@ -182,63 +182,63 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformElect, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformElect"),
-                                Values("%bool"), ValuesIn(scopes), Values(""),
-                                Values("")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformElect, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformElect"),
+                                 Values("%bool"), ValuesIn(scopes), Values(""),
+                                 Values("")));
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformVote, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformAll",
-                                       "OpGroupNonUniformAny",
-                                       "OpGroupNonUniformAllEqual"),
-                                Values("%bool"), ValuesIn(scopes),
-                                Values("%true"), Values("")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformVote, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformAll",
+                                        "OpGroupNonUniformAny",
+                                        "OpGroupNonUniformAllEqual"),
+                                 Values("%bool"), ValuesIn(scopes),
+                                 Values("%true"), Values("")));
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformBroadcast, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformBroadcast"),
-                                Values("%bool"), ValuesIn(scopes),
-                                Values("%true %u32_0"), Values("")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformBroadcast, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformBroadcast"),
+                                 Values("%bool"), ValuesIn(scopes),
+                                 Values("%true %u32_0"), Values("")));
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformBroadcastFirst, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformBroadcastFirst"),
-                                Values("%bool"), ValuesIn(scopes),
-                                Values("%true"), Values("")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformBroadcastFirst, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformBroadcastFirst"),
+                                 Values("%bool"), ValuesIn(scopes),
+                                 Values("%true"), Values("")));
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformBallot, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformBallot"),
-                                Values("%u32vec4"), ValuesIn(scopes),
-                                Values("%true"), Values("")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformBallot, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformBallot"),
+                                 Values("%u32vec4"), ValuesIn(scopes),
+                                 Values("%true"), Values("")));
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformInverseBallot, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformInverseBallot"),
-                                Values("%bool"), ValuesIn(scopes),
-                                Values("%u32vec4_null"), Values("")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformInverseBallot, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformInverseBallot"),
+                                 Values("%bool"), ValuesIn(scopes),
+                                 Values("%u32vec4_null"), Values("")));
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformBallotBitExtract, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformBallotBitExtract"),
-                                Values("%bool"), ValuesIn(scopes),
-                                Values("%u32vec4_null %u32_0"), Values("")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformBallotBitExtract, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformBallotBitExtract"),
+                                 Values("%bool"), ValuesIn(scopes),
+                                 Values("%u32vec4_null %u32_0"), Values("")));
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformBallotBitCount, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformBallotBitCount"),
-                                Values("%u32"), ValuesIn(scopes),
-                                Values("Reduce %u32vec4_null"), Values("")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformBallotBitCount, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformBallotBitCount"),
+                                 Values("%u32"), ValuesIn(scopes),
+                                 Values("Reduce %u32vec4_null"), Values("")));
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformBallotFind, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformBallotFindLSB",
-                                       "OpGroupNonUniformBallotFindMSB"),
-                                Values("%u32"), ValuesIn(scopes),
-                                Values("%u32vec4_null"), Values("")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformBallotFind, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformBallotFindLSB",
+                                        "OpGroupNonUniformBallotFindMSB"),
+                                 Values("%u32"), ValuesIn(scopes),
+                                 Values("%u32vec4_null"), Values("")));
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformShuffle, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformShuffle",
-                                       "OpGroupNonUniformShuffleXor",
-                                       "OpGroupNonUniformShuffleUp",
-                                       "OpGroupNonUniformShuffleDown"),
-                                Values("%u32"), ValuesIn(scopes),
-                                Values("%u32_0 %u32_0"), Values("")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformShuffle, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformShuffle",
+                                        "OpGroupNonUniformShuffleXor",
+                                        "OpGroupNonUniformShuffleUp",
+                                        "OpGroupNonUniformShuffleDown"),
+                                 Values("%u32"), ValuesIn(scopes),
+                                 Values("%u32_0 %u32_0"), Values("")));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     GroupNonUniformIntegerArithmetic, GroupNonUniform,
     Combine(Values("OpGroupNonUniformIAdd", "OpGroupNonUniformIMul",
                    "OpGroupNonUniformSMin", "OpGroupNonUniformUMin",
@@ -248,45 +248,45 @@
             Values("%u32"), ValuesIn(scopes), Values("Reduce %u32_0"),
             Values("")));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     GroupNonUniformFloatArithmetic, GroupNonUniform,
     Combine(Values("OpGroupNonUniformFAdd", "OpGroupNonUniformFMul",
                    "OpGroupNonUniformFMin", "OpGroupNonUniformFMax"),
             Values("%float"), ValuesIn(scopes), Values("Reduce %float_0"),
             Values("")));
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformLogicalArithmetic, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformLogicalAnd",
-                                       "OpGroupNonUniformLogicalOr",
-                                       "OpGroupNonUniformLogicalXor"),
-                                Values("%bool"), ValuesIn(scopes),
-                                Values("Reduce %true"), Values("")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformLogicalArithmetic, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformLogicalAnd",
+                                        "OpGroupNonUniformLogicalOr",
+                                        "OpGroupNonUniformLogicalXor"),
+                                 Values("%bool"), ValuesIn(scopes),
+                                 Values("Reduce %true"), Values("")));
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformQuad, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformQuadBroadcast",
-                                       "OpGroupNonUniformQuadSwap"),
-                                Values("%u32"), ValuesIn(scopes),
-                                Values("%u32_0 %u32_0"), Values("")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformQuad, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformQuadBroadcast",
+                                        "OpGroupNonUniformQuadSwap"),
+                                 Values("%u32"), ValuesIn(scopes),
+                                 Values("%u32_0 %u32_0"), Values("")));
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformBallotBitCountScope, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformBallotBitCount"),
-                                Values("%u32"), ValuesIn(scopes),
-                                Values("Reduce %u32vec4_null"), Values("")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformBallotBitCountScope, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformBallotBitCount"),
+                                 Values("%u32"), ValuesIn(scopes),
+                                 Values("Reduce %u32vec4_null"), Values("")));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     GroupNonUniformBallotBitCountBadResultType, GroupNonUniform,
     Combine(
         Values("OpGroupNonUniformBallotBitCount"), Values("%float", "%int"),
         Values(SpvScopeSubgroup), Values("Reduce %u32vec4_null"),
         Values("Expected Result Type to be an unsigned integer type scalar.")));
 
-INSTANTIATE_TEST_CASE_P(GroupNonUniformBallotBitCountBadValue, GroupNonUniform,
-                        Combine(Values("OpGroupNonUniformBallotBitCount"),
-                                Values("%u32"), Values(SpvScopeSubgroup),
-                                Values("Reduce %u32vec3_null", "Reduce %u32_0",
-                                       "Reduce %float_0"),
-                                Values("Expected Value to be a vector of four "
-                                       "components of integer type scalar")));
+INSTANTIATE_TEST_SUITE_P(GroupNonUniformBallotBitCountBadValue, GroupNonUniform,
+                         Combine(Values("OpGroupNonUniformBallotBitCount"),
+                                 Values("%u32"), Values(SpvScopeSubgroup),
+                                 Values("Reduce %u32vec3_null", "Reduce %u32_0",
+                                        "Reduce %float_0"),
+                                 Values("Expected Value to be a vector of four "
+                                        "components of integer type scalar")));
 
 }  // namespace
 }  // namespace val
diff --git a/test/val/val_ssa_test.cpp b/test/val/val_ssa_test.cpp
index 5d8fa4b..e10abd7 100644
--- a/test/val/val_ssa_test.cpp
+++ b/test/val/val_ssa_test.cpp
@@ -823,7 +823,7 @@
     {"OpGetKernelWorkGroupSize", kNoNDrange},
     {"OpGetKernelPreferredWorkGroupSizeMultiple", kNoNDrange}};
 
-INSTANTIATE_TEST_CASE_P(KernelArgs, ValidateSSA, ::testing::ValuesIn(cases), );
+INSTANTIATE_TEST_SUITE_P(KernelArgs, ValidateSSA, ::testing::ValuesIn(cases));
 
 static const std::string return_instructions = R"(
   OpReturn
diff --git a/test/val/val_storage_test.cpp b/test/val/val_storage_test.cpp
index aa1eecd..c02b769 100644
--- a/test/val/val_storage_test.cpp
+++ b/test/val/val_storage_test.cpp
@@ -26,7 +26,10 @@
 namespace {
 
 using ::testing::HasSubstr;
+using ::testing::Values;
 using ValidateStorage = spvtest::ValidateBase<std::string>;
+using ValidateStorageClass =
+    spvtest::ValidateBase<std::tuple<std::string, bool, bool, std::string>>;
 
 TEST_F(ValidateStorage, FunctionStorageInsideFunction) {
   char str[] = R"(
@@ -137,7 +140,7 @@
       "Variables must have a function[7] storage class inside of a function"));
 }
 
-INSTANTIATE_TEST_CASE_P(MatrixOp, ValidateStorage,
+INSTANTIATE_TEST_SUITE_P(MatrixOp, ValidateStorage,
                         ::testing::Values(
                              "Input",
                              "Uniform",
@@ -147,7 +150,7 @@
                              "Private",
                              "PushConstant",
                              "AtomicCounter",
-                             "Image"),);
+                             "Image"));
 // clang-format on
 
 TEST_F(ValidateStorage, GenericVariableOutsideFunction) {
@@ -186,6 +189,131 @@
               HasSubstr("OpVariable storage class cannot be Generic"));
 }
 
+TEST_F(ValidateStorage, RelaxedLogicalPointerFunctionParam) {
+  const auto str = R"(
+          OpCapability Shader
+          OpCapability Linkage
+          OpMemoryModel Logical GLSL450
+%intt   = OpTypeInt 32 1
+%voidt  = OpTypeVoid
+%ptrt   = OpTypePointer Function %intt
+%vfunct = OpTypeFunction %voidt
+%vifunct = OpTypeFunction %voidt %ptrt
+%wgroupptrt = OpTypePointer Workgroup %intt
+%wgroup = OpVariable %wgroupptrt Workgroup
+%main   = OpFunction %voidt None %vfunct
+%mainl  = OpLabel
+%ret    = OpFunctionCall %voidt %func %wgroup
+          OpReturn
+          OpFunctionEnd
+%func   = OpFunction %voidt None %vifunct
+%arg    = OpFunctionParameter %ptrt
+%funcl  = OpLabel
+          OpReturn
+          OpFunctionEnd
+)";
+  CompileSuccessfully(str);
+  getValidatorOptions()->relax_logical_pointer = true;
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateStorage, RelaxedLogicalPointerFunctionParamBad) {
+  const auto str = R"(
+          OpCapability Shader
+          OpCapability Linkage
+          OpMemoryModel Logical GLSL450
+%floatt = OpTypeFloat 32
+%intt   = OpTypeInt 32 1
+%voidt  = OpTypeVoid
+%ptrt   = OpTypePointer Function %intt
+%vfunct = OpTypeFunction %voidt
+%vifunct = OpTypeFunction %voidt %ptrt
+%wgroupptrt = OpTypePointer Workgroup %floatt
+%wgroup = OpVariable %wgroupptrt Workgroup
+%main   = OpFunction %voidt None %vfunct
+%mainl  = OpLabel
+%ret    = OpFunctionCall %voidt %func %wgroup
+          OpReturn
+          OpFunctionEnd
+%func   = OpFunction %voidt None %vifunct
+%arg    = OpFunctionParameter %ptrt
+%funcl  = OpLabel
+          OpReturn
+          OpFunctionEnd
+)";
+  CompileSuccessfully(str);
+  getValidatorOptions()->relax_logical_pointer = true;
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpFunctionCall Argument <id> '"));
+}
+
+std::string GetVarDeclStr(const std::string& storage_class) {
+  if (storage_class != "Output" && storage_class != "Private" &&
+      storage_class != "Function") {
+    return "%var    = OpVariable %ptrt " + storage_class + "\n";
+  } else {
+    return "%var    = OpVariable %ptrt " + storage_class + " %null\n";
+  }
+}
+
+TEST_P(ValidateStorageClass, WebGPU) {
+  std::string storage_class = std::get<0>(GetParam());
+  bool is_local = std::get<1>(GetParam());
+  bool is_valid = std::get<2>(GetParam());
+  std::string error = std::get<3>(GetParam());
+
+  std::string str = R"(
+          OpCapability Shader
+          OpCapability VulkanMemoryModelKHR
+          OpExtension "SPV_KHR_vulkan_memory_model"
+          OpMemoryModel Logical VulkanKHR
+          OpEntryPoint Fragment %func "func"
+          OpExecutionMode %func OriginUpperLeft
+%intt   = OpTypeInt 32 1
+%voidt  = OpTypeVoid
+%vfunct = OpTypeFunction %voidt
+%null   = OpConstantNull %intt
+)";
+  str += "%ptrt   = OpTypePointer " + storage_class + " %intt\n";
+  if (!is_local) str += GetVarDeclStr(storage_class);
+  str += R"(
+%func   = OpFunction %voidt None %vfunct
+%funcl  = OpLabel
+)";
+  if (is_local) str += GetVarDeclStr(storage_class);
+  str += R"(
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(str, SPV_ENV_WEBGPU_0);
+  if (is_valid) {
+    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  } else {
+    ASSERT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions(SPV_ENV_WEBGPU_0));
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(error));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    StorageClass, ValidateStorageClass,
+    Values(std::make_tuple("UniformConstant", false, true, ""),
+           std::make_tuple("Uniform", false, true, ""),
+           std::make_tuple("StorageBuffer", false, true, ""),
+           std::make_tuple("Input", false, true, ""),
+           std::make_tuple("Output", false, true, ""),
+           std::make_tuple("Image", false, true, ""),
+           std::make_tuple("Workgroup", false, true, ""),
+           std::make_tuple("Private", false, true, ""),
+           std::make_tuple("Function", true, true, ""),
+           std::make_tuple(
+               "CrossWorkgroup", false, false,
+               "For WebGPU, OpTypePointer storage class must be one of"),
+           std::make_tuple(
+               "PushConstant", false, false,
+               "For WebGPU, OpTypePointer storage class must be one of")));
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_validation_state_test.cpp b/test/val/val_validation_state_test.cpp
index e010fe9..4581579 100644
--- a/test/val/val_validation_state_test.cpp
+++ b/test/val/val_validation_state_test.cpp
@@ -56,15 +56,16 @@
 %4 = OpTypeFunction %void
 %float = OpTypeFloat 32
 %_struct_6 = OpTypeStruct %float %float
+%null = OpConstantNull %_struct_6
 %7 = OpTypeFunction %_struct_6
 %12 = OpFunction %_struct_6 None %7
 %13 = OpLabel
-OpUnreachable
+OpReturnValue %null
 OpFunctionEnd
 %9 = OpFunction %_struct_6 None %7
 %10 = OpLabel
 %11 = OpFunctionCall %_struct_6 %12
-OpUnreachable
+OpReturnValue %null
 OpFunctionEnd
 %1 = OpFunction %void Pure|Const %4
 %8 = OpLabel
@@ -89,7 +90,7 @@
 %1 = OpFunction %void Pure|Const %4
 %8 = OpLabel
 %2 = OpFunctionCall %_struct_6 %9
-OpUnreachable
+OpReturn
 OpFunctionEnd
 )";
 
@@ -100,16 +101,17 @@
 %4 = OpTypeFunction %void
 %float = OpTypeFloat 32
 %_struct_6 = OpTypeStruct %float %float
+%null = OpConstantNull %_struct_6
 %7 = OpTypeFunction %_struct_6
 %9 = OpFunction %_struct_6 None %7
 %10 = OpLabel
 %11 = OpFunctionCall %_struct_6 %12
-OpUnreachable
+OpReturnValue %null
 OpFunctionEnd
 %12 = OpFunction %_struct_6 None %7
 %13 = OpLabel
 %14 = OpFunctionCall %_struct_6 %9
-OpUnreachable
+OpReturnValue %null
 OpFunctionEnd
 %1 = OpFunction %void Pure|Const %4
 %8 = OpLabel
@@ -303,7 +305,55 @@
             ValidateAndRetrieveValidationState(SPV_ENV_WEBGPU_0));
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("For WebGPU, functions need to be defined before being "
-                        "called.\n  %9 = OpFunctionCall %_struct_5 %10\n"));
+                        "called.\n  %10 = OpFunctionCall %_struct_5 %11\n"));
+}
+
+TEST_F(ValidationStateTest,
+       CheckWebGPUDuplicateEntryNamesDifferentFunctionsBad) {
+  std::string spirv = std::string(kVulkanMemoryHeader) + R"(
+OpEntryPoint Fragment %func_1 "main"
+OpEntryPoint Vertex %func_2 "main"
+OpExecutionMode %func_1 OriginUpperLeft
+%void    = OpTypeVoid
+%void_f  = OpTypeFunction %void
+%func_1  = OpFunction %void None %void_f
+%label_1 = OpLabel
+           OpReturn
+           OpFunctionEnd
+%func_2  = OpFunction %void None %void_f
+%label_2 = OpLabel
+           OpReturn
+           OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
+            ValidateAndRetrieveValidationState(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Entry point name \"main\" is not unique, which is not allow "
+                "in WebGPU env.\n  %1 = OpFunction %void None %4\n"));
+}
+
+TEST_F(ValidationStateTest, CheckWebGPUDuplicateEntryNamesSameFunctionBad) {
+  std::string spirv = std::string(kVulkanMemoryHeader) + R"(
+OpEntryPoint GLCompute %func_1 "main"
+OpEntryPoint Vertex %func_1 "main"
+%void    = OpTypeVoid
+%void_f  = OpTypeFunction %void
+%func_1  = OpFunction %void None %void_f
+%label_1 = OpLabel
+           OpReturn
+           OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
+            ValidateAndRetrieveValidationState(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Entry point name \"main\" is not unique, which is not allow "
+                "in WebGPU env.\n  %1 = OpFunction %void None %3\n"));
 }
 
 }  // namespace
diff --git a/test/val/val_version_test.cpp b/test/val/val_version_test.cpp
index eb9bbb9..70eb9b1 100644
--- a/test/val/val_version_test.cpp
+++ b/test/val/val_version_test.cpp
@@ -86,6 +86,9 @@
     case SPV_ENV_VULKAN_1_1:
     case SPV_ENV_WEBGPU_0:
       return "1.3";
+    case SPV_ENV_UNIVERSAL_1_4:
+    case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
+      return "1.4";
     default:
       return "0";
   }
@@ -108,7 +111,7 @@
 }
 
 // clang-format off
-INSTANTIATE_TEST_CASE_P(Universal, ValidateVersion,
+INSTANTIATE_TEST_SUITE_P(Universal, ValidateVersion,
   ::testing::Values(
     //         Binary version,        Target environment
     std::make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_0, vulkan_spirv, true),
@@ -165,7 +168,7 @@
   )
 );
 
-INSTANTIATE_TEST_CASE_P(Vulkan, ValidateVersion,
+INSTANTIATE_TEST_SUITE_P(Vulkan, ValidateVersion,
   ::testing::Values(
     //         Binary version,        Target environment
     std::make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_UNIVERSAL_1_0, vulkan_spirv, true),
@@ -179,6 +182,7 @@
     std::make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_OPENGL_4_2,    vulkan_spirv, true),
     std::make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_OPENGL_4_3,    vulkan_spirv, true),
     std::make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_OPENGL_4_5,    vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_VULKAN_1_1_SPIRV_1_4, vulkan_spirv, true),
 
     std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_UNIVERSAL_1_0, vulkan_spirv, false),
     std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_UNIVERSAL_1_1, vulkan_spirv, false),
@@ -190,11 +194,12 @@
     std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_OPENGL_4_1,    vulkan_spirv, false),
     std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_OPENGL_4_2,    vulkan_spirv, false),
     std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_OPENGL_4_3,    vulkan_spirv, false),
-    std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_OPENGL_4_5,    vulkan_spirv, false)
+    std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_OPENGL_4_5,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_VULKAN_1_1_SPIRV_1_4, vulkan_spirv, true)
   )
 );
 
-INSTANTIATE_TEST_CASE_P(OpenCL, ValidateVersion,
+INSTANTIATE_TEST_SUITE_P(OpenCL, ValidateVersion,
   ::testing::Values(
     //         Binary version,     Target environment
     std::make_tuple(SPV_ENV_OPENCL_2_0, SPV_ENV_UNIVERSAL_1_0,       opencl_spirv, true),
@@ -247,7 +252,7 @@
   )
 );
 
-INSTANTIATE_TEST_CASE_P(OpenCLEmbedded, ValidateVersion,
+INSTANTIATE_TEST_SUITE_P(OpenCLEmbedded, ValidateVersion,
   ::testing::Values(
     //         Binary version,              Target environment
     std::make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_0, SPV_ENV_UNIVERSAL_1_0,       opencl_spirv, true),
diff --git a/test/val/val_webgpu_test.cpp b/test/val/val_webgpu_test.cpp
index 48ea21d..c55f927 100644
--- a/test/val/val_webgpu_test.cpp
+++ b/test/val/val_webgpu_test.cpp
@@ -276,6 +276,63 @@
                         "\"SPV_KHR_8bit_storage\"\n"));
 }
 
+spv_binary GenerateTrivialBinary(bool need_little_endian) {
+  // Smallest possible valid WebGPU SPIR-V binary in little endian. Contains all
+  // the required boilerplate and a trivial entry point function.
+  static const uint8_t binary_bytes[] = {
+      // clang-format off
+    0x03, 0x02, 0x23, 0x07, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00,
+    0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00,
+    0x01, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0xE1, 0x14, 0x00, 0x00,
+    0x0A, 0x00, 0x08, 0x00, 0x53, 0x50, 0x56, 0x5F, 0x4B, 0x48, 0x52, 0x5F,
+    0x76, 0x75, 0x6C, 0x6B, 0x61, 0x6E, 0x5F, 0x6D, 0x65, 0x6D, 0x6F, 0x72,
+    0x79, 0x5F, 0x6D, 0x6F, 0x64, 0x65, 0x6C, 0x00, 0x0E, 0x00, 0x03, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x05, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x73, 0x68, 0x61, 0x64,
+    0x65, 0x72, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
+    0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+    0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00,
+    0x04, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00
+      // clang-format on
+  };
+  static const size_t word_count = sizeof(binary_bytes) / sizeof(uint32_t);
+  std::unique_ptr<spv_binary_t> result(new spv_binary_t);
+  if (!result) return nullptr;
+
+  result->wordCount = word_count;
+  result->code = new uint32_t[word_count];
+  if (!result->code) return nullptr;
+
+  if (need_little_endian) {
+    memcpy(result->code, binary_bytes, sizeof(binary_bytes));
+  } else {
+    uint8_t* code_bytes = reinterpret_cast<uint8_t*>(result->code);
+    for (size_t word = 0; word < word_count; ++word) {
+      code_bytes[4 * word] = binary_bytes[4 * word + 3];
+      code_bytes[4 * word + 1] = binary_bytes[4 * word + 2];
+      code_bytes[4 * word + 2] = binary_bytes[4 * word + 1];
+      code_bytes[4 * word + 3] = binary_bytes[4 * word];
+    }
+  }
+
+  return result.release();
+}
+
+TEST_F(ValidateWebGPU, LittleEndianGood) {
+  DestroyBinary();
+  binary_ = GenerateTrivialBinary(true);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
+TEST_F(ValidateWebGPU, BigEndianBad) {
+  DestroyBinary();
+  binary_ = GenerateTrivialBinary(false);
+  EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("WebGPU requires SPIR-V to be little endian."));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
index 9fb3a91..bce6f1b 100644
--- a/tools/CMakeLists.txt
+++ b/tools/CMakeLists.txt
@@ -12,7 +12,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-add_subdirectory(lesspipe)
+if (NOT ${SPIRV_SKIP_EXECUTABLES})
+ add_subdirectory(lesspipe)
+endif()
 add_subdirectory(emacs)
 
 # Add a SPIR-V Tools command line tool. Signature:
@@ -42,7 +44,9 @@
   add_spvtools_tool(TARGET spirv-dis SRCS dis/dis.cpp LIBS ${SPIRV_TOOLS})
   add_spvtools_tool(TARGET spirv-val SRCS val/val.cpp util/cli_consumer.cpp LIBS ${SPIRV_TOOLS})
   add_spvtools_tool(TARGET spirv-opt SRCS opt/opt.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS})
-  add_spvtools_tool(TARGET spirv-reduce SRCS reduce/reduce.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-reduce ${SPIRV_TOOLS})
+  if (NOT DEFINED IOS_PLATFORM) # iOS does not allow std::system calls which spirv-reduce requires
+    add_spvtools_tool(TARGET spirv-reduce SRCS reduce/reduce.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-reduce ${SPIRV_TOOLS})
+  endif()
   add_spvtools_tool(TARGET spirv-link SRCS link/linker.cpp LIBS SPIRV-Tools-link ${SPIRV_TOOLS})
   add_spvtools_tool(TARGET spirv-stats
 	            SRCS stats/stats.cpp
@@ -62,7 +66,10 @@
                                                  ${SPIRV_HEADER_INCLUDE_DIR})
 
   set(SPIRV_INSTALL_TARGETS spirv-as spirv-dis spirv-val spirv-opt spirv-stats
-                            spirv-cfg spirv-link spirv-reduce)
+                            spirv-cfg spirv-link)
+  if(NOT DEFINED IOS_PLATFORM)
+    set(SPIRV_INSTALL_TARGETS ${SPIRV_INSTALL_TARGETS} spirv-reduce)
+  endif()
 
   if(SPIRV_BUILD_COMPRESSION)
     add_spvtools_tool(TARGET spirv-markv
diff --git a/tools/as/as.cpp b/tools/as/as.cpp
index 287ba51..1568579 100644
--- a/tools/as/as.cpp
+++ b/tools/as/as.cpp
@@ -41,14 +41,14 @@
                   Numeric IDs in the binary will have the same values as in the
                   source. Non-numeric IDs are allocated by filling in the gaps,
                   starting with 1 and going up.
-  --target-env {vulkan1.0|vulkan1.1|spv1.0|spv1.1|spv1.2|spv1.3}
+  --target-env {vulkan1.0|vulkan1.1|spv1.0|spv1.1|spv1.2|spv1.3|spv1.4}
                   Use Vulkan 1.0, Vulkan 1.1, SPIR-V 1.0, SPIR-V 1.1,
-                  SPIR-V 1.2, or SPIR-V 1.3
+                  SPIR-V 1.2, SPIR-V 1.3, or SPIR-V 1.4
 )",
       argv0, argv0);
 }
 
-static const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_3;
+static const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_4;
 
 int main(int argc, char** argv) {
   const char* inFile = nullptr;
diff --git a/tools/cfg/cfg.cpp b/tools/cfg/cfg.cpp
index 9e2c448..411ef88 100644
--- a/tools/cfg/cfg.cpp
+++ b/tools/cfg/cfg.cpp
@@ -44,7 +44,7 @@
       argv0, argv0);
 }
 
-static const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_2;
+static const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_4;
 
 int main(int argc, char** argv) {
   const char* inFile = nullptr;
diff --git a/tools/dis/dis.cpp b/tools/dis/dis.cpp
index 6a2e269..2a6f411 100644
--- a/tools/dis/dis.cpp
+++ b/tools/dis/dis.cpp
@@ -60,7 +60,7 @@
       argv0, argv0);
 }
 
-static const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_3;
+static const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_4;
 
 int main(int argc, char** argv) {
   const char* inFile = nullptr;
diff --git a/tools/lesspipe/spirv-lesspipe.sh b/tools/lesspipe/spirv-lesspipe.sh
index 81e3355..57684a2 100644
--- a/tools/lesspipe/spirv-lesspipe.sh
+++ b/tools/lesspipe/spirv-lesspipe.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env sh
 # Copyright (c) 2016 The Khronos Group Inc.
 
 # Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/tools/link/linker.cpp b/tools/link/linker.cpp
index fb44a37..3508b13 100644
--- a/tools/link/linker.cpp
+++ b/tools/link/linker.cpp
@@ -39,8 +39,9 @@
   --allow-partial-linkage Allow partial linkage by accepting imported symbols to be unresolved.
   --verify-ids            Verify that IDs in the resulting modules are truly unique.
   --version               Display linker version information
-  --target-env            {vulkan1.0|spv1.0|spv1.1|spv1.2|opencl2.1|opencl2.2}
-                          Use Vulkan1.0/SPIR-V1.0/SPIR-V1.1/SPIR-V1.2/OpenCL-2.1/OpenCL2.2 validation rules.
+  --target-env            {vulkan1.0|vulkan1.1|spv1.0|spv1.1|spv1.2|spv1.3|spv1.4|opencl2.1|opencl2.2}
+                          Use Vulkan 1.0, Vulkan 1.1, SPIR-V 1.0, SPIR-V 1.1, SPIR-V 1.2, SPIR-V 1.3,
+                          SPIR-V1.4, OpenCL 2.1, OpenCL 2.2 validation rules.
 )",
       argv0, argv0);
 }
diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp
index 0e8088f..3e57980 100644
--- a/tools/opt/opt.cpp
+++ b/tools/opt/opt.cpp
@@ -59,7 +59,7 @@
   return ss.str();
 }
 
-const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_3;
+const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_4;
 
 std::string GetLegalizationPasses() {
   spvtools::Optimizer optimizer(kDefaultEnvironment);
@@ -79,6 +79,18 @@
   return GetListOfPassesAsString(optimizer);
 }
 
+std::string GetVulkanToWebGPUPasses() {
+  spvtools::Optimizer optimizer(SPV_ENV_WEBGPU_0);
+  optimizer.RegisterVulkanToWebGPUPasses();
+  return GetListOfPassesAsString(optimizer);
+}
+
+std::string GetWebGPUToVulkanPasses() {
+  spvtools::Optimizer optimizer(SPV_ENV_VULKAN_1_1);
+  optimizer.RegisterWebGPUToVulkanPasses();
+  return GetListOfPassesAsString(optimizer);
+}
+
 void PrintUsage(const char* program) {
   // NOTE: Please maintain flags in lexicographical order.
   printf(
@@ -93,91 +105,119 @@
 
 NOTE: The optimizer is a work in progress.
 
-Options (in lexicographical order):
+Options (in lexicographical order):)",
+      program, program);
+  printf(R"(
   --ccp
                Apply the conditional constant propagation transform.  This will
                propagate constant values throughout the program, and simplify
                expressions and conditional jumps with known predicate
                values.  Performed on entry point call tree functions and
-               exported functions.
+               exported functions.)");
+  printf(R"(
   --cfg-cleanup
                Cleanup the control flow graph. This will remove any unnecessary
                code from the CFG like unreachable code. Performed on entry
-               point call tree functions and exported functions.
+               point call tree functions and exported functions.)");
+  printf(R"(
   --combine-access-chains
                Combines chained access chains to produce a single instruction
-               where possible.
+               where possible.)");
+  printf(R"(
   --compact-ids
                Remap result ids to a compact range starting from %%1 and without
-               any gaps.
+               any gaps.)");
+  printf(R"(
   --convert-local-access-chains
                Convert constant index access chain loads/stores into
                equivalent load/stores with inserts and extracts. Performed
                on function scope variables referenced only with load, store,
                and constant index access chains in entry point call tree
-               functions.
+               functions.)");
+  printf(R"(
   --copy-propagate-arrays
                Does propagation of memory references when an array is a copy of
                another.  It will only propagate an array if the source is never
-               written to, and the only store to the target is the copy.
+               written to, and the only store to the target is the copy.)");
+  printf(R"(
   --eliminate-common-uniform
                Perform load/load elimination for duplicate uniform values.
                Converts any constant index access chain uniform loads into
                its equivalent load and extract. Some loads will be moved
                to facilitate sharing. Performed only on entry point
-               call tree functions.
+               call tree functions.)");
+  printf(R"(
   --eliminate-dead-branches
                Convert conditional branches with constant condition to the
                indicated unconditional brranch. Delete all resulting dead
-               code. Performed only on entry point call tree functions.
+               code. Performed only on entry point call tree functions.)");
+  printf(R"(
   --eliminate-dead-code-aggressive
                Delete instructions which do not contribute to a function's
-               output. Performed only on entry point call tree functions.
+               output. Performed only on entry point call tree functions.)");
+  printf(R"(
   --eliminate-dead-const
-               Eliminate dead constants.
+               Eliminate dead constants.)");
+  printf(R"(
   --eliminate-dead-functions
                Deletes functions that cannot be reached from entry points or
-               exported functions.
+               exported functions.)");
+  printf(R"(
   --eliminate-dead-inserts
                Deletes unreferenced inserts into composites, most notably
                unused stores to vector components, that are not removed by
-               aggressive dead code elimination.
+               aggressive dead code elimination.)");
+  printf(R"(
   --eliminate-dead-variables
-               Deletes module scope variables that are not referenced.
+               Deletes module scope variables that are not referenced.)");
+  printf(R"(
   --eliminate-insert-extract
                DEPRECATED.  This pass has been replaced by the simplification
                pass, and that pass will be run instead.
-               See --simplify-instructions.
+               See --simplify-instructions.)");
+  printf(R"(
   --eliminate-local-multi-store
                Replace stores and loads of function scope variables that are
                stored multiple times. Performed on variables referenceed only
                with loads and stores. Performed only on entry point call tree
-               functions.
+               functions.)");
+  printf(R"(
   --eliminate-local-single-block
                Perform single-block store/load and load/load elimination.
                Performed only on function scope variables in entry point
-               call tree functions.
+               call tree functions.)");
+  printf(R"(
   --eliminate-local-single-store
                Replace stores and loads of function scope variables that are
                only stored once. Performed on variables referenceed only with
                loads and stores. Performed only on entry point call tree
-               functions.
+               functions.)");
+  printf(R"(
   --flatten-decorations
                Replace decoration groups with repeated OpDecorate and
-               OpMemberDecorate instructions.
+               OpMemberDecorate instructions.)");
+  printf(R"(
   --fold-spec-const-op-composite
                Fold the spec constants defined by OpSpecConstantOp or
                OpSpecConstantComposite instructions to front-end constants
-               when possible.
+               when possible.)");
+  printf(R"(
   --freeze-spec-const
                Freeze the values of specialization constants to their default
-               values.
+               values.)");
+  printf(R"(
+  --generate-webgpu-initializers
+               Adds initial values to OpVariable instructions that are missing
+               them, due to their storage type requiring them for WebGPU.)");
+  printf(R"(
   --if-conversion
-               Convert if-then-else like assignments into OpSelect.
+               Convert if-then-else like assignments into OpSelect.)");
+  printf(R"(
   --inline-entry-points-exhaustive
                Exhaustively inline all function calls in entry point call tree
                functions. Currently does not inline calls to functions with
-               early return in a loop.
+               early return in a loop.)");
+  printf(R"(
   --legalize-hlsl
                Runs a series of optimizations that attempts to take SPIR-V
                generated by an HLSL front-end and generates legal Vulkan SPIR-V.
@@ -185,46 +225,58 @@
                %s
 
                Note this does not guarantee legal code. This option passes the
-               option --relax-logical-pointer to the validator.
+               option --relax-logical-pointer to the validator.)",
+         GetLegalizationPasses().c_str());
+  printf(R"(
   --local-redundancy-elimination
                Looks for instructions in the same basic block that compute the
-               same value, and deletes the redundant ones.
+               same value, and deletes the redundant ones.)");
+  printf(R"(
   --loop-fission
                Splits any top level loops in which the register pressure has
                exceeded a given threshold. The threshold must follow the use of
-               this flag and must be a positive integer value.
+               this flag and must be a positive integer value.)");
+  printf(R"(
   --loop-fusion
                Identifies adjacent loops with the same lower and upper bound.
                If this is legal, then merge the loops into a single loop.
                Includes heuristics to ensure it does not increase number of
                registers too much, while reducing the number of loads from
                memory. Takes an additional positive integer argument to set
-               the maximum number of registers.
+               the maximum number of registers.)");
+  printf(R"(
   --loop-invariant-code-motion
                Identifies code in loops that has the same value for every
-               iteration of the loop, and move it to the loop pre-header.
+               iteration of the loop, and move it to the loop pre-header.)");
+  printf(R"(
   --loop-unroll
-               Fully unrolls loops marked with the Unroll flag
+               Fully unrolls loops marked with the Unroll flag)");
+  printf(R"(
   --loop-unroll-partial
                Partially unrolls loops marked with the Unroll flag. Takes an
                additional non-0 integer argument to set the unroll factor, or
-               how many times a loop body should be duplicated
+               how many times a loop body should be duplicated)");
+  printf(R"(
   --loop-peeling
                Execute few first (respectively last) iterations before
-               (respectively after) the loop if it can elide some branches.
+               (respectively after) the loop if it can elide some branches.)");
+  printf(R"(
   --loop-peeling-threshold
                Takes a non-0 integer argument to set the loop peeling code size
                growth threshold. The threshold prevents the loop peeling
                from happening if the code size increase created by
-               the optimization is above the threshold.
+               the optimization is above the threshold.)");
+  printf(R"(
   --max-id-bound=<n>
                Sets the maximum value for the id bound for the moudle.  The
                default is the minimum value for this limit, 0x3FFFFF.  See
-               section 2.17 of the Spir-V specification.
+               section 2.17 of the Spir-V specification.)");
+  printf(R"(
   --merge-blocks
                Join two blocks into a single block if the second has the
                first as its only predecessor. Performed only on entry point
-               call tree functions.
+               call tree functions.)");
+  printf(R"(
   --merge-return
                Changes functions that have multiple return statements so they
                have a single return statement.
@@ -238,17 +290,21 @@
                label and an OpBranch to the header, nothing else.
 
                These conditions are guaranteed to be met after running
-               dead-branch elimination.
+               dead-branch elimination.)");
+  printf(R"(
   --loop-unswitch
                Hoists loop-invariant conditionals out of loops by duplicating
                the loop on each branch of the conditional and adjusting each
-               copy of the loop.
+               copy of the loop.)");
+  printf(R"(
   -O
                Optimize for performance. Apply a sequence of transformations
                in an attempt to improve the performance of the generated
                code. For this version of the optimizer, this flag is equivalent
                to specifying the following optimization code names:
-               %s
+               %s)",
+         GetOptimizationPasses().c_str());
+  printf(R"(
   -Os
                Optimize for size. Apply a sequence of transformations in an
                attempt to minimize the size of the generated code. For this
@@ -257,7 +313,9 @@
                %s
 
                NOTE: The specific transformations done by -O and -Os change
-                     from release to release.
+                     from release to release.)",
+         GetSizePasses().c_str());
+  printf(R"(
   -Oconfig=<file>
                Apply the sequence of transformations indicated in <file>.
                This file contains a sequence of strings separated by whitespace
@@ -285,93 +343,148 @@
                that position in the command line. For example, the invocation
                'spirv-opt --merge-blocks -O ...' applies the transformation
                --merge-blocks followed by all the transformations implied by
-               -O.
+               -O.)");
+  printf(R"(
   --print-all
                Print SPIR-V assembly to standard error output before each pass
-               and after the last pass.
+               and after the last pass.)");
+  printf(R"(
   --private-to-local
                Change the scope of private variables that are used in a single
-               function to that function.
+               function to that function.)");
+  printf(R"(
   --reduce-load-size
                Replaces loads of composite objects where not every component is
-               used by loads of just the elements that are used.
+               used by loads of just the elements that are used.)");
+  printf(R"(
   --redundancy-elimination
                Looks for instructions in the same function that compute the
-               same value, and deletes the redundant ones.
+               same value, and deletes the redundant ones.)");
+  printf(R"(
   --relax-struct-store
                Allow store from one struct type to a different type with
                compatible layout and members. This option is forwarded to the
-               validator.
+               validator.)");
+  printf(R"(
   --remove-duplicates
                Removes duplicate types, decorations, capabilities and extension
-               instructions.
+               instructions.)");
+  printf(R"(
   --replace-invalid-opcode
                Replaces instructions whose opcode is valid for shader modules,
                but not for the current shader stage.  To have an effect, all
-               entry points must have the same execution model.
+               entry points must have the same execution model.)");
+  printf(R"(
   --ssa-rewrite
                Replace loads and stores to function local variables with
-               operations on SSA IDs.
+               operations on SSA IDs.)");
+  printf(R"(
   --scalar-replacement[=<n>]
                Replace aggregate function scope variables that are only accessed
                via their elements with new function variables representing each
                element.  <n> is a limit on the size of the aggragates that will
                be replaced.  0 means there is no limit.  The default value is
-               100.
+               100.)");
+  printf(R"(
   --set-spec-const-default-value "<spec id>:<default value> ..."
                Set the default values of the specialization constants with
                <spec id>:<default value> pairs specified in a double-quoted
                string. <spec id>:<default value> pairs must be separated by
                blank spaces, and in each pair, spec id and default value must
                be separated with colon ':' without any blank spaces in between.
-               e.g.: --set-spec-const-default-value "1:100 2:400"
+               e.g.: --set-spec-const-default-value "1:100 2:400")");
+  printf(R"(
   --simplify-instructions
                Will simplify all instructions in the function as much as
-               possible.
+               possible.)");
+  printf(R"(
   --skip-validation
                Will not validate the SPIR-V before optimizing.  If the SPIR-V
                is invalid, the optimizer may fail or generate incorrect code.
-               This options should be used rarely, and with caution.
+               This options should be used rarely, and with caution.)");
+  printf(R"(
   --strength-reduction
-               Replaces instructions with equivalent and less expensive ones.
+               Replaces instructions with equivalent and less expensive ones.)");
+  printf(R"(
+  --strip-atomic-counter-memory
+               Removes AtomicCountMemory bit from memory semantics values.)");
+  printf(R"(
   --strip-debug
-               Remove all debug instructions.
+               Remove all debug instructions.)");
+  printf(R"(
   --strip-reflect
                Remove all reflection information.  For now, this covers
-               reflection information defined by SPV_GOOGLE_hlsl_functionality1.
+               reflection information defined by SPV_GOOGLE_hlsl_functionality1.)");
+  printf(R"(
   --target-env=<env>
                Set the target environment. Without this flag the target
                enviroment defaults to spv1.3.
                <env> must be one of vulkan1.0, vulkan1.1, opencl2.2, spv1.0,
-               spv1.1, spv1.2, spv1.3, or webgpu0.
+               spv1.1, spv1.2, spv1.3, or webgpu0.)");
+  printf(R"(
   --time-report
                Print the resource utilization of each pass (e.g., CPU time,
                RSS) to standard error output. Currently it supports only Unix
                systems. This option is the same as -ftime-report in GCC. It
                prints CPU/WALL/USR/SYS time (and RSS if possible), but note that
                USR/SYS time are returned by getrusage() and can have a small
-               error.
+               error.)");
+  printf(R"(
   --upgrade-memory-model
                Upgrades the Logical GLSL450 memory model to Logical VulkanKHR.
                Transforms memory, image, atomic and barrier operations to conform
-               to that model's requirements.
+               to that model's requirements.)");
+  printf(R"(
   --vector-dce
                This pass looks for components of vectors that are unused, and
                removes them from the vector.  Note this would still leave around
-               lots of dead code that a pass of ADCE will be able to remove.
+               lots of dead code that a pass of ADCE will be able to remove.)");
+  printf(R"(
+  --vulkan-to-webgpu
+               Turns on the prescribed passes for converting from Vulkan to
+               WebGPU and sets the target environment to webgpu0. Other passes
+               may be turned on via additional flags, but such combinations are
+               not tested.
+               Using --target-env with this flag is not allowed.
+
+               This flag is the equivalent of passing in --target-env=webgpu0
+               and specifying the following optimization code names:
+               %s
+
+               NOTE: This flag is a WIP and its behaviour is subject to change.)",
+         GetVulkanToWebGPUPasses().c_str());
+  printf(R"(
+  --webgpu-to-vulkan
+               Turns on the prescribed passes for converting from WebGPU to
+               Vulkan and sets the target environment to vulkan1.1. Other passes
+               may be turned on via additional flags, but such combinations are
+               not tested.
+               Using --target-env with this flag is not allowed.
+
+               This flag is the equivalent of passing in --target-env=vulkan1.1
+               and specifying the following optimization code names:
+               %s
+
+               NOTE: This flag is a WIP and its behaviour is subject to change.)",
+         GetWebGPUToVulkanPasses().c_str());
+  printf(R"(
   --workaround-1209
                Rewrites instructions for which there are known driver bugs to
                avoid triggering those bugs.
-               Current workarounds: Avoid OpUnreachable in loops.
+               Current workarounds: Avoid OpUnreachable in loops.)");
+  printf(R"(
   --unify-const
-               Remove the duplicated constants.
+               Remove the duplicated constants.)");
+  printf(R"(
+  --validate-after-all
+               Validate the module after each pass is performed.)");
+  printf(R"(
   -h, --help
-               Print this help.
+               Print this help.)");
+  printf(R"(
   --version
                Display optimizer version information.
-)",
-      program, program, GetLegalizationPasses().c_str(),
-      GetOptimizationPasses().c_str(), GetSizePasses().c_str());
+)");
 }
 
 // Reads command-line flags  the file specified in |oconfig_flag|. This string
@@ -429,12 +542,15 @@
 // Parses and handles the -Oconfig flag. |prog_name| contains the name of
 // the spirv-opt binary (used to build a new argv vector for the recursive
 // invocation to ParseFlags). |opt_flag| contains the -Oconfig=FILENAME flag.
-// |optimizer|, |in_file| and |out_file| are as in ParseFlags.
+// |optimizer|, |in_file|, |out_file|, |validator_options|, and
+// |optimizer_options| are as in ParseFlags.
 //
 // This returns the same OptStatus instance returned by ParseFlags.
 OptStatus ParseOconfigFlag(const char* prog_name, const char* opt_flag,
                            spvtools::Optimizer* optimizer, const char** in_file,
-                           const char** out_file) {
+                           const char** out_file,
+                           spvtools::ValidatorOptions* validator_options,
+                           spvtools::OptimizerOptions* optimizer_options) {
   std::vector<std::string> flags;
   flags.push_back(prog_name);
 
@@ -457,8 +573,11 @@
     new_argv[i] = flags[i].c_str();
   }
 
-  return ParseFlags(static_cast<int>(flags.size()), new_argv, optimizer,
-                    in_file, out_file, nullptr, nullptr);
+  auto ret_val =
+      ParseFlags(static_cast<int>(flags.size()), new_argv, optimizer, in_file,
+                 out_file, validator_options, optimizer_options);
+  delete[] new_argv;
+  return ret_val;
 }
 
 // Canonicalize the flag in |argv[argi]| of the form '--pass arg' into
@@ -515,6 +634,9 @@
                      spvtools::ValidatorOptions* validator_options,
                      spvtools::OptimizerOptions* optimizer_options) {
   std::vector<std::string> pass_flags;
+  bool target_env_set = false;
+  bool vulkan_to_webgpu_set = false;
+  bool webgpu_to_vulkan_set = false;
   for (int argi = 1; argi < argc; ++argi) {
     const char* cur_arg = argv[argi];
     if ('-' == cur_arg[0]) {
@@ -543,7 +665,8 @@
         }
       } else if (0 == strncmp(cur_arg, "-Oconfig=", sizeof("-Oconfig=") - 1)) {
         OptStatus status =
-            ParseOconfigFlag(argv[0], cur_arg, optimizer, in_file, out_file);
+            ParseOconfigFlag(argv[0], cur_arg, optimizer, in_file, out_file,
+                             validator_options, optimizer_options);
         if (status.action != OPT_CONTINUE) {
           return status;
         }
@@ -574,6 +697,18 @@
                                              max_id_bound);
       } else if (0 == strncmp(cur_arg,
                               "--target-env=", sizeof("--target-env=") - 1)) {
+        if (vulkan_to_webgpu_set) {
+          spvtools::Error(opt_diagnostic, nullptr, {},
+                          "Cannot use both --vulkan-to-webgpu and --target-env "
+                          "at the same time");
+          return {OPT_STOP, 1};
+        }
+        if (webgpu_to_vulkan_set) {
+          spvtools::Error(opt_diagnostic, nullptr, {},
+                          "Cannot use both --webgpu-to-vulkan and --target-env "
+                          "at the same time");
+          return {OPT_STOP, 1};
+        }
         const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
         const auto target_env_str = split_flag.second.c_str();
         spv_target_env target_env;
@@ -583,6 +718,40 @@
           return {OPT_STOP, 1};
         }
         optimizer->SetTargetEnv(target_env);
+      } else if (0 == strcmp(cur_arg, "--vulkan-to-webgpu")) {
+        if (target_env_set) {
+          spvtools::Error(opt_diagnostic, nullptr, {},
+                          "Cannot use both --vulkan-to-webgpu and --target-env "
+                          "at the same time");
+          return {OPT_STOP, 1};
+        }
+        if (webgpu_to_vulkan_set) {
+          spvtools::Error(opt_diagnostic, nullptr, {},
+                          "Cannot use both --vulkan-to-webgpu and "
+                          "--webgpu-to-vulkan at the same time");
+          return {OPT_STOP, 1};
+        }
+
+        optimizer->SetTargetEnv(SPV_ENV_WEBGPU_0);
+        optimizer->RegisterVulkanToWebGPUPasses();
+      } else if (0 == strcmp(cur_arg, "--webgpu-to-vulkan")) {
+        if (target_env_set) {
+          spvtools::Error(opt_diagnostic, nullptr, {},
+                          "Cannot use both --webgpu-to-vulkan and --target-env "
+                          "at the same time");
+          return {OPT_STOP, 1};
+        }
+        if (vulkan_to_webgpu_set) {
+          spvtools::Error(opt_diagnostic, nullptr, {},
+                          "Cannot use both --webgpu-to-vulkan and "
+                          "--vulkan-to-webgpu at the same time");
+          return {OPT_STOP, 1};
+        }
+
+        optimizer->SetTargetEnv(SPV_ENV_VULKAN_1_1);
+        optimizer->RegisterWebGPUToVulkanPasses();
+      } else if (0 == strcmp(cur_arg, "--validate-after-all")) {
+        optimizer->SetValidateAfterAll(true);
       } else {
         // Some passes used to accept the form '--pass arg', canonicalize them
         // to '--pass=arg'.
@@ -620,7 +789,6 @@
 
   spv_target_env target_env = kDefaultEnvironment;
 
-
   spvtools::Optimizer optimizer(target_env);
   optimizer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
 
diff --git a/tools/reduce/reduce.cpp b/tools/reduce/reduce.cpp
index 390724a..55d0983 100644
--- a/tools/reduce/reduce.cpp
+++ b/tools/reduce/reduce.cpp
@@ -20,21 +20,12 @@
 #include "source/opt/build_module.h"
 #include "source/opt/ir_context.h"
 #include "source/opt/log.h"
-#include "source/reduce/operand_to_const_reduction_pass.h"
-#include "source/reduce/operand_to_dominating_id_reduction_pass.h"
 #include "source/reduce/reducer.h"
-#include "source/reduce/remove_opname_instruction_reduction_pass.h"
-#include "source/reduce/remove_unreferenced_instruction_reduction_pass.h"
-#include "source/reduce/structured_loop_to_selection_reduction_pass.h"
 #include "source/spirv_reducer_options.h"
-#include "source/util/make_unique.h"
 #include "source/util/string_utils.h"
-#include "spirv-tools/libspirv.hpp"
 #include "tools/io.h"
 #include "tools/util/cli_consumer.h"
 
-using namespace spvtools::reduce;
-
 namespace {
 
 using ErrorOrInt = std::pair<std::string, int>;
@@ -76,18 +67,48 @@
 The SPIR-V binary is read from <input>.
 
 Whether a binary is interesting is determined by <interestingness-test>, which
-is typically a script.
+should be the path to a script.
+
+ * The script must be executable.
+
+ * The script should take the path to a SPIR-V binary file (.spv) as its single
+   argument, and exit with code 0 if and only if the binary file is
+   interesting.
+
+ * Example: an interestingness test for reducing a SPIR-V binary file that
+   causes tool "foo" to exit with error code 1 and print "Fatal error: bar" to
+   standard error should:
+     - invoke "foo" on the binary passed as the script argument;
+     - capture the return code and standard error from "bar";
+     - exit with code 0 if and only if the return code of "foo" was 1 and the
+       standard error from "bar" contained "Fatal error: bar".
+
+ * The reducer does not place a time limit on how long the interestingness test
+   takes to run, so it is advisable to use per-command timeouts inside the
+   script when invoking SPIR-V-processing tools (such as "foo" in the above
+   example).
 
 NOTE: The reducer is a work in progress.
 
 Options (in lexicographical order):
+
+  --fail-on-validation-error
+               Stop reduction with an error if any reduction step produces a
+               SPIR-V module that fails to validate.
   -h, --help
                Print this help.
   --step-limit
-               32-bit unsigned integer specifying maximum number of
-               steps the reducer will take before giving up.
+               32-bit unsigned integer specifying maximum number of steps the
+               reducer will take before giving up.
   --version
                Display reducer version information.
+
+Supported validator options are as follows. See `spirv-val --help` for details.
+  --relax-logical-pointer
+  --relax-block-layout
+  --scalar-block-layout
+  --skip-block-layout
+  --relax-struct-store
 )",
       program, program);
 }
@@ -105,7 +126,8 @@
 
 ReduceStatus ParseFlags(int argc, const char** argv, const char** in_file,
                         const char** interestingness_test,
-                        spvtools::ReducerOptions* reducer_options) {
+                        spvtools::ReducerOptions* reducer_options,
+                        spvtools::ValidatorOptions* validator_options) {
   uint32_t positional_arg_index = 0;
 
   for (int argi = 1; argi < argc; ++argi) {
@@ -142,6 +164,18 @@
       assert(!*interestingness_test);
       *interestingness_test = cur_arg;
       positional_arg_index++;
+    } else if (0 == strcmp(cur_arg, "--fail-on-validation-error")) {
+      reducer_options->set_fail_on_validation_error(true);
+    } else if (0 == strcmp(cur_arg, "--relax-logical-pointer")) {
+      validator_options->SetRelaxLogicalPointer(true);
+    } else if (0 == strcmp(cur_arg, "--relax-block-layout")) {
+      validator_options->SetRelaxBlockLayout(true);
+    } else if (0 == strcmp(cur_arg, "--scalar-block-layout")) {
+      validator_options->SetScalarBlockLayout(true);
+    } else if (0 == strcmp(cur_arg, "--skip-block-layout")) {
+      validator_options->SetSkipBlockLayout(true);
+    } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
+      validator_options->SetRelaxStructStore(true);
     } else {
       spvtools::Error(ReduceDiagnostic, nullptr, {},
                       "Too many positional arguments specified");
@@ -165,7 +199,24 @@
 
 }  // namespace
 
-const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_3;
+// Dumps |binary| to file |filename|. Useful for interactive debugging.
+void DumpShader(const std::vector<uint32_t>& binary, const char* filename) {
+  auto write_file_succeeded =
+      WriteFile(filename, "wb", &binary[0], binary.size());
+  if (!write_file_succeeded) {
+    std::cerr << "Failed to dump shader" << std::endl;
+  }
+}
+
+// Dumps the SPIRV-V module in |context| to file |filename|. Useful for
+// interactive debugging.
+void DumpShader(spvtools::opt::IRContext* context, const char* filename) {
+  std::vector<uint32_t> binary;
+  context->module()->ToBinary(&binary, false);
+  DumpShader(binary, filename);
+}
+
+const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_4;
 
 int main(int argc, const char** argv) {
   const char* in_file = nullptr;
@@ -173,9 +224,10 @@
 
   spv_target_env target_env = kDefaultEnvironment;
   spvtools::ReducerOptions reducer_options;
+  spvtools::ValidatorOptions validator_options;
 
-  ReduceStatus status =
-      ParseFlags(argc, argv, &in_file, &interestingness_test, &reducer_options);
+  ReduceStatus status = ParseFlags(argc, argv, &in_file, &interestingness_test,
+                                   &reducer_options, &validator_options);
 
   if (status.action == REDUCE_STOP) {
     return status.code;
@@ -187,7 +239,7 @@
     return 2;
   }
 
-  Reducer reducer(target_env);
+  spvtools::reduce::Reducer reducer(target_env);
 
   reducer.SetInterestingnessFunction(
       [interestingness_test](std::vector<uint32_t> binary,
@@ -205,17 +257,7 @@
         return ExecuteCommand(command);
       });
 
-  reducer.AddReductionPass(
-      spvtools::MakeUnique<RemoveOpNameInstructionReductionPass>(target_env));
-  reducer.AddReductionPass(
-      spvtools::MakeUnique<OperandToConstReductionPass>(target_env));
-  reducer.AddReductionPass(
-      spvtools::MakeUnique<OperandToDominatingIdReductionPass>(target_env));
-  reducer.AddReductionPass(
-      spvtools::MakeUnique<RemoveUnreferencedInstructionReductionPass>(
-          target_env));
-  reducer.AddReductionPass(
-      spvtools::MakeUnique<StructuredLoopToSelectionReductionPass>(target_env));
+  reducer.AddDefaultReductionPasses();
 
   reducer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
 
@@ -225,11 +267,11 @@
   }
 
   std::vector<uint32_t> binary_out;
-  const auto reduction_status =
-      reducer.Run(std::move(binary_in), &binary_out, reducer_options);
+  const auto reduction_status = reducer.Run(std::move(binary_in), &binary_out,
+                                            reducer_options, validator_options);
 
-  if (reduction_status ==
-          Reducer::ReductionResultStatus::kInitialStateNotInteresting ||
+  if (reduction_status == spvtools::reduce::Reducer::ReductionResultStatus::
+                              kInitialStateNotInteresting ||
       !WriteFile<uint32_t>("_reduced_final.spv", "wb", binary_out.data(),
                            binary_out.size())) {
     return 1;
diff --git a/tools/val/val.cpp b/tools/val/val.cpp
index 8b1d048..49ff8a9 100644
--- a/tools/val/val.cpp
+++ b/tools/val/val.cpp
@@ -51,6 +51,8 @@
   --relax-block-layout             Enable VK_KHR_relaxed_block_layout when checking standard
                                    uniform, storage buffer, and push constant layouts.
                                    This is the default when targeting Vulkan 1.1 or later.
+  --uniform-buffer-standard-layout Enable VK_KHR_uniform_buffer_standard_layout when checking standard
+                                   uniform buffer layouts.
   --scalar-block-layout            Enable VK_EXT_scalar_block_layout when checking standard
                                    uniform, storage buffer, and push constant layouts.  Scalar layout
                                    rules are more permissive than relaxed block layout so in effect
@@ -61,16 +63,18 @@
                                    different type with compatible layout and
                                    members.
   --version                        Display validator version information.
-  --target-env                     {vulkan1.0|vulkan1.1|opencl2.2|spv1.0|spv1.1|spv1.2|spv1.3|webgpu0}
-                                   Use Vulkan 1.0, Vulkan 1.1, OpenCL 2.2, SPIR-V 1.0,
-                                   SPIR-V 1.1, SPIR-V 1.2, SPIR-V 1.3 or WIP WebGPU validation rules.
+  --target-env                     {vulkan1.0|vulkan1.1|vulkan1.1spv1.4|opencl2.2|spv1.0|spv1.1|
+                                    spv1.2|spv1.3|spv1.4|webgpu0}
+                                   Use Vulkan 1.0, Vulkan 1.1, Vulkan 1.1 with SPIR-V 1.4,
+                                   OpenCL 2.2, SPIR-V 1.0, SPIR-V 1.1, SPIR-V 1.2, SPIR-V 1.3,
+                                   SPIR-V 1.4, or WIP WebGPU validation rules.
 )",
       argv0, argv0);
 }
 
 int main(int argc, char** argv) {
   const char* inFile = nullptr;
-  spv_target_env target_env = SPV_ENV_UNIVERSAL_1_3;
+  spv_target_env target_env = SPV_ENV_UNIVERSAL_1_4;
   spvtools::ValidatorOptions options;
   bool continue_processing = true;
   int return_code = 0;
@@ -102,15 +106,19 @@
         }
       } else if (0 == strcmp(cur_arg, "--version")) {
         printf("%s\n", spvSoftwareVersionDetailsString());
-        printf("Targets:\n  %s\n  %s\n  %s\n  %s\n  %s\n  %s\n  %s\n  %s\n",
-               spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_0),
-               spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_1),
-               spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_2),
-               spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_3),
-               spvTargetEnvDescription(SPV_ENV_OPENCL_2_2),
-               spvTargetEnvDescription(SPV_ENV_VULKAN_1_0),
-               spvTargetEnvDescription(SPV_ENV_VULKAN_1_1),
-               spvTargetEnvDescription(SPV_ENV_WEBGPU_0));
+        printf(
+            "Targets:\n  %s\n  %s\n  %s\n  %s\n  %s\n  %s\n  %s\n  %s\n  %s\n  "
+            "%s\n",
+            spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_0),
+            spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_1),
+            spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_2),
+            spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_3),
+            spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_4),
+            spvTargetEnvDescription(SPV_ENV_OPENCL_2_2),
+            spvTargetEnvDescription(SPV_ENV_VULKAN_1_0),
+            spvTargetEnvDescription(SPV_ENV_VULKAN_1_1),
+            spvTargetEnvDescription(SPV_ENV_VULKAN_1_1_SPIRV_1_4),
+            spvTargetEnvDescription(SPV_ENV_WEBGPU_0));
         continue_processing = false;
         return_code = 0;
       } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
@@ -134,6 +142,8 @@
         options.SetRelaxLogicalPointer(true);
       } else if (0 == strcmp(cur_arg, "--relax-block-layout")) {
         options.SetRelaxBlockLayout(true);
+      } else if (0 == strcmp(cur_arg, "--uniform-buffer-standard-layout")) {
+        options.SetUniformBufferStandardLayout(true);
       } else if (0 == strcmp(cur_arg, "--scalar-block-layout")) {
         options.SetScalarBlockLayout(true);
       } else if (0 == strcmp(cur_arg, "--skip-block-layout")) {
diff --git a/utils/check_copyright.py b/utils/check_copyright.py
index 85ea128..fc249e9 100755
--- a/utils/check_copyright.py
+++ b/utils/check_copyright.py
@@ -32,9 +32,9 @@
            'Google Inc.',
            'Google LLC',
            'Pierre Moreau']
-CURRENT_YEAR='2018'
+CURRENT_YEAR='2019'
 
-YEARS = '(2014-2016|2015-2016|2016|2016-2017|2017|2018)'
+YEARS = '(2014-2016|2015-2016|2016|2016-2017|2017|2018|2019)'
 COPYRIGHT_RE = re.compile(
         'Copyright \(c\) {} ({})'.format(YEARS, '|'.join(AUTHORS)))
 
diff --git a/utils/check_symbol_exports.py b/utils/check_symbol_exports.py
index c9c0364..d4c8579 100755
--- a/utils/check_symbol_exports.py
+++ b/utils/check_symbol_exports.py
@@ -81,7 +81,7 @@
         print('{}: error: {} does not exist'.format(PROG, args.library))
         sys.exit(1)
 
-    if os.name is 'posix':
+    if os.name == 'posix':
         status = check_library(args.library)
         sys.exit(status)
     else:
diff --git a/utils/generate_grammar_tables.py b/utils/generate_grammar_tables.py
index aabdad5..2001401 100755
--- a/utils/generate_grammar_tables.py
+++ b/utils/generate_grammar_tables.py
@@ -405,11 +405,12 @@
             min_version=self.version)
 
 
-def generate_enum_operand_kind_entry(entry):
+def generate_enum_operand_kind_entry(entry, extension_map):
     """Returns the C initializer for the given operand enum entry.
 
     Arguments:
       - entry: a dict containing information about an enum entry
+      - extension_map: a dict mapping enum value to list of extensions
 
     Returns:
       a string containing the C initializer for spv_operand_desc_t
@@ -417,7 +418,10 @@
     enumerant = entry.get('enumerant')
     value = entry.get('value')
     caps = entry.get('capabilities', [])
-    exts = entry.get('extensions', [])
+    if value in extension_map:
+      exts = extension_map[value]
+    else:
+      exts = []
     params = entry.get('parameters', [])
     params = [p.get('kind') for p in params]
     params = zip(params, [''] * len(params))
@@ -430,22 +434,41 @@
         enumerant, value, caps, exts, params, version))
 
 
-def generate_enum_operand_kind(enum):
-    """Returns the C definition for the given operand kind."""
+def generate_enum_operand_kind(enum, synthetic_exts_list):
+    """Returns the C definition for the given operand kind.
+    Also appends to |synthetic_exts_list| a list of extension
+    lists used."""
     kind = enum.get('kind')
     assert kind is not None
 
-    # Sort all enumerants first according to their values and then
-    # their names so that the symbols with the same values are
-    # grouped together.
+    # Sort all enumerants according to their values, but otherwise
+    # preserve their order so the first name listed in the grammar
+    # as the preferred name for disassembly.
     if enum.get('category') == 'ValueEnum':
-        functor = lambda k: (k['value'], k['enumerant'])
+        functor = lambda k: (k['value'])
     else:
-        functor = lambda k: (int(k['value'], 16), k['enumerant'])
+        functor = lambda k: (int(k['value'], 16))
     entries = sorted(enum.get('enumerants', []), key=functor)
 
+    # SubgroupEqMask and SubgroupEqMaskKHR are the same number with
+    # same semantics, but one has no extension list while the other
+    # does.  Both should have the extension list.
+    # So create a mapping from enum value to the union of the extensions
+    # across all those grammar entries.  Preserve order.
+    extension_map = { }
+    for e in entries:
+      value = e.get('value')
+      extension_map[value] = []
+    for e in entries:
+      value = e.get('value')
+      exts = e.get('extensions', [])
+      for ext in exts:
+        if ext not in extension_map[value]:
+          extension_map[value].append(ext)
+    synthetic_exts_list.extend(extension_map.values())
+
     name = '{}_{}Entries'.format(PYGEN_VARIABLE_PREFIX, kind)
-    entries = ['  {}'.format(generate_enum_operand_kind_entry(e))
+    entries = ['  {}'.format(generate_enum_operand_kind_entry(e, extension_map))
                for e in entries]
 
     template = ['static const spv_operand_desc_t {name}[] = {{',
@@ -470,9 +493,9 @@
     exts = [entry.get('extensions', [])
             for enum in enums
             for entry in enum.get('enumerants', [])]
+    enums = [generate_enum_operand_kind(e, exts) for e in enums]
     exts_arrays = generate_extension_arrays(exts)
 
-    enums = [generate_enum_operand_kind(e) for e in enums]
     # We have three operand kinds that requires their optional counterpart to
     # exist in the operand info table.
     three_optional_enums = ['ImageOperands', 'AccessQualifier', 'MemoryAccess']
@@ -608,6 +631,12 @@
     return '\n\n'.join(tables)
 
 
+def precondition_operand_kinds(operand_kinds):
+    """For operand kinds that have the same number, make sure they all have
+    the same extension list"""
+    return operand_kinds
+
+
 def main():
     import argparse
     parser = argparse.ArgumentParser(description='Generate SPIR-V info tables')
@@ -701,6 +730,7 @@
                 operand_kinds.extend(core_grammar['operand_kinds'])
                 operand_kinds.extend(debuginfo_grammar['operand_kinds'])
                 extensions = get_extension_list(instructions, operand_kinds)
+                operand_kinds = precondition_operand_kinds(operand_kinds)
         if args.core_insts_output is not None:
             make_path_to_file(args.core_insts_output)
             make_path_to_file(args.operand_kinds_output)