Merge remote-tracking branch 'upstream/sdk-1.3.231' into master

Bug: 112606
Change-Id: Ibc6d4575e5696569f4ec9164a6393e35b3fda902
diff --git a/.gitignore b/.gitignore
index b2af56e..ec709ba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,7 +2,7 @@
 .ycm_extra_conf.py*
 *.pyc
 compile_commands.json
-/build/
+/build*/
 /buildtools/
 /external/googletest
 /external/SPIRV-Headers
@@ -20,6 +20,7 @@
 bazel-genfiles
 bazel-out
 bazel-spirv-tools
+bazel-SPIRV-Tools
 bazel-testlogs
 
 # Vim
diff --git a/Android.mk b/Android.mk
index bc748e5..80c61b0 100644
--- a/Android.mk
+++ b/Android.mk
@@ -61,6 +61,7 @@
 		source/val/validate_instruction.cpp \
 		source/val/validate_memory.cpp \
 		source/val/validate_memory_semantics.cpp \
+		source/val/validate_mesh_shading.cpp \
 		source/val/validate_misc.cpp \
 		source/val/validate_mode_setting.cpp \
 		source/val/validate_layout.cpp \
@@ -68,6 +69,8 @@
 		source/val/validate_logicals.cpp \
 		source/val/validate_non_uniform.cpp \
 		source/val/validate_primitives.cpp \
+		source/val/validate_ray_query.cpp \
+		source/val/validate_ray_tracing.cpp \
 		source/val/validate_scopes.cpp \
 		source/val/validate_small_type_uses.cpp \
 		source/val/validate_type.cpp
@@ -106,8 +109,10 @@
 		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_input_components_pass.cpp \
 		source/opt/eliminate_dead_members_pass.cpp \
 		source/opt/feature_manager.cpp \
+		source/opt/fix_func_call_arguments.cpp \
 		source/opt/fix_storage_class.cpp \
 		source/opt/flatten_decoration_pass.cpp \
 		source/opt/fold.cpp \
@@ -126,6 +131,7 @@
 		source/opt/instruction.cpp \
 		source/opt/instruction_list.cpp \
 		source/opt/instrument_pass.cpp \
+		source/opt/interface_var_sroa.cpp \
 		source/opt/interp_fixup_pass.cpp \
 		source/opt/ir_context.cpp \
 		source/opt/ir_loader.cpp \
@@ -156,6 +162,7 @@
 		source/opt/redundancy_elimination.cpp \
 		source/opt/register_pressure.cpp \
 		source/opt/relax_float_ops_pass.cpp \
+		source/opt/remove_dontinline_pass.cpp \
 		source/opt/remove_duplicates_pass.cpp \
 		source/opt/remove_unused_interface_variables_pass.cpp \
 		source/opt/replace_desc_array_access_using_var_index.cpp \
@@ -165,10 +172,11 @@
 		source/opt/scalar_replacement_pass.cpp \
 		source/opt/set_spec_constant_default_value_pass.cpp \
 		source/opt/simplification_pass.cpp \
+		source/opt/spread_volatile_semantics.cpp \
 		source/opt/ssa_rewrite_pass.cpp \
 		source/opt/strength_reduction_pass.cpp \
 		source/opt/strip_debug_info_pass.cpp \
-		source/opt/strip_reflect_info_pass.cpp \
+		source/opt/strip_nonsemantic_info_pass.cpp \
 		source/opt/struct_cfg_analysis.cpp \
 		source/opt/type_manager.cpp \
 		source/opt/types.cpp \
@@ -299,7 +307,7 @@
         $(LOCAL_PATH)/utils/update_build_version.py \
         $(LOCAL_PATH)/CHANGES
 		@$(HOST_PYTHON) $(LOCAL_PATH)/utils/update_build_version.py \
-		                $(LOCAL_PATH) $(1)/build-version.inc
+		                $(LOCAL_PATH)/CHANGES $(1)/build-version.inc
 		@echo "[$(TARGET_ARCH_ABI)] Generate       : build-version.inc <= CHANGES"
 $(LOCAL_PATH)/source/software_version.cpp: $(1)/build-version.inc
 endef
diff --git a/BUILD.bazel b/BUILD.bazel
index b2031de..35dfd66 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -81,7 +81,8 @@
     srcs = ["@spirv_headers//:spirv_xml_registry"],
     outs = ["generators.inc"],
     cmd = "$(location generate_registry_tables) --xml=$(location @spirv_headers//:spirv_xml_registry) --generator-output=$(location generators.inc)",
-    tools = [":generate_registry_tables"],
+    cmd_bat = "$(location //:generate_registry_tables) --xml=$(location @spirv_headers//:spirv_xml_registry) --generator-output=$(location generators.inc)",
+    exec_tools = [":generate_registry_tables"],
 )
 
 py_binary(
@@ -93,8 +94,9 @@
     name = "gen_build_version",
     srcs = ["CHANGES"],
     outs = ["build-version.inc"],
-    cmd = "SOURCE_DATE_EPOCH=0 $(location update_build_version) $$(dirname $(location CHANGES)) $(location build-version.inc)",
-    tools = [":update_build_version"],
+    cmd = "SOURCE_DATE_EPOCH=0 $(location update_build_version) $(location CHANGES) $(location build-version.inc)",
+    cmd_bat = "set SOURCE_DATE_EPOCH=0  && $(location //:update_build_version) $(location CHANGES) $(location build-version.inc)",
+    exec_tools = [":update_build_version"],
 )
 
 # Libraries
diff --git a/BUILD.gn b/BUILD.gn
index d0656f6..fd22648 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -19,6 +19,17 @@
   import("//third_party/protobuf/proto_library.gni")
 }
 
+# SPIRV-Tools may be part of multiple projects in the Chromium tree.
+# Only enable building executables if this is the main copy.
+abspath = get_path_info(".", "abspath")
+spvtools_chromium_third_party = (abspath == "//third_party/vulkan-deps/spirv-tools/src/")
+spvtools_build_executables = build_with_chromium && spvtools_chromium_third_party
+# Fuchsia also requires building the executables.
+# TODO(b/158002593): Avoid the use of dependent-specific variables.
+if (defined(is_fuchsia_tree) && is_fuchsia_tree) {
+  spvtools_build_executables = true
+}
+
 spirv_headers = spirv_tools_spirv_headers_dir
 spirv_is_winuwp = is_win && target_os == "winuwp"
 
@@ -245,7 +256,7 @@
 action("spvtools_build_version") {
   script = "utils/update_build_version.py"
 
-  src_dir = "."
+  changes_file = "CHANGES"
   inc_file = "${target_gen_dir}/build-version.inc"
 
   inputs = [
@@ -255,7 +266,7 @@
 
   outputs = [ inc_file ]
   args = [
-    rebase_path(src_dir, root_build_dir),
+    rebase_path(changes_file, root_build_dir),
     rebase_path(inc_file, root_build_dir),
   ]
 }
@@ -455,6 +466,7 @@
     "source/util/bit_vector.cpp",
     "source/util/bit_vector.h",
     "source/util/bitutils.h",
+    "source/util/hash_combine.h",
     "source/util/hex_float.h",
     "source/util/ilist.h",
     "source/util/ilist_node.h",
@@ -521,10 +533,13 @@
     "source/val/validate_memory.cpp",
     "source/val/validate_memory_semantics.cpp",
     "source/val/validate_memory_semantics.h",
+    "source/val/validate_mesh_shading.cpp",
     "source/val/validate_misc.cpp",
     "source/val/validate_mode_setting.cpp",
     "source/val/validate_non_uniform.cpp",
     "source/val/validate_primitives.cpp",
+    "source/val/validate_ray_query.cpp",
+    "source/val/validate_ray_tracing.cpp",
     "source/val/validate_scopes.cpp",
     "source/val/validate_scopes.h",
     "source/val/validate_small_type_uses.cpp",
@@ -616,11 +631,15 @@
     "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_input_components_pass.cpp",
+    "source/opt/eliminate_dead_input_components_pass.h",
     "source/opt/eliminate_dead_members_pass.cpp",
     "source/opt/eliminate_dead_members_pass.h",
     "source/opt/empty_pass.h",
     "source/opt/feature_manager.cpp",
     "source/opt/feature_manager.h",
+    "source/opt/fix_func_call_arguments.cpp",
+    "source/opt/fix_func_call_arguments.h",
     "source/opt/fix_storage_class.cpp",
     "source/opt/fix_storage_class.h",
     "source/opt/flatten_decoration_pass.cpp",
@@ -657,6 +676,8 @@
     "source/opt/instruction_list.h",
     "source/opt/instrument_pass.cpp",
     "source/opt/instrument_pass.h",
+    "source/opt/interface_var_sroa.cpp",
+    "source/opt/interface_var_sroa.h",
     "source/opt/interp_fixup_pass.cpp",
     "source/opt/interp_fixup_pass.h",
     "source/opt/ir_builder.h",
@@ -721,6 +742,8 @@
     "source/opt/register_pressure.h",
     "source/opt/relax_float_ops_pass.cpp",
     "source/opt/relax_float_ops_pass.h",
+    "source/opt/remove_dontinline_pass.cpp",
+    "source/opt/remove_dontinline_pass.h",
     "source/opt/remove_duplicates_pass.cpp",
     "source/opt/remove_duplicates_pass.h",
     "source/opt/remove_unused_interface_variables_pass.cpp",
@@ -739,14 +762,16 @@
     "source/opt/set_spec_constant_default_value_pass.h",
     "source/opt/simplification_pass.cpp",
     "source/opt/simplification_pass.h",
+    "source/opt/spread_volatile_semantics.cpp",
+    "source/opt/spread_volatile_semantics.h",
     "source/opt/ssa_rewrite_pass.cpp",
     "source/opt/ssa_rewrite_pass.h",
     "source/opt/strength_reduction_pass.cpp",
     "source/opt/strength_reduction_pass.h",
     "source/opt/strip_debug_info_pass.cpp",
     "source/opt/strip_debug_info_pass.h",
-    "source/opt/strip_reflect_info_pass.cpp",
-    "source/opt/strip_reflect_info_pass.h",
+    "source/opt/strip_nonsemantic_info_pass.cpp",
+    "source/opt/strip_nonsemantic_info_pass.h",
     "source/opt/struct_cfg_analysis.cpp",
     "source/opt/struct_cfg_analysis.h",
     "source/opt/tree_iterator.h",
@@ -876,7 +901,7 @@
   configs += [ ":spvtools_internal_config" ]
 }
 
-if (build_with_chromium) {
+if (build_with_chromium && spvtools_build_executables) {
   # The spirv-fuzz library is only built when in a Chromium checkout
   # due to its dependency on protobuf.
 
@@ -1305,7 +1330,7 @@
 
 # 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) {
+if (build_with_chromium && spvtools_build_executables) {
   test("spvtools_test") {
     sources = [
       "test/assembly_context_test.cpp",
@@ -1414,73 +1439,75 @@
   configs += [ ":spvtools_internal_config" ]
 }
 
-executable("spirv-as") {
-  sources = [ "tools/as/as.cpp" ]
-  deps = [
-    ":spvtools",
-    ":spvtools_software_version",
-  ]
-  configs += [ ":spvtools_internal_config" ]
+if (spvtools_build_executables) {
+  executable("spirv-as") {
+    sources = [ "tools/as/as.cpp" ]
+    deps = [
+      ":spvtools",
+      ":spvtools_software_version",
+    ]
+    configs += [ ":spvtools_internal_config" ]
+  }
+
+  executable("spirv-dis") {
+    sources = [ "tools/dis/dis.cpp" ]
+    deps = [
+      ":spvtools",
+      ":spvtools_software_version",
+    ]
+    configs += [ ":spvtools_internal_config" ]
+  }
+
+  executable("spirv-val") {
+    sources = [ "tools/val/val.cpp" ]
+    deps = [
+      ":spvtools",
+      ":spvtools_software_version",
+      ":spvtools_util_cli_consumer",
+      ":spvtools_val",
+    ]
+    configs += [ ":spvtools_internal_config" ]
+  }
+
+  executable("spirv-cfg") {
+    sources = [
+      "tools/cfg/bin_to_dot.cpp",
+      "tools/cfg/bin_to_dot.h",
+      "tools/cfg/cfg.cpp",
+    ]
+    deps = [
+      ":spvtools",
+      ":spvtools_software_version",
+    ]
+    configs += [ ":spvtools_internal_config" ]
+  }
+
+  executable("spirv-opt") {
+    sources = [ "tools/opt/opt.cpp" ]
+    deps = [
+      ":spvtools",
+      ":spvtools_opt",
+      ":spvtools_software_version",
+      ":spvtools_util_cli_consumer",
+      ":spvtools_val",
+    ]
+    configs += [ ":spvtools_internal_config" ]
+  }
+
+  executable("spirv-link") {
+    sources = [ "tools/link/linker.cpp" ]
+    deps = [
+      ":spvtools",
+      ":spvtools_link",
+      ":spvtools_opt",
+      ":spvtools_software_version",
+      ":spvtools_val",
+    ]
+    configs += [ ":spvtools_internal_config" ]
+  }
 }
 
-executable("spirv-dis") {
-  sources = [ "tools/dis/dis.cpp" ]
-  deps = [
-    ":spvtools",
-    ":spvtools_software_version",
-  ]
-  configs += [ ":spvtools_internal_config" ]
-}
-
-executable("spirv-val") {
-  sources = [ "tools/val/val.cpp" ]
-  deps = [
-    ":spvtools",
-    ":spvtools_software_version",
-    ":spvtools_util_cli_consumer",
-    ":spvtools_val",
-  ]
-  configs += [ ":spvtools_internal_config" ]
-}
-
-executable("spirv-cfg") {
-  sources = [
-    "tools/cfg/bin_to_dot.cpp",
-    "tools/cfg/bin_to_dot.h",
-    "tools/cfg/cfg.cpp",
-  ]
-  deps = [
-    ":spvtools",
-    ":spvtools_software_version",
-  ]
-  configs += [ ":spvtools_internal_config" ]
-}
-
-executable("spirv-opt") {
-  sources = [ "tools/opt/opt.cpp" ]
-  deps = [
-    ":spvtools",
-    ":spvtools_opt",
-    ":spvtools_software_version",
-    ":spvtools_util_cli_consumer",
-    ":spvtools_val",
-  ]
-  configs += [ ":spvtools_internal_config" ]
-}
-
-executable("spirv-link") {
-  sources = [ "tools/link/linker.cpp" ]
-  deps = [
-    ":spvtools",
-    ":spvtools_link",
-    ":spvtools_opt",
-    ":spvtools_software_version",
-    ":spvtools_val",
-  ]
-  configs += [ ":spvtools_internal_config" ]
-}
-
-if (!is_ios && !spirv_is_winuwp && build_with_chromium) {
+if (!is_ios && !spirv_is_winuwp && build_with_chromium && spvtools_build_executables) {
   # iOS and UWP do not allow std::system calls which spirv-fuzz
   # requires. Additionally, spirv-fuzz is only built when in a
   # Chromium checkout due to its dependency on protobuf.
@@ -1501,7 +1528,7 @@
   }
 }
 
-if (!is_ios && !spirv_is_winuwp) {
+if (!is_ios && !spirv_is_winuwp && spvtools_build_executables) {
   # iOS and UWP do not allow std::system calls which spirv-reduce
   # requires.
 
@@ -1519,19 +1546,21 @@
   }
 }
 
-group("all_spirv_tools") {
-  deps = [
-    ":spirv-as",
-    ":spirv-cfg",
-    ":spirv-dis",
-    ":spirv-link",
-    ":spirv-opt",
-    ":spirv-val",
-  ]
-  if (!is_ios && !spirv_is_winuwp && build_with_chromium) {
-    deps += [ ":spirv-fuzz" ]
-  }
-  if (!is_ios && !spirv_is_winuwp) {
-    deps += [ ":spirv-reduce" ]
+if (spvtools_build_executables){
+  group("all_spirv_tools") {
+    deps = [
+      ":spirv-as",
+      ":spirv-cfg",
+      ":spirv-dis",
+      ":spirv-link",
+      ":spirv-opt",
+      ":spirv-val",
+    ]
+    if (!is_ios && !spirv_is_winuwp && build_with_chromium) {
+      deps += [ ":spirv-fuzz" ]
+    }
+    if (!is_ios && !spirv_is_winuwp) {
+      deps += [ ":spirv-reduce" ]
+    }
   }
 }
diff --git a/CHANGES b/CHANGES
index 4128db1..56a2d52 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,117 @@
 Revision history for SPIRV-Tools
 
-v2021.4-dev 2021-11-09
+v2022.4 2022-10-12
+  - General
+    - Support Narrow Types in BitCast Folding Rule (#4941)
+    - spirv-diff: Allow no SpecId (#4904)
+    - build: cmake: Add support for GNU/Hurd (#4895)
+    - Implement tool changes for SPV_EXT_mesh_shader. (#4915)
+  - Validator
+    - spirv-val: Add SPV_ARM_core_builtins validation (#4958)
+    - spirv-val: Add an option to use friendly names or not (#4951)
+    - spirv-val: Consistently quote ids in messages (#4950)
+    - spirv-val: Add initial SPV_EXT_mesh_shader validation (#4924)
+    - spirv-val: Make it legal to use arrays of ray queries (#4938)
+    - spirv-val: Better message for using OpTypeBool in input/output (#4901)
+    - spirv-val: Add SPV_KHR_ray_tracing storage class (#4868)
+  - Optimizer
+    - spirv-opt: Fix stacked CompositeExtract constant folds (#4932)
+    - Improve time to build dominators (#4916)
+    - Fix ADCE to mark scope and inlined_at of line instructions as live. (#4910)
+    - Improve algorithm to reorder blocks in a function (#4911)
+    - Add structs to eliminate dead input components (#4894)
+    - spirv-opt: fix copy-propagate-arrays index opti on structs. (#4891)
+    - Fix ADCE to not eliminate top level DebugInfo instructions (#4889)
+    - Fix array copy propagation (#4890)
+
+v2022.3 2022-08-08
+  - General
+    - Add SPV_KHR_fragment_shader_barycentric support (#4805)
+    - Add support for SPV_KHR_subgroup_rotate (#4786)
+    - use exec_tools instead of tools for better RBE compatibility (#4837)
+    - Write binary files to stdout in binary on windows. (#4834)
+    - Allow spirv-opt print-all to show pretty IDs (#4888)
+  - Validator
+    - spirv-val: Add PerVertexKHR (#4807)
+    - spirv-opt : Add FixFuncCallArgumentsPass (#4775)
+    - spirv-val: Add CullMaskKHR support (#4792)
+    - Require ColMajor or RowMajor for matrices (#4878)
+    - spirv-val: Add SPV_KHR_ray_query (#4848)
+    - spirv-val: Add SPV_KHR_ray_tracing instructions (#4871)
+    - Implement SPV_NV_bindless_texture related changes (#4847)
+    - spirv-val: Add OpConvertUToAccelerationStructureKHR (#4838)
+    - spirv-val: Add support for SPV_AMD_shader_early_and_late_fragment_tests (#4812)
+  - Optimizer
+    - Fold multiply and subtraction into FMA with negation (#4808)
+    - Add more folding for composite instructions (#4802)
+    - spirv-opt: add pass for interface variable scalar replacement (#4779)
+    - Don't try to unroll loop with step count 0. (#4769)
+    - spirv-opt: SPV_NV_bindless_texture related changes (#4870)
+  - Linker
+    - linker: Recalculate interface variables (#4784)
+
+v2022.2 2022-04-07
+  - General
+    - Add OpModuleProcessed to debug opcode (#4694)
+  - Optimizer
+    - Complete handling of RayQueryKHR type (#4690)
+    - Have scalar replacement use undef instead of null (#4691)
+    - Optimize Instruction::Instruction (#4705)
+    - Handle propagation of arrays with decorations (#4717)
+    - spirv-opt: Add OpExecutionModeId support (#4719)
+    - Optimize Type::HashValue (#4707)
+    - Optimize DefUseManager allocations (#4709)
+    - Add pass to remove DontInline function control (#4747)
+    - Better handling of 0xFFFFFFFF when folding vector shuffle (#4743)
+    - Reset the id bound on the module in compact ids (#4744)
+    - spirv-opt: (WIP) Eliminate Dead Input Component Pass (#4720)
+    - Support SPV_KHR_uniform_group_instructions (#4734)
+    - Handle shaders without execution model in spread-volatile-semantics (#4766)
+  - Validator
+    - Fix handling of Nontemporal image operand (#4692)
+    - [spirv-val] Allow 0 Component Count for DebugTypeArray for Shader (#4706)
+    - spirv-val: Validate DebugTypeMatrix (#4732)
+    - spirv-val: Label Vulkan VUID 04734 (#4739)
+    - spirv-val: Label VUID 06491 (#4745)
+    - spirv-val: Disallow array of push constants (#4742)
+    - spirv-val: Label Vulkan RuntimeArray VUID (#4749)
+    - spirv-val: Add Vulkan Image VUID 06214 (#4750)
+    - spirv-val: Add Vulkan Dref not allowed 3D dim VUID (#4751)
+    - spirv-val: Label and add test for PSB Aligned (#4756)
+    - spirv-val: Add Vulkan 32-bit bit op Base (#4758)
+    - spirv-val: Add more Vulkan VUID labels (#4764)
+  - Diff
+    - Introduce spirv-diff (#4611)
+    - Stabilize the output of spirv-diff (#4698)
+    - spirv-diff: Handle OpSpecConstant array sizes (#4700)
+    - spirv-diff: Match OpSpecConstantComposite correctly (#4704)
+    - spirv-diff: Use GetSingleWord*Operand (#4768)
+    - spirv-diff: Basic support for OpTypeForwardPointer (#4761)
+    - spirv-diff: Fix OpTypeFunction matching w.r.t operand count (#4771)
+
+v2022.1 2022-01-26
+  - General
+    - Add SPIR-V 1.6 support to wasm build (#4674)
+    - Improvements to disassembly within PassManager (#4677)
+    - Basic support for SPIR-V 1.6 (#4663)
+    - reflect debug (#4662)
+    - Fix endianness of string literals (#4622)
+  - Optimizer
+    - spirv-opt: add pass to Spread Volatile semantics (#4667)
+    - Fix constant propagation and folding of FClamp instructions (#4651)
+    - Manually fold floating point division by zero (#4637)
+    - Allow ADCE to remove dead inputs (#4629)
+  - Linker
+    - Linker improvements (#4679)
+      * test/linker: Code factorisation and small tweaks
+      * linker: Do not fail when going over limits
+  - Validator
+    - val: interface struct with builtins must be Block (#4665)
+  - Fuzzer
+    - Avoid id bound errors during opt fuzzing (#4658)
+    - Avoid uninitialised read when parsing hex float (#4646)
+
+v2021.4 2021-11-11
   - General
     - Add a WebAssembly build (#3752)
     - Make cxx exceptions controllable (#4591)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 70caf85..1b8fe92 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -56,8 +56,12 @@
   set(SPIRV_TIMER_ENABLED ${SPIRV_ALLOW_TIMERS})
 elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "FreeBSD")
   add_definitions(-DSPIRV_FREEBSD)
+elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "OpenBSD")
+  add_definitions(-DSPIRV_OPENBSD)
 elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Fuchsia")
   add_definitions(-DSPIRV_FUCHSIA)
+elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "GNU")
+  add_definitions(-DSPIRV_GNU)
 else()
   message(FATAL_ERROR "Your platform '${CMAKE_SYSTEM_NAME}' is not supported!")
 endif()
@@ -116,7 +120,7 @@
     set(SPIRV_WARNINGS ${SPIRV_WARNINGS} -Werror)
   endif()
 elseif(MSVC)
-  set(SPIRV_WARNINGS -D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS /wd4800)
+  set(SPIRV_WARNINGS -D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS /wd4800 /wd4819)
 
   if(${SPIRV_WERROR})
     set(SPIRV_WARNINGS ${SPIRV_WARNINGS} /WX)
diff --git a/DEPS b/DEPS
index 2aaf11f..3a5f614 100644
--- a/DEPS
+++ b/DEPS
@@ -3,10 +3,10 @@
 vars = {
   'github': 'https://github.com',
 
-  'effcee_revision': 'ddf5e2bb92957dc8a12c5392f8495333d6844133',
-  'googletest_revision': 'bf0701daa9f5b30e5882e2f8f9a5280bcba87e77',
-  're2_revision': '4244cd1cb492fa1d10986ec67f862964c073f844',
-  'spirv_headers_revision': '814e728b30ddd0f4509233099a3ad96fd4318c07',
+  'effcee_revision': '35912e1b7778ec2ddcff7e7188177761539e59e0',
+  'googletest_revision': 'd9bb8412d60b993365abb53f00b6dad9b2c01b62',
+  're2_revision': 'd2836d1b1c34c4e330a85a1006201db474bf2c8a',
+  'spirv_headers_revision': '85a1ed200d50660786c1a88d9166e871123cce39',
 }
 
 deps = {
diff --git a/README.md b/README.md
index 14db1e7..e951e37 100644
--- a/README.md
+++ b/README.md
@@ -212,6 +212,24 @@
 "Fuzzer:" as the start of its title.
 
 
+### Diff
+
+*Note:* The diff tool is still under development.
+
+The diff tool takes two SPIR-V files, either in binary or text format and
+produces a diff-style comparison between the two.  The instructions between the
+src and dst modules are matched as best as the tool can, and output is produced
+(in src id-space) that shows which instructions are removed in src, added in dst
+or modified between them.  The order of instructions are not retained.
+
+Matching instructions between two SPIR-V modules is not trivial, and thus a
+number of heuristics are applied in this tool.  In particular, without debug
+information, match functions is nontrivial as they can be reordered.  As such,
+this tool is primarily useful to produce the diff of two SPIR-V modules derived
+from the same source, for example before and after a modification to the shader,
+before and after a transformation, or SPIR-V produced from different tools.
+
+
 ### Extras
 
 * [Utility filters](#utility-filters)
@@ -399,7 +417,7 @@
 - [Python 3](http://www.python.org/): for utility scripts and running the test
 suite.
 - [Bazel](https://bazel.build/) (optional): if building the source with Bazel,
-you need to install Bazel Version 0.29.1 on your machine. Other versions may
+you need to install Bazel Version 5.0.0 on your machine. Other versions may
 also work, but are not verified.
 - [Emscripten SDK](https://emscripten.org) (optional): if building the
   WebAssembly module.
@@ -624,6 +642,15 @@
 * `spirv-cfg` - the control flow graph dumper
   * `<spirv-dir>/tools/cfg`
 
+### Diff tool
+
+*Warning:* This functionality is under development, and is incomplete.
+
+The diff tool produces a diff-style comparison between two SPIR-V modules.
+
+* `spirv-diff` - the standalone diff tool
+  * `<spirv-dir>`/tools/diff`
+
 ### Utility filters
 
 * `spirv-lesspipe.sh` - Automatically disassembles `.spv` binary files for the
diff --git a/build_defs.bzl b/build_defs.bzl
index b2cd41b..7189137 100644
--- a/build_defs.bzl
+++ b/build_defs.bzl
@@ -68,7 +68,15 @@
             "--core-insts-output=$(location {3}) " +
             "--operand-kinds-output=$(location {4})"
         ).format(*fmtargs),
-        tools = [":generate_grammar_tables"],
+        cmd_bat = (
+            "$(location :generate_grammar_tables) " +
+            "--spirv-core-grammar=$(location {0}) " +
+            "--extinst-debuginfo-grammar=$(location {1}) " +
+            "--extinst-cldebuginfo100-grammar=$(location {2}) " +
+            "--core-insts-output=$(location {3}) " +
+            "--operand-kinds-output=$(location {4})"
+        ).format(*fmtargs),
+        exec_tools = [":generate_grammar_tables"],
         visibility = ["//visibility:private"],
     )
 
@@ -97,7 +105,15 @@
             "--extension-enum-output=$(location {3}) " +
             "--enum-string-mapping-output=$(location {4})"
         ).format(*fmtargs),
-        tools = [":generate_grammar_tables"],
+        cmd_bat = (
+            "$(location :generate_grammar_tables) " +
+            "--spirv-core-grammar=$(location {0}) " +
+            "--extinst-debuginfo-grammar=$(location {1}) " +
+            "--extinst-cldebuginfo100-grammar=$(location {2}) " +
+            "--extension-enum-output=$(location {3}) " +
+            "--enum-string-mapping-output=$(location {4})"
+        ).format(*fmtargs),
+        exec_tools = [":generate_grammar_tables"],
         visibility = ["//visibility:private"],
     )
 
@@ -118,7 +134,12 @@
             "--extinst-opencl-grammar=$(location {0}) " +
             "--opencl-insts-output=$(location {1})"
         ).format(*fmtargs),
-        tools = [":generate_grammar_tables"],
+        cmd_bat = (
+            "$(location :generate_grammar_tables) " +
+            "--extinst-opencl-grammar=$(location {0}) " +
+            "--opencl-insts-output=$(location {1})"
+        ).format(*fmtargs),
+        exec_tools = [":generate_grammar_tables"],
         visibility = ["//visibility:private"],
     )
 
@@ -139,7 +160,12 @@
             "--extinst-glsl-grammar=$(location {0}) " +
             "--glsl-insts-output=$(location {1})"
         ).format(*fmtargs),
-        tools = [":generate_grammar_tables"],
+        cmd_bat = (
+            "$(location :generate_grammar_tables) " +
+            "--extinst-glsl-grammar=$(location {0}) " +
+            "--glsl-insts-output=$(location {1})"
+        ).format(*fmtargs),
+        exec_tools = [":generate_grammar_tables"],
         visibility = ["//visibility:private"],
     )
 
@@ -161,7 +187,13 @@
             "--vendor-insts-output=$(location {1}) " +
             "--vendor-operand-kind-prefix={2}"
         ).format(*fmtargs),
-        tools = [":generate_grammar_tables"],
+        cmd_bat = (
+            "$(location :generate_grammar_tables) " +
+            "--extinst-vendor-grammar=$(location {0}) " +
+            "--vendor-insts-output=$(location {1}) " +
+            "--vendor-operand-kind-prefix={2}"
+        ).format(*fmtargs),
+        exec_tools = [":generate_grammar_tables"],
         visibility = ["//visibility:private"],
     )
 
@@ -179,7 +211,12 @@
             "--extinst-grammar=$< " +
             "--extinst-output-path=$(location {0})"
         ).format(*fmtargs),
-        tools = [":generate_language_headers"],
+        cmd_bat = (
+            "$(location :generate_language_headers) " +
+            "--extinst-grammar=$< " +
+            "--extinst-output-path=$(location {0})"
+        ).format(*fmtargs),
+        exec_tools = [":generate_language_headers"],
         visibility = ["//visibility:private"],
     )
 
diff --git a/include/spirv-tools/libspirv.h b/include/spirv-tools/libspirv.h
index 8df14f5..b549efb 100644
--- a/include/spirv-tools/libspirv.h
+++ b/include/spirv-tools/libspirv.h
@@ -482,6 +482,7 @@
 //    SPV_ENV_VULKAN_1_1           ->  SPIR-V 1.3
 //    SPV_ENV_VULKAN_1_1_SPIRV_1_4 ->  SPIR-V 1.4
 //    SPV_ENV_VULKAN_1_2           ->  SPIR-V 1.5
+//    SPV_ENV_VULKAN_1_3           ->  SPIR-V 1.6
 // Consult the description of API entry points for specific rules.
 typedef enum {
   SPV_ENV_UNIVERSAL_1_0,  // SPIR-V 1.0 latest revision, no other restrictions.
@@ -516,7 +517,11 @@
 
   SPV_ENV_UNIVERSAL_1_5,  // SPIR-V 1.5 latest revision, no other restrictions.
   SPV_ENV_VULKAN_1_2,     // Vulkan 1.2 latest revision.
-  SPV_ENV_MAX             // Keep this as the last enum value.
+
+  SPV_ENV_UNIVERSAL_1_6,  // SPIR-V 1.6 latest revision, no other restrictions.
+  SPV_ENV_VULKAN_1_3,     // Vulkan 1.3 latest revision.
+
+  SPV_ENV_MAX  // Keep this as the last enum value.
 } spv_target_env;
 
 // SPIR-V Validator can be parameterized with the following Universal Limits.
@@ -555,7 +560,7 @@
 // Creates a context object for most of the SPIRV-Tools API.
 // Returns null if env is invalid.
 //
-// See specific API calls for how the target environment is interpeted
+// See specific API calls for how the target environment is interpreted
 // (particularly assembly and validation).
 SPIRV_TOOLS_EXPORT spv_context spvContextCreate(spv_target_env env);
 
@@ -607,7 +612,7 @@
 //    set that option.
 // 2) Pointers that are pass as parameters to function calls do not have to
 //    match the storage class of the formal parameter.
-// 3) Pointers that are actaul parameters on function calls do not have to point
+// 3) Pointers that are actual parameters on function calls do not have to point
 //    to the same type pointed as the formal parameter.  The types just need to
 //    logically match.
 // 4) GLSLstd450 Interpolate* instructions can have a load of an interpolant
@@ -633,7 +638,7 @@
 // Records whether the validator should use "scalar" block layout rules.
 // Scalar layout rules are more permissive than relaxed block layout.
 //
-// See Vulkan extnesion VK_EXT_scalar_block_layout.  The scalar alignment is
+// See Vulkan extension VK_EXT_scalar_block_layout.  The scalar alignment is
 // defined as follows:
 // - scalar alignment of a scalar is the scalar size
 // - scalar alignment of a vector is the scalar alignment of its component
@@ -665,6 +670,10 @@
 SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetAllowLocalSizeId(
     spv_validator_options options, bool val);
 
+// Whether friendly names should be used in validation error messages.
+SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetFriendlyNames(
+    spv_validator_options options, bool val);
+
 // Creates an optimizer options object with default options. Returns a valid
 // options object. The object remains valid until it is passed into
 // |spvOptimizerOptionsDestroy|.
diff --git a/include/spirv-tools/libspirv.hpp b/include/spirv-tools/libspirv.hpp
index 8dfb46b..408e3eb 100644
--- a/include/spirv-tools/libspirv.hpp
+++ b/include/spirv-tools/libspirv.hpp
@@ -36,7 +36,7 @@
  public:
   // Constructs a context targeting the given environment |env|.
   //
-  // See specific API calls for how the target environment is interpeted
+  // See specific API calls for how the target environment is interpreted
   // (particularly assembly and validation).
   //
   // The constructed instance will have an empty message consumer, which just
@@ -139,7 +139,7 @@
   //    set that option.
   // 2) Pointers that are pass as parameters to function calls do not have to
   //    match the storage class of the formal parameter.
-  // 3) Pointers that are actaul parameters on function calls do not have to
+  // 3) Pointers that are actual parameters on function calls do not have to
   //    point to the same type pointed as the formal parameter.  The types just
   //    need to logically match.
   // 4) GLSLstd450 Interpolate* instructions can have a load of an interpolant
@@ -148,6 +148,11 @@
     spvValidatorOptionsSetBeforeHlslLegalization(options_, val);
   }
 
+  // Whether friendly names should be used in validation error messages.
+  void SetFriendlyNames(bool val) {
+    spvValidatorOptionsSetFriendlyNames(options_, val);
+  }
+
  private:
   spv_validator_options options_;
 };
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index 21059cb..9497356 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -43,7 +43,7 @@
   // consumed by the RegisterPass() method. Tokens are one-time objects that
   // only support move; copying is not allowed.
   struct PassToken {
-    struct Impl;  // Opaque struct for holding inernal data.
+    struct Impl;  // Opaque struct for holding internal data.
 
     PassToken(std::unique_ptr<Impl>);
 
@@ -227,16 +227,17 @@
 
 // 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.
+// Section 3.42.2 of the SPIR-V spec) of the SPIR-V module to be optimized.
 Optimizer::PassToken CreateStripDebugInfoPass();
 
-// Creates a strip-reflect-info pass.
-// A strip-reflect-info pass removes all reflections instructions.
-// For now, this is limited to removing decorations defined in
-// SPV_GOOGLE_hlsl_functionality1.  The coverage may expand in
-// the future.
+// [Deprecated] This will create a strip-nonsemantic-info pass.  See below.
 Optimizer::PassToken CreateStripReflectInfoPass();
 
+// Creates a strip-nonsemantic-info pass.
+// A strip-nonsemantic-info pass removes all reflections and explicitly
+// non-semantic instructions.
+Optimizer::PassToken CreateStripNonSemanticInfoPass();
+
 // Creates an eliminate-dead-functions pass.
 // An eliminate-dead-functions pass will remove all functions that are not in
 // the call trees rooted at entry points and exported functions.  These
@@ -295,11 +296,11 @@
 // and can be changed in future. A spec constant is foldable if all of its
 // value(s) can be determined from the module. E.g., an integer spec constant
 // defined with OpSpecConstantOp instruction can be folded if its value won't
-// change later. This pass will replace the original OpSpecContantOp instruction
-// with an OpConstant instruction. When folding composite spec constants,
-// new instructions may be inserted to define the components of the composite
-// constant first, then the original spec constants will be replaced by
-// OpConstantComposite instructions.
+// change later. This pass will replace the original OpSpecConstantOp
+// instruction with an OpConstant instruction. When folding composite spec
+// constants, new instructions may be inserted to define the components of the
+// composite constant first, then the original spec constants will be replaced
+// by OpConstantComposite instructions.
 //
 // There are some operations not supported yet:
 //   OpSConvert, OpFConvert, OpQuantizeToF16 and
@@ -325,7 +326,7 @@
 
 // Creates a eliminate-dead-constant pass.
 // A eliminate-dead-constant pass removes dead constants, including normal
-// contants defined by OpConstant, OpConstantComposite, OpConstantTrue, or
+// constants defined by OpConstant, OpConstantComposite, OpConstantTrue, or
 // OpConstantFalse and spec constants defined by OpSpecConstant,
 // OpSpecConstantComposite, OpSpecConstantTrue, OpSpecConstantFalse or
 // OpSpecConstantOp.
@@ -389,7 +390,7 @@
 // Only modules with relaxed logical addressing (see opt/instruction.h) are
 // currently processed.
 //
-// This pass is most effective if preceeded by Inlining and
+// This pass is most effective if preceded by Inlining and
 // LocalAccessChainConvert. This pass will reduce the work needed to be done
 // by LocalSingleStoreElim and LocalMultiStoreElim.
 //
@@ -407,7 +408,7 @@
 // Note that some branches and blocks may be left to avoid creating invalid
 // control flow. Improving this is left to future work.
 //
-// This pass is most effective when preceeded by passes which eliminate
+// This pass is most effective when preceded by passes which eliminate
 // local loads and stores, effectively propagating constant values where
 // possible.
 Optimizer::PassToken CreateDeadBranchElimPass();
@@ -424,7 +425,7 @@
 // are currently processed. Currently modules with any extensions enabled are
 // not processed. This is left for future work.
 //
-// This pass is most effective if preceeded by Inlining and
+// This pass is most effective if preceded by Inlining and
 // LocalAccessChainConvert. LocalSingleStoreElim and LocalSingleBlockElim
 // will reduce the work that this pass has to do.
 Optimizer::PassToken CreateLocalMultiStoreElimPass();
@@ -519,7 +520,8 @@
 // interface are considered live and are not eliminated. This mode is needed
 // by GPU-Assisted validation instrumentation, where a change in the interface
 // is not allowed.
-Optimizer::PassToken CreateAggressiveDCEPass(bool preserve_interface = false);
+Optimizer::PassToken CreateAggressiveDCEPass();
+Optimizer::PassToken CreateAggressiveDCEPass(bool preserve_interface);
 
 // Creates a remove-unused-interface-variables pass.
 // Removes variables referenced on the |OpEntryPoint| instruction that are not
@@ -628,7 +630,7 @@
 Optimizer::PassToken CreateScalarReplacementPass(uint32_t size_limit = 100);
 
 // Create a private to local pass.
-// This pass looks for variables delcared in the private storage class that are
+// This pass looks for variables declared in the private storage class that are
 // used in only one function.  Those variables are moved to the function storage
 // class in the function that they are used.
 Optimizer::PassToken CreatePrivateToLocalPass();
@@ -833,6 +835,19 @@
 //   inclusive.
 Optimizer::PassToken CreateGraphicsRobustAccessPass();
 
+// Create a pass to spread Volatile semantics to variables with SMIDNV,
+// WarpIDNV, SubgroupSize, SubgroupLocalInvocationId, SubgroupEqMask,
+// SubgroupGeMask, SubgroupGtMask, SubgroupLeMask, or SubgroupLtMask BuiltIn
+// decorations or OpLoad for them when the shader model is the ray generation,
+// closest hit, miss, intersection, or callable. This pass can be used for
+// VUID-StandaloneSpirv-VulkanMemoryModel-04678 and
+// VUID-StandaloneSpirv-VulkanMemoryModel-04679 (See "Standalone SPIR-V
+// Validation" section of Vulkan spec "Appendix A: Vulkan Environment for
+// SPIR-V"). When the SPIR-V version is 1.6 or above, the pass also spreads
+// the Volatile semantics to a variable with HelperInvocation BuiltIn decoration
+// in the fragement shader.
+Optimizer::PassToken CreateSpreadVolatileSemanticsPass();
+
 // Create a pass to replace a descriptor access using variable index.
 // This pass replaces every access using a variable index to array variable
 // |desc| that has a DescriptorSet and Binding decorations with a constant
@@ -871,6 +886,13 @@
 // propagated into their final positions.
 Optimizer::PassToken CreateInterpolateFixupPass();
 
+// Removes unused components from composite input variables. Current
+// implementation just removes trailing unused components from input arrays.
+// The pass performs best after maximizing dead code removal. A subsequent dead
+// code elimination pass would be beneficial in removing newly unused component
+// types.
+Optimizer::PassToken CreateEliminateDeadInputComponentsPass();
+
 // Creates a convert-to-sampled-image pass to convert images and/or
 // samplers with given pairs of descriptor set and binding to sampled image.
 // If a pair of an image and a sampler have the same pair of descriptor set and
@@ -881,6 +903,20 @@
     const std::vector<opt::DescriptorSetAndBinding>&
         descriptor_set_binding_pairs);
 
+// Create an interface-variable-scalar-replacement pass that replaces array or
+// matrix interface variables with a series of scalar or vector interface
+// variables. For example, it replaces `float3 foo[2]` with `float3 foo0, foo1`.
+Optimizer::PassToken CreateInterfaceVariableScalarReplacementPass();
+
+// Creates a remove-dont-inline pass to remove the |DontInline| function control
+// from every function in the module.  This is useful if you want the inliner to
+// inline these functions some reason.
+Optimizer::PassToken CreateRemoveDontInlinePass();
+// Create a fix-func-call-param pass to fix non memory argument for the function
+// call, as spirv-validation requires function parameters to be an memory
+// object, currently the pass would remove accesschain pointer argument passed
+// to the function
+Optimizer::PassToken CreateFixFuncCallArgumentsPass();
 }  // namespace spvtools
 
 #endif  // INCLUDE_SPIRV_TOOLS_OPTIMIZER_HPP_
diff --git a/kokoro/macos-clang-release-bazel/build.sh b/kokoro/macos-clang-release-bazel/build.sh
index d2a516f..c62611a 100644
--- a/kokoro/macos-clang-release-bazel/build.sh
+++ b/kokoro/macos-clang-release-bazel/build.sh
@@ -31,14 +31,14 @@
 git clone --depth=1 https://github.com/google/effcee              external/effcee
 git clone --depth=1 https://github.com/google/re2                 external/re2
 
-# Get bazel 0.29.1.
-gsutil cp gs://bazel/0.29.1/release/bazel-0.29.1-darwin-x86_64 .
-chmod +x bazel-0.29.1-darwin-x86_64
+# Get bazel 5.0.0
+gsutil cp gs://bazel/5.0.0/release/bazel-5.0.0-darwin-x86_64 .
+chmod +x bazel-5.0.0-darwin-x86_64
 
 echo $(date): Build everything...
-./bazel-0.29.1-darwin-x86_64 build :all
+./bazel-5.0.0-darwin-x86_64 build :all
 echo $(date): Build completed.
 
 echo $(date): Starting bazel test...
-./bazel-0.29.1-darwin-x86_64 test :all
+./bazel-5.0.0-darwin-x86_64 test :all
 echo $(date): Bazel test completed.
diff --git a/kokoro/scripts/linux/build-docker.sh b/kokoro/scripts/linux/build-docker.sh
index 8f76803..80043b8 100755
--- a/kokoro/scripts/linux/build-docker.sh
+++ b/kokoro/scripts/linux/build-docker.sh
@@ -195,7 +195,7 @@
 
   echo $(date): ndk-build completed.
 elif [ $TOOL = "bazel" ]; then
-  using bazel-3.1.0
+  using bazel-5.0.0
 
   echo $(date): Build everything...
   bazel build :all
diff --git a/kokoro/scripts/linux/build.sh b/kokoro/scripts/linux/build.sh
index 4731ebd..85d4b61 100644
--- a/kokoro/scripts/linux/build.sh
+++ b/kokoro/scripts/linux/build.sh
@@ -26,7 +26,9 @@
 TOOL=$3
 BUILD_SHA=${KOKORO_GITHUB_COMMIT:-$KOKORO_GITHUB_PULL_REQUEST_COMMIT}
 
+# "--privileged" is required to run ptrace in the asan builds.
 docker run --rm -i \
+  --privileged \
   --volume "${ROOT_DIR}:${ROOT_DIR}" \
   --volume "${KOKORO_ARTIFACTS_DIR}:${KOKORO_ARTIFACTS_DIR}" \
   --workdir "${ROOT_DIR}" \
diff --git a/kokoro/scripts/windows/build.bat b/kokoro/scripts/windows/build.bat
index 24e29cc..8c9d689 100644
--- a/kokoro/scripts/windows/build.bat
+++ b/kokoro/scripts/windows/build.bat
@@ -22,7 +22,7 @@
 set VS_VERSION=%2
 
 :: Force usage of python 3.6
-set PATH=C:\python36;"C:\Program Files\CMake\bin";%PATH%
+set PATH=C:\python36;"C:\Program Files\cmake-3.23.1-windows-x86_64\bin";%PATH%
 
 cd %SRC%
 git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers external/spirv-headers
diff --git a/kokoro/windows-msvc-2015-release-bazel/build.bat b/kokoro/windows-msvc-2015-release-bazel/build.bat
index 2f721af..de20b0a 100644
--- a/kokoro/windows-msvc-2015-release-bazel/build.bat
+++ b/kokoro/windows-msvc-2015-release-bazel/build.bat
@@ -30,14 +30,13 @@
 git clone --depth=1 https://github.com/google/re2                 external/re2
 
 :: REM Install Bazel.
-wget -q https://github.com/bazelbuild/bazel/releases/download/0.29.1/bazel-0.29.1-windows-x86_64.zip
-unzip -q bazel-0.29.1-windows-x86_64.zip
+wget -q https://github.com/bazelbuild/bazel/releases/download/5.0.0/bazel-5.0.0-windows-x86_64.zip
+unzip -q bazel-5.0.0-windows-x86_64.zip
 
 :: Set up MSVC
 call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x64
 set BAZEL_VS=C:\Program Files (x86)\Microsoft Visual Studio 14.0
 set BAZEL_VC=C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC
-set BAZEL_SH=c:\tools\msys64\usr\bin\bash.exe
 set BAZEL_PYTHON=c:\tools\python2\python.exe
 
 :: #########################################
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index 331ff67..668579a 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -197,7 +197,7 @@
 add_custom_command(OUTPUT ${SPIRV_TOOLS_BUILD_VERSION_INC}
    COMMAND ${PYTHON_EXECUTABLE}
            ${SPIRV_TOOLS_BUILD_VERSION_INC_GENERATOR}
-           ${spirv-tools_SOURCE_DIR} ${SPIRV_TOOLS_BUILD_VERSION_INC}
+           ${SPIRV_TOOLS_CHANGES_FILE} ${SPIRV_TOOLS_BUILD_VERSION_INC}
    DEPENDS ${SPIRV_TOOLS_BUILD_VERSION_INC_GENERATOR}
            ${SPIRV_TOOLS_CHANGES_FILE}
    COMMENT "Update build-version.inc in the SPIRV-Tools build directory (if necessary).")
@@ -217,12 +217,14 @@
 add_subdirectory(fuzz)
 add_subdirectory(link)
 add_subdirectory(lint)
+add_subdirectory(diff)
 
 set(SPIRV_SOURCES
   ${spirv-tools_SOURCE_DIR}/include/spirv-tools/libspirv.h
 
   ${CMAKE_CURRENT_SOURCE_DIR}/util/bitutils.h
   ${CMAKE_CURRENT_SOURCE_DIR}/util/bit_vector.h
+  ${CMAKE_CURRENT_SOURCE_DIR}/util/hash_combine.h
   ${CMAKE_CURRENT_SOURCE_DIR}/util/hex_float.h
   ${CMAKE_CURRENT_SOURCE_DIR}/util/make_unique.h
   ${CMAKE_CURRENT_SOURCE_DIR}/util/parse_number.h
@@ -316,10 +318,13 @@
   ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_logicals.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_memory.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_memory_semantics.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_mesh_shading.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_misc.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_mode_setting.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_non_uniform.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_primitives.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_ray_query.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_ray_tracing.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_scopes.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_small_type_uses.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_type.cpp
@@ -404,7 +409,7 @@
   find_library(LIBRT rt)
   if(LIBRT)
     foreach(target ${SPIRV_TOOLS_TARGETS})
-      target_link_libraries(${target} ${LIBRT})
+      target_link_libraries(${target} rt)
     endforeach()
   endif()
 endif()
diff --git a/source/assembly_grammar.cpp b/source/assembly_grammar.cpp
index 79f18ee..4f5942a 100644
--- a/source/assembly_grammar.cpp
+++ b/source/assembly_grammar.cpp
@@ -62,9 +62,9 @@
     end = std::find(begin, text_end, separator);
 
     spv_operand_desc entry = nullptr;
-    if (spvOperandTableNameLookup(env, operandTable, type, begin, end - begin,
-                                  &entry)) {
-      return SPV_ERROR_INVALID_TEXT;
+    if (auto error = spvOperandTableNameLookup(env, operandTable, type, begin,
+                                               end - begin, &entry)) {
+      return error;
     }
     value |= entry->value;
 
diff --git a/source/binary.cpp b/source/binary.cpp
index 93d5da7..24d32f8 100644
--- a/source/binary.cpp
+++ b/source/binary.cpp
@@ -33,6 +33,7 @@
 #include "source/operand.h"
 #include "source/spirv_constant.h"
 #include "source/spirv_endian.h"
+#include "source/util/string_utils.h"
 
 spv_result_t spvBinaryHeaderGet(const spv_const_binary binary,
                                 const spv_endianness_t endian,
@@ -62,6 +63,15 @@
   return SPV_SUCCESS;
 }
 
+std::string spvDecodeLiteralStringOperand(const spv_parsed_instruction_t& inst,
+                                          const uint16_t operand_index) {
+  assert(operand_index < inst.num_operands);
+  const spv_parsed_operand_t& operand = inst.operands[operand_index];
+
+  return spvtools::utils::MakeString(inst.words + operand.offset,
+                                     operand.num_words);
+}
+
 namespace {
 
 // A SPIR-V binary parser.  A parser instance communicates detailed parse
@@ -205,7 +215,7 @@
     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
+    // Is the SPIR-V binary in a different endianness from the host native
     // endianness?
     bool requires_endian_conversion;
 
@@ -290,7 +300,7 @@
   const uint32_t first_word = peek();
 
   // If the module's endianness is different from the host native endianness,
-  // then converted_words contains the the endian-translated words in the
+  // then converted_words contains the endian-translated words in the
   // instruction.
   _.endian_converted_words.clear();
   _.endian_converted_words.push_back(first_word);
@@ -577,27 +587,18 @@
 
     case SPV_OPERAND_TYPE_LITERAL_STRING:
     case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_STRING: {
-      convert_operand_endianness = false;
-      const char* string =
-          reinterpret_cast<const char*>(_.words + _.word_index);
-      // Compute the length of the string, but make sure we don't run off the
-      // end of the input.
-      const size_t remaining_input_bytes =
-          sizeof(uint32_t) * (_.num_words - _.word_index);
-      const size_t string_num_content_bytes =
-          spv_strnlen_s(string, remaining_input_bytes);
-      // If there was no terminating null byte, then that's an end-of-input
-      // error.
-      if (string_num_content_bytes == remaining_input_bytes)
+      const size_t max_words = _.num_words - _.word_index;
+      std::string string =
+          spvtools::utils::MakeString(_.words + _.word_index, max_words, false);
+
+      if (string.length() == max_words * 4)
         return exhaustedInputDiagnostic(inst_offset, opcode, type);
-      // Account for null in the word length, so add 1 for null, then add 3 to
-      // make sure we round up.  The following is equivalent to:
-      //    (string_num_content_bytes + 1 + 3) / 4
-      const size_t string_num_words = string_num_content_bytes / 4 + 1;
+
       // Make sure we can record the word count without overflow.
       //
       // This error can't currently be triggered because of validity
       // checks elsewhere.
+      const size_t string_num_words = string.length() / 4 + 1;
       if (string_num_words > std::numeric_limits<uint16_t>::max()) {
         return diagnostic() << "Literal string is longer than "
                             << std::numeric_limits<uint16_t>::max()
@@ -611,7 +612,7 @@
         // There is only one string literal argument to OpExtInstImport,
         // so it's sufficient to guard this just on the opcode.
         const spv_ext_inst_type_t ext_inst_type =
-            spvExtInstImportTypeGet(string);
+            spvExtInstImportTypeGet(string.c_str());
         if (SPV_EXT_INST_TYPE_NONE == ext_inst_type) {
           return diagnostic()
                  << "Invalid extended instruction import '" << string << "'";
diff --git a/source/binary.h b/source/binary.h
index 66d24c7..eb3beac 100644
--- a/source/binary.h
+++ b/source/binary.h
@@ -15,6 +15,8 @@
 #ifndef SOURCE_BINARY_H_
 #define SOURCE_BINARY_H_
 
+#include <string>
+
 #include "source/spirv_definition.h"
 #include "spirv-tools/libspirv.h"
 
@@ -33,4 +35,9 @@
 // replacement for C11's strnlen_s which might not exist in all environments.
 size_t spv_strnlen_s(const char* str, size_t strsz);
 
+// Decode the string literal operand with index operand_index from instruction
+// inst.
+std::string spvDecodeLiteralStringOperand(const spv_parsed_instruction_t& inst,
+                                          const uint16_t operand_index);
+
 #endif  // SOURCE_BINARY_H_
diff --git a/source/cfa.h b/source/cfa.h
index 0d09014..2743ab4 100644
--- a/source/cfa.h
+++ b/source/cfa.h
@@ -42,7 +42,7 @@
 
   /// Returns true if a block with @p id is found in the @p work_list vector
   ///
-  /// @param[in] work_list  Set of blocks visited in the the depth first
+  /// @param[in] work_list  Set of blocks visited in the depth first
   /// traversal
   ///                       of the CFG
   /// @param[in] id         The ID of the block being checked
@@ -56,8 +56,33 @@
   ///
   /// This function performs a depth first traversal from the \p entry
   /// BasicBlock and calls the pre/postorder functions when it needs to process
+  /// the node in pre order, post order.
+  ///
+  /// @param[in] entry      The root BasicBlock of a CFG
+  /// @param[in] successor_func  A function which will return a pointer to the
+  ///                            successor nodes
+  /// @param[in] preorder   A function that will be called for every block in a
+  ///                       CFG following preorder traversal semantics
+  /// @param[in] postorder  A function that will be called for every block in a
+  ///                       CFG following postorder traversal semantics
+  /// @param[in] terminal   A function that will be called to determine if the
+  ///                       search should stop at the given node.
+  /// NOTE: The @p successor_func and predecessor_func each return a pointer to
+  /// a collection such that iterators to that collection remain valid for the
+  /// lifetime of the algorithm.
+  static void DepthFirstTraversal(const BB* entry,
+                                  get_blocks_func successor_func,
+                                  std::function<void(cbb_ptr)> preorder,
+                                  std::function<void(cbb_ptr)> postorder,
+                                  std::function<bool(cbb_ptr)> terminal);
+
+  /// @brief Depth first traversal starting from the \p entry BasicBlock
+  ///
+  /// This function performs a depth first traversal from the \p entry
+  /// BasicBlock and calls the pre/postorder functions when it needs to process
   /// the node in pre order, post order. It also calls the backedge function
-  /// when a back edge is encountered.
+  /// when a back edge is encountered. The backedge function can be empty.  The
+  /// runtime of the algorithm is improved if backedge is empty.
   ///
   /// @param[in] entry      The root BasicBlock of a CFG
   /// @param[in] successor_func  A function which will return a pointer to the
@@ -67,16 +92,18 @@
   /// @param[in] postorder  A function that will be called for every block in a
   ///                       CFG following postorder traversal semantics
   /// @param[in] backedge   A function that will be called when a backedge is
-  ///                       encountered during a traversal
+  ///                       encountered during a traversal.
+  /// @param[in] terminal   A function that will be called to determine if the
+  ///                       search should stop at the given node.
   /// NOTE: The @p successor_func and predecessor_func each return a pointer to
-  /// a
-  /// collection such that iterators to that collection remain valid for the
+  /// a collection such that iterators to that collection remain valid for the
   /// lifetime of the algorithm.
   static void DepthFirstTraversal(
       const BB* entry, get_blocks_func successor_func,
       std::function<void(cbb_ptr)> preorder,
       std::function<void(cbb_ptr)> postorder,
-      std::function<void(cbb_ptr, cbb_ptr)> backedge);
+      std::function<void(cbb_ptr, cbb_ptr)> backedge,
+      std::function<bool(cbb_ptr)> terminal);
 
   /// @brief Calculates dominator edges for a set of blocks
   ///
@@ -134,11 +161,27 @@
 }
 
 template <class BB>
+void CFA<BB>::DepthFirstTraversal(const BB* entry,
+                                  get_blocks_func successor_func,
+                                  std::function<void(cbb_ptr)> preorder,
+                                  std::function<void(cbb_ptr)> postorder,
+                                  std::function<bool(cbb_ptr)> terminal) {
+  DepthFirstTraversal(entry, successor_func, preorder, postorder,
+                      /* backedge = */ {}, terminal);
+}
+
+template <class BB>
 void CFA<BB>::DepthFirstTraversal(
     const BB* entry, get_blocks_func successor_func,
     std::function<void(cbb_ptr)> preorder,
     std::function<void(cbb_ptr)> postorder,
-    std::function<void(cbb_ptr, cbb_ptr)> backedge) {
+    std::function<void(cbb_ptr, cbb_ptr)> backedge,
+    std::function<bool(cbb_ptr)> terminal) {
+  assert(successor_func && "The successor function cannot be empty.");
+  assert(preorder && "The preorder function cannot be empty.");
+  assert(postorder && "The postorder function cannot be empty.");
+  assert(terminal && "The terminal function cannot be empty.");
+
   std::unordered_set<uint32_t> processed;
 
   /// NOTE: work_list is the sequence of nodes from the root node to the node
@@ -152,13 +195,13 @@
 
   while (!work_list.empty()) {
     block_info& top = work_list.back();
-    if (top.iter == end(*successor_func(top.block))) {
+    if (terminal(top.block) || top.iter == end(*successor_func(top.block))) {
       postorder(top.block);
       work_list.pop_back();
     } else {
       BB* child = *top.iter;
       top.iter++;
-      if (FindInWorkList(work_list, child->id())) {
+      if (backedge && FindInWorkList(work_list, child->id())) {
         backedge(top.block, child);
       }
       if (processed.count(child->id()) == 0) {
@@ -265,12 +308,12 @@
 
   auto mark_visited = [&visited](const BB* b) { visited.insert(b); };
   auto ignore_block = [](const BB*) {};
-  auto ignore_blocks = [](const BB*, const BB*) {};
+  auto no_terminal_blocks = [](const BB*) { return false; };
 
   auto traverse_from_root = [&mark_visited, &succ_func, &ignore_block,
-                             &ignore_blocks](const BB* entry) {
+                             &no_terminal_blocks](const BB* entry) {
     DepthFirstTraversal(entry, succ_func, mark_visited, ignore_block,
-                        ignore_blocks);
+                        no_terminal_blocks);
   };
 
   std::vector<BB*> result;
diff --git a/source/diff/CMakeLists.txt b/source/diff/CMakeLists.txt
new file mode 100644
index 0000000..1328699
--- /dev/null
+++ b/source/diff/CMakeLists.txt
@@ -0,0 +1,54 @@
+# Copyright (c) 2022 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.
+set(SPIRV_TOOLS_DIFF_SOURCES
+  diff.h
+  lcs.h
+
+  diff.cpp
+)
+
+add_library(SPIRV-Tools-diff ${SPIRV_TOOLS_LIBRARY_TYPE} ${SPIRV_TOOLS_DIFF_SOURCES})
+
+spvtools_default_compile_options(SPIRV-Tools-diff)
+target_include_directories(SPIRV-Tools-diff
+  PUBLIC
+	$<BUILD_INTERFACE:${spirv-tools_SOURCE_DIR}/include>
+	$<BUILD_INTERFACE:${SPIRV_HEADER_INCLUDE_DIR}>
+	$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
+  PRIVATE ${spirv-tools_BINARY_DIR}
+)
+# We need the assembling and disassembling functionalities in the main library.
+target_link_libraries(SPIRV-Tools-diff
+  PUBLIC ${SPIRV_TOOLS_FULL_VISIBILITY})
+# We need the internals of spirv-opt.
+target_link_libraries(SPIRV-Tools-diff
+  PUBLIC SPIRV-Tools-opt)
+
+set_property(TARGET SPIRV-Tools-diff PROPERTY FOLDER "SPIRV-Tools libraries")
+spvtools_check_symbol_exports(SPIRV-Tools-diff)
+
+if(ENABLE_SPIRV_TOOLS_INSTALL)
+  install(TARGETS SPIRV-Tools-diff EXPORT SPIRV-Tools-diffTargets
+    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
+  export(EXPORT SPIRV-Tools-diffTargets FILE SPIRV-Tools-diffTargets.cmake)
+
+  spvtools_config_package_dir(SPIRV-Tools-diff PACKAGE_DIR)
+  install(EXPORT SPIRV-Tools-diffTargets FILE SPIRV-Tools-diffTargets.cmake
+  	DESTINATION ${PACKAGE_DIR})
+
+  spvtools_generate_config_file(SPIRV-Tools-diff)
+  install(FILES ${CMAKE_BINARY_DIR}/SPIRV-Tools-diffConfig.cmake DESTINATION ${PACKAGE_DIR})
+endif(ENABLE_SPIRV_TOOLS_INSTALL)
diff --git a/source/diff/diff.cpp b/source/diff/diff.cpp
new file mode 100644
index 0000000..7ed41de
--- /dev/null
+++ b/source/diff/diff.cpp
@@ -0,0 +1,2869 @@
+// Copyright (c) 2022 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/diff/diff.h"
+
+#include "source/diff/lcs.h"
+#include "source/disassemble.h"
+#include "source/ext_inst.h"
+#include "source/latest_version_spirv_header.h"
+#include "source/print.h"
+#include "spirv-tools/libspirv.hpp"
+
+namespace spvtools {
+namespace diff {
+
+namespace {
+
+// A map from an id to the instruction that defines it.
+using IdToInstructionMap = std::vector<const opt::Instruction*>;
+// A map from an id to the instructions that decorate it, or name it, etc.
+using IdToInfoMap = std::vector<std::vector<const opt::Instruction*>>;
+// A map from an instruction to another, used for instructions without id.
+using InstructionToInstructionMap =
+    std::unordered_map<const opt::Instruction*, const opt::Instruction*>;
+// A flat list of instructions in a function for easier iteration.
+using InstructionList = std::vector<const opt::Instruction*>;
+// A map from a function to its list of instructions.
+using FunctionInstMap = std::map<uint32_t, InstructionList>;
+// A list of ids with some similar property, for example functions with the same
+// name.
+using IdGroup = std::vector<uint32_t>;
+// A map of names to ids with the same name.  This is an ordered map so
+// different implementations produce identical results.
+using IdGroupMapByName = std::map<std::string, IdGroup>;
+using IdGroupMapByTypeId = std::map<uint32_t, IdGroup>;
+using IdGroupMapByOp = std::map<SpvOp, IdGroup>;
+using IdGroupMapByStorageClass = std::map<SpvStorageClass, IdGroup>;
+
+// A set of potential id mappings that haven't been resolved yet.  Any id in src
+// may map in any id in dst.  Note that ids are added in the same order as they
+// appear in src and dst to facilitate matching dependent instructions.  For
+// example, this guarantees that when matching OpTypeVector, the basic type of
+// the vector is already (potentially) matched.
+struct PotentialIdMap {
+  std::vector<uint32_t> src_ids;
+  std::vector<uint32_t> dst_ids;
+};
+
+void CompactIds(std::vector<uint32_t>& ids) {
+  size_t write_index = 0;
+  for (size_t i = 0; i < ids.size(); ++i) {
+    if (ids[i] != 0) {
+      ids[write_index++] = ids[i];
+    }
+  }
+  ids.resize(write_index);
+}
+
+// A mapping between src and dst ids.
+class IdMap {
+ public:
+  IdMap(size_t id_bound) { id_map_.resize(id_bound, 0); }
+
+  void MapIds(uint32_t from, uint32_t to) {
+    assert(from != 0);
+    assert(to != 0);
+    assert(from < id_map_.size());
+    assert(id_map_[from] == 0);
+
+    id_map_[from] = to;
+  }
+
+  uint32_t MappedId(uint32_t from) const {
+    assert(from != 0);
+    return from < id_map_.size() ? id_map_[from] : 0;
+  }
+  const opt::Instruction* MappedInst(const opt::Instruction* from_inst) const {
+    assert(from_inst != nullptr);
+    assert(!from_inst->HasResultId());
+
+    auto mapped = inst_map_.find(from_inst);
+    if (mapped == inst_map_.end()) {
+      return nullptr;
+    }
+    return mapped->second;
+  }
+
+  bool IsMapped(uint32_t from) const {
+    assert(from != 0);
+    return from < id_map_.size() && id_map_[from] != 0;
+  }
+
+  // Map any ids in src and dst that have not been mapped to new ids in dst and
+  // src respectively.
+  void MapUnmatchedIds(IdMap& other_way);
+
+  // Some instructions don't have result ids.  Those are mapped by pointer.
+  void MapInsts(const opt::Instruction* from_inst,
+                const opt::Instruction* to_inst) {
+    assert(from_inst != nullptr);
+    assert(to_inst != nullptr);
+    assert(inst_map_.find(from_inst) == inst_map_.end());
+
+    inst_map_[from_inst] = to_inst;
+  }
+
+  uint32_t IdBound() const { return static_cast<uint32_t>(id_map_.size()); }
+
+ private:
+  // Given an id, returns the corresponding id in the other module, or 0 if not
+  // matched yet.
+  std::vector<uint32_t> id_map_;
+
+  // Same for instructions that don't have an id.
+  InstructionToInstructionMap inst_map_;
+};
+
+// Two way mapping of ids.
+class SrcDstIdMap {
+ public:
+  SrcDstIdMap(size_t src_id_bound, size_t dst_id_bound)
+      : src_to_dst_(src_id_bound), dst_to_src_(dst_id_bound) {}
+
+  void MapIds(uint32_t src, uint32_t dst) {
+    src_to_dst_.MapIds(src, dst);
+    dst_to_src_.MapIds(dst, src);
+  }
+
+  uint32_t MappedDstId(uint32_t src) {
+    uint32_t dst = src_to_dst_.MappedId(src);
+    assert(dst == 0 || dst_to_src_.MappedId(dst) == src);
+    return dst;
+  }
+  uint32_t MappedSrcId(uint32_t dst) {
+    uint32_t src = dst_to_src_.MappedId(dst);
+    assert(src == 0 || src_to_dst_.MappedId(src) == dst);
+    return src;
+  }
+
+  bool IsSrcMapped(uint32_t src) { return src_to_dst_.IsMapped(src); }
+  bool IsDstMapped(uint32_t dst) { return dst_to_src_.IsMapped(dst); }
+
+  // Map any ids in src and dst that have not been mapped to new ids in dst and
+  // src respectively.
+  void MapUnmatchedIds();
+
+  // Some instructions don't have result ids.  Those are mapped by pointer.
+  void MapInsts(const opt::Instruction* src_inst,
+                const opt::Instruction* dst_inst) {
+    assert(src_inst->HasResultId() == dst_inst->HasResultId());
+    if (src_inst->HasResultId()) {
+      MapIds(src_inst->result_id(), dst_inst->result_id());
+    } else {
+      src_to_dst_.MapInsts(src_inst, dst_inst);
+      dst_to_src_.MapInsts(dst_inst, src_inst);
+    }
+  }
+
+  const IdMap& SrcToDstMap() const { return src_to_dst_; }
+  const IdMap& DstToSrcMap() const { return dst_to_src_; }
+
+ private:
+  IdMap src_to_dst_;
+  IdMap dst_to_src_;
+};
+
+struct IdInstructions {
+  IdInstructions(const opt::Module* module)
+      : inst_map_(module->IdBound(), nullptr),
+        name_map_(module->IdBound()),
+        decoration_map_(module->IdBound()),
+        forward_pointer_map_(module->IdBound()) {
+    // Map ids from all sections to instructions that define them.
+    MapIdsToInstruction(module->ext_inst_imports());
+    MapIdsToInstruction(module->types_values());
+    for (const opt::Function& function : *module) {
+      function.ForEachInst(
+          [this](const opt::Instruction* inst) {
+            if (inst->HasResultId()) {
+              MapIdToInstruction(inst->result_id(), inst);
+            }
+          },
+          true, true);
+    }
+
+    // Gather decorations applied to ids that could be useful in matching them
+    // between src and dst modules.
+    MapIdsToInfos(module->debugs2());
+    MapIdsToInfos(module->annotations());
+    MapIdsToInfos(module->types_values());
+  }
+
+  void MapIdToInstruction(uint32_t id, const opt::Instruction* inst);
+
+  void MapIdsToInstruction(
+      opt::IteratorRange<opt::Module::const_inst_iterator> section);
+  void MapIdsToInfos(
+      opt::IteratorRange<opt::Module::const_inst_iterator> section);
+
+  IdToInstructionMap inst_map_;
+  IdToInfoMap name_map_;
+  IdToInfoMap decoration_map_;
+  IdToInstructionMap forward_pointer_map_;
+};
+
+class Differ {
+ public:
+  Differ(opt::IRContext* src, opt::IRContext* dst, std::ostream& out,
+         Options options)
+      : src_context_(src),
+        dst_context_(dst),
+        src_(src->module()),
+        dst_(dst->module()),
+        options_(options),
+        out_(out),
+        src_id_to_(src_),
+        dst_id_to_(dst_),
+        id_map_(src_->IdBound(), dst_->IdBound()) {
+    // Cache function bodies in canonicalization order.
+    GetFunctionBodies(src_context_, &src_funcs_, &src_func_insts_);
+    GetFunctionBodies(dst_context_, &dst_funcs_, &dst_func_insts_);
+  }
+
+  // Match ids or instructions of different sections.
+  void MatchCapabilities();
+  void MatchExtensions();
+  void MatchExtInstImportIds();
+  void MatchMemoryModel();
+  void MatchEntryPointIds();
+  void MatchExecutionModes();
+  void MatchTypeForwardPointers();
+  void MatchTypeIds();
+  void MatchConstants();
+  void MatchVariableIds();
+  void MatchFunctions();
+
+  // Debug info and annotations are matched only after ids are matched.
+  void MatchDebugs1();
+  void MatchDebugs2();
+  void MatchDebugs3();
+  void MatchExtInstDebugInfo();
+  void MatchAnnotations();
+
+  // Output the diff.
+  spv_result_t Output();
+
+  void DumpIdMap() {
+    if (!options_.dump_id_map) {
+      return;
+    }
+
+    out_ << " Src ->  Dst\n";
+    for (uint32_t src_id = 1; src_id < src_->IdBound(); ++src_id) {
+      uint32_t dst_id = id_map_.MappedDstId(src_id);
+      if (src_id_to_.inst_map_[src_id] != nullptr && dst_id != 0)
+        out_ << std::setw(4) << src_id << " -> " << std::setw(4) << dst_id
+             << " [" << spvOpcodeString(src_id_to_.inst_map_[src_id]->opcode())
+             << "]\n";
+    }
+  }
+
+ private:
+  // Helper functions that match ids between src and dst
+  void PoolPotentialIds(
+      opt::IteratorRange<opt::Module::const_inst_iterator> section,
+      std::vector<uint32_t>& ids, bool is_src,
+      std::function<bool(const opt::Instruction&)> filter,
+      std::function<uint32_t(const opt::Instruction&)> get_id);
+  void MatchIds(
+      PotentialIdMap& potential,
+      std::function<bool(const opt::Instruction*, const opt::Instruction*)>
+          match);
+  // Helper functions that match id-less instructions between src and dst.
+  void MatchPreambleInstructions(
+      opt::IteratorRange<opt::Module::const_inst_iterator> src_insts,
+      opt::IteratorRange<opt::Module::const_inst_iterator> dst_insts);
+  InstructionList SortPreambleInstructions(
+      const opt::Module* module,
+      opt::IteratorRange<opt::Module::const_inst_iterator> insts);
+  int ComparePreambleInstructions(const opt::Instruction* a,
+                                  const opt::Instruction* b,
+                                  const opt::Module* src_inst_module,
+                                  const opt::Module* dst_inst_module);
+  // Helper functions that match debug and annotation instructions of already
+  // matched ids.
+  void MatchDebugAndAnnotationInstructions(
+      opt::IteratorRange<opt::Module::const_inst_iterator> src_insts,
+      opt::IteratorRange<opt::Module::const_inst_iterator> dst_insts);
+
+  // Get various properties from an id.  These Helper functions are passed to
+  // `GroupIds` and `GroupIdsAndMatch` below (as the `get_group` argument).
+  uint32_t GroupIdsHelperGetTypeId(const IdInstructions& id_to, uint32_t id);
+  SpvStorageClass GroupIdsHelperGetTypePointerStorageClass(
+      const IdInstructions& id_to, uint32_t id);
+  SpvOp GroupIdsHelperGetTypePointerTypeOp(const IdInstructions& id_to,
+                                           uint32_t id);
+
+  // Given a list of ids, groups them based on some value.  The `get_group`
+  // function extracts a piece of information corresponding to each id, and the
+  // ids are bucketed based on that (and output in `groups`).  This is useful to
+  // attempt to match ids between src and dst only when said property is
+  // identical.
+  template <typename T>
+  void GroupIds(const IdGroup& ids, bool is_src, std::map<T, IdGroup>* groups,
+                T (Differ::*get_group)(const IdInstructions&, uint32_t));
+
+  // Calls GroupIds to bucket ids in src and dst based on a property returned by
+  // `get_group`.  This function then calls `match_group` for each bucket (i.e.
+  // "group") with identical values for said property.
+  //
+  // For example, say src and dst ids have the following properties
+  // correspondingly:
+  //
+  // - src ids' properties: {id0: A, id1: A, id2: B, id3: C, id4: B}
+  // - dst ids' properties: {id0': B, id1': C, id2': B, id3': D, id4': B}
+  //
+  // Then `match_group` is called 2 times:
+  //
+  // - Once with: ([id2, id4], [id0', id2', id4']) corresponding to B
+  // - Once with: ([id3], [id2']) corresponding to C
+  //
+  // Ids corresponding to A and D cannot match based on this property.
+  template <typename T>
+  void GroupIdsAndMatch(
+      const IdGroup& src_ids, const IdGroup& dst_ids, T invalid_group_key,
+      T (Differ::*get_group)(const IdInstructions&, uint32_t),
+      std::function<void(const IdGroup& src_group, const IdGroup& dst_group)>
+          match_group);
+
+  // Helper functions that determine if two instructions match
+  bool DoIdsMatch(uint32_t src_id, uint32_t dst_id);
+  bool DoesOperandMatch(const opt::Operand& src_operand,
+                        const opt::Operand& dst_operand);
+  bool DoOperandsMatch(const opt::Instruction* src_inst,
+                       const opt::Instruction* dst_inst,
+                       uint32_t in_operand_index_start,
+                       uint32_t in_operand_count);
+  bool DoInstructionsMatch(const opt::Instruction* src_inst,
+                           const opt::Instruction* dst_inst);
+  bool DoIdsMatchFuzzy(uint32_t src_id, uint32_t dst_id);
+  bool DoesOperandMatchFuzzy(const opt::Operand& src_operand,
+                             const opt::Operand& dst_operand);
+  bool DoInstructionsMatchFuzzy(const opt::Instruction* src_inst,
+                                const opt::Instruction* dst_inst);
+  bool AreIdenticalUintConstants(uint32_t src_id, uint32_t dst_id);
+  bool DoDebugAndAnnotationInstructionsMatch(const opt::Instruction* src_inst,
+                                             const opt::Instruction* dst_inst);
+  bool AreVariablesMatchable(uint32_t src_id, uint32_t dst_id,
+                             uint32_t flexibility);
+  bool MatchOpTypeStruct(const opt::Instruction* src_inst,
+                         const opt::Instruction* dst_inst,
+                         uint32_t flexibility);
+  bool MatchOpConstant(const opt::Instruction* src_inst,
+                       const opt::Instruction* dst_inst, uint32_t flexibility);
+  bool MatchOpSpecConstant(const opt::Instruction* src_inst,
+                           const opt::Instruction* dst_inst);
+  bool MatchOpVariable(const opt::Instruction* src_inst,
+                       const opt::Instruction* dst_inst, uint32_t flexibility);
+  bool MatchPerVertexType(uint32_t src_type_id, uint32_t dst_type_id);
+  bool MatchPerVertexVariable(const opt::Instruction* src_inst,
+                              const opt::Instruction* dst_inst);
+
+  // Helper functions for matching OpTypeForwardPointer
+  void MatchTypeForwardPointersByName(const IdGroup& src, const IdGroup& dst);
+  void MatchTypeForwardPointersByTypeOp(const IdGroup& src, const IdGroup& dst);
+
+  // Helper functions for function matching.
+  using FunctionMap = std::map<uint32_t, const opt::Function*>;
+
+  InstructionList GetFunctionBody(opt::IRContext* context,
+                                  opt::Function& function);
+  InstructionList GetFunctionHeader(const opt::Function& function);
+  void GetFunctionBodies(opt::IRContext* context, FunctionMap* functions,
+                         FunctionInstMap* function_insts);
+  void GetFunctionHeaderInstructions(const opt::Module* module,
+                                     FunctionInstMap* function_insts);
+  void BestEffortMatchFunctions(const IdGroup& src_func_ids,
+                                const IdGroup& dst_func_ids,
+                                const FunctionInstMap& src_func_insts,
+                                const FunctionInstMap& dst_func_insts);
+
+  // Calculates the diff of two function bodies.  Note that the matched
+  // instructions themselves may not be identical; output of exact matches
+  // should produce the exact instruction while inexact matches should produce a
+  // diff as well.
+  //
+  // Returns the similarity of the two bodies = 2*N_match / (N_src + N_dst)
+  void MatchFunctionParamIds(const opt::Function* src_func,
+                             const opt::Function* dst_func);
+  float MatchFunctionBodies(const InstructionList& src_body,
+                            const InstructionList& dst_body,
+                            DiffMatch* src_match_result,
+                            DiffMatch* dst_match_result);
+  void MatchIdsInFunctionBodies(const InstructionList& src_body,
+                                const InstructionList& dst_body,
+                                const DiffMatch& src_match_result,
+                                const DiffMatch& dst_match_result,
+                                uint32_t flexibility);
+  void MatchVariablesUsedByMatchedInstructions(const opt::Instruction* src_inst,
+                                               const opt::Instruction* dst_inst,
+                                               uint32_t flexibility);
+
+  // Helper functions to retrieve information pertaining to an id
+  const opt::Instruction* GetInst(const IdInstructions& id_to, uint32_t id);
+  uint32_t GetConstantUint(const IdInstructions& id_to, uint32_t constant_id);
+  SpvExecutionModel GetExecutionModel(const opt::Module* module,
+                                      uint32_t entry_point_id);
+  bool HasName(const IdInstructions& id_to, uint32_t id);
+  // Get the OpName associated with an id
+  std::string GetName(const IdInstructions& id_to, uint32_t id, bool* has_name);
+  // Get the OpName associated with an id, with argument types stripped for
+  // functions.  Some tools don't encode function argument types in the OpName
+  // string, and this improves diff between SPIR-V from those tools and others.
+  std::string GetSanitizedName(const IdInstructions& id_to, uint32_t id);
+  uint32_t GetVarTypeId(const IdInstructions& id_to, uint32_t var_id,
+                        SpvStorageClass* storage_class);
+  bool GetDecorationValue(const IdInstructions& id_to, uint32_t id,
+                          SpvDecoration decoration, uint32_t* decoration_value);
+  const opt::Instruction* GetForwardPointerInst(const IdInstructions& id_to,
+                                                uint32_t id);
+  bool IsIntType(const IdInstructions& id_to, uint32_t type_id);
+  bool IsFloatType(const IdInstructions& id_to, uint32_t type_id);
+  bool IsConstantUint(const IdInstructions& id_to, uint32_t id);
+  bool IsVariable(const IdInstructions& id_to, uint32_t pointer_id);
+  bool IsOp(const IdInstructions& id_to, uint32_t id, SpvOp opcode);
+  bool IsPerVertexType(const IdInstructions& id_to, uint32_t type_id);
+  bool IsPerVertexVariable(const IdInstructions& id_to, uint32_t type_id);
+  SpvStorageClass GetPerVertexStorageClass(const opt::Module* module,
+                                           uint32_t type_id);
+  spv_ext_inst_type_t GetExtInstType(const IdInstructions& id_to,
+                                     uint32_t set_id);
+  spv_number_kind_t GetNumberKind(const IdInstructions& id_to,
+                                  const opt::Instruction& inst,
+                                  uint32_t operand_index,
+                                  uint32_t* number_bit_width);
+  spv_number_kind_t GetTypeNumberKind(const IdInstructions& id_to, uint32_t id,
+                                      uint32_t* number_bit_width);
+
+  // Helper functions to output a diff line
+  const opt::Instruction* MappedDstInst(const opt::Instruction* src_inst);
+  const opt::Instruction* MappedSrcInst(const opt::Instruction* dst_inst);
+  const opt::Instruction* MappedInstImpl(const opt::Instruction* inst,
+                                         const IdMap& to_other,
+                                         const IdInstructions& other_id_to);
+  void OutputLine(std::function<bool()> are_lines_identical,
+                  std::function<void()> output_src_line,
+                  std::function<void()> output_dst_line);
+  template <typename InstList>
+  void OutputSection(
+      const InstList& src_insts, const InstList& dst_insts,
+      std::function<void(const opt::Instruction&, const IdInstructions&,
+                         const opt::Instruction&)>
+          write_inst);
+  void ToParsedInstruction(const opt::Instruction& inst,
+                           const IdInstructions& id_to,
+                           const opt::Instruction& original_inst,
+                           spv_parsed_instruction_t* parsed_inst,
+                           std::vector<spv_parsed_operand_t>& parsed_operands,
+                           std::vector<uint32_t>& inst_binary);
+  opt::Instruction ToMappedSrcIds(const opt::Instruction& dst_inst);
+
+  void OutputRed() {
+    if (options_.color_output) out_ << spvtools::clr::red{true};
+  }
+  void OutputGreen() {
+    if (options_.color_output) out_ << spvtools::clr::green{true};
+  }
+  void OutputResetColor() {
+    if (options_.color_output) out_ << spvtools::clr::reset{true};
+  }
+
+  opt::IRContext* src_context_;
+  opt::IRContext* dst_context_;
+  const opt::Module* src_;
+  const opt::Module* dst_;
+  Options options_;
+  std::ostream& out_;
+
+  // Helpers to look up instructions based on id.
+  IdInstructions src_id_to_;
+  IdInstructions dst_id_to_;
+
+  // The ids that have been matched between src and dst so far.
+  SrcDstIdMap id_map_;
+
+  // List of instructions in function bodies after canonicalization.  Cached
+  // here to avoid duplicate work.  More importantly, some maps use
+  // opt::Instruction pointers so they need to be unique.
+  FunctionInstMap src_func_insts_;
+  FunctionInstMap dst_func_insts_;
+  FunctionMap src_funcs_;
+  FunctionMap dst_funcs_;
+};
+
+void IdMap::MapUnmatchedIds(IdMap& other_way) {
+  const uint32_t src_id_bound = static_cast<uint32_t>(id_map_.size());
+  const uint32_t dst_id_bound = static_cast<uint32_t>(other_way.id_map_.size());
+
+  uint32_t next_src_id = src_id_bound;
+  uint32_t next_dst_id = dst_id_bound;
+
+  for (uint32_t src_id = 1; src_id < src_id_bound; ++src_id) {
+    if (!IsMapped(src_id)) {
+      MapIds(src_id, next_dst_id);
+
+      other_way.id_map_.push_back(0);
+      other_way.MapIds(next_dst_id++, src_id);
+    }
+  }
+
+  for (uint32_t dst_id = 1; dst_id < dst_id_bound; ++dst_id) {
+    if (!other_way.IsMapped(dst_id)) {
+      id_map_.push_back(0);
+      MapIds(next_src_id, dst_id);
+
+      other_way.MapIds(dst_id, next_src_id++);
+    }
+  }
+}
+
+void SrcDstIdMap::MapUnmatchedIds() {
+  src_to_dst_.MapUnmatchedIds(dst_to_src_);
+}
+
+void IdInstructions::MapIdToInstruction(uint32_t id,
+                                        const opt::Instruction* inst) {
+  assert(id != 0);
+  assert(id < inst_map_.size());
+  assert(inst_map_[id] == nullptr);
+
+  inst_map_[id] = inst;
+}
+
+void IdInstructions::MapIdsToInstruction(
+    opt::IteratorRange<opt::Module::const_inst_iterator> section) {
+  for (const opt::Instruction& inst : section) {
+    uint32_t result_id = inst.result_id();
+    if (result_id == 0) {
+      continue;
+    }
+
+    MapIdToInstruction(result_id, &inst);
+  }
+}
+
+void IdInstructions::MapIdsToInfos(
+    opt::IteratorRange<opt::Module::const_inst_iterator> section) {
+  for (const opt::Instruction& inst : section) {
+    IdToInfoMap* info_map = nullptr;
+    uint32_t id_operand = 0;
+
+    switch (inst.opcode()) {
+      case SpvOpName:
+        info_map = &name_map_;
+        break;
+      case SpvOpMemberName:
+        info_map = &name_map_;
+        break;
+      case SpvOpDecorate:
+        info_map = &decoration_map_;
+        break;
+      case SpvOpMemberDecorate:
+        info_map = &decoration_map_;
+        break;
+      case SpvOpTypeForwardPointer: {
+        uint32_t id = inst.GetSingleWordOperand(0);
+        assert(id != 0);
+
+        assert(id < forward_pointer_map_.size());
+        forward_pointer_map_[id] = &inst;
+        continue;
+      }
+      default:
+        // Currently unsupported instruction, don't attempt to use it for
+        // matching.
+        break;
+    }
+
+    if (info_map == nullptr) {
+      continue;
+    }
+
+    uint32_t id = inst.GetOperand(id_operand).AsId();
+    assert(id != 0);
+
+    assert(id < info_map->size());
+    assert(std::find((*info_map)[id].begin(), (*info_map)[id].end(), &inst) ==
+           (*info_map)[id].end());
+
+    (*info_map)[id].push_back(&inst);
+  }
+}
+
+void Differ::PoolPotentialIds(
+    opt::IteratorRange<opt::Module::const_inst_iterator> section,
+    std::vector<uint32_t>& ids, bool is_src,
+    std::function<bool(const opt::Instruction&)> filter,
+    std::function<uint32_t(const opt::Instruction&)> get_id) {
+  for (const opt::Instruction& inst : section) {
+    if (!filter(inst)) {
+      continue;
+    }
+
+    uint32_t result_id = get_id(inst);
+    assert(result_id != 0);
+
+    assert(std::find(ids.begin(), ids.end(), result_id) == ids.end());
+
+    // Don't include ids that are already matched, for example through
+    // OpTypeForwardPointer.
+    const bool is_matched = is_src ? id_map_.IsSrcMapped(result_id)
+                                   : id_map_.IsDstMapped(result_id);
+    if (is_matched) {
+      continue;
+    }
+
+    ids.push_back(result_id);
+  }
+}
+
+void Differ::MatchIds(
+    PotentialIdMap& potential,
+    std::function<bool(const opt::Instruction*, const opt::Instruction*)>
+        match) {
+  for (size_t src_index = 0; src_index < potential.src_ids.size();
+       ++src_index) {
+    for (size_t dst_index = 0; dst_index < potential.dst_ids.size();
+         ++dst_index) {
+      const uint32_t src_id = potential.src_ids[src_index];
+      const uint32_t dst_id = potential.dst_ids[dst_index];
+
+      if (dst_id == 0) {
+        // Already matched.
+        continue;
+      }
+
+      const opt::Instruction* src_inst = src_id_to_.inst_map_[src_id];
+      const opt::Instruction* dst_inst = dst_id_to_.inst_map_[dst_id];
+
+      if (match(src_inst, dst_inst)) {
+        id_map_.MapIds(src_id, dst_id);
+
+        // Remove the ids from the potential list.
+        potential.src_ids[src_index] = 0;
+        potential.dst_ids[dst_index] = 0;
+
+        // Find a match for the next src id.
+        break;
+      }
+    }
+  }
+
+  // Remove matched ids to make the next iteration faster.
+  CompactIds(potential.src_ids);
+  CompactIds(potential.dst_ids);
+}
+
+void Differ::MatchPreambleInstructions(
+    opt::IteratorRange<opt::Module::const_inst_iterator> src_insts,
+    opt::IteratorRange<opt::Module::const_inst_iterator> dst_insts) {
+  // First, pool all instructions from each section and sort them.
+  InstructionList sorted_src_insts = SortPreambleInstructions(src_, src_insts);
+  InstructionList sorted_dst_insts = SortPreambleInstructions(dst_, dst_insts);
+
+  // Then walk and match them.
+  size_t src_cur = 0;
+  size_t dst_cur = 0;
+
+  while (src_cur < sorted_src_insts.size() &&
+         dst_cur < sorted_dst_insts.size()) {
+    const opt::Instruction* src_inst = sorted_src_insts[src_cur];
+    const opt::Instruction* dst_inst = sorted_dst_insts[dst_cur];
+
+    int compare = ComparePreambleInstructions(src_inst, dst_inst, src_, dst_);
+    if (compare == 0) {
+      id_map_.MapInsts(src_inst, dst_inst);
+    }
+    if (compare <= 0) {
+      ++src_cur;
+    }
+    if (compare >= 0) {
+      ++dst_cur;
+    }
+  }
+}
+
+InstructionList Differ::SortPreambleInstructions(
+    const opt::Module* module,
+    opt::IteratorRange<opt::Module::const_inst_iterator> insts) {
+  InstructionList sorted;
+  for (const opt::Instruction& inst : insts) {
+    sorted.push_back(&inst);
+  }
+  std::sort(
+      sorted.begin(), sorted.end(),
+      [this, module](const opt::Instruction* a, const opt::Instruction* b) {
+        return ComparePreambleInstructions(a, b, module, module) < 0;
+      });
+  return sorted;
+}
+
+int Differ::ComparePreambleInstructions(const opt::Instruction* a,
+                                        const opt::Instruction* b,
+                                        const opt::Module* src_inst_module,
+                                        const opt::Module* dst_inst_module) {
+  assert(a->opcode() == b->opcode());
+  assert(!a->HasResultId());
+  assert(!a->HasResultType());
+
+  const uint32_t a_operand_count = a->NumOperands();
+  const uint32_t b_operand_count = b->NumOperands();
+
+  if (a_operand_count < b_operand_count) {
+    return -1;
+  }
+  if (a_operand_count > b_operand_count) {
+    return 1;
+  }
+
+  // Instead of comparing OpExecutionMode entry point ids as ids, compare them
+  // through their corresponding execution model.  This simplifies traversing
+  // the sorted list of instructions between src and dst modules.
+  if (a->opcode() == SpvOpExecutionMode) {
+    const SpvExecutionModel src_model =
+        GetExecutionModel(src_inst_module, a->GetSingleWordOperand(0));
+    const SpvExecutionModel dst_model =
+        GetExecutionModel(dst_inst_module, b->GetSingleWordOperand(0));
+
+    if (src_model < dst_model) {
+      return -1;
+    }
+    if (src_model > dst_model) {
+      return 1;
+    }
+  }
+
+  // Match every operand of the instruction.
+  for (uint32_t operand_index = 0; operand_index < a_operand_count;
+       ++operand_index) {
+    const opt::Operand& a_operand = a->GetOperand(operand_index);
+    const opt::Operand& b_operand = b->GetOperand(operand_index);
+
+    if (a_operand.type < b_operand.type) {
+      return -1;
+    }
+    if (a_operand.type > b_operand.type) {
+      return 1;
+    }
+
+    switch (a_operand.type) {
+      case SPV_OPERAND_TYPE_ID:
+        // Don't compare ids, there can't be multiple instances of the
+        // OpExecutionMode with different ids of the same execution model.
+        break;
+      case SPV_OPERAND_TYPE_TYPE_ID:
+      case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
+      case SPV_OPERAND_TYPE_SCOPE_ID:
+        assert(false && "Unreachable");
+        break;
+      case SPV_OPERAND_TYPE_LITERAL_STRING: {
+        int str_compare =
+            strcmp(a_operand.AsString().c_str(), b_operand.AsString().c_str());
+        if (str_compare != 0) {
+          return str_compare;
+        }
+        break;
+      }
+      default:
+        // Expect literal values to match.
+        assert(a_operand.words.size() == 1);
+        assert(b_operand.words.size() == 1);
+
+        if (a_operand.words[0] < b_operand.words[0]) {
+          return -1;
+        }
+        if (a_operand.words[0] > b_operand.words[0]) {
+          return 1;
+        }
+        break;
+    }
+  }
+
+  return 0;
+}
+
+void Differ::MatchDebugAndAnnotationInstructions(
+    opt::IteratorRange<opt::Module::const_inst_iterator> src_insts,
+    opt::IteratorRange<opt::Module::const_inst_iterator> dst_insts) {
+  for (const opt::Instruction& src_inst : src_insts) {
+    for (const opt::Instruction& dst_inst : dst_insts) {
+      if (MappedSrcInst(&dst_inst) != nullptr) {
+        continue;
+      }
+
+      // Map instructions as soon as they match.  Debug and annotation
+      // instructions are matched such that there can't be multiple matches.
+      if (DoDebugAndAnnotationInstructionsMatch(&src_inst, &dst_inst)) {
+        id_map_.MapInsts(&src_inst, &dst_inst);
+        break;
+      }
+    }
+  }
+}
+
+uint32_t Differ::GroupIdsHelperGetTypeId(const IdInstructions& id_to,
+                                         uint32_t id) {
+  return GetInst(id_to, id)->type_id();
+}
+
+SpvStorageClass Differ::GroupIdsHelperGetTypePointerStorageClass(
+    const IdInstructions& id_to, uint32_t id) {
+  const opt::Instruction* inst = GetInst(id_to, id);
+  assert(inst && inst->opcode() == SpvOpTypePointer);
+  return SpvStorageClass(inst->GetSingleWordInOperand(0));
+}
+
+SpvOp Differ::GroupIdsHelperGetTypePointerTypeOp(const IdInstructions& id_to,
+                                                 uint32_t id) {
+  const opt::Instruction* inst = GetInst(id_to, id);
+  assert(inst && inst->opcode() == SpvOpTypePointer);
+
+  const uint32_t type_id = inst->GetSingleWordInOperand(1);
+  const opt::Instruction* type_inst = GetInst(id_to, type_id);
+  assert(type_inst);
+
+  return type_inst->opcode();
+}
+
+template <typename T>
+void Differ::GroupIds(const IdGroup& ids, bool is_src,
+                      std::map<T, IdGroup>* groups,
+                      T (Differ::*get_group)(const IdInstructions&, uint32_t)) {
+  assert(groups->empty());
+
+  const IdInstructions& id_to = is_src ? src_id_to_ : dst_id_to_;
+
+  for (const uint32_t id : ids) {
+    // Don't include ids that are already matched, for example through
+    // OpEntryPoint.
+    const bool is_matched =
+        is_src ? id_map_.IsSrcMapped(id) : id_map_.IsDstMapped(id);
+    if (is_matched) {
+      continue;
+    }
+
+    T group = (this->*get_group)(id_to, id);
+    (*groups)[group].push_back(id);
+  }
+}
+
+template <typename T>
+void Differ::GroupIdsAndMatch(
+    const IdGroup& src_ids, const IdGroup& dst_ids, T invalid_group_key,
+    T (Differ::*get_group)(const IdInstructions&, uint32_t),
+    std::function<void(const IdGroup& src_group, const IdGroup& dst_group)>
+        match_group) {
+  // Group the ids based on a key (get_group)
+  std::map<T, IdGroup> src_groups;
+  std::map<T, IdGroup> dst_groups;
+
+  GroupIds<T>(src_ids, true, &src_groups, get_group);
+  GroupIds<T>(dst_ids, false, &dst_groups, get_group);
+
+  // Iterate over the groups, and match those with identical keys
+  for (const auto& iter : src_groups) {
+    const T& key = iter.first;
+    const IdGroup& src_group = iter.second;
+
+    if (key == invalid_group_key) {
+      continue;
+    }
+
+    const IdGroup& dst_group = dst_groups[key];
+
+    // Let the caller match the groups as appropriate.
+    match_group(src_group, dst_group);
+  }
+}
+
+bool Differ::DoIdsMatch(uint32_t src_id, uint32_t dst_id) {
+  assert(dst_id != 0);
+  return id_map_.MappedDstId(src_id) == dst_id;
+}
+
+bool Differ::DoesOperandMatch(const opt::Operand& src_operand,
+                              const opt::Operand& dst_operand) {
+  assert(src_operand.type == dst_operand.type);
+
+  switch (src_operand.type) {
+    case SPV_OPERAND_TYPE_ID:
+    case SPV_OPERAND_TYPE_TYPE_ID:
+    case SPV_OPERAND_TYPE_RESULT_ID:
+    case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
+    case SPV_OPERAND_TYPE_SCOPE_ID:
+      // Match ids only if they are already matched in the id map.
+      return DoIdsMatch(src_operand.AsId(), dst_operand.AsId());
+    case SPV_OPERAND_TYPE_LITERAL_STRING:
+      return src_operand.AsString() == dst_operand.AsString();
+    default:
+      // Otherwise expect them to match exactly.
+      assert(src_operand.type != SPV_OPERAND_TYPE_LITERAL_STRING);
+      if (src_operand.words.size() != dst_operand.words.size()) {
+        return false;
+      }
+      for (size_t i = 0; i < src_operand.words.size(); ++i) {
+        if (src_operand.words[i] != dst_operand.words[i]) {
+          return false;
+        }
+      }
+      return true;
+  }
+}
+
+bool Differ::DoOperandsMatch(const opt::Instruction* src_inst,
+                             const opt::Instruction* dst_inst,
+                             uint32_t in_operand_index_start,
+                             uint32_t in_operand_count) {
+  // Caller should have returned early for instructions with different opcode.
+  assert(src_inst->opcode() == dst_inst->opcode());
+
+  bool match = true;
+  for (uint32_t i = 0; i < in_operand_count; ++i) {
+    const uint32_t in_operand_index = in_operand_index_start + i;
+
+    const opt::Operand& src_operand = src_inst->GetInOperand(in_operand_index);
+    const opt::Operand& dst_operand = dst_inst->GetInOperand(in_operand_index);
+
+    match = match && DoesOperandMatch(src_operand, dst_operand);
+  }
+
+  return match;
+}
+
+bool Differ::DoInstructionsMatch(const opt::Instruction* src_inst,
+                                 const opt::Instruction* dst_inst) {
+  // Check whether the two instructions are identical, that is the instructions
+  // themselves are matched, every id is matched, and every other value is
+  // identical.
+  if (MappedDstInst(src_inst) != dst_inst) {
+    return false;
+  }
+
+  assert(src_inst->opcode() == dst_inst->opcode());
+  if (src_inst->NumOperands() != dst_inst->NumOperands()) {
+    return false;
+  }
+
+  for (uint32_t operand_index = 0; operand_index < src_inst->NumOperands();
+       ++operand_index) {
+    const opt::Operand& src_operand = src_inst->GetOperand(operand_index);
+    const opt::Operand& dst_operand = dst_inst->GetOperand(operand_index);
+
+    if (!DoesOperandMatch(src_operand, dst_operand)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool Differ::DoIdsMatchFuzzy(uint32_t src_id, uint32_t dst_id) {
+  assert(dst_id != 0);
+  const uint32_t mapped_dst_id = id_map_.MappedDstId(src_id);
+
+  // Consider unmatched ids as a match.  In function bodies, no result id is
+  // matched yet and thus they are excluded from instruction matching when used
+  // as parameters in subsequent instructions.
+  if (mapped_dst_id == 0 || mapped_dst_id == dst_id) {
+    return true;
+  }
+
+  // Int and Uint constants are interchangeable, match them in that case.
+  if (AreIdenticalUintConstants(src_id, dst_id)) {
+    return true;
+  }
+
+  return false;
+}
+
+bool Differ::DoesOperandMatchFuzzy(const opt::Operand& src_operand,
+                                   const opt::Operand& dst_operand) {
+  if (src_operand.type != dst_operand.type) {
+    return false;
+  }
+
+  assert(src_operand.type != SPV_OPERAND_TYPE_RESULT_ID);
+  assert(dst_operand.type != SPV_OPERAND_TYPE_RESULT_ID);
+
+  switch (src_operand.type) {
+    case SPV_OPERAND_TYPE_ID:
+    case SPV_OPERAND_TYPE_TYPE_ID:
+    case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
+    case SPV_OPERAND_TYPE_SCOPE_ID:
+      // Match id operands only if they are already matched in the id map.
+      return DoIdsMatchFuzzy(src_operand.AsId(), dst_operand.AsId());
+    default:
+      // Otherwise allow everything to match.
+      return true;
+  }
+}
+
+bool Differ::DoInstructionsMatchFuzzy(const opt::Instruction* src_inst,
+                                      const opt::Instruction* dst_inst) {
+  // Similar to DoOperandsMatch, but only checks that ids that have already been
+  // matched are identical.  Ids that are unknown are allowed to match, as well
+  // as any non-id operand.
+  if (src_inst->opcode() != dst_inst->opcode()) {
+    return false;
+  }
+  // For external instructions, make sure the set and opcode of the external
+  // instruction matches too.
+  if (src_inst->opcode() == SpvOpExtInst) {
+    if (!DoOperandsMatch(src_inst, dst_inst, 0, 2)) {
+      return false;
+    }
+  }
+
+  assert(src_inst->HasResultType() == dst_inst->HasResultType());
+  if (src_inst->HasResultType() &&
+      !DoIdsMatchFuzzy(src_inst->type_id(), dst_inst->type_id())) {
+    return false;
+  }
+
+  // TODO: allow some instructions to match with different instruction lengths,
+  // for example OpImage* with additional operands.
+  if (src_inst->NumInOperandWords() != dst_inst->NumInOperandWords()) {
+    return false;
+  }
+
+  bool match = true;
+  for (uint32_t in_operand_index = 0;
+       in_operand_index < src_inst->NumInOperandWords(); ++in_operand_index) {
+    const opt::Operand& src_operand = src_inst->GetInOperand(in_operand_index);
+    const opt::Operand& dst_operand = dst_inst->GetInOperand(in_operand_index);
+
+    match = match && DoesOperandMatchFuzzy(src_operand, dst_operand);
+  }
+
+  return match;
+}
+
+bool Differ::AreIdenticalUintConstants(uint32_t src_id, uint32_t dst_id) {
+  return IsConstantUint(src_id_to_, src_id) &&
+         IsConstantUint(dst_id_to_, dst_id) &&
+         GetConstantUint(src_id_to_, src_id) ==
+             GetConstantUint(dst_id_to_, dst_id);
+}
+
+bool Differ::DoDebugAndAnnotationInstructionsMatch(
+    const opt::Instruction* src_inst, const opt::Instruction* dst_inst) {
+  if (src_inst->opcode() != dst_inst->opcode()) {
+    return false;
+  }
+
+  switch (src_inst->opcode()) {
+    case SpvOpString:
+    case SpvOpSourceExtension:
+    case SpvOpModuleProcessed:
+      return DoesOperandMatch(src_inst->GetOperand(0), dst_inst->GetOperand(0));
+    case SpvOpSource:
+      return DoOperandsMatch(src_inst, dst_inst, 0, 2);
+    case SpvOpSourceContinued:
+      return true;
+    case SpvOpName:
+      return DoOperandsMatch(src_inst, dst_inst, 0, 1);
+    case SpvOpMemberName:
+      return DoOperandsMatch(src_inst, dst_inst, 0, 2);
+    case SpvOpDecorate:
+      return DoOperandsMatch(src_inst, dst_inst, 0, 2);
+    case SpvOpMemberDecorate:
+      return DoOperandsMatch(src_inst, dst_inst, 0, 3);
+    case SpvOpExtInst:
+    case SpvOpDecorationGroup:
+    case SpvOpGroupDecorate:
+    case SpvOpGroupMemberDecorate:
+      return false;
+    default:
+      return false;
+  }
+}
+
+bool Differ::AreVariablesMatchable(uint32_t src_id, uint32_t dst_id,
+                                   uint32_t flexibility) {
+  // Variables must match by their built-in decorations.
+  uint32_t src_built_in_decoration = 0, dst_built_in_decoration = 0;
+  const bool src_is_built_in = GetDecorationValue(
+      src_id_to_, src_id, SpvDecorationBuiltIn, &src_built_in_decoration);
+  const bool dst_is_built_in = GetDecorationValue(
+      dst_id_to_, dst_id, SpvDecorationBuiltIn, &dst_built_in_decoration);
+
+  if (src_is_built_in != dst_is_built_in) {
+    return false;
+  }
+  if (src_is_built_in && src_built_in_decoration != dst_built_in_decoration) {
+    return false;
+  }
+
+  // Check their types and storage classes.
+  SpvStorageClass src_storage_class, dst_storage_class;
+  const uint32_t src_type_id =
+      GetVarTypeId(src_id_to_, src_id, &src_storage_class);
+  const uint32_t dst_type_id =
+      GetVarTypeId(dst_id_to_, dst_id, &dst_storage_class);
+
+  if (!DoIdsMatch(src_type_id, dst_type_id)) {
+    return false;
+  }
+  switch (flexibility) {
+    case 0:
+      if (src_storage_class != dst_storage_class) {
+        return false;
+      }
+      break;
+    case 1:
+      if (src_storage_class != dst_storage_class) {
+        // Allow one of the two to be Private while the other is Input or
+        // Output, this allows matching in/out variables that have been turned
+        // global as part of linking two stages (as done in ANGLE).
+        const bool src_is_io = src_storage_class == SpvStorageClassInput ||
+                               src_storage_class == SpvStorageClassOutput;
+        const bool dst_is_io = dst_storage_class == SpvStorageClassInput ||
+                               dst_storage_class == SpvStorageClassOutput;
+        const bool src_is_private = src_storage_class == SpvStorageClassPrivate;
+        const bool dst_is_private = dst_storage_class == SpvStorageClassPrivate;
+
+        if (!((src_is_io && dst_is_private) || (src_is_private && dst_is_io))) {
+          return false;
+        }
+      }
+      break;
+    default:
+      assert(false && "Unreachable");
+      return false;
+  }
+
+  // TODO: Is there any other way to check compatiblity of the variables?  It's
+  // easy to tell when the variables definitely don't match, but there's little
+  // information that can be used for a definite match.
+  return true;
+}
+
+bool Differ::MatchOpTypeStruct(const opt::Instruction* src_inst,
+                               const opt::Instruction* dst_inst,
+                               uint32_t flexibility) {
+  const uint32_t src_type_id = src_inst->result_id();
+  const uint32_t dst_type_id = dst_inst->result_id();
+
+  bool src_has_name = false, dst_has_name = false;
+  std::string src_name = GetName(src_id_to_, src_type_id, &src_has_name);
+  std::string dst_name = GetName(dst_id_to_, dst_type_id, &dst_has_name);
+
+  // If debug info is present, always match the structs by name.
+  if (src_has_name && dst_has_name) {
+    if (src_name != dst_name) {
+      return false;
+    }
+
+    // For gl_PerVertex, find the type pointer of this type (array) and make
+    // sure the storage classes of src and dst match; geometry and tessellation
+    // shaders have two instances of gl_PerVertex.
+    if (src_name == "gl_PerVertex") {
+      return MatchPerVertexType(src_type_id, dst_type_id);
+    }
+
+    return true;
+  }
+
+  // If debug info is not present, match the structs by their type.
+
+  // For gl_PerVertex, find the type pointer of this type (array) and match by
+  // storage class. The gl_PerVertex struct is itself found by the BuiltIn
+  // decorations applied to its members.
+  const bool src_is_per_vertex = IsPerVertexType(src_id_to_, src_type_id);
+  const bool dst_is_per_vertex = IsPerVertexType(dst_id_to_, dst_type_id);
+  if (src_is_per_vertex != dst_is_per_vertex) {
+    return false;
+  }
+
+  if (src_is_per_vertex) {
+    return MatchPerVertexType(src_type_id, dst_type_id);
+  }
+
+  switch (flexibility) {
+    case 0:
+      if (src_inst->NumInOperandWords() != dst_inst->NumInOperandWords()) {
+        return false;
+      }
+      return DoOperandsMatch(src_inst, dst_inst, 0,
+                             src_inst->NumInOperandWords());
+    case 1:
+      // TODO: match by taking a diff of the fields, and see if there's a >75%
+      // match.  Need to then make sure OpMemberName, OpMemberDecorate,
+      // OpAccessChain etc are aware of the struct field matching.
+      return false;
+    default:
+      assert(false && "Unreachable");
+      return false;
+  }
+}
+
+bool Differ::MatchOpConstant(const opt::Instruction* src_inst,
+                             const opt::Instruction* dst_inst,
+                             uint32_t flexibility) {
+  // The constants' type must match.  In flexibility == 1, match constants of
+  // int and uint, as they are generally interchangeable.
+  switch (flexibility) {
+    case 0:
+      if (!DoesOperandMatch(src_inst->GetOperand(0), dst_inst->GetOperand(0))) {
+        return false;
+      }
+      break;
+    case 1:
+      if (!IsIntType(src_id_to_, src_inst->type_id()) ||
+          !IsIntType(dst_id_to_, dst_inst->type_id())) {
+        return false;
+      }
+      break;
+    default:
+      assert(false && "Unreachable");
+      return false;
+  }
+
+  const opt::Operand& src_value_operand = src_inst->GetOperand(2);
+  const opt::Operand& dst_value_operand = dst_inst->GetOperand(2);
+
+  const uint64_t src_value = src_value_operand.AsLiteralUint64();
+  const uint64_t dst_value = dst_value_operand.AsLiteralUint64();
+
+  // If values are identical, it's a match.
+  if (src_value == dst_value) {
+    return true;
+  }
+
+  // Otherwise, only allow flexibility for float types.
+  if (IsFloatType(src_id_to_, src_inst->type_id()) && flexibility == 1) {
+    // Tolerance is:
+    //
+    // - For float: allow 4 bits of mantissa as error
+    // - For double: allow 6 bits of mantissa as error
+    //
+    // TODO: the above values are arbitrary and a placeholder; investigate the
+    // amount of error resulting from using `printf("%f", f)` and `printf("%lf",
+    // d)` and having glslang parse them.
+    const uint64_t tolerance = src_value_operand.words.size() == 1 ? 16 : 64;
+    return src_value - dst_value < tolerance ||
+           dst_value - src_value < tolerance;
+  }
+
+  return false;
+}
+
+bool Differ::MatchOpSpecConstant(const opt::Instruction* src_inst,
+                                 const opt::Instruction* dst_inst) {
+  const uint32_t src_id = src_inst->result_id();
+  const uint32_t dst_id = dst_inst->result_id();
+
+  bool src_has_name = false, dst_has_name = false;
+  std::string src_name = GetName(src_id_to_, src_id, &src_has_name);
+  std::string dst_name = GetName(dst_id_to_, dst_id, &dst_has_name);
+
+  // If debug info is present, always match the spec consts by name.
+  if (src_has_name && dst_has_name) {
+    return src_name == dst_name;
+  }
+
+  // Otherwise, match them by SpecId.
+  uint32_t src_spec_id, dst_spec_id;
+
+  if (GetDecorationValue(src_id_to_, src_id, SpvDecorationSpecId,
+                         &src_spec_id) &&
+      GetDecorationValue(dst_id_to_, dst_id, SpvDecorationSpecId,
+                         &dst_spec_id)) {
+    return src_spec_id == dst_spec_id;
+  }
+
+  // There is no SpecId decoration, while not practical, still valid.
+  // SpecConstantOp don't have SpecId and can be matched by operands
+  if (src_inst->opcode() == SpvOpSpecConstantOp) {
+    if (src_inst->NumInOperandWords() == dst_inst->NumInOperandWords()) {
+      return DoOperandsMatch(src_inst, dst_inst, 0,
+                             src_inst->NumInOperandWords());
+    }
+  }
+
+  return false;
+}
+
+bool Differ::MatchOpVariable(const opt::Instruction* src_inst,
+                             const opt::Instruction* dst_inst,
+                             uint32_t flexibility) {
+  const uint32_t src_id = src_inst->result_id();
+  const uint32_t dst_id = dst_inst->result_id();
+
+  const bool src_is_pervertex = IsPerVertexVariable(src_id_to_, src_id);
+  const bool dst_is_pervertex = IsPerVertexVariable(dst_id_to_, dst_id);
+
+  // For gl_PerVertex, make sure the input and output instances are matched
+  // correctly.
+  if (src_is_pervertex != dst_is_pervertex) {
+    return false;
+  }
+  if (src_is_pervertex) {
+    return MatchPerVertexVariable(src_inst, dst_inst);
+  }
+
+  bool src_has_name = false, dst_has_name = false;
+  std::string src_name = GetName(src_id_to_, src_id, &src_has_name);
+  std::string dst_name = GetName(dst_id_to_, dst_id, &dst_has_name);
+
+  // If debug info is present, always match the variables by name.
+  if (src_has_name && dst_has_name) {
+    return src_name == dst_name;
+  }
+
+  // If debug info is not present, see if the variables can be matched by their
+  // built-in decorations.
+  uint32_t src_built_in_decoration;
+  const bool src_is_built_in = GetDecorationValue(
+      src_id_to_, src_id, SpvDecorationBuiltIn, &src_built_in_decoration);
+
+  if (src_is_built_in && AreVariablesMatchable(src_id, dst_id, flexibility)) {
+    return true;
+  }
+
+  SpvStorageClass src_storage_class, dst_storage_class;
+  GetVarTypeId(src_id_to_, src_id, &src_storage_class);
+  GetVarTypeId(dst_id_to_, dst_id, &dst_storage_class);
+
+  if (src_storage_class != dst_storage_class) {
+    return false;
+  }
+
+  // If variables are decorated with set/binding, match by the value of those
+  // decorations.
+  if (!options_.ignore_set_binding) {
+    uint32_t src_set = 0, dst_set = 0;
+    uint32_t src_binding = 0, dst_binding = 0;
+
+    const bool src_has_set = GetDecorationValue(
+        src_id_to_, src_id, SpvDecorationDescriptorSet, &src_set);
+    const bool dst_has_set = GetDecorationValue(
+        dst_id_to_, dst_id, SpvDecorationDescriptorSet, &dst_set);
+    const bool src_has_binding =
+        GetDecorationValue(src_id_to_, src_id, SpvDecorationBinding, &src_set);
+    const bool dst_has_binding =
+        GetDecorationValue(dst_id_to_, dst_id, SpvDecorationBinding, &dst_set);
+
+    if (src_has_set && dst_has_set && src_has_binding && dst_has_binding) {
+      return src_set == dst_set && src_binding == dst_binding;
+    }
+  }
+
+  // If variables are decorated with location, match by the value of that
+  // decoration.
+  if (!options_.ignore_location) {
+    uint32_t src_location, dst_location;
+
+    const bool src_has_location = GetDecorationValue(
+        src_id_to_, src_id, SpvDecorationLocation, &src_location);
+    const bool dst_has_location = GetDecorationValue(
+        dst_id_to_, dst_id, SpvDecorationLocation, &dst_location);
+
+    if (src_has_location && dst_has_location) {
+      return src_location == dst_location;
+    }
+  }
+
+  // Currently, there's no other way to match variables.
+  return false;
+}
+
+bool Differ::MatchPerVertexType(uint32_t src_type_id, uint32_t dst_type_id) {
+  // For gl_PerVertex, find the type pointer of this type (array) and make sure
+  // the storage classes of src and dst match; geometry and tessellation shaders
+  // have two instances of gl_PerVertex.
+  SpvStorageClass src_storage_class =
+      GetPerVertexStorageClass(src_, src_type_id);
+  SpvStorageClass dst_storage_class =
+      GetPerVertexStorageClass(dst_, dst_type_id);
+
+  assert(src_storage_class == SpvStorageClassInput ||
+         src_storage_class == SpvStorageClassOutput);
+  assert(dst_storage_class == SpvStorageClassInput ||
+         dst_storage_class == SpvStorageClassOutput);
+
+  return src_storage_class == dst_storage_class;
+}
+
+bool Differ::MatchPerVertexVariable(const opt::Instruction* src_inst,
+                                    const opt::Instruction* dst_inst) {
+  SpvStorageClass src_storage_class =
+      SpvStorageClass(src_inst->GetSingleWordInOperand(0));
+  SpvStorageClass dst_storage_class =
+      SpvStorageClass(dst_inst->GetSingleWordInOperand(0));
+
+  return src_storage_class == dst_storage_class;
+}
+
+void Differ::MatchTypeForwardPointersByName(const IdGroup& src,
+                                            const IdGroup& dst) {
+  // Given two sets of compatible groups of OpTypeForwardPointer instructions,
+  // attempts to match them by name.
+
+  // Group them by debug info and loop over them.
+  GroupIdsAndMatch<std::string>(
+      src, dst, "", &Differ::GetSanitizedName,
+      [this](const IdGroup& src_group, const IdGroup& dst_group) {
+
+        // Match only if there's a unique forward declaration with this debug
+        // name.
+        if (src_group.size() == 1 && dst_group.size() == 1) {
+          id_map_.MapIds(src_group[0], dst_group[0]);
+        }
+      });
+}
+
+void Differ::MatchTypeForwardPointersByTypeOp(const IdGroup& src,
+                                              const IdGroup& dst) {
+  // Given two sets of compatible groups of OpTypeForwardPointer instructions,
+  // attempts to match them by type op.  Must be called after
+  // MatchTypeForwardPointersByName to match as many as possible by debug info.
+
+  // Remove ids that are matched with debug info in
+  // MatchTypeForwardPointersByName.
+  IdGroup src_unmatched_ids;
+  IdGroup dst_unmatched_ids;
+
+  std::copy_if(src.begin(), src.end(), std::back_inserter(src_unmatched_ids),
+               [this](uint32_t id) { return !id_map_.IsSrcMapped(id); });
+  std::copy_if(dst.begin(), dst.end(), std::back_inserter(dst_unmatched_ids),
+               [this](uint32_t id) { return !id_map_.IsDstMapped(id); });
+
+  // Match only if there's a unique forward declaration with this
+  // storage class and type opcode.  If both have debug info, they
+  // must not have been matchable.
+  if (src_unmatched_ids.size() == 1 && dst_unmatched_ids.size() == 1) {
+    uint32_t src_id = src_unmatched_ids[0];
+    uint32_t dst_id = dst_unmatched_ids[0];
+    if (!HasName(src_id_to_, src_id) || !HasName(dst_id_to_, dst_id)) {
+      id_map_.MapIds(src_id, dst_id);
+    }
+  }
+}
+
+InstructionList Differ::GetFunctionBody(opt::IRContext* context,
+                                        opt::Function& function) {
+  // Canonicalize the blocks of the function to produce better diff, for example
+  // to not produce any diff if the src and dst have the same switch/case blocks
+  // but with the cases simply reordered.
+  std::list<opt::BasicBlock*> order;
+  context->cfg()->ComputeStructuredOrder(&function, &*function.begin(), &order);
+
+  // Go over the instructions of the function and add the instructions to a flat
+  // list to simplify future iterations.
+  InstructionList body;
+  for (opt::BasicBlock* block : order) {
+    block->ForEachInst(
+        [&body](const opt::Instruction* inst) { body.push_back(inst); }, true);
+  }
+  body.push_back(function.EndInst());
+
+  return body;
+}
+
+InstructionList Differ::GetFunctionHeader(const opt::Function& function) {
+  // Go over the instructions of the function and add the header instructions to
+  // a flat list to simplify diff generation.
+  InstructionList body;
+  function.WhileEachInst(
+      [&body](const opt::Instruction* inst) {
+        if (inst->opcode() == SpvOpLabel) {
+          return false;
+        }
+        body.push_back(inst);
+        return true;
+      },
+      true, true);
+
+  return body;
+}
+
+void Differ::GetFunctionBodies(opt::IRContext* context, FunctionMap* functions,
+                               FunctionInstMap* function_insts) {
+  for (opt::Function& function : *context->module()) {
+    uint32_t id = function.result_id();
+    assert(functions->find(id) == functions->end());
+    assert(function_insts->find(id) == function_insts->end());
+
+    (*functions)[id] = &function;
+
+    InstructionList body = GetFunctionBody(context, function);
+    (*function_insts)[id] = std::move(body);
+  }
+}
+
+void Differ::GetFunctionHeaderInstructions(const opt::Module* module,
+                                           FunctionInstMap* function_insts) {
+  for (opt::Function& function : *module) {
+    InstructionList body = GetFunctionHeader(function);
+    (*function_insts)[function.result_id()] = std::move(body);
+  }
+}
+
+void Differ::BestEffortMatchFunctions(const IdGroup& src_func_ids,
+                                      const IdGroup& dst_func_ids,
+                                      const FunctionInstMap& src_func_insts,
+                                      const FunctionInstMap& dst_func_insts) {
+  struct MatchResult {
+    uint32_t src_id;
+    uint32_t dst_id;
+    DiffMatch src_match;
+    DiffMatch dst_match;
+    float match_rate;
+    bool operator<(const MatchResult& other) const {
+      return match_rate > other.match_rate;
+    }
+  };
+  std::vector<MatchResult> all_match_results;
+
+  for (const uint32_t src_func_id : src_func_ids) {
+    if (id_map_.IsSrcMapped(src_func_id)) {
+      continue;
+    }
+    const std::string src_name = GetSanitizedName(src_id_to_, src_func_id);
+
+    for (const uint32_t dst_func_id : dst_func_ids) {
+      if (id_map_.IsDstMapped(dst_func_id)) {
+        continue;
+      }
+
+      // Don't match functions that are named, but the names are different.
+      const std::string dst_name = GetSanitizedName(dst_id_to_, dst_func_id);
+      if (src_name != "" && dst_name != "" && src_name != dst_name) {
+        continue;
+      }
+
+      DiffMatch src_match_result, dst_match_result;
+      float match_rate = MatchFunctionBodies(
+          src_func_insts.at(src_func_id), dst_func_insts.at(dst_func_id),
+          &src_match_result, &dst_match_result);
+
+      // Only consider the functions a match if there's at least 60% match.
+      // This is an arbitrary limit that should be tuned.
+      constexpr float pass_match_rate = 0.6f;
+      if (match_rate >= pass_match_rate) {
+        all_match_results.emplace_back(
+            MatchResult{src_func_id, dst_func_id, std::move(src_match_result),
+                        std::move(dst_match_result), match_rate});
+      }
+    }
+  }
+
+  std::sort(all_match_results.begin(), all_match_results.end());
+
+  for (const MatchResult& match_result : all_match_results) {
+    if (id_map_.IsSrcMapped(match_result.src_id) ||
+        id_map_.IsDstMapped(match_result.dst_id)) {
+      continue;
+    }
+
+    id_map_.MapIds(match_result.src_id, match_result.dst_id);
+
+    MatchIdsInFunctionBodies(src_func_insts.at(match_result.src_id),
+                             dst_func_insts.at(match_result.dst_id),
+                             match_result.src_match, match_result.dst_match, 0);
+  }
+}
+
+void Differ::MatchFunctionParamIds(const opt::Function* src_func,
+                                   const opt::Function* dst_func) {
+  IdGroup src_params;
+  IdGroup dst_params;
+  src_func->ForEachParam(
+      [&src_params](const opt::Instruction* param) {
+        src_params.push_back(param->result_id());
+      },
+      false);
+  dst_func->ForEachParam(
+      [&dst_params](const opt::Instruction* param) {
+        dst_params.push_back(param->result_id());
+      },
+      false);
+
+  GroupIdsAndMatch<std::string>(
+      src_params, dst_params, "", &Differ::GetSanitizedName,
+      [this](const IdGroup& src_group, const IdGroup& dst_group) {
+
+        // There shouldn't be two parameters with the same name, so the ids
+        // should match. There is nothing restricting the SPIR-V however to have
+        // two parameters with the same name, so be resilient against that.
+        if (src_group.size() == 1 && dst_group.size() == 1) {
+          id_map_.MapIds(src_group[0], dst_group[0]);
+        }
+      });
+
+  // Then match the parameters by their type.  If there are multiple of them,
+  // match them by their order.
+  GroupIdsAndMatch<uint32_t>(
+      src_params, dst_params, 0, &Differ::GroupIdsHelperGetTypeId,
+      [this](const IdGroup& src_group_by_type_id,
+             const IdGroup& dst_group_by_type_id) {
+
+        const size_t shared_param_count =
+            std::min(src_group_by_type_id.size(), dst_group_by_type_id.size());
+
+        for (size_t param_index = 0; param_index < shared_param_count;
+             ++param_index) {
+          id_map_.MapIds(src_group_by_type_id[0], dst_group_by_type_id[0]);
+        }
+      });
+}
+
+float Differ::MatchFunctionBodies(const InstructionList& src_body,
+                                  const InstructionList& dst_body,
+                                  DiffMatch* src_match_result,
+                                  DiffMatch* dst_match_result) {
+  LongestCommonSubsequence<std::vector<const opt::Instruction*>> lcs(src_body,
+                                                                     dst_body);
+
+  uint32_t best_match_length = lcs.Get<const opt::Instruction*>(
+      [this](const opt::Instruction* src_inst,
+             const opt::Instruction* dst_inst) {
+        return DoInstructionsMatchFuzzy(src_inst, dst_inst);
+      },
+      src_match_result, dst_match_result);
+
+  // TODO: take the gaps in between matches and match those again with a relaxed
+  // instruction-and-type-only comparison.  This can produce a better diff for
+  // example if an array index is changed, causing the OpAccessChain id to not
+  // match and subsequently every operation that's derived from that id.
+  // Usually this mismatch cascades until the next OpStore which doesn't produce
+  // an id.
+
+  return static_cast<float>(best_match_length) * 2.0f /
+         static_cast<float>(src_body.size() + dst_body.size());
+}
+
+void Differ::MatchIdsInFunctionBodies(const InstructionList& src_body,
+                                      const InstructionList& dst_body,
+                                      const DiffMatch& src_match_result,
+                                      const DiffMatch& dst_match_result,
+                                      uint32_t flexibility) {
+  size_t src_cur = 0;
+  size_t dst_cur = 0;
+
+  while (src_cur < src_body.size() && dst_cur < dst_body.size()) {
+    if (src_match_result[src_cur] && dst_match_result[dst_cur]) {
+      // Match instructions the src and dst instructions.
+      //
+      // TODO: count the matchings between variables discovered this way and
+      // choose the "best match" after all functions have been diffed and all
+      // instructions analyzed.
+      const opt::Instruction* src_inst = src_body[src_cur++];
+      const opt::Instruction* dst_inst = dst_body[dst_cur++];
+
+      // Record the matching between the instructions.  This is done only once
+      // (hence flexibility == 0).  Calls with non-zero flexibility values will
+      // only deal with matching other ids based on the operands.
+      if (flexibility == 0) {
+        id_map_.MapInsts(src_inst, dst_inst);
+      }
+
+      // Match any unmatched variables referenced by the instructions.
+      MatchVariablesUsedByMatchedInstructions(src_inst, dst_inst, flexibility);
+      continue;
+    }
+    if (!src_match_result[src_cur]) {
+      ++src_cur;
+    }
+    if (!dst_match_result[dst_cur]) {
+      ++dst_cur;
+    }
+  }
+}
+
+void Differ::MatchVariablesUsedByMatchedInstructions(
+    const opt::Instruction* src_inst, const opt::Instruction* dst_inst,
+    uint32_t flexibility) {
+  // For OpAccessChain, OpLoad and OpStore instructions that reference unmatched
+  // variables, match them as a best effort.
+  assert(src_inst->opcode() == dst_inst->opcode());
+  switch (src_inst->opcode()) {
+    default:
+      // TODO: match functions based on OpFunctionCall?
+      break;
+    case SpvOpAccessChain:
+    case SpvOpInBoundsAccessChain:
+    case SpvOpPtrAccessChain:
+    case SpvOpInBoundsPtrAccessChain:
+    case SpvOpLoad:
+    case SpvOpStore:
+      const uint32_t src_pointer_id = src_inst->GetSingleWordInOperand(0);
+      const uint32_t dst_pointer_id = dst_inst->GetSingleWordInOperand(0);
+      if (IsVariable(src_id_to_, src_pointer_id) &&
+          IsVariable(dst_id_to_, dst_pointer_id) &&
+          !id_map_.IsSrcMapped(src_pointer_id) &&
+          !id_map_.IsDstMapped(dst_pointer_id) &&
+          AreVariablesMatchable(src_pointer_id, dst_pointer_id, flexibility)) {
+        id_map_.MapIds(src_pointer_id, dst_pointer_id);
+      }
+      break;
+  }
+}
+
+const opt::Instruction* Differ::GetInst(const IdInstructions& id_to,
+                                        uint32_t id) {
+  assert(id != 0);
+  assert(id < id_to.inst_map_.size());
+
+  const opt::Instruction* inst = id_to.inst_map_[id];
+  assert(inst != nullptr);
+
+  return inst;
+}
+
+uint32_t Differ::GetConstantUint(const IdInstructions& id_to,
+                                 uint32_t constant_id) {
+  const opt::Instruction* constant_inst = GetInst(id_to, constant_id);
+  assert(constant_inst->opcode() == SpvOpConstant);
+  assert(GetInst(id_to, constant_inst->type_id())->opcode() == SpvOpTypeInt);
+
+  return constant_inst->GetSingleWordInOperand(0);
+}
+
+SpvExecutionModel Differ::GetExecutionModel(const opt::Module* module,
+                                            uint32_t entry_point_id) {
+  for (const opt::Instruction& inst : module->entry_points()) {
+    assert(inst.opcode() == SpvOpEntryPoint);
+    if (inst.GetSingleWordOperand(1) == entry_point_id) {
+      return SpvExecutionModel(inst.GetSingleWordOperand(0));
+    }
+  }
+
+  assert(false && "Unreachable");
+  return SpvExecutionModel(0xFFF);
+}
+
+bool Differ::HasName(const IdInstructions& id_to, uint32_t id) {
+  assert(id != 0);
+  assert(id < id_to.name_map_.size());
+
+  for (const opt::Instruction* inst : id_to.name_map_[id]) {
+    if (inst->opcode() == SpvOpName) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+std::string Differ::GetName(const IdInstructions& id_to, uint32_t id,
+                            bool* has_name) {
+  assert(id != 0);
+  assert(id < id_to.name_map_.size());
+
+  for (const opt::Instruction* inst : id_to.name_map_[id]) {
+    if (inst->opcode() == SpvOpName) {
+      *has_name = true;
+      return inst->GetOperand(1).AsString();
+    }
+  }
+
+  *has_name = false;
+  return "";
+}
+
+std::string Differ::GetSanitizedName(const IdInstructions& id_to, uint32_t id) {
+  bool has_name = false;
+  std::string name = GetName(id_to, id, &has_name);
+
+  if (!has_name) {
+    return "";
+  }
+
+  // Remove args from the name, in case this is a function name
+  return name.substr(0, name.find('('));
+}
+
+uint32_t Differ::GetVarTypeId(const IdInstructions& id_to, uint32_t var_id,
+                              SpvStorageClass* storage_class) {
+  const opt::Instruction* var_inst = GetInst(id_to, var_id);
+  assert(var_inst->opcode() == SpvOpVariable);
+
+  *storage_class = SpvStorageClass(var_inst->GetSingleWordInOperand(0));
+
+  // Get the type pointer from the variable.
+  const uint32_t type_pointer_id = var_inst->type_id();
+  const opt::Instruction* type_pointer_inst = GetInst(id_to, type_pointer_id);
+
+  // Get the type from the type pointer.
+  return type_pointer_inst->GetSingleWordInOperand(1);
+}
+
+bool Differ::GetDecorationValue(const IdInstructions& id_to, uint32_t id,
+                                SpvDecoration decoration,
+                                uint32_t* decoration_value) {
+  assert(id != 0);
+  assert(id < id_to.decoration_map_.size());
+
+  for (const opt::Instruction* inst : id_to.decoration_map_[id]) {
+    if (inst->opcode() == SpvOpDecorate &&
+        inst->GetSingleWordOperand(0) == id &&
+        inst->GetSingleWordOperand(1) == decoration) {
+      *decoration_value = inst->GetSingleWordOperand(2);
+      return true;
+    }
+  }
+
+  return false;
+}
+
+const opt::Instruction* Differ::GetForwardPointerInst(
+    const IdInstructions& id_to, uint32_t id) {
+  assert(id != 0);
+  assert(id < id_to.forward_pointer_map_.size());
+  return id_to.forward_pointer_map_[id];
+}
+
+bool Differ::IsIntType(const IdInstructions& id_to, uint32_t type_id) {
+  return IsOp(id_to, type_id, SpvOpTypeInt);
+}
+
+bool Differ::IsFloatType(const IdInstructions& id_to, uint32_t type_id) {
+  return IsOp(id_to, type_id, SpvOpTypeFloat);
+}
+
+bool Differ::IsConstantUint(const IdInstructions& id_to, uint32_t id) {
+  const opt::Instruction* constant_inst = GetInst(id_to, id);
+  if (constant_inst->opcode() != SpvOpConstant) {
+    return false;
+  }
+
+  const opt::Instruction* type_inst = GetInst(id_to, constant_inst->type_id());
+  return type_inst->opcode() == SpvOpTypeInt;
+}
+
+bool Differ::IsVariable(const IdInstructions& id_to, uint32_t pointer_id) {
+  return IsOp(id_to, pointer_id, SpvOpVariable);
+}
+
+bool Differ::IsOp(const IdInstructions& id_to, uint32_t id, SpvOp op) {
+  return GetInst(id_to, id)->opcode() == op;
+}
+
+bool Differ::IsPerVertexType(const IdInstructions& id_to, uint32_t type_id) {
+  assert(type_id != 0);
+  assert(type_id < id_to.decoration_map_.size());
+
+  for (const opt::Instruction* inst : id_to.decoration_map_[type_id]) {
+    if (inst->opcode() == SpvOpMemberDecorate &&
+        inst->GetSingleWordOperand(0) == type_id &&
+        inst->GetSingleWordOperand(2) == SpvDecorationBuiltIn) {
+      SpvBuiltIn built_in = SpvBuiltIn(inst->GetSingleWordOperand(3));
+
+      // Only gl_PerVertex can have, and it can only have, the following
+      // built-in decorations.
+      return built_in == SpvBuiltInPosition ||
+             built_in == SpvBuiltInPointSize ||
+             built_in == SpvBuiltInClipDistance ||
+             built_in == SpvBuiltInCullDistance;
+    }
+  }
+
+  return false;
+}
+
+bool Differ::IsPerVertexVariable(const IdInstructions& id_to, uint32_t var_id) {
+  // Get the type from the type pointer.
+  SpvStorageClass storage_class;
+  uint32_t type_id = GetVarTypeId(id_to, var_id, &storage_class);
+  const opt::Instruction* type_inst = GetInst(id_to, type_id);
+
+  // If array, get the element type.
+  if (type_inst->opcode() == SpvOpTypeArray) {
+    type_id = type_inst->GetSingleWordInOperand(0);
+  }
+
+  // Now check if the type is gl_PerVertex.
+  return IsPerVertexType(id_to, type_id);
+}
+
+SpvStorageClass Differ::GetPerVertexStorageClass(const opt::Module* module,
+                                                 uint32_t type_id) {
+  for (const opt::Instruction& inst : module->types_values()) {
+    switch (inst.opcode()) {
+      case SpvOpTypeArray:
+        // The gl_PerVertex instance could be an array, look for a variable of
+        // the array type instead.
+        if (inst.GetSingleWordInOperand(0) == type_id) {
+          type_id = inst.result_id();
+        }
+        break;
+      case SpvOpTypePointer:
+        // Find the storage class of the pointer to this type.
+        if (inst.GetSingleWordInOperand(1) == type_id) {
+          return SpvStorageClass(inst.GetSingleWordInOperand(0));
+        }
+        break;
+      default:
+        break;
+    }
+  }
+
+  // gl_PerVertex is declared, but is unused.  Return either of Input or Output
+  // classes just so it matches one in the other module.  This should be highly
+  // unlikely, perhaps except for ancient GS-used-to-emulate-CS scenarios.
+  return SpvStorageClassOutput;
+}
+
+spv_ext_inst_type_t Differ::GetExtInstType(const IdInstructions& id_to,
+                                           uint32_t set_id) {
+  const opt::Instruction* set_inst = GetInst(id_to, set_id);
+  return spvExtInstImportTypeGet(set_inst->GetInOperand(0).AsString().c_str());
+}
+
+spv_number_kind_t Differ::GetNumberKind(const IdInstructions& id_to,
+                                        const opt::Instruction& inst,
+                                        uint32_t operand_index,
+                                        uint32_t* number_bit_width) {
+  const opt::Operand& operand = inst.GetOperand(operand_index);
+  *number_bit_width = 0;
+
+  // A very limited version of Parser::parseOperand.
+  switch (operand.type) {
+    case SPV_OPERAND_TYPE_LITERAL_INTEGER:
+    case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER:
+      // Always unsigned integers.
+      *number_bit_width = 32;
+      return SPV_NUMBER_UNSIGNED_INT;
+    case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER:
+    case SPV_OPERAND_TYPE_OPTIONAL_TYPED_LITERAL_INTEGER:
+      switch (inst.opcode()) {
+        case SpvOpSwitch:
+        case SpvOpConstant:
+        case SpvOpSpecConstant:
+          // Same kind of number as the selector (OpSwitch) or the type
+          // (Op*Constant).
+          return GetTypeNumberKind(id_to, inst.GetSingleWordOperand(0),
+                                   number_bit_width);
+        default:
+          assert(false && "Unreachable");
+          break;
+      }
+      break;
+    default:
+      break;
+  }
+
+  return SPV_NUMBER_NONE;
+}
+
+spv_number_kind_t Differ::GetTypeNumberKind(const IdInstructions& id_to,
+                                            uint32_t id,
+                                            uint32_t* number_bit_width) {
+  const opt::Instruction* type_inst = GetInst(id_to, id);
+  if (!spvOpcodeIsScalarType(type_inst->opcode())) {
+    type_inst = GetInst(id_to, type_inst->type_id());
+  }
+
+  switch (type_inst->opcode()) {
+    case SpvOpTypeInt:
+      *number_bit_width = type_inst->GetSingleWordOperand(1);
+      return type_inst->GetSingleWordOperand(2) == 0 ? SPV_NUMBER_UNSIGNED_INT
+                                                     : SPV_NUMBER_SIGNED_INT;
+      break;
+    case SpvOpTypeFloat:
+      *number_bit_width = type_inst->GetSingleWordOperand(1);
+      return SPV_NUMBER_FLOATING;
+    default:
+      assert(false && "Unreachable");
+      return SPV_NUMBER_NONE;
+  }
+}
+
+void Differ::MatchCapabilities() {
+  MatchPreambleInstructions(src_->capabilities(), dst_->capabilities());
+}
+
+void Differ::MatchExtensions() {
+  MatchPreambleInstructions(src_->extensions(), dst_->extensions());
+}
+
+void Differ::MatchExtInstImportIds() {
+  // Bunch all of this section's ids as potential matches.
+  PotentialIdMap potential_id_map;
+  auto get_result_id = [](const opt::Instruction& inst) {
+    return inst.result_id();
+  };
+  auto accept_all = [](const opt::Instruction&) { return true; };
+
+  PoolPotentialIds(src_->ext_inst_imports(), potential_id_map.src_ids, true,
+                   accept_all, get_result_id);
+  PoolPotentialIds(dst_->ext_inst_imports(), potential_id_map.dst_ids, false,
+                   accept_all, get_result_id);
+
+  // Then match the ids.
+  MatchIds(potential_id_map, [](const opt::Instruction* src_inst,
+                                const opt::Instruction* dst_inst) {
+    // Match OpExtInstImport by exact name, which is operand 1
+    const opt::Operand& src_name = src_inst->GetOperand(1);
+    const opt::Operand& dst_name = dst_inst->GetOperand(1);
+
+    return src_name.AsString() == dst_name.AsString();
+  });
+}
+void Differ::MatchMemoryModel() {
+  // Always match the memory model instructions, there is always a single one of
+  // it.
+  id_map_.MapInsts(src_->GetMemoryModel(), dst_->GetMemoryModel());
+}
+
+void Differ::MatchEntryPointIds() {
+  // Match OpEntryPoint ids (at index 1) by ExecutionModel (at index 0) and
+  // possibly name (at index 2).  OpEntryPoint doesn't produce a result id, so
+  // this function doesn't use the helpers the other functions use.
+
+  // Map from execution model to OpEntryPoint instructions of that model.
+  using ExecutionModelMap =
+      std::unordered_map<uint32_t, std::vector<const opt::Instruction*>>;
+  ExecutionModelMap src_entry_points_map;
+  ExecutionModelMap dst_entry_points_map;
+  std::set<uint32_t> all_execution_models;
+
+  for (const opt::Instruction& src_inst : src_->entry_points()) {
+    uint32_t execution_model = src_inst.GetSingleWordOperand(0);
+    src_entry_points_map[execution_model].push_back(&src_inst);
+    all_execution_models.insert(execution_model);
+  }
+  for (const opt::Instruction& dst_inst : dst_->entry_points()) {
+    uint32_t execution_model = dst_inst.GetSingleWordOperand(0);
+    dst_entry_points_map[execution_model].push_back(&dst_inst);
+    all_execution_models.insert(execution_model);
+  }
+
+  // Go through each model and match the ids.
+  for (const uint32_t execution_model : all_execution_models) {
+    auto& src_insts = src_entry_points_map[execution_model];
+    auto& dst_insts = dst_entry_points_map[execution_model];
+
+    // If there is only one entry point in src and dst with that model, match
+    // them unconditionally.
+    if (src_insts.size() == 1 && dst_insts.size() == 1) {
+      uint32_t src_id = src_insts[0]->GetSingleWordOperand(1);
+      uint32_t dst_id = dst_insts[0]->GetSingleWordOperand(1);
+      id_map_.MapIds(src_id, dst_id);
+      id_map_.MapInsts(src_insts[0], dst_insts[0]);
+      continue;
+    }
+
+    // Otherwise match them by name.
+    bool matched = false;
+    for (const opt::Instruction* src_inst : src_insts) {
+      for (const opt::Instruction* dst_inst : dst_insts) {
+        const opt::Operand& src_name = src_inst->GetOperand(2);
+        const opt::Operand& dst_name = dst_inst->GetOperand(2);
+
+        if (src_name.AsString() == dst_name.AsString()) {
+          uint32_t src_id = src_inst->GetSingleWordOperand(1);
+          uint32_t dst_id = dst_inst->GetSingleWordOperand(1);
+          id_map_.MapIds(src_id, dst_id);
+          id_map_.MapInsts(src_inst, dst_inst);
+          matched = true;
+          break;
+        }
+      }
+      if (matched) {
+        break;
+      }
+    }
+  }
+}
+
+void Differ::MatchExecutionModes() {
+  MatchPreambleInstructions(src_->execution_modes(), dst_->execution_modes());
+}
+
+void Differ::MatchTypeForwardPointers() {
+  // Bunch all of type forward pointers as potential matches.
+  PotentialIdMap potential_id_map;
+  auto get_pointer_type_id = [](const opt::Instruction& inst) {
+    return inst.GetSingleWordOperand(0);
+  };
+  auto accept_type_forward_pointer_ops = [](const opt::Instruction& inst) {
+    return inst.opcode() == SpvOpTypeForwardPointer;
+  };
+
+  PoolPotentialIds(src_->types_values(), potential_id_map.src_ids, true,
+                   accept_type_forward_pointer_ops, get_pointer_type_id);
+  PoolPotentialIds(dst_->types_values(), potential_id_map.dst_ids, false,
+                   accept_type_forward_pointer_ops, get_pointer_type_id);
+
+  // Matching types with cyclical references (i.e. in the style of linked lists)
+  // can get very complex.  Currently, the diff tool matches types bottom up, so
+  // on every instruction it expects to know if its operands are already matched
+  // or not.  With cyclical references, it cannot know that.  Type matching may
+  // need significant modifications to be able to support this use case.
+  //
+  // Currently, forwarded types are only matched by storage class and debug
+  // info, with minimal matching of the type being forwarded:
+  //
+  // - Group by class
+  //   - Group by OpType being pointed to
+  //     - Group by debug info
+  //       - If same name and unique, match
+  //     - If leftover is unique, match
+
+  // Group forwarded pointers by storage class first and loop over them.
+  GroupIdsAndMatch<SpvStorageClass>(
+      potential_id_map.src_ids, potential_id_map.dst_ids, SpvStorageClassMax,
+      &Differ::GroupIdsHelperGetTypePointerStorageClass,
+      [this](const IdGroup& src_group_by_storage_class,
+             const IdGroup& dst_group_by_storage_class) {
+
+        // Group them further by the type they are pointing to and loop over
+        // them.
+        GroupIdsAndMatch<SpvOp>(
+            src_group_by_storage_class, dst_group_by_storage_class, SpvOpMax,
+            &Differ::GroupIdsHelperGetTypePointerTypeOp,
+            [this](const IdGroup& src_group_by_type_op,
+                   const IdGroup& dst_group_by_type_op) {
+
+              // Group them even further by debug info, if possible and match by
+              // debug name.
+              MatchTypeForwardPointersByName(src_group_by_type_op,
+                                             dst_group_by_type_op);
+
+              // Match the leftovers only if they lack debug info and there is
+              // only one instance of them.
+              MatchTypeForwardPointersByTypeOp(src_group_by_type_op,
+                                               dst_group_by_type_op);
+            });
+      });
+
+  // Match the instructions that forward declare the same type themselves
+  for (uint32_t src_id : potential_id_map.src_ids) {
+    uint32_t dst_id = id_map_.MappedDstId(src_id);
+    if (dst_id == 0) continue;
+
+    const opt::Instruction* src_forward_inst =
+        GetForwardPointerInst(src_id_to_, src_id);
+    const opt::Instruction* dst_forward_inst =
+        GetForwardPointerInst(dst_id_to_, dst_id);
+
+    assert(src_forward_inst);
+    assert(dst_forward_inst);
+
+    id_map_.MapInsts(src_forward_inst, dst_forward_inst);
+  }
+}
+
+void Differ::MatchTypeIds() {
+  // Bunch all of type ids as potential matches.
+  PotentialIdMap potential_id_map;
+  auto get_result_id = [](const opt::Instruction& inst) {
+    return inst.result_id();
+  };
+  auto accept_type_ops = [](const opt::Instruction& inst) {
+    return spvOpcodeGeneratesType(inst.opcode());
+  };
+
+  PoolPotentialIds(src_->types_values(), potential_id_map.src_ids, true,
+                   accept_type_ops, get_result_id);
+  PoolPotentialIds(dst_->types_values(), potential_id_map.dst_ids, false,
+                   accept_type_ops, get_result_id);
+
+  // Then match the ids.  Start with exact matches, then match the leftover with
+  // gradually loosening degrees of strictness.  For example, in the absence of
+  // debug info, two block types will be matched if they differ only in a few of
+  // the fields.
+  for (uint32_t flexibility = 0; flexibility < 2; ++flexibility) {
+    MatchIds(potential_id_map, [this, flexibility](
+                                   const opt::Instruction* src_inst,
+                                   const opt::Instruction* dst_inst) {
+      const SpvOp src_op = src_inst->opcode();
+      const SpvOp dst_op = dst_inst->opcode();
+
+      // Don't match if the opcode is not the same.
+      if (src_op != dst_op) {
+        return false;
+      }
+
+      switch (src_op) {
+        case SpvOpTypeVoid:
+        case SpvOpTypeBool:
+        case SpvOpTypeSampler:
+          // void, bool and sampler are unique, match them.
+          return true;
+        case SpvOpTypeInt:
+        case SpvOpTypeFloat:
+        case SpvOpTypeVector:
+        case SpvOpTypeMatrix:
+        case SpvOpTypeSampledImage:
+        case SpvOpTypeRuntimeArray:
+        case SpvOpTypePointer:
+          // Match these instructions when all operands match.
+          assert(src_inst->NumInOperandWords() ==
+                 dst_inst->NumInOperandWords());
+          return DoOperandsMatch(src_inst, dst_inst, 0,
+                                 src_inst->NumInOperandWords());
+
+        case SpvOpTypeFunction:
+        case SpvOpTypeImage:
+          // Match function types only if they have the same number of operands,
+          // and they all match.
+          // Match image types similarly, expecting the optional final parameter
+          // to match (if provided in both)
+          if (src_inst->NumInOperandWords() != dst_inst->NumInOperandWords()) {
+            return false;
+          }
+          return DoOperandsMatch(src_inst, dst_inst, 0,
+                                 src_inst->NumInOperandWords());
+
+        case SpvOpTypeArray:
+          // Match arrays only if the element type and length match.  The length
+          // is an id of a constant, so the actual constant it's defining is
+          // compared instead.
+          if (!DoOperandsMatch(src_inst, dst_inst, 0, 1)) {
+            return false;
+          }
+
+          if (AreIdenticalUintConstants(src_inst->GetSingleWordInOperand(1),
+                                        dst_inst->GetSingleWordInOperand(1))) {
+            return true;
+          }
+
+          // If size is not OpConstant, expect the ids to match exactly (for
+          // example if a spec contant is used).
+          return DoOperandsMatch(src_inst, dst_inst, 1, 1);
+
+        case SpvOpTypeStruct:
+          return MatchOpTypeStruct(src_inst, dst_inst, flexibility);
+
+        default:
+          return false;
+      }
+    });
+  }
+}
+
+void Differ::MatchConstants() {
+  // Bunch all of constant ids as potential matches.
+  PotentialIdMap potential_id_map;
+  auto get_result_id = [](const opt::Instruction& inst) {
+    return inst.result_id();
+  };
+  auto accept_type_ops = [](const opt::Instruction& inst) {
+    return spvOpcodeIsConstant(inst.opcode());
+  };
+
+  PoolPotentialIds(src_->types_values(), potential_id_map.src_ids, true,
+                   accept_type_ops, get_result_id);
+  PoolPotentialIds(dst_->types_values(), potential_id_map.dst_ids, false,
+                   accept_type_ops, get_result_id);
+
+  // Then match the ids.  Constants are matched exactly, except for float types
+  // that are first matched exactly, then leftovers are matched with a small
+  // error.
+  for (uint32_t flexibility = 0; flexibility < 2; ++flexibility) {
+    MatchIds(potential_id_map, [this, flexibility](
+                                   const opt::Instruction* src_inst,
+                                   const opt::Instruction* dst_inst) {
+      const SpvOp src_op = src_inst->opcode();
+      const SpvOp dst_op = dst_inst->opcode();
+
+      // Don't match if the opcode is not the same.
+      if (src_op != dst_op) {
+        return false;
+      }
+
+      switch (src_op) {
+        case SpvOpConstantTrue:
+        case SpvOpConstantFalse:
+          // true and false are unique, match them.
+          return true;
+        case SpvOpConstant:
+          return MatchOpConstant(src_inst, dst_inst, flexibility);
+        case SpvOpConstantComposite:
+        case SpvOpSpecConstantComposite:
+          // Composite constants must match in type and value.
+          //
+          // TODO: match OpConstantNull with OpConstantComposite with all zeros
+          // at flexibility == 1
+          // TODO: match constants from structs that have been flexibly-matched.
+          if (src_inst->NumInOperandWords() != dst_inst->NumInOperandWords()) {
+            return false;
+          }
+          return DoesOperandMatch(src_inst->GetOperand(0),
+                                  dst_inst->GetOperand(0)) &&
+                 DoOperandsMatch(src_inst, dst_inst, 0,
+                                 src_inst->NumInOperandWords());
+        case SpvOpConstantSampler:
+          // Match sampler constants exactly.
+          // TODO: Allow flexibility in parameters to better diff shaders where
+          // the sampler param has changed.
+          assert(src_inst->NumInOperandWords() ==
+                 dst_inst->NumInOperandWords());
+          return DoOperandsMatch(src_inst, dst_inst, 0,
+                                 src_inst->NumInOperandWords());
+        case SpvOpConstantNull:
+          // Match null constants as long as the type matches.
+          return DoesOperandMatch(src_inst->GetOperand(0),
+                                  dst_inst->GetOperand(0));
+
+        case SpvOpSpecConstantTrue:
+        case SpvOpSpecConstantFalse:
+        case SpvOpSpecConstant:
+        case SpvOpSpecConstantOp:
+          // Match spec constants by name if available, then by the SpecId
+          // decoration.
+          return MatchOpSpecConstant(src_inst, dst_inst);
+
+        default:
+          return false;
+      }
+    });
+  }
+}
+
+void Differ::MatchVariableIds() {
+  // Bunch all of variable ids as potential matches.
+  PotentialIdMap potential_id_map;
+  auto get_result_id = [](const opt::Instruction& inst) {
+    return inst.result_id();
+  };
+  auto accept_type_ops = [](const opt::Instruction& inst) {
+    return inst.opcode() == SpvOpVariable;
+  };
+
+  PoolPotentialIds(src_->types_values(), potential_id_map.src_ids, true,
+                   accept_type_ops, get_result_id);
+  PoolPotentialIds(dst_->types_values(), potential_id_map.dst_ids, false,
+                   accept_type_ops, get_result_id);
+
+  // Then match the ids.  Start with exact matches, then match the leftover with
+  // gradually loosening degrees of strictness.  For example, in the absence of
+  // debug info, two otherwise identical variables will be matched if one of
+  // them has a Private storage class and the other doesn't.
+  for (uint32_t flexibility = 0; flexibility < 2; ++flexibility) {
+    MatchIds(potential_id_map,
+             [this, flexibility](const opt::Instruction* src_inst,
+                                 const opt::Instruction* dst_inst) {
+               assert(src_inst->opcode() == SpvOpVariable);
+               assert(dst_inst->opcode() == SpvOpVariable);
+
+               return MatchOpVariable(src_inst, dst_inst, flexibility);
+             });
+  }
+}
+
+void Differ::MatchFunctions() {
+  IdGroup src_func_ids;
+  IdGroup dst_func_ids;
+
+  for (const auto& func : src_funcs_) {
+    src_func_ids.push_back(func.first);
+  }
+  for (const auto& func : dst_funcs_) {
+    dst_func_ids.push_back(func.first);
+  }
+
+  // Base the matching of functions on debug info when available.
+  GroupIdsAndMatch<std::string>(
+      src_func_ids, dst_func_ids, "", &Differ::GetSanitizedName,
+      [this](const IdGroup& src_group, const IdGroup& dst_group) {
+
+        // If there is a single function with this name in src and dst, it's a
+        // definite match.
+        if (src_group.size() == 1 && dst_group.size() == 1) {
+          id_map_.MapIds(src_group[0], dst_group[0]);
+          return;
+        }
+
+        // If there are multiple functions with the same name, group them by
+        // type, and match only if the types match (and are unique).
+        GroupIdsAndMatch<uint32_t>(src_group, dst_group, 0,
+                                   &Differ::GroupIdsHelperGetTypeId,
+                                   [this](const IdGroup& src_group_by_type_id,
+                                          const IdGroup& dst_group_by_type_id) {
+
+                                     if (src_group_by_type_id.size() == 1 &&
+                                         dst_group_by_type_id.size() == 1) {
+                                       id_map_.MapIds(src_group_by_type_id[0],
+                                                      dst_group_by_type_id[0]);
+                                     }
+                                   });
+      });
+
+  // Any functions that are left are pooled together and matched as if unnamed,
+  // with the only exception that two functions with mismatching names are not
+  // matched.
+  //
+  // Before that however, the diff of the functions that are matched are taken
+  // and processed, so that more of the global variables can be matched before
+  // attempting to match the rest of the functions.  They can contribute to the
+  // precision of the diff of those functions.
+  for (const uint32_t src_func_id : src_func_ids) {
+    const uint32_t dst_func_id = id_map_.MappedDstId(src_func_id);
+    if (dst_func_id == 0) {
+      continue;
+    }
+
+    // Since these functions are definite matches, match their parameters for a
+    // better diff.
+    MatchFunctionParamIds(src_funcs_[src_func_id], dst_funcs_[dst_func_id]);
+
+    // Take the diff of the two functions.
+    DiffMatch src_match_result, dst_match_result;
+    MatchFunctionBodies(src_func_insts_[src_func_id],
+                        dst_func_insts_[dst_func_id], &src_match_result,
+                        &dst_match_result);
+
+    // Match ids between the two function bodies; which can also result in
+    // global variables getting matched.
+    MatchIdsInFunctionBodies(src_func_insts_[src_func_id],
+                             dst_func_insts_[dst_func_id], src_match_result,
+                             dst_match_result, 0);
+  }
+
+  // Best effort match functions with matching type.
+  GroupIdsAndMatch<uint32_t>(
+      src_func_ids, dst_func_ids, 0, &Differ::GroupIdsHelperGetTypeId,
+      [this](const IdGroup& src_group_by_type_id,
+             const IdGroup& dst_group_by_type_id) {
+
+        BestEffortMatchFunctions(src_group_by_type_id, dst_group_by_type_id,
+                                 src_func_insts_, dst_func_insts_);
+      });
+
+  // Any function that's left, best effort match them.
+  BestEffortMatchFunctions(src_func_ids, dst_func_ids, src_func_insts_,
+                           dst_func_insts_);
+}
+
+void Differ::MatchDebugs1() {
+  // This section in cludes: OpString, OpSourceExtension, OpSource,
+  // OpSourceContinued
+  MatchDebugAndAnnotationInstructions(src_->debugs1(), dst_->debugs1());
+}
+
+void Differ::MatchDebugs2() {
+  // This section includes: OpName, OpMemberName
+  MatchDebugAndAnnotationInstructions(src_->debugs2(), dst_->debugs2());
+}
+
+void Differ::MatchDebugs3() {
+  // This section includes: OpModuleProcessed
+  MatchDebugAndAnnotationInstructions(src_->debugs3(), dst_->debugs3());
+}
+
+void Differ::MatchExtInstDebugInfo() {
+  // This section includes OpExtInst for DebugInfo extension
+  MatchDebugAndAnnotationInstructions(src_->ext_inst_debuginfo(),
+                                      dst_->ext_inst_debuginfo());
+}
+
+void Differ::MatchAnnotations() {
+  // This section includes OpDecorate and family.
+  MatchDebugAndAnnotationInstructions(src_->annotations(), dst_->annotations());
+}
+
+const opt::Instruction* Differ::MappedDstInst(
+    const opt::Instruction* src_inst) {
+  return MappedInstImpl(src_inst, id_map_.SrcToDstMap(), dst_id_to_);
+}
+
+const opt::Instruction* Differ::MappedSrcInst(
+    const opt::Instruction* dst_inst) {
+  return MappedInstImpl(dst_inst, id_map_.DstToSrcMap(), src_id_to_);
+}
+
+const opt::Instruction* Differ::MappedInstImpl(
+    const opt::Instruction* inst, const IdMap& to_other,
+    const IdInstructions& other_id_to) {
+  if (inst->HasResultId()) {
+    if (to_other.IsMapped(inst->result_id())) {
+      const uint32_t other_result_id = to_other.MappedId(inst->result_id());
+
+      assert(other_result_id < other_id_to.inst_map_.size());
+      return other_id_to.inst_map_[other_result_id];
+    }
+
+    return nullptr;
+  }
+
+  return to_other.MappedInst(inst);
+}
+
+void Differ::OutputLine(std::function<bool()> are_lines_identical,
+                        std::function<void()> output_src_line,
+                        std::function<void()> output_dst_line) {
+  if (are_lines_identical()) {
+    out_ << " ";
+    output_src_line();
+  } else {
+    OutputRed();
+    out_ << "-";
+    output_src_line();
+
+    OutputGreen();
+    out_ << "+";
+    output_dst_line();
+
+    OutputResetColor();
+  }
+}
+
+const opt::Instruction* IterInst(opt::Module::const_inst_iterator& iter) {
+  return &*iter;
+}
+
+const opt::Instruction* IterInst(InstructionList::const_iterator& iter) {
+  return *iter;
+}
+
+template <typename InstList>
+void Differ::OutputSection(
+    const InstList& src_insts, const InstList& dst_insts,
+    std::function<void(const opt::Instruction&, const IdInstructions&,
+                       const opt::Instruction&)>
+        write_inst) {
+  auto src_iter = src_insts.begin();
+  auto dst_iter = dst_insts.begin();
+
+  // - While src_inst doesn't have a match, output it with -
+  // - While dst_inst doesn't have a match, output it with +
+  // - Now src_inst and dst_inst both have matches; might not match each other!
+  //   * If section is unordered, just process src_inst and its match (dst_inst
+  //   or not),
+  //     dst_inst will eventually be processed when its match is seen.
+  //   * If section is ordered, also just process src_inst and its match.  Its
+  //   match must
+  //     necessarily be dst_inst.
+  while (src_iter != src_insts.end() || dst_iter != dst_insts.end()) {
+    OutputRed();
+    while (src_iter != src_insts.end() &&
+           MappedDstInst(IterInst(src_iter)) == nullptr) {
+      out_ << "-";
+      write_inst(*IterInst(src_iter), src_id_to_, *IterInst(src_iter));
+      ++src_iter;
+    }
+    OutputGreen();
+    while (dst_iter != dst_insts.end() &&
+           MappedSrcInst(IterInst(dst_iter)) == nullptr) {
+      out_ << "+";
+      write_inst(ToMappedSrcIds(*IterInst(dst_iter)), dst_id_to_,
+                 *IterInst(dst_iter));
+      ++dst_iter;
+    }
+    OutputResetColor();
+
+    if (src_iter != src_insts.end() && dst_iter != dst_insts.end()) {
+      const opt::Instruction* src_inst = IterInst(src_iter);
+      const opt::Instruction* matched_dst_inst = MappedDstInst(src_inst);
+
+      assert(matched_dst_inst != nullptr);
+      assert(MappedSrcInst(IterInst(dst_iter)) != nullptr);
+
+      OutputLine(
+          [this, src_inst, matched_dst_inst]() {
+            return DoInstructionsMatch(src_inst, matched_dst_inst);
+          },
+          [this, src_inst, &write_inst]() {
+            write_inst(*src_inst, src_id_to_, *src_inst);
+          },
+          [this, matched_dst_inst, &write_inst]() {
+            write_inst(ToMappedSrcIds(*matched_dst_inst), dst_id_to_,
+                       *matched_dst_inst);
+          });
+
+      ++src_iter;
+      ++dst_iter;
+    }
+  }
+}
+
+void Differ::ToParsedInstruction(
+    const opt::Instruction& inst, const IdInstructions& id_to,
+    const opt::Instruction& original_inst,
+    spv_parsed_instruction_t* parsed_inst,
+    std::vector<spv_parsed_operand_t>& parsed_operands,
+    std::vector<uint32_t>& inst_binary) {
+  inst.ToBinaryWithoutAttachedDebugInsts(&inst_binary);
+  parsed_operands.resize(inst.NumOperands());
+
+  parsed_inst->words = inst_binary.data();
+  parsed_inst->num_words = static_cast<uint16_t>(inst_binary.size());
+  parsed_inst->opcode = static_cast<uint16_t>(inst.opcode());
+  parsed_inst->ext_inst_type =
+      inst.opcode() == SpvOpExtInst
+          ? GetExtInstType(id_to, original_inst.GetSingleWordInOperand(0))
+          : SPV_EXT_INST_TYPE_NONE;
+  parsed_inst->type_id =
+      inst.HasResultType() ? inst.GetSingleWordOperand(0) : 0;
+  parsed_inst->result_id = inst.HasResultId() ? inst.result_id() : 0;
+  parsed_inst->operands = parsed_operands.data();
+  parsed_inst->num_operands = static_cast<uint16_t>(parsed_operands.size());
+
+  // Word 0 is always op and num_words, so operands start at offset 1.
+  uint32_t offset = 1;
+  for (uint16_t operand_index = 0; operand_index < parsed_inst->num_operands;
+       ++operand_index) {
+    const opt::Operand& operand = inst.GetOperand(operand_index);
+    spv_parsed_operand_t& parsed_operand = parsed_operands[operand_index];
+
+    parsed_operand.offset = static_cast<uint16_t>(offset);
+    parsed_operand.num_words = static_cast<uint16_t>(operand.words.size());
+    parsed_operand.type = operand.type;
+    parsed_operand.number_kind = GetNumberKind(
+        id_to, original_inst, operand_index, &parsed_operand.number_bit_width);
+
+    offset += parsed_operand.num_words;
+  }
+}
+
+opt::Instruction Differ::ToMappedSrcIds(const opt::Instruction& dst_inst) {
+  // Create an identical instruction to dst_inst, except ids are changed to the
+  // mapped one.
+  opt::Instruction mapped_inst = dst_inst;
+
+  for (uint32_t operand_index = 0; operand_index < mapped_inst.NumOperands();
+       ++operand_index) {
+    opt::Operand& operand = mapped_inst.GetOperand(operand_index);
+
+    if (spvIsIdType(operand.type)) {
+      assert(id_map_.IsDstMapped(operand.AsId()));
+      operand.words[0] = id_map_.MappedSrcId(operand.AsId());
+    }
+  }
+
+  return mapped_inst;
+}
+
+spv_result_t Differ::Output() {
+  id_map_.MapUnmatchedIds();
+  src_id_to_.inst_map_.resize(id_map_.SrcToDstMap().IdBound(), nullptr);
+  dst_id_to_.inst_map_.resize(id_map_.DstToSrcMap().IdBound(), nullptr);
+
+  const spv_target_env target_env = SPV_ENV_UNIVERSAL_1_6;
+  spv_opcode_table opcode_table;
+  spv_operand_table operand_table;
+  spv_ext_inst_table ext_inst_table;
+  spv_result_t result;
+
+  result = spvOpcodeTableGet(&opcode_table, target_env);
+  if (result != SPV_SUCCESS) return result;
+
+  result = spvOperandTableGet(&operand_table, target_env);
+  if (result != SPV_SUCCESS) return result;
+
+  result = spvExtInstTableGet(&ext_inst_table, target_env);
+  if (result != SPV_SUCCESS) return result;
+
+  spv_context_t context{
+      target_env,
+      opcode_table,
+      operand_table,
+      ext_inst_table,
+  };
+
+  const AssemblyGrammar grammar(&context);
+  if (!grammar.isValid()) return SPV_ERROR_INVALID_TABLE;
+
+  uint32_t disassembly_options = SPV_BINARY_TO_TEXT_OPTION_PRINT;
+  if (options_.indent) {
+    disassembly_options |= SPV_BINARY_TO_TEXT_OPTION_INDENT;
+  }
+
+  NameMapper name_mapper = GetTrivialNameMapper();
+  disassemble::InstructionDisassembler dis(grammar, out_, disassembly_options,
+                                           name_mapper);
+
+  if (!options_.no_header) {
+    // Output the header
+    // TODO: when using diff with text, the assembler overrides the version and
+    // generator, so these aren't reflected correctly in the output.  Could
+    // potentially extract this info from the header comment.
+    OutputLine([]() { return true; }, [&dis]() { dis.EmitHeaderSpirv(); },
+               []() { assert(false && "Unreachable"); });
+    OutputLine([this]() { return src_->version() == dst_->version(); },
+               [this, &dis]() { dis.EmitHeaderVersion(src_->version()); },
+               [this, &dis]() { dis.EmitHeaderVersion(dst_->version()); });
+    OutputLine([this]() { return src_->generator() == dst_->generator(); },
+               [this, &dis]() { dis.EmitHeaderGenerator(src_->generator()); },
+               [this, &dis]() { dis.EmitHeaderGenerator(dst_->generator()); });
+    OutputLine(
+        [this]() { return src_->IdBound() == id_map_.SrcToDstMap().IdBound(); },
+        [this, &dis]() { dis.EmitHeaderIdBound(src_->IdBound()); },
+        [this, &dis]() {
+          dis.EmitHeaderIdBound(id_map_.SrcToDstMap().IdBound());
+        });
+    OutputLine([this]() { return src_->schema() == dst_->schema(); },
+               [this, &dis]() { dis.EmitHeaderSchema(src_->schema()); },
+               [this, &dis]() { dis.EmitHeaderSchema(dst_->schema()); });
+  }
+
+  // For each section, iterate both modules and output the disassembly.
+  auto write_inst = [this, &dis](const opt::Instruction& inst,
+                                 const IdInstructions& id_to,
+                                 const opt::Instruction& original_inst) {
+    spv_parsed_instruction_t parsed_inst;
+    std::vector<spv_parsed_operand_t> parsed_operands;
+    std::vector<uint32_t> inst_binary;
+
+    ToParsedInstruction(inst, id_to, original_inst, &parsed_inst,
+                        parsed_operands, inst_binary);
+
+    dis.EmitInstruction(parsed_inst, 0);
+  };
+
+  OutputSection(src_->capabilities(), dst_->capabilities(), write_inst);
+  OutputSection(src_->extensions(), dst_->extensions(), write_inst);
+  OutputSection(src_->ext_inst_imports(), dst_->ext_inst_imports(), write_inst);
+
+  // There is only one memory model.
+  OutputLine(
+      [this]() {
+        return DoInstructionsMatch(src_->GetMemoryModel(),
+                                   dst_->GetMemoryModel());
+      },
+      [this, &write_inst]() {
+        write_inst(*src_->GetMemoryModel(), src_id_to_,
+                   *src_->GetMemoryModel());
+      },
+      [this, &write_inst]() {
+        write_inst(*dst_->GetMemoryModel(), dst_id_to_,
+                   *dst_->GetMemoryModel());
+      });
+
+  OutputSection(src_->entry_points(), dst_->entry_points(), write_inst);
+  OutputSection(src_->execution_modes(), dst_->execution_modes(), write_inst);
+  OutputSection(src_->debugs1(), dst_->debugs1(), write_inst);
+  OutputSection(src_->debugs2(), dst_->debugs2(), write_inst);
+  OutputSection(src_->debugs3(), dst_->debugs3(), write_inst);
+  OutputSection(src_->ext_inst_debuginfo(), dst_->ext_inst_debuginfo(),
+                write_inst);
+  OutputSection(src_->annotations(), dst_->annotations(), write_inst);
+  OutputSection(src_->types_values(), dst_->types_values(), write_inst);
+
+  // Get the body of all the functions.
+  FunctionInstMap src_func_header_insts;
+  FunctionInstMap dst_func_header_insts;
+
+  GetFunctionHeaderInstructions(src_, &src_func_header_insts);
+  GetFunctionHeaderInstructions(dst_, &dst_func_header_insts);
+
+  for (const auto& src_func : src_func_insts_) {
+    const uint32_t src_func_id = src_func.first;
+    const InstructionList& src_insts = src_func.second;
+    const InstructionList& src_header_insts =
+        src_func_header_insts[src_func_id];
+
+    const uint32_t dst_func_id = id_map_.MappedDstId(src_func_id);
+    if (dst_func_insts_.find(dst_func_id) == dst_func_insts_.end()) {
+      OutputSection(src_header_insts, InstructionList(), write_inst);
+      OutputSection(src_insts, InstructionList(), write_inst);
+      continue;
+    }
+
+    const InstructionList& dst_insts = dst_func_insts_[dst_func_id];
+    const InstructionList& dst_header_insts =
+        dst_func_header_insts[dst_func_id];
+    OutputSection(src_header_insts, dst_header_insts, write_inst);
+    OutputSection(src_insts, dst_insts, write_inst);
+  }
+
+  for (const auto& dst_func : dst_func_insts_) {
+    const uint32_t dst_func_id = dst_func.first;
+    const InstructionList& dst_insts = dst_func.second;
+    const InstructionList& dst_header_insts =
+        dst_func_header_insts[dst_func_id];
+
+    const uint32_t src_func_id = id_map_.MappedSrcId(dst_func_id);
+    if (src_func_insts_.find(src_func_id) == src_func_insts_.end()) {
+      OutputSection(InstructionList(), dst_header_insts, write_inst);
+      OutputSection(InstructionList(), dst_insts, write_inst);
+    }
+  }
+
+  out_ << std::flush;
+
+  return SPV_SUCCESS;
+}
+
+}  // anonymous namespace
+
+spv_result_t Diff(opt::IRContext* src, opt::IRContext* dst, std::ostream& out,
+                  Options options) {
+  // High level algorithm:
+  //
+  // - Some sections of SPIR-V don't deal with ids; instructions in those
+  //   sections are matched identically.  For example OpCapability instructions.
+  // - Some sections produce ids, and they can be trivially matched by their
+  //   parameters.  For example OpExtInstImport instructions.
+  // - Some sections annotate ids.  These are matched at the end, after the ids
+  //   themselves are matched.  For example OpName or OpDecorate instructions.
+  // - Some sections produce ids that depend on other ids and they can be
+  //   recursively matched.  For example OpType* instructions.
+  // - Some sections produce ids that are not trivially matched.  For these ids,
+  //   the debug info is used when possible, or a best guess (such as through
+  //   decorations) is used.  For example OpVariable instructions.
+  // - Matching functions is done with multiple attempts:
+  //   * Functions with identical debug names are matched if there are no
+  //     overloads.
+  //   * Otherwise, functions with identical debug names and types are matched.
+  //   * The rest of the functions are best-effort matched, first in groups of
+  //     identical type, then any with any.
+  //     * The best-effort matching takes the diff of every pair of functions in
+  //       a group and selects the top matches that also meet a similarity
+  //       index.
+  //   * Once a pair of functions are matched, the fuzzy diff of the
+  //     instructions is used to match the instructions in the function body.
+  //     The fuzzy diff makes sure that sufficiently similar instructions are
+  //     matched and that yet-to-be-matched result ids don't result in a larger
+  //     diff.
+  //
+  // Once the instructions are matched between the src and dst SPIR-V, the src
+  // is traversed and its disassembly is output.  In the process, any unmatched
+  // instruction is prefixed with -, and any unmatched instruction in dst in the
+  // same section is output prefixed with +.  To avoid confusion, the
+  // instructions in dst are output with matching ids in src so the output
+  // assembly is consistent.
+
+  Differ differ(src, dst, out, options);
+
+  // First, match instructions between the different non-annotation sections of
+  // the SPIR-V.
+  differ.MatchCapabilities();
+  differ.MatchExtensions();
+  differ.MatchExtInstImportIds();
+  differ.MatchMemoryModel();
+  differ.MatchEntryPointIds();
+  differ.MatchExecutionModes();
+  differ.MatchTypeForwardPointers();
+  differ.MatchTypeIds();
+  differ.MatchConstants();
+  differ.MatchVariableIds();
+  differ.MatchFunctions();
+
+  // Match instructions that annotate previously-matched ids.
+  differ.MatchDebugs1();
+  differ.MatchDebugs2();
+  differ.MatchDebugs3();
+  differ.MatchExtInstDebugInfo();
+  differ.MatchAnnotations();
+
+  // Show the disassembly with the diff.
+  //
+  // TODO: Based on an option, output either based on src or dst, i.e. the diff
+  // can show the ids and instruction/function order either from src or dst.
+  spv_result_t result = differ.Output();
+
+  differ.DumpIdMap();
+
+  return result;
+}
+
+}  // namespace diff
+}  // namespace spvtools
diff --git a/source/diff/diff.h b/source/diff/diff.h
new file mode 100644
index 0000000..932de9e
--- /dev/null
+++ b/source/diff/diff.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2022 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_DIFF_DIFF_H_
+#define SOURCE_DIFF_DIFF_H_
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace diff {
+
+struct Options {
+  bool ignore_set_binding = false;
+  bool ignore_location = false;
+  bool indent = false;
+  bool no_header = false;
+  bool color_output = false;
+  bool dump_id_map = false;
+};
+
+// Given two SPIR-V modules, this function outputs the textual diff of their
+// assembly in `out`.  The diff is *semantic*, so that the ordering of certain
+// instructions wouldn't matter.
+//
+// The output is a disassembly of src, with diff(1)-style + and - lines that
+// show how the src is changed into dst.  To make this disassembly
+// self-consistent, the ids that are output are all in the space of the src
+// module; e.g. any + lines (showing instructions from the dst module) have
+// their ids mapped to the matched instruction in the src module (or a new id
+// allocated in the src module if unmatched).
+spv_result_t Diff(opt::IRContext* src, opt::IRContext* dst, std::ostream& out,
+                  Options options);
+
+}  // namespace diff
+}  // namespace spvtools
+
+#endif  // SOURCE_DIFF_DIFF_H_
diff --git a/source/diff/lcs.h b/source/diff/lcs.h
new file mode 100644
index 0000000..6c00e86
--- /dev/null
+++ b/source/diff/lcs.h
@@ -0,0 +1,224 @@
+// Copyright (c) 2022 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_DIFF_LCS_H_
+#define SOURCE_DIFF_LCS_H_
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <functional>
+#include <stack>
+#include <vector>
+
+namespace spvtools {
+namespace diff {
+
+// The result of a diff.
+using DiffMatch = std::vector<bool>;
+
+// Helper class to find the longest common subsequence between two function
+// bodies.
+template <typename Sequence>
+class LongestCommonSubsequence {
+ public:
+  LongestCommonSubsequence(const Sequence& src, const Sequence& dst)
+      : src_(src),
+        dst_(dst),
+        table_(src.size(), std::vector<DiffMatchEntry>(dst.size())) {}
+
+  // Given two sequences, it creates a matching between them.  The elements are
+  // simply marked as matched in src and dst, with any unmatched element in src
+  // implying a removal and any unmatched element in dst implying an addition.
+  //
+  // Returns the length of the longest common subsequence.
+  template <typename T>
+  uint32_t Get(std::function<bool(T src_elem, T dst_elem)> match,
+               DiffMatch* src_match_result, DiffMatch* dst_match_result);
+
+ private:
+  struct DiffMatchIndex {
+    uint32_t src_offset;
+    uint32_t dst_offset;
+  };
+
+  template <typename T>
+  void CalculateLCS(std::function<bool(T src_elem, T dst_elem)> match);
+  void RetrieveMatch(DiffMatch* src_match_result, DiffMatch* dst_match_result);
+  bool IsInBound(DiffMatchIndex index) {
+    return index.src_offset < src_.size() && index.dst_offset < dst_.size();
+  }
+  bool IsCalculated(DiffMatchIndex index) {
+    assert(IsInBound(index));
+    return table_[index.src_offset][index.dst_offset].valid;
+  }
+  bool IsCalculatedOrOutOfBound(DiffMatchIndex index) {
+    return !IsInBound(index) || IsCalculated(index);
+  }
+  uint32_t GetMemoizedLength(DiffMatchIndex index) {
+    if (!IsInBound(index)) {
+      return 0;
+    }
+    assert(IsCalculated(index));
+    return table_[index.src_offset][index.dst_offset].best_match_length;
+  }
+  bool IsMatched(DiffMatchIndex index) {
+    assert(IsCalculated(index));
+    return table_[index.src_offset][index.dst_offset].matched;
+  }
+  void MarkMatched(DiffMatchIndex index, uint32_t best_match_length,
+                   bool matched) {
+    assert(IsInBound(index));
+    DiffMatchEntry& entry = table_[index.src_offset][index.dst_offset];
+    assert(!entry.valid);
+
+    entry.best_match_length = best_match_length & 0x3FFFFFFF;
+    assert(entry.best_match_length == best_match_length);
+    entry.matched = matched;
+    entry.valid = true;
+  }
+
+  const Sequence& src_;
+  const Sequence& dst_;
+
+  struct DiffMatchEntry {
+    DiffMatchEntry() : best_match_length(0), matched(false), valid(false) {}
+
+    uint32_t best_match_length : 30;
+    // Whether src[i] and dst[j] matched.  This is an optimization to avoid
+    // calling the `match` function again when walking the LCS table.
+    uint32_t matched : 1;
+    // Use for the recursive algorithm to know if the contents of this entry are
+    // valid.
+    uint32_t valid : 1;
+  };
+
+  std::vector<std::vector<DiffMatchEntry>> table_;
+};
+
+template <typename Sequence>
+template <typename T>
+uint32_t LongestCommonSubsequence<Sequence>::Get(
+    std::function<bool(T src_elem, T dst_elem)> match,
+    DiffMatch* src_match_result, DiffMatch* dst_match_result) {
+  CalculateLCS(match);
+  RetrieveMatch(src_match_result, dst_match_result);
+  return GetMemoizedLength({0, 0});
+}
+
+template <typename Sequence>
+template <typename T>
+void LongestCommonSubsequence<Sequence>::CalculateLCS(
+    std::function<bool(T src_elem, T dst_elem)> match) {
+  // The LCS algorithm is simple.  Given sequences s and d, with a:b depicting a
+  // range in python syntax:
+  //
+  //     lcs(s[i:], d[j:]) =
+  //         lcs(s[i+1:], d[j+1:]) + 1                        if s[i] == d[j]
+  //         max(lcs(s[i+1:], d[j:]), lcs(s[i:], d[j+1:]))               o.w.
+  //
+  // Once the LCS table is filled according to the above, it can be walked and
+  // the best match retrieved.
+  //
+  // This is a recursive function with memoization, which avoids filling table
+  // entries where unnecessary.  This makes the best case O(N) instead of
+  // O(N^2).  The implemention uses a std::stack to avoid stack overflow on long
+  // sequences.
+
+  if (src_.empty() || dst_.empty()) {
+    return;
+  }
+
+  std::stack<DiffMatchIndex> to_calculate;
+  to_calculate.push({0, 0});
+
+  while (!to_calculate.empty()) {
+    DiffMatchIndex current = to_calculate.top();
+    to_calculate.pop();
+    assert(IsInBound(current));
+
+    // If already calculated through another path, ignore it.
+    if (IsCalculated(current)) {
+      continue;
+    }
+
+    if (match(src_[current.src_offset], dst_[current.dst_offset])) {
+      // If the current elements match, advance both indices and calculate the
+      // LCS if not already.  Visit `current` again afterwards, so its
+      // corresponding entry will be updated.
+      DiffMatchIndex next = {current.src_offset + 1, current.dst_offset + 1};
+      if (IsCalculatedOrOutOfBound(next)) {
+        MarkMatched(current, GetMemoizedLength(next) + 1, true);
+      } else {
+        to_calculate.push(current);
+        to_calculate.push(next);
+      }
+      continue;
+    }
+
+    // We've reached a pair of elements that don't match.  Calculate the LCS for
+    // both cases of either being left unmatched and take the max.  Visit
+    // `current` again afterwards, so its corresponding entry will be updated.
+    DiffMatchIndex next_src = {current.src_offset + 1, current.dst_offset};
+    DiffMatchIndex next_dst = {current.src_offset, current.dst_offset + 1};
+
+    if (IsCalculatedOrOutOfBound(next_src) &&
+        IsCalculatedOrOutOfBound(next_dst)) {
+      uint32_t best_match_length =
+          std::max(GetMemoizedLength(next_src), GetMemoizedLength(next_dst));
+      MarkMatched(current, best_match_length, false);
+      continue;
+    }
+
+    to_calculate.push(current);
+    if (!IsCalculatedOrOutOfBound(next_src)) {
+      to_calculate.push(next_src);
+    }
+    if (!IsCalculatedOrOutOfBound(next_dst)) {
+      to_calculate.push(next_dst);
+    }
+  }
+}
+
+template <typename Sequence>
+void LongestCommonSubsequence<Sequence>::RetrieveMatch(
+    DiffMatch* src_match_result, DiffMatch* dst_match_result) {
+  src_match_result->clear();
+  dst_match_result->clear();
+
+  src_match_result->resize(src_.size(), false);
+  dst_match_result->resize(dst_.size(), false);
+
+  DiffMatchIndex current = {0, 0};
+  while (IsInBound(current)) {
+    if (IsMatched(current)) {
+      (*src_match_result)[current.src_offset++] = true;
+      (*dst_match_result)[current.dst_offset++] = true;
+      continue;
+    }
+
+    if (GetMemoizedLength({current.src_offset + 1, current.dst_offset}) >=
+        GetMemoizedLength({current.src_offset, current.dst_offset + 1})) {
+      ++current.src_offset;
+    } else {
+      ++current.dst_offset;
+    }
+  }
+}
+
+}  // namespace diff
+}  // namespace spvtools
+
+#endif  // SOURCE_DIFF_LCS_H_
diff --git a/source/disassemble.cpp b/source/disassemble.cpp
index c553988..1d61b9f 100644
--- a/source/disassemble.cpp
+++ b/source/disassemble.cpp
@@ -17,6 +17,8 @@
 // This file contains a disassembler:  It converts a SPIR-V binary
 // to text.
 
+#include "source/disassemble.h"
+
 #include <algorithm>
 #include <cassert>
 #include <cstring>
@@ -28,9 +30,7 @@
 #include "source/assembly_grammar.h"
 #include "source/binary.h"
 #include "source/diagnostic.h"
-#include "source/disassemble.h"
 #include "source/ext_inst.h"
-#include "source/name_mapper.h"
 #include "source/opcode.h"
 #include "source/parsed_operand.h"
 #include "source/print.h"
@@ -40,29 +40,21 @@
 #include "source/util/make_unique.h"
 #include "spirv-tools/libspirv.h"
 
+namespace spvtools {
 namespace {
 
 // A Disassembler instance converts a SPIR-V binary to its assembly
 // representation.
 class Disassembler {
  public:
-  Disassembler(const spvtools::AssemblyGrammar& grammar, uint32_t options,
-               spvtools::NameMapper name_mapper)
-      : grammar_(grammar),
-        print_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_PRINT, options)),
-        color_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_COLOR, options)),
-        indent_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_INDENT, options)
-                    ? kStandardIndent
-                    : 0),
-        comment_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_COMMENT, options)),
+  Disassembler(const AssemblyGrammar& grammar, uint32_t options,
+               NameMapper name_mapper)
+      : print_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_PRINT, options)),
         text_(),
         out_(print_ ? out_stream() : out_stream(text_)),
-        stream_(out_.get()),
+        instruction_disassembler_(grammar, out_.get(), options, name_mapper),
         header_(!spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER, options)),
-        show_byte_offset_(spvIsInBitfield(
-            SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET, options)),
-        byte_offset_(0),
-        name_mapper_(std::move(name_mapper)) {}
+        byte_offset_(0) {}
 
   // Emits the assembly header for the module, and sets up internal state
   // so subsequent callbacks can handle the cases where the entire module
@@ -78,56 +70,13 @@
   spv_result_t SaveTextResult(spv_text* text_result) const;
 
  private:
-  enum { kStandardIndent = 15 };
-
-  using out_stream = spvtools::out_stream;
-
-  // Emits an operand for the given instruction, where the instruction
-  // is at offset words from the start of the binary.
-  void EmitOperand(const spv_parsed_instruction_t& inst,
-                   const uint16_t operand_index);
-
-  // Emits a mask expression for the given mask word of the specified type.
-  void EmitMaskOperand(const spv_operand_type_t type, const uint32_t word);
-
-  // Resets the output color, if color is turned on.
-  void ResetColor() {
-    if (color_) out_.get() << spvtools::clr::reset{print_};
-  }
-  // Sets the output to grey, if color is turned on.
-  void SetGrey() {
-    if (color_) out_.get() << spvtools::clr::grey{print_};
-  }
-  // Sets the output to blue, if color is turned on.
-  void SetBlue() {
-    if (color_) out_.get() << spvtools::clr::blue{print_};
-  }
-  // Sets the output to yellow, if color is turned on.
-  void SetYellow() {
-    if (color_) out_.get() << spvtools::clr::yellow{print_};
-  }
-  // Sets the output to red, if color is turned on.
-  void SetRed() {
-    if (color_) out_.get() << spvtools::clr::red{print_};
-  }
-  // Sets the output to green, if color is turned on.
-  void SetGreen() {
-    if (color_) out_.get() << spvtools::clr::green{print_};
-  }
-
-  const spvtools::AssemblyGrammar& grammar_;
   const bool print_;  // Should we also print to the standard output stream?
-  const bool color_;  // Should we print in colour?
-  const int indent_;  // How much to indent. 0 means don't indent
-  const int comment_;        // Should we comment the source
   spv_endianness_t endian_;  // The detected endianness of the binary.
   std::stringstream text_;   // Captures the text, if not printing.
   out_stream out_;  // The Output stream.  Either to text_ or standard output.
-  std::ostream& stream_;  // The output std::stream.
-  const bool header_;     // Should we output header as the leading comment?
-  const bool show_byte_offset_;  // Should we print byte offset, in hex?
-  size_t byte_offset_;           // The number of bytes processed so far.
-  spvtools::NameMapper name_mapper_;
+  disassemble::InstructionDisassembler instruction_disassembler_;
+  const bool header_;   // Should we output header as the leading comment?
+  size_t byte_offset_;  // The number of bytes processed so far.
   bool inserted_decoration_space_ = false;
   bool inserted_debug_space_ = false;
   bool inserted_type_space_ = false;
@@ -139,21 +88,11 @@
   endian_ = endian;
 
   if (header_) {
-    const char* generator_tool =
-        spvGeneratorStr(SPV_GENERATOR_TOOL_PART(generator));
-    stream_ << "; SPIR-V\n"
-            << "; Version: " << SPV_SPIRV_VERSION_MAJOR_PART(version) << "."
-            << SPV_SPIRV_VERSION_MINOR_PART(version) << "\n"
-            << "; Generator: " << generator_tool;
-    // For unknown tools, print the numeric tool value.
-    if (0 == strcmp("Unknown", generator_tool)) {
-      stream_ << "(" << SPV_GENERATOR_TOOL_PART(generator) << ")";
-    }
-    // Print the miscellaneous part of the generator word on the same
-    // line as the tool name.
-    stream_ << "; " << SPV_GENERATOR_MISC_PART(generator) << "\n"
-            << "; Bound: " << id_bound << "\n"
-            << "; Schema: " << schema << "\n";
+    instruction_disassembler_.EmitHeaderSpirv();
+    instruction_disassembler_.EmitHeaderVersion(version);
+    instruction_disassembler_.EmitHeaderGenerator(generator);
+    instruction_disassembler_.EmitHeaderIdBound(id_bound);
+    instruction_disassembler_.EmitHeaderSchema(schema);
   }
 
   byte_offset_ = SPV_INDEX_INSTRUCTION * sizeof(uint32_t);
@@ -163,232 +102,17 @@
 
 spv_result_t Disassembler::HandleInstruction(
     const spv_parsed_instruction_t& inst) {
-  auto opcode = static_cast<SpvOp>(inst.opcode);
-  if (comment_ && opcode == SpvOpFunction) {
-    stream_ << std::endl;
-    stream_ << std::string(indent_, ' ');
-    stream_ << "; Function " << name_mapper_(inst.result_id) << std::endl;
-  }
-  if (comment_ && !inserted_decoration_space_ &&
-      spvOpcodeIsDecoration(opcode)) {
-    inserted_decoration_space_ = true;
-    stream_ << std::endl;
-    stream_ << std::string(indent_, ' ');
-    stream_ << "; Annotations" << std::endl;
-  }
-  if (comment_ && !inserted_debug_space_ && spvOpcodeIsDebug(opcode)) {
-    inserted_debug_space_ = true;
-    stream_ << std::endl;
-    stream_ << std::string(indent_, ' ');
-    stream_ << "; Debug Information" << std::endl;
-  }
-  if (comment_ && !inserted_type_space_ && spvOpcodeGeneratesType(opcode)) {
-    inserted_type_space_ = true;
-    stream_ << std::endl;
-    stream_ << std::string(indent_, ' ');
-    stream_ << "; Types, variables and constants" << std::endl;
-  }
+  instruction_disassembler_.EmitSectionComment(inst, inserted_decoration_space_,
+                                               inserted_debug_space_,
+                                               inserted_type_space_);
 
-  if (inst.result_id) {
-    SetBlue();
-    const std::string id_name = name_mapper_(inst.result_id);
-    if (indent_)
-      stream_ << std::setw(std::max(0, indent_ - 3 - int(id_name.size())));
-    stream_ << "%" << id_name;
-    ResetColor();
-    stream_ << " = ";
-  } else {
-    stream_ << std::string(indent_, ' ');
-  }
-
-  stream_ << "Op" << spvOpcodeString(opcode);
-
-  for (uint16_t i = 0; i < inst.num_operands; i++) {
-    const spv_operand_type_t type = inst.operands[i].type;
-    assert(type != SPV_OPERAND_TYPE_NONE);
-    if (type == SPV_OPERAND_TYPE_RESULT_ID) continue;
-    stream_ << " ";
-    EmitOperand(inst, i);
-  }
-
-  if (comment_ && opcode == SpvOpName) {
-    const spv_parsed_operand_t& operand = inst.operands[0];
-    const uint32_t word = inst.words[operand.offset];
-    stream_ << "  ; id %" << word;
-  }
-
-  if (show_byte_offset_) {
-    SetGrey();
-    auto saved_flags = stream_.flags();
-    auto saved_fill = stream_.fill();
-    stream_ << " ; 0x" << std::setw(8) << std::hex << std::setfill('0')
-            << byte_offset_;
-    stream_.flags(saved_flags);
-    stream_.fill(saved_fill);
-    ResetColor();
-  }
+  instruction_disassembler_.EmitInstruction(inst, byte_offset_);
 
   byte_offset_ += inst.num_words * sizeof(uint32_t);
 
-  stream_ << "\n";
   return SPV_SUCCESS;
 }
 
-void Disassembler::EmitOperand(const spv_parsed_instruction_t& inst,
-                               const uint16_t operand_index) {
-  assert(operand_index < inst.num_operands);
-  const spv_parsed_operand_t& operand = inst.operands[operand_index];
-  const uint32_t word = inst.words[operand.offset];
-  switch (operand.type) {
-    case SPV_OPERAND_TYPE_RESULT_ID:
-      assert(false && "<result-id> is not supposed to be handled here");
-      SetBlue();
-      stream_ << "%" << name_mapper_(word);
-      break;
-    case SPV_OPERAND_TYPE_ID:
-    case SPV_OPERAND_TYPE_TYPE_ID:
-    case SPV_OPERAND_TYPE_SCOPE_ID:
-    case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
-      SetYellow();
-      stream_ << "%" << name_mapper_(word);
-      break;
-    case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: {
-      spv_ext_inst_desc ext_inst;
-      SetRed();
-      if (grammar_.lookupExtInst(inst.ext_inst_type, word, &ext_inst) ==
-          SPV_SUCCESS) {
-        stream_ << ext_inst->name;
-      } else {
-        if (!spvExtInstIsNonSemantic(inst.ext_inst_type)) {
-          assert(false && "should have caught this earlier");
-        } else {
-          // for non-semantic instruction sets we can just print the number
-          stream_ << word;
-        }
-      }
-    } break;
-    case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: {
-      spv_opcode_desc opcode_desc;
-      if (grammar_.lookupOpcode(SpvOp(word), &opcode_desc))
-        assert(false && "should have caught this earlier");
-      SetRed();
-      stream_ << opcode_desc->name;
-    } break;
-    case SPV_OPERAND_TYPE_LITERAL_INTEGER:
-    case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER: {
-      SetRed();
-      spvtools::EmitNumericLiteral(&stream_, inst, operand);
-      ResetColor();
-    } break;
-    case SPV_OPERAND_TYPE_LITERAL_STRING: {
-      stream_ << "\"";
-      SetGreen();
-      // Strings are always little-endian, and null-terminated.
-      // Write out the characters, escaping as needed, and without copying
-      // the entire string.
-      auto c_str = reinterpret_cast<const char*>(inst.words + operand.offset);
-      for (auto p = c_str; *p; ++p) {
-        if (*p == '"' || *p == '\\') stream_ << '\\';
-        stream_ << *p;
-      }
-      ResetColor();
-      stream_ << '"';
-    } break;
-    case SPV_OPERAND_TYPE_CAPABILITY:
-    case SPV_OPERAND_TYPE_SOURCE_LANGUAGE:
-    case SPV_OPERAND_TYPE_EXECUTION_MODEL:
-    case SPV_OPERAND_TYPE_ADDRESSING_MODEL:
-    case SPV_OPERAND_TYPE_MEMORY_MODEL:
-    case SPV_OPERAND_TYPE_EXECUTION_MODE:
-    case SPV_OPERAND_TYPE_STORAGE_CLASS:
-    case SPV_OPERAND_TYPE_DIMENSIONALITY:
-    case SPV_OPERAND_TYPE_SAMPLER_ADDRESSING_MODE:
-    case SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE:
-    case SPV_OPERAND_TYPE_SAMPLER_IMAGE_FORMAT:
-    case SPV_OPERAND_TYPE_FP_ROUNDING_MODE:
-    case SPV_OPERAND_TYPE_LINKAGE_TYPE:
-    case SPV_OPERAND_TYPE_ACCESS_QUALIFIER:
-    case SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE:
-    case SPV_OPERAND_TYPE_DECORATION:
-    case SPV_OPERAND_TYPE_BUILT_IN:
-    case SPV_OPERAND_TYPE_GROUP_OPERATION:
-    case SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS:
-    case SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO:
-    case SPV_OPERAND_TYPE_RAY_FLAGS:
-    case SPV_OPERAND_TYPE_RAY_QUERY_INTERSECTION:
-    case SPV_OPERAND_TYPE_RAY_QUERY_COMMITTED_INTERSECTION_TYPE:
-    case SPV_OPERAND_TYPE_RAY_QUERY_CANDIDATE_INTERSECTION_TYPE:
-    case SPV_OPERAND_TYPE_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING:
-    case SPV_OPERAND_TYPE_DEBUG_COMPOSITE_TYPE:
-    case SPV_OPERAND_TYPE_DEBUG_TYPE_QUALIFIER:
-    case SPV_OPERAND_TYPE_DEBUG_OPERATION:
-    case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING:
-    case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_COMPOSITE_TYPE:
-    case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_TYPE_QUALIFIER:
-    case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_OPERATION:
-    case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_IMPORTED_ENTITY:
-    case SPV_OPERAND_TYPE_FPDENORM_MODE:
-    case SPV_OPERAND_TYPE_FPOPERATION_MODE:
-    case SPV_OPERAND_TYPE_QUANTIZATION_MODES:
-    case SPV_OPERAND_TYPE_OVERFLOW_MODES: {
-      spv_operand_desc entry;
-      if (grammar_.lookupOperand(operand.type, word, &entry))
-        assert(false && "should have caught this earlier");
-      stream_ << entry->name;
-    } break;
-    case SPV_OPERAND_TYPE_FP_FAST_MATH_MODE:
-    case SPV_OPERAND_TYPE_FUNCTION_CONTROL:
-    case SPV_OPERAND_TYPE_LOOP_CONTROL:
-    case SPV_OPERAND_TYPE_IMAGE:
-    case SPV_OPERAND_TYPE_MEMORY_ACCESS:
-    case SPV_OPERAND_TYPE_SELECTION_CONTROL:
-    case SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS:
-    case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_INFO_FLAGS:
-      EmitMaskOperand(operand.type, word);
-      break;
-    default:
-      if (spvOperandIsConcreteMask(operand.type)) {
-        EmitMaskOperand(operand.type, word);
-      } else if (spvOperandIsConcrete(operand.type)) {
-        spv_operand_desc entry;
-        if (grammar_.lookupOperand(operand.type, word, &entry))
-          assert(false && "should have caught this earlier");
-        stream_ << entry->name;
-      } else {
-        assert(false && "unhandled or invalid case");
-      }
-      break;
-  }
-  ResetColor();
-}
-
-void Disassembler::EmitMaskOperand(const spv_operand_type_t type,
-                                   const uint32_t word) {
-  // Scan the mask from least significant bit to most significant bit.  For each
-  // set bit, emit the name of that bit. Separate multiple names with '|'.
-  uint32_t remaining_word = word;
-  uint32_t mask;
-  int num_emitted = 0;
-  for (mask = 1; remaining_word; mask <<= 1) {
-    if (remaining_word & mask) {
-      remaining_word ^= mask;
-      spv_operand_desc entry;
-      if (grammar_.lookupOperand(type, mask, &entry))
-        assert(false && "should have caught this earlier");
-      if (num_emitted) stream_ << "|";
-      stream_ << entry->name;
-      num_emitted++;
-    }
-  }
-  if (!num_emitted) {
-    // An operand value of 0 was provided, so represent it by the name
-    // of the 0 value. In many cases, that's "None".
-    spv_operand_desc entry;
-    if (SPV_SUCCESS == grammar_.lookupOperand(type, 0, &entry))
-      stream_ << entry->name;
-  }
-}
-
 spv_result_t Disassembler::SaveTextResult(spv_text* text_result) const {
   if (!print_) {
     size_t length = text_.str().size();
@@ -470,8 +194,342 @@
   return SPV_SUCCESS;
 }
 
+constexpr int kStandardIndent = 15;
 }  // namespace
 
+namespace disassemble {
+InstructionDisassembler::InstructionDisassembler(const AssemblyGrammar& grammar,
+                                                 std::ostream& stream,
+                                                 uint32_t options,
+                                                 NameMapper name_mapper)
+    : grammar_(grammar),
+      stream_(stream),
+      print_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_PRINT, options)),
+      color_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_COLOR, options)),
+      indent_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_INDENT, options)
+                  ? kStandardIndent
+                  : 0),
+      comment_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_COMMENT, options)),
+      show_byte_offset_(
+          spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET, options)),
+      name_mapper_(std::move(name_mapper)) {}
+
+void InstructionDisassembler::EmitHeaderSpirv() { stream_ << "; SPIR-V\n"; }
+
+void InstructionDisassembler::EmitHeaderVersion(uint32_t version) {
+  stream_ << "; Version: " << SPV_SPIRV_VERSION_MAJOR_PART(version) << "."
+          << SPV_SPIRV_VERSION_MINOR_PART(version) << "\n";
+}
+
+void InstructionDisassembler::EmitHeaderGenerator(uint32_t generator) {
+  const char* generator_tool =
+      spvGeneratorStr(SPV_GENERATOR_TOOL_PART(generator));
+  stream_ << "; Generator: " << generator_tool;
+  // For unknown tools, print the numeric tool value.
+  if (0 == strcmp("Unknown", generator_tool)) {
+    stream_ << "(" << SPV_GENERATOR_TOOL_PART(generator) << ")";
+  }
+  // Print the miscellaneous part of the generator word on the same
+  // line as the tool name.
+  stream_ << "; " << SPV_GENERATOR_MISC_PART(generator) << "\n";
+}
+
+void InstructionDisassembler::EmitHeaderIdBound(uint32_t id_bound) {
+  stream_ << "; Bound: " << id_bound << "\n";
+}
+
+void InstructionDisassembler::EmitHeaderSchema(uint32_t schema) {
+  stream_ << "; Schema: " << schema << "\n";
+}
+
+void InstructionDisassembler::EmitInstruction(
+    const spv_parsed_instruction_t& inst, size_t inst_byte_offset) {
+  auto opcode = static_cast<SpvOp>(inst.opcode);
+
+  if (inst.result_id) {
+    SetBlue();
+    const std::string id_name = name_mapper_(inst.result_id);
+    if (indent_)
+      stream_ << std::setw(std::max(0, indent_ - 3 - int(id_name.size())));
+    stream_ << "%" << id_name;
+    ResetColor();
+    stream_ << " = ";
+  } else {
+    stream_ << std::string(indent_, ' ');
+  }
+
+  stream_ << "Op" << spvOpcodeString(opcode);
+
+  for (uint16_t i = 0; i < inst.num_operands; i++) {
+    const spv_operand_type_t type = inst.operands[i].type;
+    assert(type != SPV_OPERAND_TYPE_NONE);
+    if (type == SPV_OPERAND_TYPE_RESULT_ID) continue;
+    stream_ << " ";
+    EmitOperand(inst, i);
+  }
+
+  if (comment_ && opcode == SpvOpName) {
+    const spv_parsed_operand_t& operand = inst.operands[0];
+    const uint32_t word = inst.words[operand.offset];
+    stream_ << "  ; id %" << word;
+  }
+
+  if (show_byte_offset_) {
+    SetGrey();
+    auto saved_flags = stream_.flags();
+    auto saved_fill = stream_.fill();
+    stream_ << " ; 0x" << std::setw(8) << std::hex << std::setfill('0')
+            << inst_byte_offset;
+    stream_.flags(saved_flags);
+    stream_.fill(saved_fill);
+    ResetColor();
+  }
+  stream_ << "\n";
+}
+
+void InstructionDisassembler::EmitSectionComment(
+    const spv_parsed_instruction_t& inst, bool& inserted_decoration_space,
+    bool& inserted_debug_space, bool& inserted_type_space) {
+  auto opcode = static_cast<SpvOp>(inst.opcode);
+  if (comment_ && opcode == SpvOpFunction) {
+    stream_ << std::endl;
+    stream_ << std::string(indent_, ' ');
+    stream_ << "; Function " << name_mapper_(inst.result_id) << std::endl;
+  }
+  if (comment_ && !inserted_decoration_space && spvOpcodeIsDecoration(opcode)) {
+    inserted_decoration_space = true;
+    stream_ << std::endl;
+    stream_ << std::string(indent_, ' ');
+    stream_ << "; Annotations" << std::endl;
+  }
+  if (comment_ && !inserted_debug_space && spvOpcodeIsDebug(opcode)) {
+    inserted_debug_space = true;
+    stream_ << std::endl;
+    stream_ << std::string(indent_, ' ');
+    stream_ << "; Debug Information" << std::endl;
+  }
+  if (comment_ && !inserted_type_space && spvOpcodeGeneratesType(opcode)) {
+    inserted_type_space = true;
+    stream_ << std::endl;
+    stream_ << std::string(indent_, ' ');
+    stream_ << "; Types, variables and constants" << std::endl;
+  }
+}
+
+void InstructionDisassembler::EmitOperand(const spv_parsed_instruction_t& inst,
+                                          const uint16_t operand_index) {
+  assert(operand_index < inst.num_operands);
+  const spv_parsed_operand_t& operand = inst.operands[operand_index];
+  const uint32_t word = inst.words[operand.offset];
+  switch (operand.type) {
+    case SPV_OPERAND_TYPE_RESULT_ID:
+      assert(false && "<result-id> is not supposed to be handled here");
+      SetBlue();
+      stream_ << "%" << name_mapper_(word);
+      break;
+    case SPV_OPERAND_TYPE_ID:
+    case SPV_OPERAND_TYPE_TYPE_ID:
+    case SPV_OPERAND_TYPE_SCOPE_ID:
+    case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
+      SetYellow();
+      stream_ << "%" << name_mapper_(word);
+      break;
+    case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: {
+      spv_ext_inst_desc ext_inst;
+      SetRed();
+      if (grammar_.lookupExtInst(inst.ext_inst_type, word, &ext_inst) ==
+          SPV_SUCCESS) {
+        stream_ << ext_inst->name;
+      } else {
+        if (!spvExtInstIsNonSemantic(inst.ext_inst_type)) {
+          assert(false && "should have caught this earlier");
+        } else {
+          // for non-semantic instruction sets we can just print the number
+          stream_ << word;
+        }
+      }
+    } break;
+    case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: {
+      spv_opcode_desc opcode_desc;
+      if (grammar_.lookupOpcode(SpvOp(word), &opcode_desc))
+        assert(false && "should have caught this earlier");
+      SetRed();
+      stream_ << opcode_desc->name;
+    } break;
+    case SPV_OPERAND_TYPE_LITERAL_INTEGER:
+    case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER: {
+      SetRed();
+      EmitNumericLiteral(&stream_, inst, operand);
+      ResetColor();
+    } break;
+    case SPV_OPERAND_TYPE_LITERAL_STRING: {
+      stream_ << "\"";
+      SetGreen();
+
+      std::string str = spvDecodeLiteralStringOperand(inst, operand_index);
+      for (char const& c : str) {
+        if (c == '"' || c == '\\') stream_ << '\\';
+        stream_ << c;
+      }
+      ResetColor();
+      stream_ << '"';
+    } break;
+    case SPV_OPERAND_TYPE_CAPABILITY:
+    case SPV_OPERAND_TYPE_SOURCE_LANGUAGE:
+    case SPV_OPERAND_TYPE_EXECUTION_MODEL:
+    case SPV_OPERAND_TYPE_ADDRESSING_MODEL:
+    case SPV_OPERAND_TYPE_MEMORY_MODEL:
+    case SPV_OPERAND_TYPE_EXECUTION_MODE:
+    case SPV_OPERAND_TYPE_STORAGE_CLASS:
+    case SPV_OPERAND_TYPE_DIMENSIONALITY:
+    case SPV_OPERAND_TYPE_SAMPLER_ADDRESSING_MODE:
+    case SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE:
+    case SPV_OPERAND_TYPE_SAMPLER_IMAGE_FORMAT:
+    case SPV_OPERAND_TYPE_FP_ROUNDING_MODE:
+    case SPV_OPERAND_TYPE_LINKAGE_TYPE:
+    case SPV_OPERAND_TYPE_ACCESS_QUALIFIER:
+    case SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE:
+    case SPV_OPERAND_TYPE_DECORATION:
+    case SPV_OPERAND_TYPE_BUILT_IN:
+    case SPV_OPERAND_TYPE_GROUP_OPERATION:
+    case SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS:
+    case SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO:
+    case SPV_OPERAND_TYPE_RAY_FLAGS:
+    case SPV_OPERAND_TYPE_RAY_QUERY_INTERSECTION:
+    case SPV_OPERAND_TYPE_RAY_QUERY_COMMITTED_INTERSECTION_TYPE:
+    case SPV_OPERAND_TYPE_RAY_QUERY_CANDIDATE_INTERSECTION_TYPE:
+    case SPV_OPERAND_TYPE_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING:
+    case SPV_OPERAND_TYPE_DEBUG_COMPOSITE_TYPE:
+    case SPV_OPERAND_TYPE_DEBUG_TYPE_QUALIFIER:
+    case SPV_OPERAND_TYPE_DEBUG_OPERATION:
+    case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING:
+    case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_COMPOSITE_TYPE:
+    case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_TYPE_QUALIFIER:
+    case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_OPERATION:
+    case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_IMPORTED_ENTITY:
+    case SPV_OPERAND_TYPE_FPDENORM_MODE:
+    case SPV_OPERAND_TYPE_FPOPERATION_MODE:
+    case SPV_OPERAND_TYPE_QUANTIZATION_MODES:
+    case SPV_OPERAND_TYPE_OVERFLOW_MODES: {
+      spv_operand_desc entry;
+      if (grammar_.lookupOperand(operand.type, word, &entry))
+        assert(false && "should have caught this earlier");
+      stream_ << entry->name;
+    } break;
+    case SPV_OPERAND_TYPE_FP_FAST_MATH_MODE:
+    case SPV_OPERAND_TYPE_FUNCTION_CONTROL:
+    case SPV_OPERAND_TYPE_LOOP_CONTROL:
+    case SPV_OPERAND_TYPE_IMAGE:
+    case SPV_OPERAND_TYPE_MEMORY_ACCESS:
+    case SPV_OPERAND_TYPE_SELECTION_CONTROL:
+    case SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS:
+    case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_INFO_FLAGS:
+      EmitMaskOperand(operand.type, word);
+      break;
+    default:
+      if (spvOperandIsConcreteMask(operand.type)) {
+        EmitMaskOperand(operand.type, word);
+      } else if (spvOperandIsConcrete(operand.type)) {
+        spv_operand_desc entry;
+        if (grammar_.lookupOperand(operand.type, word, &entry))
+          assert(false && "should have caught this earlier");
+        stream_ << entry->name;
+      } else {
+        assert(false && "unhandled or invalid case");
+      }
+      break;
+  }
+  ResetColor();
+}
+
+void InstructionDisassembler::EmitMaskOperand(const spv_operand_type_t type,
+                                              const uint32_t word) {
+  // Scan the mask from least significant bit to most significant bit.  For each
+  // set bit, emit the name of that bit. Separate multiple names with '|'.
+  uint32_t remaining_word = word;
+  uint32_t mask;
+  int num_emitted = 0;
+  for (mask = 1; remaining_word; mask <<= 1) {
+    if (remaining_word & mask) {
+      remaining_word ^= mask;
+      spv_operand_desc entry;
+      if (grammar_.lookupOperand(type, mask, &entry))
+        assert(false && "should have caught this earlier");
+      if (num_emitted) stream_ << "|";
+      stream_ << entry->name;
+      num_emitted++;
+    }
+  }
+  if (!num_emitted) {
+    // An operand value of 0 was provided, so represent it by the name
+    // of the 0 value. In many cases, that's "None".
+    spv_operand_desc entry;
+    if (SPV_SUCCESS == grammar_.lookupOperand(type, 0, &entry))
+      stream_ << entry->name;
+  }
+}
+
+void InstructionDisassembler::ResetColor() {
+  if (color_) stream_ << spvtools::clr::reset{print_};
+}
+void InstructionDisassembler::SetGrey() {
+  if (color_) stream_ << spvtools::clr::grey{print_};
+}
+void InstructionDisassembler::SetBlue() {
+  if (color_) stream_ << spvtools::clr::blue{print_};
+}
+void InstructionDisassembler::SetYellow() {
+  if (color_) stream_ << spvtools::clr::yellow{print_};
+}
+void InstructionDisassembler::SetRed() {
+  if (color_) stream_ << spvtools::clr::red{print_};
+}
+void InstructionDisassembler::SetGreen() {
+  if (color_) stream_ << spvtools::clr::green{print_};
+}
+}  // namespace disassemble
+
+std::string spvInstructionBinaryToText(const spv_target_env env,
+                                       const uint32_t* instCode,
+                                       const size_t instWordCount,
+                                       const uint32_t* code,
+                                       const size_t wordCount,
+                                       const uint32_t options) {
+  spv_context context = spvContextCreate(env);
+  const AssemblyGrammar grammar(context);
+  if (!grammar.isValid()) {
+    spvContextDestroy(context);
+    return "";
+  }
+
+  // Generate friendly names for Ids if requested.
+  std::unique_ptr<FriendlyNameMapper> friendly_mapper;
+  NameMapper name_mapper = GetTrivialNameMapper();
+  if (options & SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) {
+    friendly_mapper = MakeUnique<FriendlyNameMapper>(context, code, wordCount);
+    name_mapper = friendly_mapper->GetNameMapper();
+  }
+
+  // Now disassemble!
+  Disassembler disassembler(grammar, options, name_mapper);
+  WrappedDisassembler wrapped(&disassembler, instCode, instWordCount);
+  spvBinaryParse(context, &wrapped, code, wordCount, DisassembleTargetHeader,
+                 DisassembleTargetInstruction, nullptr);
+
+  spv_text text = nullptr;
+  std::string output;
+  if (disassembler.SaveTextResult(&text) == SPV_SUCCESS) {
+    output.assign(text->str, text->str + text->length);
+    // Drop trailing newline characters.
+    while (!output.empty() && output.back() == '\n') output.pop_back();
+  }
+  spvTextDestroy(text);
+  spvContextDestroy(context);
+
+  return output;
+}
+}  // namespace spvtools
+
 spv_result_t spvBinaryToText(const spv_const_context context,
                              const uint32_t* code, const size_t wordCount,
                              const uint32_t options, spv_text* pText,
@@ -495,53 +553,13 @@
   }
 
   // Now disassemble!
-  Disassembler disassembler(grammar, options, name_mapper);
-  if (auto error = spvBinaryParse(&hijack_context, &disassembler, code,
-                                  wordCount, DisassembleHeader,
-                                  DisassembleInstruction, pDiagnostic)) {
+  spvtools::Disassembler disassembler(grammar, options, name_mapper);
+  if (auto error =
+          spvBinaryParse(&hijack_context, &disassembler, code, wordCount,
+                         spvtools::DisassembleHeader,
+                         spvtools::DisassembleInstruction, pDiagnostic)) {
     return error;
   }
 
   return disassembler.SaveTextResult(pText);
 }
-
-std::string spvtools::spvInstructionBinaryToText(const spv_target_env env,
-                                                 const uint32_t* instCode,
-                                                 const size_t instWordCount,
-                                                 const uint32_t* code,
-                                                 const size_t wordCount,
-                                                 const uint32_t options) {
-  spv_context context = spvContextCreate(env);
-  const spvtools::AssemblyGrammar grammar(context);
-  if (!grammar.isValid()) {
-    spvContextDestroy(context);
-    return "";
-  }
-
-  // Generate friendly names for Ids if requested.
-  std::unique_ptr<spvtools::FriendlyNameMapper> friendly_mapper;
-  spvtools::NameMapper name_mapper = spvtools::GetTrivialNameMapper();
-  if (options & SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) {
-    friendly_mapper = spvtools::MakeUnique<spvtools::FriendlyNameMapper>(
-        context, code, wordCount);
-    name_mapper = friendly_mapper->GetNameMapper();
-  }
-
-  // Now disassemble!
-  Disassembler disassembler(grammar, options, name_mapper);
-  WrappedDisassembler wrapped(&disassembler, instCode, instWordCount);
-  spvBinaryParse(context, &wrapped, code, wordCount, DisassembleTargetHeader,
-                 DisassembleTargetInstruction, nullptr);
-
-  spv_text text = nullptr;
-  std::string output;
-  if (disassembler.SaveTextResult(&text) == SPV_SUCCESS) {
-    output.assign(text->str, text->str + text->length);
-    // Drop trailing newline characters.
-    while (!output.empty() && output.back() == '\n') output.pop_back();
-  }
-  spvTextDestroy(text);
-  spvContextDestroy(context);
-
-  return output;
-}
diff --git a/source/disassemble.h b/source/disassemble.h
index ac35742..b520a1e 100644
--- a/source/disassemble.h
+++ b/source/disassemble.h
@@ -15,17 +15,20 @@
 #ifndef SOURCE_DISASSEMBLE_H_
 #define SOURCE_DISASSEMBLE_H_
 
+#include <iosfwd>
 #include <string>
 
+#include "source/name_mapper.h"
 #include "spirv-tools/libspirv.h"
 
 namespace spvtools {
 
 // Decodes the given SPIR-V instruction binary representation to its assembly
 // text. The context is inferred from the provided module binary. The options
-// parameter is a bit field of spv_binary_to_text_options_t. Decoded text will
-// be stored into *text. Any error will be written into *diagnostic if
-// diagnostic is non-null.
+// parameter is a bit field of spv_binary_to_text_options_t (note: the option
+// SPV_BINARY_TO_TEXT_OPTION_PRINT will be ignored). Decoded text will be
+// stored into *text. Any error will be written into *diagnostic if diagnostic
+// is non-null.
 std::string spvInstructionBinaryToText(const spv_target_env env,
                                        const uint32_t* inst_binary,
                                        const size_t inst_word_count,
@@ -33,6 +36,63 @@
                                        const size_t word_count,
                                        const uint32_t options);
 
+class AssemblyGrammar;
+namespace disassemble {
+
+// Shared code with other tools (than the disassembler) that might need to
+// output disassembly. An InstructionDisassembler instance converts SPIR-V
+// binary for an instruction to its assembly representation.
+class InstructionDisassembler {
+ public:
+  InstructionDisassembler(const AssemblyGrammar& grammar, std::ostream& stream,
+                          uint32_t options, NameMapper name_mapper);
+
+  // Emits the assembly header for the module.
+  void EmitHeaderSpirv();
+  void EmitHeaderVersion(uint32_t version);
+  void EmitHeaderGenerator(uint32_t generator);
+  void EmitHeaderIdBound(uint32_t id_bound);
+  void EmitHeaderSchema(uint32_t schema);
+
+  // Emits the assembly text for the given instruction.
+  void EmitInstruction(const spv_parsed_instruction_t& inst,
+                       size_t inst_byte_offset);
+
+  // Emits a comment between different sections of the module.
+  void EmitSectionComment(const spv_parsed_instruction_t& inst,
+                          bool& inserted_decoration_space,
+                          bool& inserted_debug_space,
+                          bool& inserted_type_space);
+
+  // Resets the output color, if color is turned on.
+  void ResetColor();
+  // Set the output color, if color is turned on.
+  void SetGrey();
+  void SetBlue();
+  void SetYellow();
+  void SetRed();
+  void SetGreen();
+
+ private:
+  // Emits an operand for the given instruction, where the instruction
+  // is at offset words from the start of the binary.
+  void EmitOperand(const spv_parsed_instruction_t& inst,
+                   const uint16_t operand_index);
+
+  // Emits a mask expression for the given mask word of the specified type.
+  void EmitMaskOperand(const spv_operand_type_t type, const uint32_t word);
+
+  const spvtools::AssemblyGrammar& grammar_;
+  std::ostream& stream_;
+  const bool print_;   // Should we also print to the standard output stream?
+  const bool color_;   // Should we print in colour?
+  const int indent_;   // How much to indent. 0 means don't indent
+  const int comment_;  // Should we comment the source
+  const bool show_byte_offset_;  // Should we print byte offset, in hex?
+  spvtools::NameMapper name_mapper_;
+};
+
+}  // namespace disassemble
 }  // namespace spvtools
 
 #endif  // SOURCE_DISASSEMBLE_H_
diff --git a/source/ext_inst.cpp b/source/ext_inst.cpp
index 812053e..4e27954 100644
--- a/source/ext_inst.cpp
+++ b/source/ext_inst.cpp
@@ -96,6 +96,8 @@
     case SPV_ENV_UNIVERSAL_1_4:
     case SPV_ENV_UNIVERSAL_1_5:
     case SPV_ENV_VULKAN_1_2:
+    case SPV_ENV_UNIVERSAL_1_6:
+    case SPV_ENV_VULKAN_1_3:
       *pExtInstTable = &kTable_1_0;
       return SPV_SUCCESS;
     default:
diff --git a/source/ext_inst.h b/source/ext_inst.h
index aff6e30..4027f4c 100644
--- a/source/ext_inst.h
+++ b/source/ext_inst.h
@@ -27,7 +27,7 @@
 // Returns true if the extended instruction set is debug info
 bool spvExtInstIsDebugInfo(const spv_ext_inst_type_t type);
 
-// Finds the named extented instruction of the given type in the given extended
+// Finds the named extended instruction of the given type in the given extended
 // instruction table. On success, returns SPV_SUCCESS and writes a handle of
 // the instruction entry into *entry.
 spv_result_t spvExtInstTableNameLookup(const spv_ext_inst_table table,
@@ -35,7 +35,7 @@
                                        const char* name,
                                        spv_ext_inst_desc* entry);
 
-// Finds the extented instruction of the given type in the given extended
+// Finds the extended instruction of the given type in the given extended
 // instruction table by value. On success, returns SPV_SUCCESS and writes a
 // handle of the instruction entry into *entry.
 spv_result_t spvExtInstTableValueLookup(const spv_ext_inst_table table,
diff --git a/source/extensions.cpp b/source/extensions.cpp
index a94db27..049a3ad 100644
--- a/source/extensions.cpp
+++ b/source/extensions.cpp
@@ -18,6 +18,7 @@
 #include <sstream>
 #include <string>
 
+#include "source/binary.h"
 #include "source/enum_string_mapping.h"
 
 namespace spvtools {
@@ -30,8 +31,9 @@
   const auto& operand = inst->operands[0];
   assert(operand.type == SPV_OPERAND_TYPE_LITERAL_STRING);
   assert(inst->num_words > operand.offset);
+  (void)operand; /* No unused variables in release builds. */
 
-  return reinterpret_cast<const char*>(inst->words + operand.offset);
+  return spvDecodeLiteralStringOperand(*inst, 0);
 }
 
 std::string ExtensionSetToString(const ExtensionSet& extensions) {
diff --git a/source/fuzz/fact_manager/fact_manager.h b/source/fuzz/fact_manager/fact_manager.h
index 5cf5b18..ce28ae4 100644
--- a/source/fuzz/fact_manager/fact_manager.h
+++ b/source/fuzz/fact_manager/fact_manager.h
@@ -163,7 +163,7 @@
   std::vector<const protobufs::DataDescriptor*> GetSynonymsForDataDescriptor(
       const protobufs::DataDescriptor& data_descriptor) const;
 
-  // Returns true if and ony if |data_descriptor1| and |data_descriptor2| are
+  // Returns true if and only if |data_descriptor1| and |data_descriptor2| are
   // known to be synonymous.
   bool IsSynonymous(const protobufs::DataDescriptor& data_descriptor1,
                     const protobufs::DataDescriptor& data_descriptor2) const;
@@ -174,7 +174,7 @@
   //==============================
   // Querying facts about dead blocks
 
-  // Returns true if and ony if |block_id| is the id of a block known to be
+  // Returns true if and only if |block_id| is the id of a block known to be
   // dynamically unreachable.
   bool BlockIsDead(uint32_t block_id) const;
 
@@ -184,7 +184,7 @@
   //==============================
   // Querying facts about livesafe function
 
-  // Returns true if and ony if |function_id| is the id of a function known
+  // Returns true if and only if |function_id| is the id of a function known
   // to be livesafe.
   bool FunctionIsLivesafe(uint32_t function_id) const;
 
@@ -194,7 +194,7 @@
   //==============================
   // Querying facts about irrelevant values
 
-  // Returns true if and ony if the value of the pointee associated with
+  // Returns true if and only if the value of the pointee associated with
   // |pointer_id| is irrelevant.
   bool PointeeValueIsIrrelevant(uint32_t pointer_id) const;
 
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index c217542..7e34cc3 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -21,7 +21,7 @@
 
 namespace {
 
-// An offset between the the module's id bound and the minimum fresh id.
+// An offset between the module's id bound and the minimum fresh id.
 //
 // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2541): consider
 //  the case where the maximum id bound is reached.
diff --git a/source/fuzz/fuzzer_pass.cpp b/source/fuzz/fuzzer_pass.cpp
index d91482c..6a87985 100644
--- a/source/fuzz/fuzzer_pass.cpp
+++ b/source/fuzz/fuzzer_pass.cpp
@@ -261,7 +261,7 @@
 
 uint32_t FuzzerPass::FindOrCreateFunctionType(
     uint32_t return_type_id, const std::vector<uint32_t>& argument_id) {
-  // FindFunctionType has a sigle argument for OpTypeFunction operands
+  // FindFunctionType has a single argument for OpTypeFunction operands
   // so we will have to copy them all in this vector
   std::vector<uint32_t> type_ids(argument_id.size() + 1);
   type_ids[0] = return_type_id;
diff --git a/source/fuzz/fuzzer_pass_donate_modules.cpp b/source/fuzz/fuzzer_pass_donate_modules.cpp
index 5bdf697..29ede58 100644
--- a/source/fuzz/fuzzer_pass_donate_modules.cpp
+++ b/source/fuzz/fuzzer_pass_donate_modules.cpp
@@ -479,7 +479,7 @@
              "should have been donated.");
 
       // It is OK to have duplicate constant composite definitions, so add
-      // this to the module using remapped versions of all consituent ids and
+      // this to the module using remapped versions of all constituent ids and
       // the result type.
       new_result_id = GetFuzzerContext()->GetFreshId();
       std::vector<uint32_t> constituent_ids;
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index cba9397..e71b6a3 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -1961,10 +1961,10 @@
   // A descriptor for the boolean constant id we would like to replace
   IdUseDescriptor id_use_descriptor = 1;
 
-  // Id for the constant to be used on the LHS of the comparision
+  // Id for the constant to be used on the LHS of the comparison
   uint32 lhs_id = 2;
 
-  // Id for the constant to be used on the RHS of the comparision
+  // Id for the constant to be used on the RHS of the comparison
   uint32 rhs_id = 3;
 
   // Opcode for binary operator
@@ -2403,7 +2403,7 @@
   // va = vector(..., a, ...)
   // vb = vector(..., b, ...)
   //
-  // where a and b are in the same position i in each of their correponding vector
+  // where a and b are in the same position i in each of their corresponding vector
   // and a is synonymous with va[i] and b is synonymous with vb[i].
   //
   // The transformation then add an instruction vc = va op vb where c is synonymous
diff --git a/source/fuzz/transformation_add_constant_composite.cpp b/source/fuzz/transformation_add_constant_composite.cpp
index e6cd5a9..89007ab 100644
--- a/source/fuzz/transformation_add_constant_composite.cpp
+++ b/source/fuzz/transformation_add_constant_composite.cpp
@@ -75,7 +75,7 @@
       // We do not create constants of structs decorated with Block nor
       // BufferBlock.  The SPIR-V spec does not explicitly disallow this, but it
       // seems like a strange thing to do, so we disallow it to avoid triggering
-      // low priorty edge case issues related to it.
+      // low priority edge case issues related to it.
       if (fuzzerutil::HasBlockOrBufferBlockDecoration(
               ir_context, composite_type_instruction->result_id())) {
         return false;
diff --git a/source/fuzz/transformation_duplicate_region_with_selection.cpp b/source/fuzz/transformation_duplicate_region_with_selection.cpp
index a80becd..db88610 100644
--- a/source/fuzz/transformation_duplicate_region_with_selection.cpp
+++ b/source/fuzz/transformation_duplicate_region_with_selection.cpp
@@ -325,7 +325,7 @@
   std::map<uint32_t, uint32_t> original_id_to_phi_id =
       fuzzerutil::RepeatedUInt32PairToMap(message_.original_id_to_phi_id());
 
-  // Use oveflow ids to fill in any required ids that are missing from these
+  // Use overflow ids to fill in any required ids that are missing from these
   // maps.
   for (auto block : region_blocks) {
     if (original_label_to_duplicate_label.count(block->id()) == 0) {
diff --git a/source/fuzz/transformation_replace_id_with_synonym.h b/source/fuzz/transformation_replace_id_with_synonym.h
index 4570fce..66f8e43 100644
--- a/source/fuzz/transformation_replace_id_with_synonym.h
+++ b/source/fuzz/transformation_replace_id_with_synonym.h
@@ -32,7 +32,7 @@
       protobufs::IdUseDescriptor id_use_descriptor, uint32_t synonymous_id);
 
   // - The fact manager must know that the id identified by
-  //   |message_.id_use_descriptor| is synonomous with |message_.synonymous_id|.
+  //   |message_.id_use_descriptor| is synonymous with |message_.synonymous_id|.
   // - Replacing the id in |message_.id_use_descriptor| by
   //   |message_.synonymous_id| must respect SPIR-V's rules about uses being
   //   dominated by their definitions.
diff --git a/source/libspirv.cpp b/source/libspirv.cpp
index 0bc0935..be76caa 100644
--- a/source/libspirv.cpp
+++ b/source/libspirv.cpp
@@ -99,7 +99,9 @@
   spv_text spvtext = nullptr;
   spv_result_t status = spvBinaryToText(impl_->context, binary, binary_size,
                                         options, &spvtext, nullptr);
-  if (status == SPV_SUCCESS) {
+  if (status == SPV_SUCCESS &&
+      (options & SPV_BINARY_TO_TEXT_OPTION_PRINT) == 0) {
+    assert(spvtext);
     text->assign(spvtext->str, spvtext->str + spvtext->length);
   }
   spvTextDestroy(spvtext);
diff --git a/source/link/CMakeLists.txt b/source/link/CMakeLists.txt
index c8dd2f7..a452a10 100644
--- a/source/link/CMakeLists.txt
+++ b/source/link/CMakeLists.txt
@@ -23,7 +23,7 @@
 	$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
   PRIVATE ${spirv-tools_BINARY_DIR}
 )
-# We need the IR functionnalities from the optimizer
+# We need the IR functionalities from the optimizer
 target_link_libraries(SPIRV-Tools-link
   PUBLIC SPIRV-Tools-opt)
 
diff --git a/source/link/linker.cpp b/source/link/linker.cpp
index c5ca562..3b388cc 100644
--- a/source/link/linker.cpp
+++ b/source/link/linker.cpp
@@ -19,6 +19,7 @@
 #include <cstring>
 #include <iostream>
 #include <memory>
+#include <numeric>
 #include <string>
 #include <unordered_map>
 #include <unordered_set>
@@ -33,10 +34,12 @@
 #include "source/opt/ir_loader.h"
 #include "source/opt/pass_manager.h"
 #include "source/opt/remove_duplicates_pass.h"
+#include "source/opt/remove_unused_interface_variables_pass.h"
 #include "source/opt/type_manager.h"
 #include "source/spirv_constant.h"
 #include "source/spirv_target_env.h"
 #include "source/util/make_unique.h"
+#include "source/util/string_utils.h"
 #include "spirv-tools/libspirv.hpp"
 
 namespace spvtools {
@@ -86,10 +89,6 @@
 //
 // |header| should not be null, |modules| should not be empty and pointers
 // should be non-null. |max_id_bound| should be strictly greater than 0.
-//
-// TODO(pierremoreau): What to do when binaries use different versions of
-//                     SPIR-V? For now, use the max of all versions found in
-//                     the input modules.
 spv_result_t GenerateHeader(const MessageConsumer& consumer,
                             const std::vector<opt::Module*>& modules,
                             uint32_t max_id_bound, opt::ModuleHeader* header);
@@ -130,7 +129,7 @@
 
 // Remove linkage specific instructions, such as prototypes of imported
 // functions, declarations of imported variables, import (and export if
-// necessary) linkage attribtes.
+// necessary) linkage attributes.
 //
 // |linked_context| and |decoration_manager| should not be null, and the
 // 'RemoveDuplicatePass' should be run first.
@@ -148,6 +147,15 @@
 spv_result_t VerifyIds(const MessageConsumer& consumer,
                        opt::IRContext* linked_context);
 
+// Verify that the universal limits are not crossed, and warn the user
+// otherwise.
+//
+// TODO(pierremoreau):
+// - Verify against the limits of the environment (e.g. Vulkan limits if
+//   consuming vulkan1.x)
+spv_result_t VerifyLimits(const MessageConsumer& consumer,
+                          const opt::IRContext& linked_context);
+
 spv_result_t ShiftIdsInModules(const MessageConsumer& consumer,
                                std::vector<opt::Module*>* modules,
                                uint32_t* max_id_bound) {
@@ -163,29 +171,31 @@
     return DiagnosticStream(position, consumer, "", SPV_ERROR_INVALID_DATA)
            << "|max_id_bound| of ShiftIdsInModules should not be null.";
 
-  uint32_t id_bound = modules->front()->IdBound() - 1u;
+  const size_t id_bound =
+      std::accumulate(modules->begin(), modules->end(), static_cast<size_t>(1),
+                      [](const size_t& accumulation, opt::Module* module) {
+                        return accumulation + module->IdBound() - 1u;
+                      });
+  if (id_bound > std::numeric_limits<uint32_t>::max())
+    return DiagnosticStream(position, consumer, "", SPV_ERROR_INVALID_DATA)
+           << "Too many IDs (" << id_bound
+           << "): combining all modules would overflow the 32-bit word of the "
+              "SPIR-V header.";
+
+  *max_id_bound = static_cast<uint32_t>(id_bound);
+
+  uint32_t id_offset = modules->front()->IdBound() - 1u;
   for (auto module_iter = modules->begin() + 1; module_iter != modules->end();
        ++module_iter) {
     Module* module = *module_iter;
-    module->ForEachInst([&id_bound](Instruction* insn) {
-      insn->ForEachId([&id_bound](uint32_t* id) { *id += id_bound; });
+    module->ForEachInst([&id_offset](Instruction* insn) {
+      insn->ForEachId([&id_offset](uint32_t* id) { *id += id_offset; });
     });
-    id_bound += module->IdBound() - 1u;
-    if (id_bound > 0x3FFFFF)
-      return DiagnosticStream(position, consumer, "", SPV_ERROR_INVALID_ID)
-             << "The limit of IDs, 4194303, was exceeded:"
-             << " " << id_bound << " is the current ID bound.";
+    id_offset += module->IdBound() - 1u;
 
     // Invalidate the DefUseManager
     module->context()->InvalidateAnalyses(opt::IRContext::kAnalysisDefUse);
   }
-  ++id_bound;
-  if (id_bound > 0x3FFFFF)
-    return DiagnosticStream(position, consumer, "", SPV_ERROR_INVALID_ID)
-           << "The limit of IDs, 4194303, was exceeded:"
-           << " " << id_bound << " is the current ID bound.";
-
-  *max_id_bound = id_bound;
 
   return SPV_SUCCESS;
 }
@@ -202,15 +212,25 @@
     return DiagnosticStream(position, consumer, "", SPV_ERROR_INVALID_DATA)
            << "|max_id_bound| of GenerateHeader should not be null.";
 
-  uint32_t version = 0u;
-  for (const auto& module : modules)
-    version = std::max(version, module->version());
+  const uint32_t linked_version = modules.front()->version();
+  for (std::size_t i = 1; i < modules.size(); ++i) {
+    const uint32_t module_version = modules[i]->version();
+    if (module_version != linked_version)
+      return DiagnosticStream({0, 0, 1}, consumer, "", SPV_ERROR_INTERNAL)
+             << "Conflicting SPIR-V versions: "
+             << SPV_SPIRV_VERSION_MAJOR_PART(linked_version) << "."
+             << SPV_SPIRV_VERSION_MINOR_PART(linked_version)
+             << " (input modules 1 through " << i << ") vs "
+             << SPV_SPIRV_VERSION_MAJOR_PART(module_version) << "."
+             << SPV_SPIRV_VERSION_MINOR_PART(module_version)
+             << " (input module " << (i + 1) << ").";
+  }
 
   header->magic_number = SpvMagicNumber;
-  header->version = version;
+  header->version = linked_version;
   header->generator = SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_LINKER, 0);
   header->bound = max_id_bound;
-  header->reserved = 0u;
+  header->schema = 0u;
 
   return SPV_SUCCESS;
 }
@@ -243,55 +263,65 @@
       linked_module->AddExtInstImport(
           std::unique_ptr<Instruction>(inst.Clone(linked_context)));
 
-  do {
-    const Instruction* memory_model_inst = input_modules[0]->GetMemoryModel();
-    if (memory_model_inst == nullptr) break;
+  const Instruction* linked_memory_model_inst =
+      input_modules.front()->GetMemoryModel();
+  if (linked_memory_model_inst == nullptr) {
+    return DiagnosticStream(position, consumer, "", SPV_ERROR_INVALID_BINARY)
+           << "Input module 1 is lacking an OpMemoryModel instruction.";
+  }
+  const uint32_t linked_addressing_model =
+      linked_memory_model_inst->GetSingleWordOperand(0u);
+  const uint32_t linked_memory_model =
+      linked_memory_model_inst->GetSingleWordOperand(1u);
 
-    uint32_t addressing_model = memory_model_inst->GetSingleWordOperand(0u);
-    uint32_t memory_model = memory_model_inst->GetSingleWordOperand(1u);
-    for (const auto& module : input_modules) {
-      memory_model_inst = module->GetMemoryModel();
-      if (memory_model_inst == nullptr) continue;
+  for (std::size_t i = 1; i < input_modules.size(); ++i) {
+    const Module* module = input_modules[i];
+    const Instruction* memory_model_inst = module->GetMemoryModel();
+    if (memory_model_inst == nullptr)
+      return DiagnosticStream(position, consumer, "", SPV_ERROR_INVALID_BINARY)
+             << "Input module " << (i + 1)
+             << " is lacking an OpMemoryModel instruction.";
 
-      if (addressing_model != memory_model_inst->GetSingleWordOperand(0u)) {
-        spv_operand_desc initial_desc = nullptr, current_desc = nullptr;
-        grammar.lookupOperand(SPV_OPERAND_TYPE_ADDRESSING_MODEL,
-                              addressing_model, &initial_desc);
-        grammar.lookupOperand(SPV_OPERAND_TYPE_ADDRESSING_MODEL,
-                              memory_model_inst->GetSingleWordOperand(0u),
-                              &current_desc);
-        return DiagnosticStream(position, consumer, "", SPV_ERROR_INTERNAL)
-               << "Conflicting addressing models: " << initial_desc->name
-               << " vs " << current_desc->name << ".";
-      }
-      if (memory_model != memory_model_inst->GetSingleWordOperand(1u)) {
-        spv_operand_desc initial_desc = nullptr, current_desc = nullptr;
-        grammar.lookupOperand(SPV_OPERAND_TYPE_MEMORY_MODEL, memory_model,
-                              &initial_desc);
-        grammar.lookupOperand(SPV_OPERAND_TYPE_MEMORY_MODEL,
-                              memory_model_inst->GetSingleWordOperand(1u),
-                              &current_desc);
-        return DiagnosticStream(position, consumer, "", SPV_ERROR_INTERNAL)
-               << "Conflicting memory models: " << initial_desc->name << " vs "
-               << current_desc->name << ".";
-      }
+    const uint32_t module_addressing_model =
+        memory_model_inst->GetSingleWordOperand(0u);
+    if (module_addressing_model != linked_addressing_model) {
+      spv_operand_desc linked_desc = nullptr, module_desc = nullptr;
+      grammar.lookupOperand(SPV_OPERAND_TYPE_ADDRESSING_MODEL,
+                            linked_addressing_model, &linked_desc);
+      grammar.lookupOperand(SPV_OPERAND_TYPE_ADDRESSING_MODEL,
+                            module_addressing_model, &module_desc);
+      return DiagnosticStream(position, consumer, "", SPV_ERROR_INTERNAL)
+             << "Conflicting addressing models: " << linked_desc->name
+             << " (input modules 1 through " << i << ") vs "
+             << module_desc->name << " (input module " << (i + 1) << ").";
     }
 
-    if (memory_model_inst != nullptr)
-      linked_module->SetMemoryModel(std::unique_ptr<Instruction>(
-          memory_model_inst->Clone(linked_context)));
-  } while (false);
+    const uint32_t module_memory_model =
+        memory_model_inst->GetSingleWordOperand(1u);
+    if (module_memory_model != linked_memory_model) {
+      spv_operand_desc linked_desc = nullptr, module_desc = nullptr;
+      grammar.lookupOperand(SPV_OPERAND_TYPE_MEMORY_MODEL, linked_memory_model,
+                            &linked_desc);
+      grammar.lookupOperand(SPV_OPERAND_TYPE_MEMORY_MODEL, module_memory_model,
+                            &module_desc);
+      return DiagnosticStream(position, consumer, "", SPV_ERROR_INTERNAL)
+             << "Conflicting memory models: " << linked_desc->name
+             << " (input modules 1 through " << i << ") vs "
+             << module_desc->name << " (input module " << (i + 1) << ").";
+    }
+  }
+  linked_module->SetMemoryModel(std::unique_ptr<Instruction>(
+      linked_memory_model_inst->Clone(linked_context)));
 
-  std::vector<std::pair<uint32_t, const char*>> entry_points;
+  std::vector<std::pair<uint32_t, std::string>> entry_points;
   for (const auto& module : input_modules)
     for (const auto& inst : module->entry_points()) {
       const uint32_t model = inst.GetSingleWordInOperand(0);
-      const char* const name =
-          reinterpret_cast<const char*>(inst.GetInOperand(2).words.data());
+      const std::string name = inst.GetInOperand(2).AsString();
       const auto i = std::find_if(
           entry_points.begin(), entry_points.end(),
-          [model, name](const std::pair<uint32_t, const char*>& v) {
-            return v.first == model && strcmp(name, v.second) == 0;
+          [model, name](const std::pair<uint32_t, std::string>& v) {
+            return v.first == model && v.second == name;
           });
       if (i != entry_points.end()) {
         spv_operand_desc desc = nullptr;
@@ -332,13 +362,10 @@
 
   // If the generated module uses SPIR-V 1.1 or higher, add an
   // OpModuleProcessed instruction about the linking step.
-  if (linked_module->version() >= 0x10100) {
+  if (linked_module->version() >= SPV_SPIRV_VERSION_WORD(1, 1)) {
     const std::string processed_string("Linked by SPIR-V Tools Linker");
-    const auto num_chars = processed_string.size();
-    // Compute num words, accommodate the terminating null character.
-    const auto num_words = (num_chars + 1 + 3) / 4;
-    std::vector<uint32_t> processed_words(num_words, 0u);
-    std::memcpy(processed_words.data(), processed_string.data(), num_chars);
+    std::vector<uint32_t> processed_words =
+        spvtools::utils::MakeVector(processed_string);
     linked_module->AddDebug3Inst(std::unique_ptr<Instruction>(
         new Instruction(linked_context, SpvOpModuleProcessed, 0u, 0u,
                         {{SPV_OPERAND_TYPE_LITERAL_STRING, processed_words}})));
@@ -352,18 +379,12 @@
   // TODO(pierremoreau): Since the modules have not been validate, should we
   //                     expect SpvStorageClassFunction variables outside
   //                     functions?
-  uint32_t num_global_values = 0u;
   for (const auto& module : input_modules) {
     for (const auto& inst : module->types_values()) {
       linked_module->AddType(
           std::unique_ptr<Instruction>(inst.Clone(linked_context)));
-      num_global_values += inst.opcode() == SpvOpVariable;
     }
   }
-  if (num_global_values > 0xFFFF)
-    return DiagnosticStream(position, consumer, "", SPV_ERROR_INTERNAL)
-           << "The limit of global values, 65535, was exceeded;"
-           << " " << num_global_values << " global values were found.";
 
   // Process functions and their basic blocks
   for (const auto& module : input_modules) {
@@ -414,8 +435,7 @@
     const uint32_t type = decoration.GetSingleWordInOperand(3u);
 
     LinkageSymbolInfo symbol_info;
-    symbol_info.name =
-        reinterpret_cast<const char*>(decoration.GetInOperand(2u).words.data());
+    symbol_info.name = decoration.GetInOperand(2u).AsString();
     symbol_info.id = id;
     symbol_info.type_id = 0u;
 
@@ -636,6 +656,34 @@
   return SPV_SUCCESS;
 }
 
+spv_result_t VerifyLimits(const MessageConsumer& consumer,
+                          const opt::IRContext& linked_context) {
+  spv_position_t position = {};
+
+  const uint32_t max_id_bound = linked_context.module()->id_bound();
+  if (max_id_bound >= SPV_LIMIT_RESULT_ID_BOUND)
+    DiagnosticStream({0u, 0u, 4u}, consumer, "", SPV_WARNING)
+        << "The minimum limit of IDs, " << (SPV_LIMIT_RESULT_ID_BOUND - 1)
+        << ", was exceeded:"
+        << " " << max_id_bound << " is the current ID bound.\n"
+        << "The resulting module might not be supported by all "
+           "implementations.";
+
+  size_t num_global_values = 0u;
+  for (const auto& inst : linked_context.module()->types_values()) {
+    num_global_values += inst.opcode() == SpvOpVariable;
+  }
+  if (num_global_values >= SPV_LIMIT_GLOBAL_VARIABLES_MAX)
+    DiagnosticStream(position, consumer, "", SPV_WARNING)
+        << "The minimum limit of global values, "
+        << (SPV_LIMIT_GLOBAL_VARIABLES_MAX - 1) << ", was exceeded;"
+        << " " << num_global_values << " global values were found.\n"
+        << "The resulting module might not be supported by all "
+           "implementations.";
+
+  return SPV_SUCCESS;
+}
+
 }  // namespace
 
 spv_result_t Link(const Context& context,
@@ -760,7 +808,16 @@
   pass_res = manager.Run(&linked_context);
   if (pass_res == opt::Pass::Status::Failure) return SPV_ERROR_INVALID_DATA;
 
-  // Phase 11: Output the module
+  // Phase 11: Recompute EntryPoint variables
+  manager.AddPass<opt::RemoveUnusedInterfaceVariablesPass>();
+  pass_res = manager.Run(&linked_context);
+  if (pass_res == opt::Pass::Status::Failure) return SPV_ERROR_INVALID_DATA;
+
+  // Phase 12: Warn if SPIR-V limits were exceeded
+  res = VerifyLimits(consumer, linked_context);
+  if (res != SPV_SUCCESS) return res;
+
+  // Phase 13: Output the module
   linked_context.module()->ToBinary(linked_binary, true);
 
   return SPV_SUCCESS;
diff --git a/source/name_mapper.cpp b/source/name_mapper.cpp
index eb08f8f..3b31d33 100644
--- a/source/name_mapper.cpp
+++ b/source/name_mapper.cpp
@@ -22,10 +22,10 @@
 #include <unordered_map>
 #include <unordered_set>
 
-#include "spirv-tools/libspirv.h"
-
+#include "source/binary.h"
 #include "source/latest_version_spirv_header.h"
 #include "source/parsed_operand.h"
+#include "spirv-tools/libspirv.h"
 
 namespace spvtools {
 namespace {
@@ -172,7 +172,7 @@
   const auto result_id = inst.result_id;
   switch (inst.opcode) {
     case SpvOpName:
-      SaveName(inst.words[1], reinterpret_cast<const char*>(inst.words + 2));
+      SaveName(inst.words[1], spvDecodeLiteralStringOperand(inst, 1));
       break;
     case SpvOpDecorate:
       // Decorations come after OpName.  So OpName will take precedence over
@@ -274,9 +274,8 @@
       SaveName(result_id, "Queue");
       break;
     case SpvOpTypeOpaque:
-      SaveName(result_id,
-               std::string("Opaque_") +
-                   Sanitize(reinterpret_cast<const char*>(inst.words + 2)));
+      SaveName(result_id, std::string("Opaque_") +
+                              Sanitize(spvDecodeLiteralStringOperand(inst, 1)));
       break;
     case SpvOpTypePipeStorage:
       SaveName(result_id, "PipeStorage");
diff --git a/source/opcode.cpp b/source/opcode.cpp
index c96cde8..3f92729 100644
--- a/source/opcode.cpp
+++ b/source/opcode.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2015-2020 The Khronos Group Inc.
+// Copyright (c) 2015-2022 The Khronos Group Inc.
 // Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights
 // reserved.
 //
@@ -40,12 +40,12 @@
 static const spv_opcode_table_t kOpcodeTable = {ARRAY_SIZE(kOpcodeTableEntries),
                                                 kOpcodeTableEntries};
 
-// Represents a vendor tool entry in the SPIR-V XML Regsitry.
+// Represents a vendor tool entry in the SPIR-V XML Registry.
 struct VendorTool {
   uint32_t value;
   const char* vendor;
   const char* tool;         // Might be empty string.
-  const char* vendor_tool;  // Combiantion of vendor and tool.
+  const char* vendor_tool;  // Combination of vendor and tool.
 };
 
 const VendorTool vendor_tools[] = {
@@ -453,6 +453,7 @@
     case SpvOpTerminateInvocation:
     case SpvOpTerminateRayKHR:
     case SpvOpIgnoreIntersectionKHR:
+    case SpvOpEmitMeshTasksEXT:
       return true;
     default:
       return false;
@@ -467,11 +468,6 @@
   return spvOpcodeIsBranch(opcode) || spvOpcodeIsReturnOrAbort(opcode);
 }
 
-bool spvOpcodeTerminatesExecution(SpvOp opcode) {
-  return opcode == SpvOpKill || opcode == SpvOpTerminateInvocation ||
-         opcode == SpvOpTerminateRayKHR || opcode == SpvOpIgnoreIntersectionKHR;
-}
-
 bool spvOpcodeIsBaseOpaqueType(SpvOp opcode) {
   switch (opcode) {
     case SpvOpTypeImage:
@@ -528,6 +524,7 @@
     case SpvOpGroupNonUniformLogicalXor:
     case SpvOpGroupNonUniformQuadBroadcast:
     case SpvOpGroupNonUniformQuadSwap:
+    case SpvOpGroupNonUniformRotateKHR:
       return true;
     default:
       return false;
@@ -631,6 +628,7 @@
     case SpvOpString:
     case SpvOpLine:
     case SpvOpNoLine:
+    case SpvOpModuleProcessed:
       return true;
     default:
       return false;
diff --git a/source/opcode.h b/source/opcode.h
index c8525a2..77a0bed 100644
--- a/source/opcode.h
+++ b/source/opcode.h
@@ -110,18 +110,16 @@
 // Returns true if the given opcode is a return instruction.
 bool spvOpcodeIsReturn(SpvOp opcode);
 
-// Returns true if the given opcode aborts execution.
+// Returns true if the given opcode aborts execution.  To abort means that after
+// executing that instruction, no other instructions will be executed regardless
+// of the context in which the instruction appears.  Note that `OpUnreachable`
+// is considered an abort even if its behaviour is undefined.
 bool spvOpcodeIsAbort(SpvOp opcode);
 
 // Returns true if the given opcode is a return instruction or it aborts
 // execution.
 bool spvOpcodeIsReturnOrAbort(SpvOp opcode);
 
-// Returns true if the given opcode is a kill instruction or it terminates
-// execution. Note that branches, returns, and unreachables do not terminate
-// execution.
-bool spvOpcodeTerminatesExecution(SpvOp opcode);
-
 // Returns true if the given opcode is a basic block terminator.
 bool spvOpcodeIsBlockTerminator(SpvOp opcode);
 
diff --git a/source/operand.cpp b/source/operand.cpp
index 6d83e81..0c255a3 100644
--- a/source/operand.cpp
+++ b/source/operand.cpp
@@ -72,12 +72,16 @@
       // Note that the second rule assumes the extension enabling this operand
       // is indeed requested in the SPIR-V code; checking that should be
       // validator's work.
-      if (((version >= entry.minVersion && version <= entry.lastVersion) ||
-           entry.numExtensions > 0u || entry.numCapabilities > 0u) &&
-          nameLength == strlen(entry.name) &&
+      if (nameLength == strlen(entry.name) &&
           !strncmp(entry.name, name, nameLength)) {
-        *pEntry = &entry;
-        return SPV_SUCCESS;
+        if ((version >= entry.minVersion && version <= entry.lastVersion) ||
+            entry.numExtensions > 0u || entry.numCapabilities > 0u) {
+          *pEntry = &entry;
+          return SPV_SUCCESS;
+        } else {
+          // if there is no extension/capability then the version is wrong
+          return SPV_ERROR_WRONG_VERSION;
+        }
       }
     }
   }
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt
index 7d522fb..75fe4c0 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -12,6 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 set(SPIRV_TOOLS_OPT_SOURCES
+  fix_func_call_arguments.h
   aggressive_dead_code_elim_pass.h
   amd_ext_to_khr.h
   basic_block.h
@@ -45,6 +46,7 @@
   eliminate_dead_constant_pass.h
   eliminate_dead_functions_pass.h
   eliminate_dead_functions_util.h
+  eliminate_dead_input_components_pass.h
   eliminate_dead_members_pass.h
   empty_pass.h
   feature_manager.h
@@ -66,6 +68,7 @@
   instruction.h
   instruction_list.h
   instrument_pass.h
+  interface_var_sroa.h
   interp_fixup_pass.h
   ir_builder.h
   ir_context.h
@@ -99,6 +102,7 @@
   reflect.h
   register_pressure.h
   relax_float_ops_pass.h
+  remove_dontinline_pass.h
   remove_duplicates_pass.h
   remove_unused_interface_variables_pass.h
   replace_desc_array_access_using_var_index.h
@@ -108,10 +112,11 @@
   scalar_replacement_pass.h
   set_spec_constant_default_value_pass.h
   simplification_pass.h
+  spread_volatile_semantics.h
   ssa_rewrite_pass.h
   strength_reduction_pass.h
   strip_debug_info_pass.h
-  strip_reflect_info_pass.h
+  strip_nonsemantic_info_pass.h
   struct_cfg_analysis.h
   tree_iterator.h
   type_manager.h
@@ -123,6 +128,7 @@
   workaround1209.h
   wrap_opkill.h
 
+  fix_func_call_arguments.cpp
   aggressive_dead_code_elim_pass.cpp
   amd_ext_to_khr.cpp
   basic_block.cpp
@@ -156,6 +162,7 @@
   eliminate_dead_constant_pass.cpp
   eliminate_dead_functions_pass.cpp
   eliminate_dead_functions_util.cpp
+  eliminate_dead_input_components_pass.cpp
   eliminate_dead_members_pass.cpp
   feature_manager.cpp
   fix_storage_class.cpp
@@ -176,6 +183,7 @@
   instruction.cpp
   instruction_list.cpp
   instrument_pass.cpp
+  interface_var_sroa.cpp
   interp_fixup_pass.cpp
   ir_context.cpp
   ir_loader.cpp
@@ -206,6 +214,7 @@
   redundancy_elimination.cpp
   register_pressure.cpp
   relax_float_ops_pass.cpp
+  remove_dontinline_pass.cpp
   remove_duplicates_pass.cpp
   remove_unused_interface_variables_pass.cpp
   replace_desc_array_access_using_var_index.cpp
@@ -215,10 +224,11 @@
   scalar_replacement_pass.cpp
   set_spec_constant_default_value_pass.cpp
   simplification_pass.cpp
+  spread_volatile_semantics.cpp
   ssa_rewrite_pass.cpp
   strength_reduction_pass.cpp
   strip_debug_info_pass.cpp
-  strip_reflect_info_pass.cpp
+  strip_nonsemantic_info_pass.cpp
   struct_cfg_analysis.cpp
   type_manager.cpp
   types.cpp
diff --git a/source/opt/aggressive_dead_code_elim_pass.cpp b/source/opt/aggressive_dead_code_elim_pass.cpp
index 0b54d5e..7fa5c8a 100644
--- a/source/opt/aggressive_dead_code_elim_pass.cpp
+++ b/source/opt/aggressive_dead_code_elim_pass.cpp
@@ -27,6 +27,7 @@
 #include "source/opt/iterator.h"
 #include "source/opt/reflect.h"
 #include "source/spirv_constant.h"
+#include "source/util/string_utils.h"
 
 namespace spvtools {
 namespace opt {
@@ -146,8 +147,7 @@
 bool AggressiveDCEPass::AllExtensionsSupported() const {
   // If any extension not in allowlist, return false
   for (auto& ei : get_module()->extensions()) {
-    const char* extName =
-        reinterpret_cast<const char*>(&ei.GetInOperand(0).words[0]);
+    const std::string extName = ei.GetInOperand(0).AsString();
     if (extensions_allowlist_.find(extName) == extensions_allowlist_.end())
       return false;
   }
@@ -156,11 +156,9 @@
   for (auto& inst : context()->module()->ext_inst_imports()) {
     assert(inst.opcode() == SpvOpExtInstImport &&
            "Expecting an import of an extension's instruction set.");
-    const char* extension_name =
-        reinterpret_cast<const char*>(&inst.GetInOperand(0).words[0]);
-    if (0 == std::strncmp(extension_name, "NonSemantic.", 12) &&
-        0 != std::strncmp(extension_name, "NonSemantic.Shader.DebugInfo.100",
-                          32)) {
+    const std::string extension_name = inst.GetInOperand(0).AsString();
+    if (spvtools::utils::starts_with(extension_name, "NonSemantic.") &&
+        extension_name != "NonSemantic.Shader.DebugInfo.100") {
       return false;
     }
   }
@@ -340,23 +338,25 @@
   }
 }
 
+void AggressiveDCEPass::AddDebugScopeToWorkList(const Instruction* inst) {
+  auto scope = inst->GetDebugScope();
+  auto lex_scope_id = scope.GetLexicalScope();
+  if (lex_scope_id != kNoDebugScope)
+    AddToWorklist(get_def_use_mgr()->GetDef(lex_scope_id));
+  auto inlined_at_id = scope.GetInlinedAt();
+  if (inlined_at_id != kNoInlinedAt)
+    AddToWorklist(get_def_use_mgr()->GetDef(inlined_at_id));
+}
+
 void AggressiveDCEPass::AddDebugInstructionsToWorkList(
     const Instruction* inst) {
   for (auto& line_inst : inst->dbg_line_insts()) {
     if (line_inst.IsDebugLineInst()) {
       AddOperandsToWorkList(&line_inst);
     }
+    AddDebugScopeToWorkList(&line_inst);
   }
-
-  if (inst->GetDebugScope().GetLexicalScope() != kNoDebugScope) {
-    auto* scope =
-        get_def_use_mgr()->GetDef(inst->GetDebugScope().GetLexicalScope());
-    AddToWorklist(scope);
-  }
-  if (inst->GetDebugInlinedAt() != kNoInlinedAt) {
-    auto* inlined_at = get_def_use_mgr()->GetDef(inst->GetDebugInlinedAt());
-    AddToWorklist(inlined_at);
-  }
+  AddDebugScopeToWorkList(inst);
 }
 
 void AggressiveDCEPass::AddDecorationsToWorkList(const Instruction* inst) {
@@ -569,12 +569,7 @@
   }
   // Keep all entry points.
   for (auto& entry : get_module()->entry_points()) {
-    if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4) &&
-        !preserve_interface_) {
-      // 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.
+    if (!preserve_interface_) {
       live_insts_.Set(entry.unique_id());
       // The actual function is live always.
       AddToWorklist(
@@ -582,8 +577,9 @@
       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) {
+        // Vulkan support outputs without an associated input, but not inputs
+        // without an associated output.
+        if (storage_class == SpvStorageClassOutput) {
           AddToWorklist(var);
         }
       }
@@ -636,6 +632,16 @@
     auto dbg_none = context()->get_debug_info_mgr()->GetDebugInfoNone();
     AddToWorklist(dbg_none);
   }
+
+  // Add top level DebugInfo to worklist
+  for (auto& dbg : get_module()->ext_inst_debuginfo()) {
+    auto op = dbg.GetShader100DebugOpcode();
+    if (op == NonSemanticShaderDebugInfo100DebugCompilationUnit ||
+        op == NonSemanticShaderDebugInfo100DebugEntryPoint ||
+        op == NonSemanticShaderDebugInfo100DebugSourceContinued) {
+      AddToWorklist(&dbg);
+    }
+  }
 }
 
 Pass::Status AggressiveDCEPass::ProcessImpl() {
@@ -665,9 +671,14 @@
 
   InitializeModuleScopeLiveInstructions();
 
-  // Process all entry point functions.
-  ProcessFunction pfn = [this](Function* fp) { return AggressiveDCE(fp); };
-  modified |= context()->ProcessReachableCallTree(pfn);
+  // Run |AggressiveDCE| on the remaining functions.  The order does not matter,
+  // since |AggressiveDCE| is intra-procedural.  This can mean that function
+  // will become dead if all function call to them are removed.  These dead
+  // function will still be in the module after this pass.  We expect this to be
+  // rare.
+  for (Function& fp : *context()->module()) {
+    modified |= AggressiveDCE(&fp);
+  }
 
   // If the decoration manager is kept live then the context will try to keep it
   // up to date.  ADCE deals with group decorations by changing the operands in
@@ -693,8 +704,9 @@
   }
 
   // Cleanup all CFG including all unreachable blocks.
-  ProcessFunction cleanup = [this](Function* f) { return CFGCleanup(f); };
-  modified |= context()->ProcessReachableCallTree(cleanup);
+  for (Function& fp : *context()->module()) {
+    modified |= CFGCleanup(&fp);
+  }
 
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
@@ -885,8 +897,7 @@
     }
   }
 
-  if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4) &&
-      !preserve_interface_) {
+  if (!preserve_interface_) {
     // Remove the dead interface variables from the entry point interface list.
     for (auto& entry : get_module()->entry_points()) {
       std::vector<Operand> new_operands;
@@ -974,6 +985,8 @@
       "SPV_KHR_integer_dot_product",
       "SPV_EXT_shader_image_int64",
       "SPV_KHR_non_semantic_info",
+      "SPV_KHR_uniform_group_instructions",
+      "SPV_KHR_fragment_shader_barycentric",
   });
 }
 
diff --git a/source/opt/aggressive_dead_code_elim_pass.h b/source/opt/aggressive_dead_code_elim_pass.h
index 1b3fd1e..c1291dc 100644
--- a/source/opt/aggressive_dead_code_elim_pass.h
+++ b/source/opt/aggressive_dead_code_elim_pass.h
@@ -178,6 +178,9 @@
   // Adds all decorations of |inst| to the work list.
   void AddDecorationsToWorkList(const Instruction* inst);
 
+  // Adds DebugScope instruction associated with |inst| to the work list.
+  void AddDebugScopeToWorkList(const Instruction* inst);
+
   // Adds all debug instruction associated with |inst| to the work list.
   void AddDebugInstructionsToWorkList(const Instruction* inst);
 
diff --git a/source/opt/amd_ext_to_khr.cpp b/source/opt/amd_ext_to_khr.cpp
index ccedc0b..dd9bafd 100644
--- a/source/opt/amd_ext_to_khr.cpp
+++ b/source/opt/amd_ext_to_khr.cpp
@@ -584,9 +584,9 @@
   }
 
   // Get the constants that will be used.
-  uint32_t f0_const_id = const_mgr->GetFloatConst(0.0);
-  uint32_t f2_const_id = const_mgr->GetFloatConst(2.0);
-  uint32_t f0_5_const_id = const_mgr->GetFloatConst(0.5);
+  uint32_t f0_const_id = const_mgr->GetFloatConstId(0.0);
+  uint32_t f2_const_id = const_mgr->GetFloatConstId(2.0);
+  uint32_t f0_5_const_id = const_mgr->GetFloatConstId(0.5);
   const analysis::Constant* vec_const =
       const_mgr->GetConstant(v2_float_type, {f0_5_const_id, f0_5_const_id});
   uint32_t vec_const_id =
@@ -731,12 +731,12 @@
   }
 
   // Get the constants that will be used.
-  uint32_t f0_const_id = const_mgr->GetFloatConst(0.0);
-  uint32_t f1_const_id = const_mgr->GetFloatConst(1.0);
-  uint32_t f2_const_id = const_mgr->GetFloatConst(2.0);
-  uint32_t f3_const_id = const_mgr->GetFloatConst(3.0);
-  uint32_t f4_const_id = const_mgr->GetFloatConst(4.0);
-  uint32_t f5_const_id = const_mgr->GetFloatConst(5.0);
+  uint32_t f0_const_id = const_mgr->GetFloatConstId(0.0);
+  uint32_t f1_const_id = const_mgr->GetFloatConstId(1.0);
+  uint32_t f2_const_id = const_mgr->GetFloatConstId(2.0);
+  uint32_t f3_const_id = const_mgr->GetFloatConstId(3.0);
+  uint32_t f4_const_id = const_mgr->GetFloatConstId(4.0);
+  uint32_t f5_const_id = const_mgr->GetFloatConstId(5.0);
 
   // Extract the input values.
   Instruction* x = ir_builder.AddCompositeExtract(float_type_id, input_id, {0});
@@ -935,8 +935,7 @@
   std::vector<Instruction*> to_be_killed;
   for (Instruction& inst : context()->module()->extensions()) {
     if (inst.opcode() == SpvOpExtension) {
-      if (ext_to_remove.count(reinterpret_cast<const char*>(
-              &(inst.GetInOperand(0).words[0]))) != 0) {
+      if (ext_to_remove.count(inst.GetInOperand(0).AsString()) != 0) {
         to_be_killed.push_back(&inst);
       }
     }
@@ -944,8 +943,7 @@
 
   for (Instruction& inst : context()->ext_inst_imports()) {
     if (inst.opcode() == SpvOpExtInstImport) {
-      if (ext_to_remove.count(reinterpret_cast<const char*>(
-              &(inst.GetInOperand(0).words[0]))) != 0) {
+      if (ext_to_remove.count(inst.GetInOperand(0).AsString()) != 0) {
         to_be_killed.push_back(&inst);
       }
     }
diff --git a/source/opt/amd_ext_to_khr.h b/source/opt/amd_ext_to_khr.h
index fd3dab4..6a39d95 100644
--- a/source/opt/amd_ext_to_khr.h
+++ b/source/opt/amd_ext_to_khr.h
@@ -23,7 +23,7 @@
 namespace opt {
 
 // Replaces the extensions VK_AMD_shader_ballot, VK_AMD_gcn_shader, and
-// VK_AMD_shader_trinary_minmax with equivalant code using core instructions and
+// VK_AMD_shader_trinary_minmax with equivalent code using core instructions and
 // capabilities.
 class AmdExtensionToKhrPass : public Pass {
  public:
diff --git a/source/opt/basic_block.h b/source/opt/basic_block.h
index 6741a50..dd3b2e2 100644
--- a/source/opt/basic_block.h
+++ b/source/opt/basic_block.h
@@ -83,7 +83,7 @@
   const Instruction* GetMergeInst() const;
   Instruction* GetMergeInst();
 
-  // Returns the OpLoopMerge instruciton in this basic block, if it exists.
+  // Returns the OpLoopMerge instruction in this basic block, if it exists.
   // Otherwise return null.  May be used whenever tail() can be used.
   const Instruction* GetLoopMergeInst() const;
   Instruction* GetLoopMergeInst();
diff --git a/source/opt/ccp_pass.cpp b/source/opt/ccp_pass.cpp
index 8b896d5..5f85502 100644
--- a/source/opt/ccp_pass.cpp
+++ b/source/opt/ccp_pass.cpp
@@ -102,6 +102,34 @@
   return SSAPropagator::kInteresting;
 }
 
+uint32_t CCPPass::ComputeLatticeMeet(Instruction* instr, uint32_t val2) {
+  // Given two values val1 and val2, the meet operation in the constant
+  // lattice uses the following rules:
+  //
+  // meet(val1, UNDEFINED) = val1
+  // meet(val1, VARYING)   = VARYING
+  // meet(val1, val2)      = val1     if val1 == val2
+  // meet(val1, val2)      = VARYING  if val1 != val2
+  //
+  // When two different values meet, the result is always varying because CCP
+  // does not allow lateral transitions in the lattice.  This prevents
+  // infinite cycles during propagation.
+  auto val1_it = values_.find(instr->result_id());
+  if (val1_it == values_.end()) {
+    return val2;
+  }
+
+  uint32_t val1 = val1_it->second;
+  if (IsVaryingValue(val1)) {
+    return val1;
+  } else if (IsVaryingValue(val2)) {
+    return val2;
+  } else if (val1 != val2) {
+    return kVaryingSSAId;
+  }
+  return val2;
+}
+
 SSAPropagator::PropStatus CCPPass::VisitAssignment(Instruction* instr) {
   assert(instr->result_id() != 0 &&
          "Expecting an instruction that produces a result");
@@ -115,8 +143,10 @@
       if (IsVaryingValue(it->second)) {
         return MarkInstructionVarying(instr);
       } else {
-        values_[instr->result_id()] = it->second;
-        return SSAPropagator::kInteresting;
+        uint32_t new_val = ComputeLatticeMeet(instr, it->second);
+        values_[instr->result_id()] = new_val;
+        return IsVaryingValue(new_val) ? SSAPropagator::kVarying
+                                       : SSAPropagator::kInteresting;
       }
     }
     return SSAPropagator::kNotInteresting;
@@ -142,9 +172,13 @@
   if (folded_inst != nullptr) {
     // We do not want to change the body of the function by adding new
     // instructions.  When folding we can only generate new constants.
-    assert(folded_inst->IsConstant() && "CCP is only interested in constant.");
-    values_[instr->result_id()] = folded_inst->result_id();
-    return SSAPropagator::kInteresting;
+    assert((folded_inst->IsConstant() ||
+            IsSpecConstantInst(folded_inst->opcode())) &&
+           "CCP is only interested in constant values.");
+    uint32_t new_val = ComputeLatticeMeet(instr, folded_inst->result_id());
+    values_[instr->result_id()] = new_val;
+    return IsVaryingValue(new_val) ? SSAPropagator::kVarying
+                                   : SSAPropagator::kInteresting;
   }
 
   // Conservatively mark this instruction as varying if any input id is varying.
diff --git a/source/opt/ccp_pass.h b/source/opt/ccp_pass.h
index fb20c78..77ea9f8 100644
--- a/source/opt/ccp_pass.h
+++ b/source/opt/ccp_pass.h
@@ -92,6 +92,22 @@
   // generated during propagation.
   analysis::ConstantManager* const_mgr_;
 
+  // Returns a new value for |instr| by computing the meet operation between
+  // its existing value and |val2|.
+  //
+  // Given two values val1 and val2, the meet operation in the constant
+  // lattice uses the following rules:
+  //
+  // meet(val1, UNDEFINED) = val1
+  // meet(val1, VARYING)   = VARYING
+  // meet(val1, val2)      = val1     if val1 == val2
+  // meet(val1, val2)      = VARYING  if val1 != val2
+  //
+  // When two different values meet, the result is always varying because CCP
+  // does not allow lateral transitions in the lattice.  This prevents
+  // infinite cycles during propagation.
+  uint32_t ComputeLatticeMeet(Instruction* instr, uint32_t val2);
+
   // Constant value table.  Each entry <id, const_decl_id> in this map
   // represents the compile-time constant value for |id| as declared by
   // |const_decl_id|. Each |const_decl_id| in this table is an OpConstant
diff --git a/source/opt/cfg.cpp b/source/opt/cfg.cpp
index ac0fcc3..a0248d5 100644
--- a/source/opt/cfg.cpp
+++ b/source/opt/cfg.cpp
@@ -74,6 +74,12 @@
 
 void CFG::ComputeStructuredOrder(Function* func, BasicBlock* root,
                                  std::list<BasicBlock*>* order) {
+  ComputeStructuredOrder(func, root, nullptr, order);
+}
+
+void CFG::ComputeStructuredOrder(Function* func, BasicBlock* root,
+                                 BasicBlock* end,
+                                 std::list<BasicBlock*>* order) {
   assert(module_->context()->get_feature_mgr()->HasCapability(
              SpvCapabilityShader) &&
          "This only works on structured control flow");
@@ -81,7 +87,8 @@
   // Compute structured successors and do DFS.
   ComputeStructuredSuccessors(func);
   auto ignore_block = [](cbb_ptr) {};
-  auto ignore_edge = [](cbb_ptr, cbb_ptr) {};
+  auto terminal = [end](cbb_ptr bb) { return bb == end; };
+
   auto get_structured_successors = [this](const BasicBlock* b) {
     return &(block2structured_succs_[b]);
   };
@@ -92,7 +99,7 @@
     order->push_front(const_cast<BasicBlock*>(b));
   };
   CFA<BasicBlock>::DepthFirstTraversal(root, get_structured_successors,
-                                       ignore_block, post_order, ignore_edge);
+                                       ignore_block, post_order, terminal);
 }
 
 void CFG::ForEachBlockInPostOrder(BasicBlock* bb,
@@ -205,7 +212,7 @@
   // Find the back edge
   BasicBlock* latch_block = nullptr;
   Function::iterator latch_block_iter = header_it;
-  while (++latch_block_iter != fn->end()) {
+  for (; latch_block_iter != fn->end(); ++latch_block_iter) {
     // If blocks are in the proper order, then the only branch that appears
     // after the header is the latch.
     if (std::find(pred.begin(), pred.end(), latch_block_iter->id()) !=
@@ -237,6 +244,15 @@
     context->set_instr_block(inst, new_header);
   });
 
+  // If |bb| was the latch block, the branch back to the header is not in
+  // |new_header|.
+  if (latch_block == bb) {
+    if (new_header->ContinueBlockId() == bb->id()) {
+      new_header->GetLoopMergeInst()->SetInOperand(1, {new_header_id});
+    }
+    latch_block = new_header;
+  }
+
   // Adjust the OpPhi instructions as needed.
   bb->ForEachPhiInst([latch_block, bb, new_header, context](Instruction* phi) {
     std::vector<uint32_t> preheader_phi_ops;
diff --git a/source/opt/cfg.h b/source/opt/cfg.h
index f280682..fa4fef2 100644
--- a/source/opt/cfg.h
+++ b/source/opt/cfg.h
@@ -30,7 +30,7 @@
  public:
   explicit CFG(Module* module);
 
-  // Return the list of predecesors for basic block with label |blkid|.
+  // Return the list of predecessors for basic block with label |blkid|.
   // TODO(dnovillo): Move this to BasicBlock.
   const std::vector<uint32_t>& preds(uint32_t blk_id) const {
     assert(label2preds_.count(blk_id));
@@ -66,6 +66,14 @@
   void ComputeStructuredOrder(Function* func, BasicBlock* root,
                               std::list<BasicBlock*>* order);
 
+  // Compute structured block order into |order| for |func| starting at |root|
+  // and ending at |end|. This order has the property that dominators come
+  // before all blocks they dominate, merge blocks come after all blocks that
+  // are in the control constructs of their header, and continue blocks come
+  // after all the blocks in the body of their loop.
+  void ComputeStructuredOrder(Function* func, BasicBlock* root, BasicBlock* end,
+                              std::list<BasicBlock*>* order);
+
   // Applies |f| to all blocks that can be reach from |bb| in post order.
   void ForEachBlockInPostOrder(BasicBlock* bb,
                                const std::function<void(BasicBlock*)>& f);
diff --git a/source/opt/compact_ids_pass.cpp b/source/opt/compact_ids_pass.cpp
index 8815b8c..5a2a54b 100644
--- a/source/opt/compact_ids_pass.cpp
+++ b/source/opt/compact_ids_pass.cpp
@@ -44,6 +44,11 @@
   bool modified = false;
   std::unordered_map<uint32_t, uint32_t> result_id_mapping;
 
+  // Disable automatic DebugInfo analysis for the life of the CompactIds pass.
+  // The DebugInfo manager requires the SPIR-V to be valid to run, but this is
+  // not true at all times in CompactIds as it remaps all ids.
+  context()->InvalidateAnalyses(IRContext::kAnalysisDebugInfo);
+
   context()->module()->ForEachInst(
       [&result_id_mapping, &modified](Instruction* inst) {
         auto operand = inst->begin();
@@ -86,7 +91,8 @@
       },
       true);
 
-  if (modified) {
+  if (context()->module()->id_bound() != result_id_mapping.size() + 1) {
+    modified = true;
     context()->module()->SetIdBound(
         static_cast<uint32_t>(result_id_mapping.size() + 1));
     // There are ids in the feature manager that could now be invalid
diff --git a/source/opt/const_folding_rules.cpp b/source/opt/const_folding_rules.cpp
index 515a3ed..0ad755c 100644
--- a/source/opt/const_folding_rules.cpp
+++ b/source/opt/const_folding_rules.cpp
@@ -22,6 +22,45 @@
 
 const uint32_t kExtractCompositeIdInIdx = 0;
 
+// Returns a constants with the value NaN of the given type.  Only works for
+// 32-bit and 64-bit float point types.  Returns |nullptr| if an error occurs.
+const analysis::Constant* GetNan(const analysis::Type* type,
+                                 analysis::ConstantManager* const_mgr) {
+  const analysis::Float* float_type = type->AsFloat();
+  if (float_type == nullptr) {
+    return nullptr;
+  }
+
+  switch (float_type->width()) {
+    case 32:
+      return const_mgr->GetFloatConst(std::numeric_limits<float>::quiet_NaN());
+    case 64:
+      return const_mgr->GetDoubleConst(
+          std::numeric_limits<double>::quiet_NaN());
+    default:
+      return nullptr;
+  }
+}
+
+// Returns a constants with the value INF of the given type.  Only works for
+// 32-bit and 64-bit float point types.  Returns |nullptr| if an error occurs.
+const analysis::Constant* GetInf(const analysis::Type* type,
+                                 analysis::ConstantManager* const_mgr) {
+  const analysis::Float* float_type = type->AsFloat();
+  if (float_type == nullptr) {
+    return nullptr;
+  }
+
+  switch (float_type->width()) {
+    case 32:
+      return const_mgr->GetFloatConst(std::numeric_limits<float>::infinity());
+    case 64:
+      return const_mgr->GetDoubleConst(std::numeric_limits<double>::infinity());
+    default:
+      return nullptr;
+  }
+}
+
 // Returns true if |type| is Float or a vector of Float.
 bool HasFloatingPoint(const analysis::Type* type) {
   if (type->AsFloat()) {
@@ -33,6 +72,23 @@
   return false;
 }
 
+// Returns a constants with the value |-val| of the given type.  Only works for
+// 32-bit and 64-bit float point types.  Returns |nullptr| if an error occurs.
+const analysis::Constant* NegateFPConst(const analysis::Type* result_type,
+                                        const analysis::Constant* val,
+                                        analysis::ConstantManager* const_mgr) {
+  const analysis::Float* float_type = result_type->AsFloat();
+  assert(float_type != nullptr);
+  if (float_type->width() == 32) {
+    float fa = val->GetFloat();
+    return const_mgr->GetFloatConst(-fa);
+  } else if (float_type->width() == 64) {
+    double da = val->GetDouble();
+    return const_mgr->GetDoubleConst(-da);
+  }
+  return nullptr;
+}
+
 // Folds an OpcompositeExtract where input is a composite constant.
 ConstantFoldingRule FoldExtractWithConstants() {
   return [](IRContext* context, Instruction* inst,
@@ -195,6 +251,193 @@
   };
 }
 
+ConstantFoldingRule FoldVectorTimesMatrix() {
+  return [](IRContext* context, Instruction* inst,
+            const std::vector<const analysis::Constant*>& constants)
+             -> const analysis::Constant* {
+    assert(inst->opcode() == SpvOpVectorTimesMatrix);
+    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
+    analysis::TypeManager* type_mgr = context->get_type_mgr();
+
+    if (!inst->IsFloatingPointFoldingAllowed()) {
+      if (HasFloatingPoint(type_mgr->GetType(inst->type_id()))) {
+        return nullptr;
+      }
+    }
+
+    const analysis::Constant* c1 = constants[0];
+    const analysis::Constant* c2 = constants[1];
+
+    if (c1 == nullptr || c2 == nullptr) {
+      return nullptr;
+    }
+
+    // Check result type.
+    const analysis::Type* result_type = type_mgr->GetType(inst->type_id());
+    const analysis::Vector* vector_type = result_type->AsVector();
+    assert(vector_type != nullptr);
+    const analysis::Type* element_type = vector_type->element_type();
+    assert(element_type != nullptr);
+    const analysis::Float* float_type = element_type->AsFloat();
+    assert(float_type != nullptr);
+
+    // Check types of c1 and c2.
+    assert(c1->type()->AsVector() == vector_type);
+    assert(c1->type()->AsVector()->element_type() == element_type &&
+           c2->type()->AsMatrix()->element_type() == vector_type);
+
+    // Get a float vector that is the result of vector-times-matrix.
+    std::vector<const analysis::Constant*> c1_components =
+        c1->GetVectorComponents(const_mgr);
+    std::vector<const analysis::Constant*> c2_components =
+        c2->AsMatrixConstant()->GetComponents();
+    uint32_t resultVectorSize = result_type->AsVector()->element_count();
+
+    std::vector<uint32_t> ids;
+
+    if ((c1 && c1->IsZero()) || (c2 && c2->IsZero())) {
+      std::vector<uint32_t> words(float_type->width() / 32, 0);
+      for (uint32_t i = 0; i < resultVectorSize; ++i) {
+        const analysis::Constant* new_elem =
+            const_mgr->GetConstant(float_type, words);
+        ids.push_back(const_mgr->GetDefiningInstruction(new_elem)->result_id());
+      }
+      return const_mgr->GetConstant(vector_type, ids);
+    }
+
+    if (float_type->width() == 32) {
+      for (uint32_t i = 0; i < resultVectorSize; ++i) {
+        float result_scalar = 0.0f;
+        const analysis::VectorConstant* c2_vec =
+            c2_components[i]->AsVectorConstant();
+        for (uint32_t j = 0; j < c2_vec->GetComponents().size(); ++j) {
+          float c1_scalar = c1_components[j]->GetFloat();
+          float c2_scalar = c2_vec->GetComponents()[j]->GetFloat();
+          result_scalar += c1_scalar * c2_scalar;
+        }
+        utils::FloatProxy<float> result(result_scalar);
+        std::vector<uint32_t> words = result.GetWords();
+        const analysis::Constant* new_elem =
+            const_mgr->GetConstant(float_type, words);
+        ids.push_back(const_mgr->GetDefiningInstruction(new_elem)->result_id());
+      }
+      return const_mgr->GetConstant(vector_type, ids);
+    } else if (float_type->width() == 64) {
+      for (uint32_t i = 0; i < c2_components.size(); ++i) {
+        double result_scalar = 0.0;
+        const analysis::VectorConstant* c2_vec =
+            c2_components[i]->AsVectorConstant();
+        for (uint32_t j = 0; j < c2_vec->GetComponents().size(); ++j) {
+          double c1_scalar = c1_components[j]->GetDouble();
+          double c2_scalar = c2_vec->GetComponents()[j]->GetDouble();
+          result_scalar += c1_scalar * c2_scalar;
+        }
+        utils::FloatProxy<double> result(result_scalar);
+        std::vector<uint32_t> words = result.GetWords();
+        const analysis::Constant* new_elem =
+            const_mgr->GetConstant(float_type, words);
+        ids.push_back(const_mgr->GetDefiningInstruction(new_elem)->result_id());
+      }
+      return const_mgr->GetConstant(vector_type, ids);
+    }
+    return nullptr;
+  };
+}
+
+ConstantFoldingRule FoldMatrixTimesVector() {
+  return [](IRContext* context, Instruction* inst,
+            const std::vector<const analysis::Constant*>& constants)
+             -> const analysis::Constant* {
+    assert(inst->opcode() == SpvOpMatrixTimesVector);
+    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
+    analysis::TypeManager* type_mgr = context->get_type_mgr();
+
+    if (!inst->IsFloatingPointFoldingAllowed()) {
+      if (HasFloatingPoint(type_mgr->GetType(inst->type_id()))) {
+        return nullptr;
+      }
+    }
+
+    const analysis::Constant* c1 = constants[0];
+    const analysis::Constant* c2 = constants[1];
+
+    if (c1 == nullptr || c2 == nullptr) {
+      return nullptr;
+    }
+
+    // Check result type.
+    const analysis::Type* result_type = type_mgr->GetType(inst->type_id());
+    const analysis::Vector* vector_type = result_type->AsVector();
+    assert(vector_type != nullptr);
+    const analysis::Type* element_type = vector_type->element_type();
+    assert(element_type != nullptr);
+    const analysis::Float* float_type = element_type->AsFloat();
+    assert(float_type != nullptr);
+
+    // Check types of c1 and c2.
+    assert(c1->type()->AsMatrix()->element_type() == vector_type);
+    assert(c2->type()->AsVector()->element_type() == element_type);
+
+    // Get a float vector that is the result of matrix-times-vector.
+    std::vector<const analysis::Constant*> c1_components =
+        c1->AsMatrixConstant()->GetComponents();
+    std::vector<const analysis::Constant*> c2_components =
+        c2->GetVectorComponents(const_mgr);
+    uint32_t resultVectorSize = result_type->AsVector()->element_count();
+
+    std::vector<uint32_t> ids;
+
+    if ((c1 && c1->IsZero()) || (c2 && c2->IsZero())) {
+      std::vector<uint32_t> words(float_type->width() / 32, 0);
+      for (uint32_t i = 0; i < resultVectorSize; ++i) {
+        const analysis::Constant* new_elem =
+            const_mgr->GetConstant(float_type, words);
+        ids.push_back(const_mgr->GetDefiningInstruction(new_elem)->result_id());
+      }
+      return const_mgr->GetConstant(vector_type, ids);
+    }
+
+    if (float_type->width() == 32) {
+      for (uint32_t i = 0; i < resultVectorSize; ++i) {
+        float result_scalar = 0.0f;
+        for (uint32_t j = 0; j < c1_components.size(); ++j) {
+          float c1_scalar = c1_components[j]
+                                ->AsVectorConstant()
+                                ->GetComponents()[i]
+                                ->GetFloat();
+          float c2_scalar = c2_components[j]->GetFloat();
+          result_scalar += c1_scalar * c2_scalar;
+        }
+        utils::FloatProxy<float> result(result_scalar);
+        std::vector<uint32_t> words = result.GetWords();
+        const analysis::Constant* new_elem =
+            const_mgr->GetConstant(float_type, words);
+        ids.push_back(const_mgr->GetDefiningInstruction(new_elem)->result_id());
+      }
+      return const_mgr->GetConstant(vector_type, ids);
+    } else if (float_type->width() == 64) {
+      for (uint32_t i = 0; i < resultVectorSize; ++i) {
+        double result_scalar = 0.0;
+        for (uint32_t j = 0; j < c1_components.size(); ++j) {
+          double c1_scalar = c1_components[j]
+                                 ->AsVectorConstant()
+                                 ->GetComponents()[i]
+                                 ->GetDouble();
+          double c2_scalar = c2_components[j]->GetDouble();
+          result_scalar += c1_scalar * c2_scalar;
+        }
+        utils::FloatProxy<double> result(result_scalar);
+        std::vector<uint32_t> words = result.GetWords();
+        const analysis::Constant* new_elem =
+            const_mgr->GetConstant(float_type, words);
+        ids.push_back(const_mgr->GetDefiningInstruction(new_elem)->result_id());
+      }
+      return const_mgr->GetConstant(vector_type, ids);
+    }
+    return nullptr;
+  };
+}
+
 ConstantFoldingRule FoldCompositeWithConstants() {
   // Folds an OpCompositeConstruct where all of the inputs are constants to a
   // constant.  A new constant is created if necessary.
@@ -492,7 +735,60 @@
 ConstantFoldingRule FoldFSub() { return FoldFPBinaryOp(FOLD_FPARITH_OP(-)); }
 ConstantFoldingRule FoldFAdd() { return FoldFPBinaryOp(FOLD_FPARITH_OP(+)); }
 ConstantFoldingRule FoldFMul() { return FoldFPBinaryOp(FOLD_FPARITH_OP(*)); }
-ConstantFoldingRule FoldFDiv() { return FoldFPBinaryOp(FOLD_FPARITH_OP(/)); }
+
+// Returns the constant that results from evaluating |numerator| / 0.0.  Returns
+// |nullptr| if the result could not be evaluated.
+const analysis::Constant* FoldFPScalarDivideByZero(
+    const analysis::Type* result_type, const analysis::Constant* numerator,
+    analysis::ConstantManager* const_mgr) {
+  if (numerator == nullptr) {
+    return nullptr;
+  }
+
+  if (numerator->IsZero()) {
+    return GetNan(result_type, const_mgr);
+  }
+
+  const analysis::Constant* result = GetInf(result_type, const_mgr);
+  if (result == nullptr) {
+    return nullptr;
+  }
+
+  if (numerator->AsFloatConstant()->GetValueAsDouble() < 0.0) {
+    result = NegateFPConst(result_type, result, const_mgr);
+  }
+  return result;
+}
+
+// Returns the result of folding |numerator| / |denominator|.  Returns |nullptr|
+// if it cannot be folded.
+const analysis::Constant* FoldScalarFPDivide(
+    const analysis::Type* result_type, const analysis::Constant* numerator,
+    const analysis::Constant* denominator,
+    analysis::ConstantManager* const_mgr) {
+  if (denominator == nullptr) {
+    return nullptr;
+  }
+
+  if (denominator->IsZero()) {
+    return FoldFPScalarDivideByZero(result_type, numerator, const_mgr);
+  }
+
+  const analysis::FloatConstant* denominator_float =
+      denominator->AsFloatConstant();
+  if (denominator_float && denominator->GetValueAsDouble() == -0.0) {
+    const analysis::Constant* result =
+        FoldFPScalarDivideByZero(result_type, numerator, const_mgr);
+    if (result != nullptr)
+      result = NegateFPConst(result_type, result, const_mgr);
+    return result;
+  } else {
+    return FOLD_FPARITH_OP(/)(result_type, numerator, denominator, const_mgr);
+  }
+}
+
+// Returns the constant folding rule to fold |OpFDiv| with two constants.
+ConstantFoldingRule FoldFDiv() { return FoldFPBinaryOp(FoldScalarFPDivide); }
 
 bool CompareFloatingPoint(bool op_result, bool op_unordered,
                           bool need_ordered) {
@@ -655,20 +951,7 @@
             analysis::ConstantManager* const_mgr) -> const analysis::Constant* {
     assert(result_type != nullptr && a != nullptr);
     assert(result_type == a->type());
-    const analysis::Float* float_type = result_type->AsFloat();
-    assert(float_type != nullptr);
-    if (float_type->width() == 32) {
-      float fa = a->GetFloat();
-      utils::FloatProxy<float> result(-fa);
-      std::vector<uint32_t> words = result.GetWords();
-      return const_mgr->GetConstant(result_type, words);
-    } else if (float_type->width() == 64) {
-      double da = a->GetDouble();
-      utils::FloatProxy<double> result(-da);
-      std::vector<uint32_t> words = result.GetWords();
-      return const_mgr->GetConstant(result_type, words);
-    }
-    return nullptr;
+    return NegateFPConst(result_type, a, const_mgr);
   };
 }
 
@@ -900,17 +1183,6 @@
   };
 }
 
-template <class IntType>
-IntType FoldIClamp(IntType x, IntType min_val, IntType max_val) {
-  if (x < min_val) {
-    x = min_val;
-  }
-  if (x > max_val) {
-    x = max_val;
-  }
-  return x;
-}
-
 const analysis::Constant* FoldMin(const analysis::Type* result_type,
                                   const analysis::Constant* a,
                                   const analysis::Constant* b,
@@ -1192,6 +1464,8 @@
 
   rules_[SpvOpVectorShuffle].push_back(FoldVectorShuffleWithConstants());
   rules_[SpvOpVectorTimesScalar].push_back(FoldVectorTimesScalar());
+  rules_[SpvOpVectorTimesMatrix].push_back(FoldVectorTimesMatrix());
+  rules_[SpvOpMatrixTimesVector].push_back(FoldMatrixTimesVector());
 
   rules_[SpvOpFNegate].push_back(FoldFNegate());
   rules_[SpvOpQuantizeToF16].push_back(FoldQuantizeToF16());
@@ -1250,7 +1524,7 @@
         FoldFPUnaryOp(FoldFTranscendentalUnary(std::log)));
 
 #ifdef __ANDROID__
-    // Android NDK r15c tageting ABI 15 doesn't have full support for C++11
+    // Android NDK r15c targeting ABI 15 doesn't have full support for C++11
     // (no std::exp2/log2). ::exp2 is available from C99 but ::log2 isn't
     // available up until ABI 18 so we use a shim
     auto log2_shim = [](double v) -> double { return log(v) / log(2.0); };
diff --git a/source/opt/constants.cpp b/source/opt/constants.cpp
index 020e248..bcff08c 100644
--- a/source/opt/constants.cpp
+++ b/source/opt/constants.cpp
@@ -158,6 +158,7 @@
 std::vector<const Constant*> ConstantManager::GetOperandConstants(
     const Instruction* inst) const {
   std::vector<const Constant*> constants;
+  constants.reserve(inst->NumInOperands());
   for (uint32_t i = 0; i < inst->NumInOperands(); i++) {
     const Operand* operand = &inst->GetInOperand(i);
     if (operand->type != SPV_OPERAND_TYPE_ID) {
@@ -420,13 +421,30 @@
   return GetConstant(type, element_ids);
 }
 
-uint32_t ConstantManager::GetFloatConst(float val) {
+uint32_t ConstantManager::GetFloatConstId(float val) {
+  const Constant* c = GetFloatConst(val);
+  return GetDefiningInstruction(c)->result_id();
+}
+
+const Constant* ConstantManager::GetFloatConst(float val) {
   Type* float_type = context()->get_type_mgr()->GetFloatType();
   utils::FloatProxy<float> v(val);
   const Constant* c = GetConstant(float_type, v.GetWords());
+  return c;
+}
+
+uint32_t ConstantManager::GetDoubleConstId(double val) {
+  const Constant* c = GetDoubleConst(val);
   return GetDefiningInstruction(c)->result_id();
 }
 
+const Constant* ConstantManager::GetDoubleConst(double val) {
+  Type* float_type = context()->get_type_mgr()->GetDoubleType();
+  utils::FloatProxy<double> v(val);
+  const Constant* c = GetConstant(float_type, v.GetWords());
+  return c;
+}
+
 uint32_t ConstantManager::GetSIntConst(int32_t val) {
   Type* sint_type = context()->get_type_mgr()->GetSIntType();
   const Constant* c = GetConstant(sint_type, {static_cast<uint32_t>(val)});
diff --git a/source/opt/constants.h b/source/opt/constants.h
index 52bd809..588ca3e 100644
--- a/source/opt/constants.h
+++ b/source/opt/constants.h
@@ -163,6 +163,21 @@
     return is_zero;
   }
 
+  uint32_t GetU32BitValue() const {
+    // Relies on unsigned values smaller than 32-bit being zero extended.  See
+    // section 2.2.1 of the SPIR-V spec.
+    assert(words().size() == 1);
+    return words()[0];
+  }
+
+  uint64_t GetU64BitValue() const {
+    // Relies on unsigned values smaller than 64-bit being zero extended.  See
+    // section 2.2.1 of the SPIR-V spec.
+    assert(words().size() == 2);
+    return static_cast<uint64_t>(words()[1]) << 32 |
+           static_cast<uint64_t>(words()[0]);
+  }
+
  protected:
   ScalarConstant(const Type* ty, const std::vector<uint32_t>& w)
       : Constant(ty), words_(w) {}
@@ -189,13 +204,6 @@
     return words()[0];
   }
 
-  uint32_t GetU32BitValue() const {
-    // Relies on unsigned values smaller than 32-bit being zero extended.  See
-    // section 2.2.1 of the SPIR-V spec.
-    assert(words().size() == 1);
-    return words()[0];
-  }
-
   int64_t GetS64BitValue() const {
     // Relies on unsigned values smaller than 64-bit being sign extended.  See
     // section 2.2.1 of the SPIR-V spec.
@@ -204,14 +212,6 @@
            static_cast<uint64_t>(words()[0]);
   }
 
-  uint64_t GetU64BitValue() const {
-    // Relies on unsigned values smaller than 64-bit being zero extended.  See
-    // section 2.2.1 of the SPIR-V spec.
-    assert(words().size() == 2);
-    return static_cast<uint64_t>(words()[1]) << 32 |
-           static_cast<uint64_t>(words()[0]);
-  }
-
   // Make a copy of this IntConstant instance.
   std::unique_ptr<IntConstant> CopyIntConstant() const {
     return MakeUnique<IntConstant>(type_->AsInteger(), words_);
@@ -541,7 +541,7 @@
   // instruction at the end of the current module's types section.
   //
   // |type_id| is an optional argument for disambiguating equivalent types. If
-  // |type_id| is specified, the contant returned will have that type id.
+  // |type_id| is specified, the constant returned will have that type id.
   Instruction* GetDefiningInstruction(const Constant* c, uint32_t type_id = 0,
                                       Module::inst_iterator* pos = nullptr);
 
@@ -637,7 +637,16 @@
   }
 
   // Returns the id of a 32-bit floating point constant with value |val|.
-  uint32_t GetFloatConst(float val);
+  uint32_t GetFloatConstId(float val);
+
+  // Returns a 32-bit float constant with the given value.
+  const Constant* GetFloatConst(float val);
+
+  // Returns the id of a 64-bit floating point constant with value |val|.
+  uint32_t GetDoubleConstId(double val);
+
+  // Returns a 64-bit float constant with the given value.
+  const Constant* GetDoubleConst(double val);
 
   // Returns the id of a 32-bit signed integer constant with value |val|.
   uint32_t GetSIntConst(int32_t val);
diff --git a/source/opt/convert_to_half_pass.cpp b/source/opt/convert_to_half_pass.cpp
index b127eab..4086e31 100644
--- a/source/opt/convert_to_half_pass.cpp
+++ b/source/opt/convert_to_half_pass.cpp
@@ -181,7 +181,7 @@
                                    uint32_t to_width) {
   // Add converts of any float operands to to_width if they are of from_width.
   // If converting to 16, change type of phi to float16 equivalent and remember
-  // result id. Converts need to be added to preceeding blocks.
+  // result id. Converts need to be added to preceding blocks.
   uint32_t ocnt = 0;
   uint32_t* prev_idp;
   bool modified = false;
diff --git a/source/opt/copy_prop_arrays.cpp b/source/opt/copy_prop_arrays.cpp
index 62ed5e7..0b23562 100644
--- a/source/opt/copy_prop_arrays.cpp
+++ b/source/opt/copy_prop_arrays.cpp
@@ -151,9 +151,17 @@
     return source->GetVariable();
   }
 
+  source->BuildConstants();
+  std::vector<uint32_t> access_ids(source->AccessChain().size());
+  std::transform(
+      source->AccessChain().cbegin(), source->AccessChain().cend(),
+      access_ids.begin(), [](const AccessChainEntry& entry) {
+        assert(entry.is_result_id && "Constants needs to be built first.");
+        return entry.result_id;
+      });
+
   return builder.AddAccessChain(source->GetPointerTypeId(this),
-                                source->GetVariable()->result_id(),
-                                source->AccessChain());
+                                source->GetVariable()->result_id(), access_ids);
 }
 
 bool CopyPropagateArrays::HasNoStores(Instruction* ptr_inst) {
@@ -168,6 +176,8 @@
       return false;
     } else if (use->opcode() == SpvOpImageTexelPointer) {
       return true;
+    } else if (use->opcode() == SpvOpEntryPoint) {
+      return true;
     }
     // Some other instruction.  Be conservative.
     return false;
@@ -268,30 +278,20 @@
 CopyPropagateArrays::BuildMemoryObjectFromExtract(Instruction* extract_inst) {
   assert(extract_inst->opcode() == SpvOpCompositeExtract &&
          "Expecting an OpCompositeExtract instruction.");
-  analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
-
   std::unique_ptr<MemoryObject> result = GetSourceObjectIfAny(
       extract_inst->GetSingleWordInOperand(kCompositeExtractObjectInOperand));
 
-  if (result) {
-    analysis::Integer int_type(32, false);
-    const analysis::Type* uint32_type =
-        context()->get_type_mgr()->GetRegisteredType(&int_type);
-
-    std::vector<uint32_t> components;
-    // Convert the indices in the extract instruction to a series of ids that
-    // can be used by the |OpAccessChain| instruction.
-    for (uint32_t i = 1; i < extract_inst->NumInOperands(); ++i) {
-      uint32_t index = extract_inst->GetSingleWordInOperand(i);
-      const analysis::Constant* index_const =
-          const_mgr->GetConstant(uint32_type, {index});
-      components.push_back(
-          const_mgr->GetDefiningInstruction(index_const)->result_id());
-    }
-    result->GetMember(components);
-    return result;
+  if (!result) {
+    return nullptr;
   }
-  return nullptr;
+
+  // Copy the indices of the extract instruction to |OpAccessChain| indices.
+  std::vector<AccessChainEntry> components;
+  for (uint32_t i = 1; i < extract_inst->NumInOperands(); ++i) {
+    components.push_back({false, {extract_inst->GetSingleWordInOperand(i)}});
+  }
+  result->PushIndirection(components);
+  return result;
 }
 
 std::unique_ptr<CopyPropagateArrays::MemoryObject>
@@ -315,19 +315,12 @@
     return nullptr;
   }
 
-  analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
-  const analysis::Constant* last_access =
-      const_mgr->FindDeclaredConstant(memory_object->AccessChain().back());
-  if (!last_access || !last_access->type()->AsInteger()) {
+  AccessChainEntry last_access = memory_object->AccessChain().back();
+  if (!IsAccessChainIndexValidAndEqualTo(last_access, 0)) {
     return nullptr;
   }
 
-  if (last_access->GetU32() != 0) {
-    return nullptr;
-  }
-
-  memory_object->GetParent();
-
+  memory_object->PopIndirection();
   if (memory_object->GetNumberOfMembers() !=
       conststruct_inst->NumInOperands()) {
     return nullptr;
@@ -349,13 +342,8 @@
       return nullptr;
     }
 
-    last_access =
-        const_mgr->FindDeclaredConstant(member_object->AccessChain().back());
-    if (!last_access || !last_access->type()->AsInteger()) {
-      return nullptr;
-    }
-
-    if (last_access->GetU32() != i) {
+    last_access = member_object->AccessChain().back();
+    if (!IsAccessChainIndexValidAndEqualTo(last_access, i)) {
       return nullptr;
     }
   }
@@ -409,17 +397,12 @@
     return nullptr;
   }
 
-  const analysis::Constant* last_access =
-      const_mgr->FindDeclaredConstant(memory_object->AccessChain().back());
-  if (!last_access || !last_access->type()->AsInteger()) {
+  AccessChainEntry last_access = memory_object->AccessChain().back();
+  if (!IsAccessChainIndexValidAndEqualTo(last_access, number_of_elements - 1)) {
     return nullptr;
   }
 
-  if (last_access->GetU32() != number_of_elements - 1) {
-    return nullptr;
-  }
-
-  memory_object->GetParent();
+  memory_object->PopIndirection();
 
   Instruction* current_insert =
       def_use_mgr->GetDef(insert_inst->GetSingleWordInOperand(1));
@@ -456,14 +439,9 @@
       return nullptr;
     }
 
-    const analysis::Constant* current_last_access =
-        const_mgr->FindDeclaredConstant(
-            current_memory_object->AccessChain().back());
-    if (!current_last_access || !current_last_access->type()->AsInteger()) {
-      return nullptr;
-    }
-
-    if (current_last_access->GetU32() != i - 1) {
+    AccessChainEntry current_last_access =
+        current_memory_object->AccessChain().back();
+    if (!IsAccessChainIndexValidAndEqualTo(current_last_access, i - 1)) {
       return nullptr;
     }
     current_insert =
@@ -473,6 +451,21 @@
   return memory_object;
 }
 
+bool CopyPropagateArrays::IsAccessChainIndexValidAndEqualTo(
+    const AccessChainEntry& entry, uint32_t value) const {
+  if (!entry.is_result_id) {
+    return entry.immediate == value;
+  }
+
+  analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
+  const analysis::Constant* constant =
+      const_mgr->FindDeclaredConstant(entry.result_id);
+  if (!constant || !constant->type()->AsInteger()) {
+    return false;
+  }
+  return constant->GetU32() == value;
+}
+
 bool CopyPropagateArrays::IsPointerToArrayType(uint32_t type_id) {
   analysis::TypeManager* type_mgr = context()->get_type_mgr();
   analysis::Pointer* pointer_type = type_mgr->GetType(type_id)->AsPointer();
@@ -530,6 +523,12 @@
             // Variable index means the type is a type where every element
             // is the same type.  Use element 0 to get the type.
             access_chain.push_back(0);
+
+            // We are trying to access a struct with variable indices.
+            // This cannot happen.
+            if (pointee_type->kind() == analysis::Type::kStruct) {
+              return false;
+            }
           }
         }
 
@@ -745,11 +744,11 @@
           context()->AnalyzeUses(use);
         }
         break;
+      case SpvOpDecorate:
+      // We treat an OpImageTexelPointer as a load.  The result type should
+      // always have the Image storage class, and should not need to be
+      // updated.
       case SpvOpImageTexelPointer:
-        // We treat an OpImageTexelPointer as a load.  The result type should
-        // always have the Image storage class, and should not need to be
-        // updated.
-
         // Replace the actual use.
         context()->ForgetUses(use);
         use->SetOperand(index, {new_ptr_inst->result_id()});
@@ -785,8 +784,8 @@
   return id;
 }
 
-void CopyPropagateArrays::MemoryObject::GetMember(
-    const std::vector<uint32_t>& access_chain) {
+void CopyPropagateArrays::MemoryObject::PushIndirection(
+    const std::vector<AccessChainEntry>& access_chain) {
   access_chain_.insert(access_chain_.end(), access_chain.begin(),
                        access_chain.end());
 }
@@ -821,23 +820,29 @@
 template <class iterator>
 CopyPropagateArrays::MemoryObject::MemoryObject(Instruction* var_inst,
                                                 iterator begin, iterator end)
-    : variable_inst_(var_inst), access_chain_(begin, end) {}
+    : variable_inst_(var_inst) {
+  std::transform(begin, end, std::back_inserter(access_chain_),
+                 [](uint32_t id) {
+                   return AccessChainEntry{true, {id}};
+                 });
+}
 
 std::vector<uint32_t> CopyPropagateArrays::MemoryObject::GetAccessIds() const {
   analysis::ConstantManager* const_mgr =
       variable_inst_->context()->get_constant_mgr();
 
-  std::vector<uint32_t> access_indices;
-  for (uint32_t id : AccessChain()) {
-    const analysis::Constant* element_index_const =
-        const_mgr->FindDeclaredConstant(id);
-    if (!element_index_const) {
-      access_indices.push_back(0);
-    } else {
-      access_indices.push_back(element_index_const->GetU32());
-    }
-  }
-  return access_indices;
+  std::vector<uint32_t> indices(AccessChain().size());
+  std::transform(AccessChain().cbegin(), AccessChain().cend(), indices.begin(),
+                 [&const_mgr](const AccessChainEntry& entry) {
+                   if (entry.is_result_id) {
+                     const analysis::Constant* constant =
+                         const_mgr->FindDeclaredConstant(entry.result_id);
+                     return constant == nullptr ? 0 : constant->GetU32();
+                   }
+
+                   return entry.immediate;
+                 });
+  return indices;
 }
 
 bool CopyPropagateArrays::MemoryObject::Contains(
@@ -858,5 +863,24 @@
   return true;
 }
 
+void CopyPropagateArrays::MemoryObject::BuildConstants() {
+  for (auto& entry : access_chain_) {
+    if (entry.is_result_id) {
+      continue;
+    }
+
+    auto context = variable_inst_->context();
+    analysis::Integer int_type(32, false);
+    const analysis::Type* uint32_type =
+        context->get_type_mgr()->GetRegisteredType(&int_type);
+    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
+    const analysis::Constant* index_const =
+        const_mgr->GetConstant(uint32_type, {entry.immediate});
+    entry.result_id =
+        const_mgr->GetDefiningInstruction(index_const)->result_id();
+    entry.is_result_id = true;
+  }
+}
+
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/copy_prop_arrays.h b/source/opt/copy_prop_arrays.h
index f4314a7..9e7641f 100644
--- a/source/opt/copy_prop_arrays.h
+++ b/source/opt/copy_prop_arrays.h
@@ -35,7 +35,7 @@
 //
 // The hard part is keeping all of the types correct.  We do not want to
 // have to do too large a search to update everything, which may not be
-// possible, do we give up if we see any instruction that might be hard to
+// possible, so we give up if we see any instruction that might be hard to
 // update.
 
 class CopyPropagateArrays : public MemPass {
@@ -52,6 +52,22 @@
   }
 
  private:
+  // Represents one index in the OpAccessChain instruction. It can be either
+  // an instruction's result_id (OpConstant by ex), or a immediate value.
+  // Immediate values are used to prepare the final access chain without
+  // creating OpConstant instructions until done.
+  struct AccessChainEntry {
+    bool is_result_id;
+    union {
+      uint32_t result_id;
+      uint32_t immediate;
+    };
+
+    bool operator!=(const AccessChainEntry& other) const {
+      return other.is_result_id != is_result_id || other.result_id != result_id;
+    }
+  };
+
   // The class used to identify a particular memory object.  This memory object
   // will be owned by a particular variable, meaning that the memory is part of
   // that variable.  It could be the entire variable or a member of the
@@ -70,12 +86,12 @@
     // (starting from the current member).  The elements in |access_chain| are
     // interpreted the same as the indices in the |OpAccessChain|
     // instruction.
-    void GetMember(const std::vector<uint32_t>& access_chain);
+    void PushIndirection(const std::vector<AccessChainEntry>& access_chain);
 
     // Change |this| to now represent the first enclosing object to which it
     // belongs.  (Remove the last element off the access_chain). It is invalid
     // to call this function if |this| does not represent a member of its owner.
-    void GetParent() {
+    void PopIndirection() {
       assert(IsMember());
       access_chain_.pop_back();
     }
@@ -95,7 +111,13 @@
     // member that |this| represents starting from the owning variable.  These
     // values are to be interpreted the same way the indices are in an
     // |OpAccessChain| instruction.
-    const std::vector<uint32_t>& AccessChain() const { return access_chain_; }
+    const std::vector<AccessChainEntry>& AccessChain() const {
+      return access_chain_;
+    }
+
+    // Converts all immediate values in the AccessChain their OpConstant
+    // equivalent.
+    void BuildConstants();
 
     // Returns the type id of the pointer type that can be used to point to this
     // memory object.
@@ -137,7 +159,7 @@
     // The access chain to reach the particular member the memory object
     // represents.  It should be interpreted the same way the indices in an
     // |OpAccessChain| are interpreted.
-    std::vector<uint32_t> access_chain_;
+    std::vector<AccessChainEntry> access_chain_;
     std::vector<uint32_t> GetAccessIds() const;
   };
 
@@ -192,10 +214,14 @@
   std::unique_ptr<MemoryObject> BuildMemoryObjectFromInsert(
       Instruction* insert_inst);
 
+  // Return true if the given entry can represent the given value.
+  bool IsAccessChainIndexValidAndEqualTo(const AccessChainEntry& entry,
+                                         uint32_t value) const;
+
   // Return true if |type_id| is a pointer type whose pointee type is an array.
   bool IsPointerToArrayType(uint32_t type_id);
 
-  // Returns true of there are not stores using |ptr_inst| or something derived
+  // Returns true if there are not stores using |ptr_inst| or something derived
   // from it.
   bool HasNoStores(Instruction* ptr_inst);
 
diff --git a/source/opt/dead_branch_elim_pass.cpp b/source/opt/dead_branch_elim_pass.cpp
index cc616ca..d99b7f7 100644
--- a/source/opt/dead_branch_elim_pass.cpp
+++ b/source/opt/dead_branch_elim_pass.cpp
@@ -459,17 +459,8 @@
   };
 
   // Reorders blocks according to structured order.
-  ProcessFunction reorder_structured = [this](Function* function) {
-    std::list<BasicBlock*> order;
-    context()->cfg()->ComputeStructuredOrder(function, &*function->begin(),
-                                             &order);
-    std::vector<BasicBlock*> blocks;
-    for (auto block : order) {
-      blocks.push_back(block);
-    }
-    for (uint32_t i = 1; i < blocks.size(); ++i) {
-      function->MoveBasicBlockToAfter(blocks[i]->id(), blocks[i - 1]);
-    }
+  ProcessFunction reorder_structured = [](Function* function) {
+    function->ReorderBasicBlocksInStructuredOrder();
     return true;
   };
 
diff --git a/source/opt/dead_branch_elim_pass.h b/source/opt/dead_branch_elim_pass.h
index 7841bc4..198bad2 100644
--- a/source/opt/dead_branch_elim_pass.h
+++ b/source/opt/dead_branch_elim_pass.h
@@ -98,7 +98,7 @@
   // Fix phis in reachable blocks so that only live (or unremovable) incoming
   // edges are present. If the block now only has a single live incoming edge,
   // remove the phi and replace its uses with its data input. If the single
-  // remaining incoming edge is from the phi itself, the the phi is in an
+  // remaining incoming edge is from the phi itself, the phi is in an
   // unreachable single block loop. Either the block is dead and will be
   // removed, or it's reachable from an unreachable continue target. In the
   // latter case that continue target block will be collapsed into a block that
@@ -158,7 +158,7 @@
       uint32_t cont_id, uint32_t header_id, uint32_t merge_id,
       std::unordered_set<BasicBlock*>* blocks_with_back_edges);
 
-  // Returns true if there is a brach to the merge node of the selection
+  // Returns true if there is a branch to the merge node of the selection
   // construct |switch_header_id| that is inside a nested selection construct or
   // in the header of the nested selection construct.
   bool SwitchHasNestedBreak(uint32_t switch_header_id);
diff --git a/source/opt/debug_info_manager.cpp b/source/opt/debug_info_manager.cpp
index 060e0d9..3585186 100644
--- a/source/opt/debug_info_manager.cpp
+++ b/source/opt/debug_info_manager.cpp
@@ -1,4 +1,5 @@
-// Copyright (c) 2020 Google LLC
+// Copyright (c) 2020-2022 Google LLC
+// Copyright (c) 2022 LunarG Inc.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -24,6 +25,7 @@
 static const uint32_t kOpLineOperandLineIndex = 1;
 static const uint32_t kLineOperandIndexDebugFunction = 7;
 static const uint32_t kLineOperandIndexDebugLexicalBlock = 5;
+static const uint32_t kLineOperandIndexDebugLine = 5;
 static const uint32_t kDebugFunctionOperandFunctionIndex = 13;
 static const uint32_t kDebugFunctionDefinitionOperandDebugFunctionIndex = 4;
 static const uint32_t kDebugFunctionDefinitionOperandOpFunctionIndex = 5;
@@ -149,7 +151,7 @@
 // Create new constant directly into global value area, bypassing the
 // Constant manager. This is used when the DefUse or Constant managers
 // are invalid and cannot be regenerated due to the module being in an
-// inconsistant state e.g. in the middle of significant modification
+// inconsistent state e.g. in the middle of significant modification
 // such as inlining. Invalidate Constant and DefUse managers if used.
 uint32_t AddNewConstInGlobals(IRContext* context, uint32_t const_value) {
   uint32_t id = context->TakeNextId();
@@ -210,7 +212,15 @@
         break;
     }
   } else {
-    line_number = line->GetSingleWordOperand(kOpLineOperandLineIndex);
+    if (line->opcode() == SpvOpLine) {
+      line_number = line->GetSingleWordOperand(kOpLineOperandLineIndex);
+    } else if (line->GetShader100DebugOpcode() ==
+               NonSemanticShaderDebugInfo100DebugLine) {
+      line_number = line->GetSingleWordOperand(kLineOperandIndexDebugLine);
+    } else {
+      assert(false &&
+             "Unreachable. A line instruction must be OpLine or DebugLine");
+    }
 
     // If we need the line number as an ID, generate that constant now.
     // If Constant or DefUse managers are invalid, generate constant
@@ -219,7 +229,8 @@
     // DefUse manager which cannot be done during inlining. The extra
     // constants that may be generated here is likely not significant
     // and will likely be cleaned up in later passes.
-    if (line_number_type == spv_operand_type_t::SPV_OPERAND_TYPE_ID) {
+    if (line_number_type == spv_operand_type_t::SPV_OPERAND_TYPE_ID &&
+        line->opcode() == SpvOpLine) {
       if (!context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse) ||
           !context()->AreAnalysesValid(IRContext::Analysis::kAnalysisConstants))
         line_number = AddNewConstInGlobals(context(), line_number);
@@ -546,10 +557,10 @@
   return false;
 }
 
-bool DebugInfoManager::AddDebugValueIfVarDeclIsVisible(
-    Instruction* scope_and_line, uint32_t variable_id, uint32_t value_id,
-    Instruction* insert_pos,
-    std::unordered_set<Instruction*>* invisible_decls) {
+bool DebugInfoManager::AddDebugValueForVariable(Instruction* scope_and_line,
+                                                uint32_t variable_id,
+                                                uint32_t value_id,
+                                                Instruction* insert_pos) {
   assert(scope_and_line != nullptr);
 
   auto dbg_decl_itr = var_id_to_dbg_decl_.find(variable_id);
@@ -557,11 +568,6 @@
 
   bool modified = false;
   for (auto* dbg_decl_or_val : dbg_decl_itr->second) {
-    if (!IsDeclareVisibleToInstr(dbg_decl_or_val, scope_and_line)) {
-      if (invisible_decls) invisible_decls->insert(dbg_decl_or_val);
-      continue;
-    }
-
     // Avoid inserting the new DebugValue between OpPhi or OpVariable
     // instructions.
     Instruction* insert_before = insert_pos->NextNode();
@@ -849,7 +855,7 @@
     fn_id_to_dbg_fn_.erase(fn_id);
   }
   if (instr->GetShader100DebugOpcode() ==
-      NonSemanticShaderDebugInfo100DebugFunction) {
+      NonSemanticShaderDebugInfo100DebugFunctionDefinition) {
     auto fn_id = instr->GetSingleWordOperand(
         kDebugFunctionDefinitionOperandOpFunctionIndex);
     fn_id_to_dbg_fn_.erase(fn_id);
diff --git a/source/opt/debug_info_manager.h b/source/opt/debug_info_manager.h
index df34b30..abb7b9a 100644
--- a/source/opt/debug_info_manager.h
+++ b/source/opt/debug_info_manager.h
@@ -15,6 +15,7 @@
 #ifndef SOURCE_OPT_DEBUG_INFO_MANAGER_H_
 #define SOURCE_OPT_DEBUG_INFO_MANAGER_H_
 
+#include <set>
 #include <unordered_map>
 #include <unordered_set>
 
@@ -144,12 +145,10 @@
   // Generates a DebugValue instruction with value |value_id| for every local
   // variable that is in the scope of |scope_and_line| and whose memory is
   // |variable_id| and inserts it after the instruction |insert_pos|.
-  // Returns whether a DebugValue is added or not. |invisible_decls| returns
-  // DebugDeclares invisible to |scope_and_line|.
-  bool AddDebugValueIfVarDeclIsVisible(
-      Instruction* scope_and_line, uint32_t variable_id, uint32_t value_id,
-      Instruction* insert_pos,
-      std::unordered_set<Instruction*>* invisible_decls);
+  // Returns whether a DebugValue is added or not.
+  bool AddDebugValueForVariable(Instruction* scope_and_line,
+                                uint32_t variable_id, uint32_t value_id,
+                                Instruction* insert_pos);
 
   // Creates a DebugValue for DebugDeclare |dbg_decl| and inserts it before
   // |insert_before|. The new DebugValue has the same line and scope as
@@ -244,9 +243,18 @@
   // operand is the function.
   std::unordered_map<uint32_t, Instruction*> fn_id_to_dbg_fn_;
 
+  // Orders Instruction* for use in associative containers (i.e. less than
+  // ordering). Unique Id is used.
+  typedef Instruction* InstPtr;
+  struct InstPtrLess {
+    bool operator()(const InstPtr& lhs, const InstPtr& rhs) const {
+      return lhs->unique_id() < rhs->unique_id();
+    }
+  };
+
   // Mapping from variable or value ids to DebugDeclare or DebugValue
   // instructions whose operand is the variable or value.
-  std::unordered_map<uint32_t, std::unordered_set<Instruction*>>
+  std::unordered_map<uint32_t, std::set<InstPtr, InstPtrLess>>
       var_id_to_dbg_decl_;
 
   // Mapping from DebugScope ids to users.
diff --git a/source/opt/def_use_manager.cpp b/source/opt/def_use_manager.cpp
index 394b9fa..d54fdb6 100644
--- a/source/opt/def_use_manager.cpp
+++ b/source/opt/def_use_manager.cpp
@@ -14,11 +14,6 @@
 
 #include "source/opt/def_use_manager.h"
 
-#include <iostream>
-
-#include "source/opt/log.h"
-#include "source/opt/reflect.h"
-
 namespace spvtools {
 namespace opt {
 namespace analysis {
@@ -58,8 +53,8 @@
       case SPV_OPERAND_TYPE_SCOPE_ID: {
         uint32_t use_id = inst->GetSingleWordOperand(i);
         Instruction* def = GetDef(use_id);
-        if (!def) assert(false && "Definition is not registered.");
-        id_to_users_.insert(UserEntry(def, inst));
+        assert(def && "Definition is not registered.");
+        id_to_users_.insert(UserEntry{def, inst});
         used_ids->push_back(use_id);
       } break;
       default:
@@ -102,13 +97,13 @@
 DefUseManager::IdToUsersMap::const_iterator DefUseManager::UsersBegin(
     const Instruction* def) const {
   return id_to_users_.lower_bound(
-      UserEntry(const_cast<Instruction*>(def), nullptr));
+      UserEntry{const_cast<Instruction*>(def), nullptr});
 }
 
 bool DefUseManager::UsersNotEnd(const IdToUsersMap::const_iterator& iter,
                                 const IdToUsersMap::const_iterator& cached_end,
                                 const Instruction* inst) const {
-  return (iter != cached_end && iter->first == inst);
+  return (iter != cached_end && iter->def == inst);
 }
 
 bool DefUseManager::UsersNotEnd(const IdToUsersMap::const_iterator& iter,
@@ -125,7 +120,7 @@
 
   auto end = id_to_users_.end();
   for (auto iter = UsersBegin(def); UsersNotEnd(iter, end, def); ++iter) {
-    if (!f(iter->second)) return false;
+    if (!f(iter->user)) return false;
   }
   return true;
 }
@@ -158,7 +153,7 @@
 
   auto end = id_to_users_.end();
   for (auto iter = UsersBegin(def); UsersNotEnd(iter, end, def); ++iter) {
-    Instruction* user = iter->second;
+    Instruction* user = iter->user;
     for (uint32_t idx = 0; idx != user->NumOperands(); ++idx) {
       const Operand& op = user->GetOperand(idx);
       if (op.type != SPV_OPERAND_TYPE_RESULT_ID && spvIsIdType(op.type)) {
@@ -258,55 +253,59 @@
   if (iter != inst_to_used_ids_.end()) {
     for (auto use_id : iter->second) {
       id_to_users_.erase(
-          UserEntry(GetDef(use_id), const_cast<Instruction*>(inst)));
+          UserEntry{GetDef(use_id), const_cast<Instruction*>(inst)});
     }
-    inst_to_used_ids_.erase(inst);
+    inst_to_used_ids_.erase(iter);
   }
 }
 
-bool operator==(const DefUseManager& lhs, const DefUseManager& rhs) {
+bool CompareAndPrintDifferences(const DefUseManager& lhs,
+                                const DefUseManager& rhs) {
+  bool same = true;
+
   if (lhs.id_to_def_ != rhs.id_to_def_) {
     for (auto p : lhs.id_to_def_) {
       if (rhs.id_to_def_.find(p.first) == rhs.id_to_def_.end()) {
-        return false;
+        printf("Diff in id_to_def: missing value in rhs\n");
       }
     }
     for (auto p : rhs.id_to_def_) {
       if (lhs.id_to_def_.find(p.first) == lhs.id_to_def_.end()) {
-        return false;
+        printf("Diff in id_to_def: missing value in lhs\n");
       }
     }
-    return false;
+    same = false;
   }
 
   if (lhs.id_to_users_ != rhs.id_to_users_) {
     for (auto p : lhs.id_to_users_) {
       if (rhs.id_to_users_.count(p) == 0) {
-        return false;
+        printf("Diff in id_to_users: missing value in rhs\n");
       }
     }
     for (auto p : rhs.id_to_users_) {
       if (lhs.id_to_users_.count(p) == 0) {
-        return false;
+        printf("Diff in id_to_users: missing value in lhs\n");
       }
     }
-    return false;
+    same = false;
   }
 
   if (lhs.inst_to_used_ids_ != rhs.inst_to_used_ids_) {
     for (auto p : lhs.inst_to_used_ids_) {
       if (rhs.inst_to_used_ids_.count(p.first) == 0) {
-        return false;
+        printf("Diff in inst_to_used_ids: missing value in rhs\n");
       }
     }
     for (auto p : rhs.inst_to_used_ids_) {
       if (lhs.inst_to_used_ids_.count(p.first) == 0) {
-        return false;
+        printf("Diff in inst_to_used_ids: missing value in lhs\n");
       }
     }
-    return false;
+    same = false;
   }
-  return true;
+
+  return same;
 }
 
 }  // namespace analysis
diff --git a/source/opt/def_use_manager.h b/source/opt/def_use_manager.h
index 0499e82..a8dbbc6 100644
--- a/source/opt/def_use_manager.h
+++ b/source/opt/def_use_manager.h
@@ -15,10 +15,8 @@
 #ifndef SOURCE_OPT_DEF_USE_MANAGER_H_
 #define SOURCE_OPT_DEF_USE_MANAGER_H_
 
-#include <list>
 #include <set>
 #include <unordered_map>
-#include <utility>
 #include <vector>
 
 #include "source/opt/instruction.h"
@@ -51,15 +49,17 @@
   return lhs.operand_index < rhs.operand_index;
 }
 
-// Definition and user pair.
-//
-// The first element of the pair is the definition.
-// The second element of the pair is the user.
-//
 // Definition should never be null. User can be null, however, such an entry
 // should be used only for searching (e.g. all users of a particular definition)
 // and never stored in a container.
-using UserEntry = std::pair<Instruction*, Instruction*>;
+struct UserEntry {
+  Instruction* def;
+  Instruction* user;
+};
+
+inline bool operator==(const UserEntry& lhs, const UserEntry& rhs) {
+  return lhs.def == rhs.def && lhs.user == rhs.user;
+}
 
 // Orders UserEntry for use in associative containers (i.e. less than ordering).
 //
@@ -72,24 +72,24 @@
 // definition (i.e. using {def, nullptr}).
 struct UserEntryLess {
   bool operator()(const UserEntry& lhs, const UserEntry& rhs) const {
-    // If lhs.first and rhs.first are both null, fall through to checking the
+    // If lhs.def and rhs.def are both null, fall through to checking the
     // second entries.
-    if (!lhs.first && rhs.first) return true;
-    if (lhs.first && !rhs.first) return false;
+    if (!lhs.def && rhs.def) return true;
+    if (lhs.def && !rhs.def) return false;
 
     // If neither definition is null, then compare unique ids.
-    if (lhs.first && rhs.first) {
-      if (lhs.first->unique_id() < rhs.first->unique_id()) return true;
-      if (rhs.first->unique_id() < lhs.first->unique_id()) return false;
+    if (lhs.def && rhs.def) {
+      if (lhs.def->unique_id() < rhs.def->unique_id()) return true;
+      if (rhs.def->unique_id() < lhs.def->unique_id()) return false;
     }
 
     // Return false on equality.
-    if (!lhs.second && !rhs.second) return false;
-    if (!lhs.second) return true;
-    if (!rhs.second) return false;
+    if (!lhs.user && !rhs.user) return false;
+    if (!lhs.user) return true;
+    if (!rhs.user) return false;
 
     // If neither user is null then compare unique ids.
-    return lhs.second->unique_id() < rhs.second->unique_id();
+    return lhs.user->unique_id() < rhs.user->unique_id();
   }
 };
 
@@ -97,7 +97,6 @@
 class DefUseManager {
  public:
   using IdToDefMap = std::unordered_map<uint32_t, Instruction*>;
-  using IdToUsersMap = std::set<UserEntry, UserEntryLess>;
 
   // Constructs a def-use manager from the given |module|. All internal messages
   // will be communicated to the outside via the given message |consumer|. This
@@ -191,14 +190,12 @@
   // Returns the annotation instrunctions which are a direct use of the given
   // |id|. This means when the decorations are applied through decoration
   // group(s), this function will just return the OpGroupDecorate
-  // instrcution(s) which refer to the given id as an operand. The OpDecorate
+  // instruction(s) which refer to the given id as an operand. The OpDecorate
   // instructions which decorate the decoration group will not be returned.
   std::vector<Instruction*> GetAnnotations(uint32_t id) const;
 
   // Returns the map from ids to their def instructions.
   const IdToDefMap& id_to_defs() const { return id_to_def_; }
-  // Returns the map from instructions to their users.
-  const IdToUsersMap& id_to_users() const { return id_to_users_; }
 
   // Clear the internal def-use record of the given instruction |inst|. This
   // method will update the use information of the operand ids of |inst|. The
@@ -210,16 +207,15 @@
   // Erases the records that a given instruction uses its operand ids.
   void EraseUseRecordsOfOperandIds(const Instruction* inst);
 
-  friend bool operator==(const DefUseManager&, const DefUseManager&);
-  friend bool operator!=(const DefUseManager& lhs, const DefUseManager& rhs) {
-    return !(lhs == rhs);
-  }
+  friend bool CompareAndPrintDifferences(const DefUseManager&,
+                                         const DefUseManager&);
 
-  // If |inst| has not already been analysed, then analyses its defintion and
+  // If |inst| has not already been analysed, then analyses its definition and
   // uses.
   void UpdateDefUse(Instruction* inst);
 
  private:
+  using IdToUsersMap = std::set<UserEntry, UserEntryLess>;
   using InstToUsedIdsMap =
       std::unordered_map<const Instruction*, std::vector<uint32_t>>;
 
diff --git a/source/opt/desc_sroa.cpp b/source/opt/desc_sroa.cpp
index bcbdde9..b130ca8 100644
--- a/source/opt/desc_sroa.cpp
+++ b/source/opt/desc_sroa.cpp
@@ -118,7 +118,7 @@
 
   if (use->NumInOperands() == 2) {
     // We are not indexing into the replacement variable.  We can replaces the
-    // access chain with the replacement varibale itself.
+    // access chain with the replacement variable itself.
     context()->ReplaceAllUsesWith(use->result_id(), replacement_var);
     context()->KillInst(use);
     return true;
@@ -135,8 +135,8 @@
   // Use the replacement variable as the base address.
   new_operands.push_back({SPV_OPERAND_TYPE_ID, {replacement_var}});
 
-  // Drop the first index because it is consumed by the replacment, and copy the
-  // rest.
+  // Drop the first index because it is consumed by the replacement, and copy
+  // the rest.
   for (uint32_t i = 4; i < use->NumOperands(); i++) {
     new_operands.emplace_back(use->GetOperand(i));
   }
@@ -169,7 +169,7 @@
     Instruction* old_var, uint32_t index, uint32_t new_var_id,
     uint32_t new_var_ptr_type_id, const bool is_old_var_array,
     const bool is_old_var_struct, Instruction* old_var_type) {
-  // Handle OpDecorate instructions.
+  // Handle OpDecorate and OpDecorateString instructions.
   for (auto old_decoration :
        get_decoration_mgr()->GetDecorationsFor(old_var->result_id(), true)) {
     uint32_t new_binding = 0;
@@ -212,7 +212,8 @@
 
 void DescriptorScalarReplacement::CreateNewDecorationForNewVariable(
     Instruction* old_decoration, uint32_t new_var_id, uint32_t new_binding) {
-  assert(old_decoration->opcode() == SpvOpDecorate);
+  assert(old_decoration->opcode() == SpvOpDecorate ||
+         old_decoration->opcode() == SpvOpDecorateString);
   std::unique_ptr<Instruction> new_decoration(old_decoration->Clone(context()));
   new_decoration->SetInOperand(0, {new_var_id});
 
diff --git a/source/opt/desc_sroa.h b/source/opt/desc_sroa.h
index fea0625..6a24fd8 100644
--- a/source/opt/desc_sroa.h
+++ b/source/opt/desc_sroa.h
@@ -115,10 +115,11 @@
                                    const bool is_old_var_struct,
                                    Instruction* old_var_type);
 
-  // Create a new OpDecorate instruction by cloning |old_decoration|. The new
-  // OpDecorate instruction will be used for a variable whose id is
-  // |new_var_ptr_type_id|. If |old_decoration| is a decoration for a binding,
-  // the new OpDecorate instruction will have |new_binding| as its binding.
+  // Create a new OpDecorate(String) instruction by cloning |old_decoration|.
+  // The new OpDecorate(String) instruction will be used for a variable whose id
+  // is |new_var_ptr_type_id|. If |old_decoration| is a decoration for a
+  // binding, the new OpDecorate(String) instruction will have |new_binding| as
+  // its binding.
   void CreateNewDecorationForNewVariable(Instruction* old_decoration,
                                          uint32_t new_var_id,
                                          uint32_t new_binding);
@@ -131,7 +132,7 @@
 
   // A map from an OpVariable instruction to the set of variables that will be
   // used to replace it. The entry |replacement_variables_[var][i]| is the id of
-  // a variable that will be used in the place of the the ith element of the
+  // a variable that will be used in the place of the ith element of the
   // array |var|. If the entry is |0|, then the variable has not been
   // created yet.
   std::map<Instruction*, std::vector<uint32_t>> replacement_variables_;
diff --git a/source/opt/dominator_tree.cpp b/source/opt/dominator_tree.cpp
index 55287f4..2680be2 100644
--- a/source/opt/dominator_tree.cpp
+++ b/source/opt/dominator_tree.cpp
@@ -48,7 +48,7 @@
 // BBType - BasicBlock type. Will either be BasicBlock or DominatorTreeNode
 // SuccessorLambda - Lamdba matching the signature of 'const
 // std::vector<BBType>*(const BBType *A)'. Will return a vector of the nodes
-// succeding BasicBlock A.
+// succeeding BasicBlock A.
 // PostLambda - Lamdba matching the signature of 'void (const BBType*)' will be
 // called on each node traversed AFTER their children.
 // PreLambda - Lamdba matching the signature of 'void (const BBType*)' will be
@@ -57,9 +57,9 @@
           typename PostLambda>
 static void DepthFirstSearch(const BBType* bb, SuccessorLambda successors,
                              PreLambda pre, PostLambda post) {
-  // Ignore backedge operation.
-  auto nop_backedge = [](const BBType*, const BBType*) {};
-  CFA<BBType>::DepthFirstTraversal(bb, successors, pre, post, nop_backedge);
+  auto no_terminal_blocks = [](const BBType*) { return false; };
+  CFA<BBType>::DepthFirstTraversal(bb, successors, pre, post,
+                                   no_terminal_blocks);
 }
 
 // Wrapper around CFA::DepthFirstTraversal to provide an interface to perform
@@ -69,7 +69,7 @@
 // BBType - BasicBlock type. Will either be BasicBlock or DominatorTreeNode
 // SuccessorLambda - Lamdba matching the signature of 'const
 // std::vector<BBType>*(const BBType *A)'. Will return a vector of the nodes
-// succeding BasicBlock A.
+// succeeding BasicBlock A.
 // PostLambda - Lamdba matching the signature of 'void (const BBType*)' will be
 // called on each node traversed after their children.
 template <typename BBType, typename SuccessorLambda, typename PostLambda>
@@ -103,7 +103,8 @@
   using Function = typename GetFunctionClass<BBType>::FunctionType;
 
   using BasicBlockListTy = std::vector<BasicBlock*>;
-  using BasicBlockMapTy = std::map<const BasicBlock*, BasicBlockListTy>;
+  using BasicBlockMapTy =
+      std::unordered_map<const BasicBlock*, BasicBlockListTy>;
 
  public:
   // For compliance with the dominance tree computation, entry nodes are
@@ -158,19 +159,7 @@
 template <typename BBType>
 void BasicBlockSuccessorHelper<BBType>::CreateSuccessorMap(
     Function& f, const BasicBlock* placeholder_start_node) {
-  std::map<uint32_t, BasicBlock*> id_to_BB_map;
-  auto GetSuccessorBasicBlock = [&f, &id_to_BB_map](uint32_t successor_id) {
-    BasicBlock*& Succ = id_to_BB_map[successor_id];
-    if (!Succ) {
-      for (BasicBlock& BBIt : f) {
-        if (successor_id == BBIt.id()) {
-          Succ = &BBIt;
-          break;
-        }
-      }
-    }
-    return Succ;
-  };
+  IRContext* context = f.DefInst().context();
 
   if (invert_graph_) {
     // For the post dominator tree, we see the inverted graph.
@@ -184,9 +173,8 @@
         BasicBlockListTy& pred_list = predecessors_[&bb];
         const auto& const_bb = bb;
         const_bb.ForEachSuccessorLabel(
-            [this, &pred_list, &bb,
-             &GetSuccessorBasicBlock](const uint32_t successor_id) {
-              BasicBlock* succ = GetSuccessorBasicBlock(successor_id);
+            [this, &pred_list, &bb, context](const uint32_t successor_id) {
+              BasicBlock* succ = context->get_instr_block(successor_id);
               // Inverted graph: our successors in the CFG
               // are our predecessors in the inverted graph.
               this->successors_[succ].push_back(&bb);
@@ -207,7 +195,7 @@
 
       const auto& const_bb = bb;
       const_bb.ForEachSuccessorLabel([&](const uint32_t successor_id) {
-        BasicBlock* succ = GetSuccessorBasicBlock(successor_id);
+        BasicBlock* succ = context->get_instr_block(successor_id);
         succ_list.push_back(succ);
         predecessors_[succ].push_back(&bb);
       });
diff --git a/source/opt/dominator_tree.h b/source/opt/dominator_tree.h
index 0024bc5..1674b22 100644
--- a/source/opt/dominator_tree.h
+++ b/source/opt/dominator_tree.h
@@ -278,7 +278,7 @@
 
  private:
   // Wrapper function which gets the list of pairs of each BasicBlocks to its
-  // immediately  dominating BasicBlock and stores the result in the the edges
+  // immediately  dominating BasicBlock and stores the result in the edges
   // parameter.
   //
   // The |edges| vector will contain the dominator tree as pairs of nodes.
diff --git a/source/opt/eliminate_dead_input_components_pass.cpp b/source/opt/eliminate_dead_input_components_pass.cpp
new file mode 100644
index 0000000..aa2776b
--- /dev/null
+++ b/source/opt/eliminate_dead_input_components_pass.cpp
@@ -0,0 +1,189 @@
+// Copyright (c) 2022 The Khronos Group Inc.
+// Copyright (c) 2022 LunarG 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.
+
+#include "source/opt/eliminate_dead_input_components_pass.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 {
+
+const uint32_t kAccessChainBaseInIdx = 0;
+const uint32_t kAccessChainIndex0InIdx = 1;
+const uint32_t kConstantValueInIdx = 0;
+const uint32_t kVariableStorageClassInIdx = 0;
+
+}  // namespace
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status EliminateDeadInputComponentsPass::Process() {
+  // Current functionality assumes shader capability
+  if (!context()->get_feature_mgr()->HasCapability(SpvCapabilityShader))
+    return Status::SuccessWithoutChange;
+  analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
+  analysis::TypeManager* type_mgr = context()->get_type_mgr();
+  bool modified = false;
+  std::vector<std::pair<Instruction*, unsigned>> arrays_to_change;
+  for (auto& var : context()->types_values()) {
+    if (var.opcode() != SpvOpVariable) {
+      continue;
+    }
+    analysis::Type* var_type = type_mgr->GetType(var.type_id());
+    analysis::Pointer* ptr_type = var_type->AsPointer();
+    if (ptr_type == nullptr) {
+      continue;
+    }
+    if (ptr_type->storage_class() != SpvStorageClassInput) {
+      continue;
+    }
+    const analysis::Array* arr_type = ptr_type->pointee_type()->AsArray();
+    if (arr_type != nullptr) {
+      unsigned arr_len_id = arr_type->LengthId();
+      Instruction* arr_len_inst = def_use_mgr->GetDef(arr_len_id);
+      if (arr_len_inst->opcode() != SpvOpConstant) {
+        continue;
+      }
+      // SPIR-V requires array size is >= 1, so this works for signed or
+      // unsigned size
+      unsigned original_max =
+          arr_len_inst->GetSingleWordInOperand(kConstantValueInIdx) - 1;
+      unsigned max_idx = FindMaxIndex(var, original_max);
+      if (max_idx != original_max) {
+        ChangeArrayLength(var, max_idx + 1);
+        modified = true;
+      }
+      continue;
+    }
+    const analysis::Struct* struct_type = ptr_type->pointee_type()->AsStruct();
+    if (struct_type == nullptr) continue;
+    const auto elt_types = struct_type->element_types();
+    unsigned original_max = static_cast<unsigned>(elt_types.size()) - 1;
+    unsigned max_idx = FindMaxIndex(var, original_max);
+    if (max_idx != original_max) {
+      ChangeStructLength(var, max_idx + 1);
+      modified = true;
+    }
+  }
+
+  return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+unsigned EliminateDeadInputComponentsPass::FindMaxIndex(Instruction& var,
+                                                        unsigned original_max) {
+  unsigned max = 0;
+  bool seen_non_const_ac = false;
+  assert(var.opcode() == SpvOpVariable && "must be variable");
+  context()->get_def_use_mgr()->WhileEachUser(
+      var.result_id(), [&max, &seen_non_const_ac, var, this](Instruction* use) {
+        auto use_opcode = use->opcode();
+        if (use_opcode == SpvOpLoad || use_opcode == SpvOpCopyMemory ||
+            use_opcode == SpvOpCopyMemorySized ||
+            use_opcode == SpvOpCopyObject) {
+          seen_non_const_ac = true;
+          return false;
+        }
+        if (use->opcode() != SpvOpAccessChain &&
+            use->opcode() != SpvOpInBoundsAccessChain) {
+          return true;
+        }
+        // OpAccessChain with no indices currently not optimized
+        if (use->NumInOperands() == 1) {
+          seen_non_const_ac = true;
+          return false;
+        }
+        unsigned base_id = use->GetSingleWordInOperand(kAccessChainBaseInIdx);
+        USE_ASSERT(base_id == var.result_id() && "unexpected base");
+        unsigned idx_id = use->GetSingleWordInOperand(kAccessChainIndex0InIdx);
+        Instruction* idx_inst = context()->get_def_use_mgr()->GetDef(idx_id);
+        if (idx_inst->opcode() != SpvOpConstant) {
+          seen_non_const_ac = true;
+          return false;
+        }
+        unsigned value = idx_inst->GetSingleWordInOperand(kConstantValueInIdx);
+        if (value > max) max = value;
+        return true;
+      });
+  return seen_non_const_ac ? original_max : max;
+}
+
+void EliminateDeadInputComponentsPass::ChangeArrayLength(Instruction& arr_var,
+                                                         unsigned length) {
+  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();
+  analysis::Pointer* ptr_type =
+      type_mgr->GetType(arr_var.type_id())->AsPointer();
+  const analysis::Array* arr_ty = ptr_type->pointee_type()->AsArray();
+  assert(arr_ty && "expecting array type");
+  uint32_t length_id = const_mgr->GetUIntConst(length);
+  analysis::Array new_arr_ty(arr_ty->element_type(),
+                             arr_ty->GetConstantLengthInfo(length_id, length));
+  analysis::Type* reg_new_arr_ty = type_mgr->GetRegisteredType(&new_arr_ty);
+  analysis::Pointer new_ptr_ty(reg_new_arr_ty, SpvStorageClassInput);
+  analysis::Type* reg_new_ptr_ty = type_mgr->GetRegisteredType(&new_ptr_ty);
+  uint32_t new_ptr_ty_id = type_mgr->GetTypeInstruction(reg_new_ptr_ty);
+  arr_var.SetResultType(new_ptr_ty_id);
+  def_use_mgr->AnalyzeInstUse(&arr_var);
+  // Move arr_var after its new type to preserve order
+  USE_ASSERT(arr_var.GetSingleWordInOperand(kVariableStorageClassInIdx) !=
+                 SpvStorageClassFunction &&
+             "cannot move Function variable");
+  Instruction* new_ptr_ty_inst = def_use_mgr->GetDef(new_ptr_ty_id);
+  arr_var.RemoveFromList();
+  arr_var.InsertAfter(new_ptr_ty_inst);
+}
+
+void EliminateDeadInputComponentsPass::ChangeStructLength(
+    Instruction& struct_var, unsigned length) {
+  analysis::TypeManager* type_mgr = context()->get_type_mgr();
+  analysis::Pointer* ptr_type =
+      type_mgr->GetType(struct_var.type_id())->AsPointer();
+  const analysis::Struct* struct_ty = ptr_type->pointee_type()->AsStruct();
+  assert(struct_ty && "expecting struct type");
+  const auto orig_elt_types = struct_ty->element_types();
+  std::vector<const analysis::Type*> new_elt_types;
+  for (unsigned u = 0; u < length; ++u)
+    new_elt_types.push_back(orig_elt_types[u]);
+  analysis::Struct new_struct_ty(new_elt_types);
+  analysis::Type* reg_new_struct_ty =
+      type_mgr->GetRegisteredType(&new_struct_ty);
+  uint32_t new_struct_ty_id = type_mgr->GetTypeInstruction(reg_new_struct_ty);
+  uint32_t old_struct_ty_id = type_mgr->GetTypeInstruction(struct_ty);
+  analysis::DecorationManager* deco_mgr = context()->get_decoration_mgr();
+  deco_mgr->CloneDecorations(old_struct_ty_id, new_struct_ty_id);
+  analysis::Pointer new_ptr_ty(reg_new_struct_ty, SpvStorageClassInput);
+  analysis::Type* reg_new_ptr_ty = type_mgr->GetRegisteredType(&new_ptr_ty);
+  uint32_t new_ptr_ty_id = type_mgr->GetTypeInstruction(reg_new_ptr_ty);
+  struct_var.SetResultType(new_ptr_ty_id);
+  analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
+  def_use_mgr->AnalyzeInstUse(&struct_var);
+  // Move struct_var after its new type to preserve order
+  USE_ASSERT(struct_var.GetSingleWordInOperand(kVariableStorageClassInIdx) !=
+                 SpvStorageClassFunction &&
+             "cannot move Function variable");
+  Instruction* new_ptr_ty_inst = def_use_mgr->GetDef(new_ptr_ty_id);
+  struct_var.RemoveFromList();
+  struct_var.InsertAfter(new_ptr_ty_inst);
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/eliminate_dead_input_components_pass.h b/source/opt/eliminate_dead_input_components_pass.h
new file mode 100644
index 0000000..a3a133c
--- /dev/null
+++ b/source/opt/eliminate_dead_input_components_pass.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2022 The Khronos Group Inc.
+// Copyright (c) 2022 LunarG 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_OPT_ELIMINATE_DEAD_INPUT_COMPONENTS_H_
+#define SOURCE_OPT_ELIMINATE_DEAD_INPUT_COMPONENTS_H_
+
+#include <unordered_map>
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// See optimizer.hpp for documentation.
+class EliminateDeadInputComponentsPass : public Pass {
+ public:
+  explicit EliminateDeadInputComponentsPass() {}
+
+  const char* name() const override {
+    return "eliminate-dead-input-components";
+  }
+
+  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:
+  // Find the max constant used to index the variable declared by |var|
+  // through OpAccessChain or OpInBoundsAccessChain. If any non-constant
+  // indices or non-Op*AccessChain use of |var|, return |original_max|.
+  unsigned FindMaxIndex(Instruction& var, unsigned original_max);
+
+  // Change the length of the array |inst| to |length|
+  void ChangeArrayLength(Instruction& inst, unsigned length);
+
+  // Change the length of the struct |struct_var| to |length|
+  void ChangeStructLength(Instruction& struct_var, unsigned length);
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_ELIMINATE_DEAD_INPUT_COMPONENTS_H_
diff --git a/source/opt/eliminate_dead_members_pass.cpp b/source/opt/eliminate_dead_members_pass.cpp
index a24ba8f..52aca52 100644
--- a/source/opt/eliminate_dead_members_pass.cpp
+++ b/source/opt/eliminate_dead_members_pass.cpp
@@ -38,7 +38,7 @@
 }
 
 void EliminateDeadMembersPass::FindLiveMembers() {
-  // Until we have implemented the rewritting of OpSpecConsantOp instructions,
+  // Until we have implemented the rewriting 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) {
@@ -570,7 +570,7 @@
     Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
     switch (type_inst->opcode()) {
       case SpvOpTypeStruct:
-        // The type will have already been rewriten, so use the new member
+        // The type will have already been rewritten, so use the new member
         // index.
         type_id = type_inst->GetSingleWordInOperand(new_member_idx);
         break;
diff --git a/source/opt/feature_manager.cpp b/source/opt/feature_manager.cpp
index 39a4a34..a590271 100644
--- a/source/opt/feature_manager.cpp
+++ b/source/opt/feature_manager.cpp
@@ -39,8 +39,7 @@
   assert(ext->opcode() == SpvOpExtension &&
          "Expecting an extension instruction.");
 
-  const std::string name =
-      reinterpret_cast<const char*>(ext->GetInOperand(0u).words.data());
+  const std::string name = ext->GetInOperand(0u).AsString();
   Extension extension;
   if (GetExtensionFromString(name.c_str(), &extension)) {
     extensions_.Add(extension);
diff --git a/source/opt/fix_func_call_arguments.cpp b/source/opt/fix_func_call_arguments.cpp
new file mode 100644
index 0000000..d140fb4
--- /dev/null
+++ b/source/opt/fix_func_call_arguments.cpp
@@ -0,0 +1,90 @@
+// Copyright (c) 2022 Advanced Micro Devices, 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.
+
+#include "fix_func_call_arguments.h"
+
+#include "ir_builder.h"
+
+using namespace spvtools;
+using namespace opt;
+
+bool FixFuncCallArgumentsPass::ModuleHasASingleFunction() {
+  auto funcsNum = get_module()->end() - get_module()->begin();
+  return funcsNum == 1;
+}
+
+Pass::Status FixFuncCallArgumentsPass::Process() {
+  bool modified = false;
+  if (ModuleHasASingleFunction()) return Status::SuccessWithoutChange;
+  for (auto& func : *get_module()) {
+    func.ForEachInst([this, &modified](Instruction* inst) {
+      if (inst->opcode() == SpvOpFunctionCall) {
+        modified |= FixFuncCallArguments(inst);
+      }
+    });
+  }
+  return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+bool FixFuncCallArgumentsPass::FixFuncCallArguments(
+    Instruction* func_call_inst) {
+  bool modified = false;
+  for (uint32_t i = 0; i < func_call_inst->NumInOperands(); ++i) {
+    Operand& op = func_call_inst->GetInOperand(i);
+    if (op.type != SPV_OPERAND_TYPE_ID) continue;
+    Instruction* operand_inst = get_def_use_mgr()->GetDef(op.AsId());
+    if (operand_inst->opcode() == SpvOpAccessChain) {
+      uint32_t var_id =
+          ReplaceAccessChainFuncCallArguments(func_call_inst, operand_inst);
+      func_call_inst->SetInOperand(i, {var_id});
+      modified = true;
+    }
+  }
+  if (modified) {
+    context()->UpdateDefUse(func_call_inst);
+  }
+  return modified;
+}
+
+uint32_t FixFuncCallArgumentsPass::ReplaceAccessChainFuncCallArguments(
+    Instruction* func_call_inst, Instruction* operand_inst) {
+  InstructionBuilder builder(
+      context(), func_call_inst,
+      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+
+  Instruction* next_insert_point = func_call_inst->NextNode();
+  // Get Variable insertion point
+  Function* func = context()->get_instr_block(func_call_inst)->GetParent();
+  Instruction* variable_insertion_point = &*(func->begin()->begin());
+  Instruction* op_ptr_type = get_def_use_mgr()->GetDef(operand_inst->type_id());
+  Instruction* op_type =
+      get_def_use_mgr()->GetDef(op_ptr_type->GetSingleWordInOperand(1));
+  uint32_t varType = context()->get_type_mgr()->FindPointerToType(
+      op_type->result_id(), SpvStorageClassFunction);
+  // Create new variable
+  builder.SetInsertPoint(variable_insertion_point);
+  Instruction* var = builder.AddVariable(varType, SpvStorageClassFunction);
+  // Load access chain to the new variable before function call
+  builder.SetInsertPoint(func_call_inst);
+
+  uint32_t operand_id = operand_inst->result_id();
+  Instruction* load = builder.AddLoad(op_type->result_id(), operand_id);
+  builder.AddStore(var->result_id(), load->result_id());
+  // Load return value to the acesschain after function call
+  builder.SetInsertPoint(next_insert_point);
+  load = builder.AddLoad(op_type->result_id(), var->result_id());
+  builder.AddStore(operand_id, load->result_id());
+
+  return var->result_id();
+}
diff --git a/source/opt/fix_func_call_arguments.h b/source/opt/fix_func_call_arguments.h
new file mode 100644
index 0000000..15781b8
--- /dev/null
+++ b/source/opt/fix_func_call_arguments.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2022 Advanced Micro Devices, 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 _VAR_FUNC_CALL_PASS_H
+#define _VAR_FUNC_CALL_PASS_H
+
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+class FixFuncCallArgumentsPass : public Pass {
+ public:
+  FixFuncCallArgumentsPass() {}
+  const char* name() const override { return "fix-for-funcall-param"; }
+  Status Process() override;
+  // Returns true if the module has one one function.
+  bool ModuleHasASingleFunction();
+  // Copies from the memory pointed to by |operand_inst| to a new function scope
+  // variable created before |func_call_inst|, and
+  // copies the value of the new variable back to the memory pointed to by
+  // |operand_inst| after |funct_call_inst|  Returns the id of
+  // the new variable.
+  uint32_t ReplaceAccessChainFuncCallArguments(Instruction* func_call_inst,
+                                               Instruction* operand_inst);
+
+  // Fix function call |func_call_inst| non memory object arguments
+  bool FixFuncCallArguments(Instruction* func_call_inst);
+
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisTypes;
+  }
+};
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // _VAR_FUNC_CALL_PASS_H
\ No newline at end of file
diff --git a/source/opt/fold.cpp b/source/opt/fold.cpp
index 6550fb4..315741a 100644
--- a/source/opt/fold.cpp
+++ b/source/opt/fold.cpp
@@ -540,7 +540,7 @@
         // in 32-bit words here. The reason of not using FoldScalars() here
         // is that we do not create temporary null constants as components
         // when the vector operand is a NullConstant because Constant creation
-        // may need extra checks for the validity and that is not manageed in
+        // may need extra checks for the validity and that is not managed in
         // here.
         if (const analysis::ScalarConstant* scalar_component =
                 vector_operand->GetComponents().at(d)->AsScalarConstant()) {
@@ -627,8 +627,7 @@
     Instruction* inst, std::function<uint32_t(uint32_t)> id_map) const {
   analysis::ConstantManager* const_mgr = context_->get_constant_mgr();
 
-  if (!inst->IsFoldableByFoldScalar() &&
-      !GetConstantFoldingRules().HasFoldingRule(inst)) {
+  if (!inst->IsFoldableByFoldScalar() && !HasConstFoldingRule(inst)) {
     return nullptr;
   }
   // Collect the values of the constant parameters.
diff --git a/source/opt/fold_spec_constant_op_and_composite_pass.cpp b/source/opt/fold_spec_constant_op_and_composite_pass.cpp
index 8ab717e..7a51870 100644
--- a/source/opt/fold_spec_constant_op_and_composite_pass.cpp
+++ b/source/opt/fold_spec_constant_op_and_composite_pass.cpp
@@ -28,6 +28,7 @@
 
 Pass::Status FoldSpecConstantOpAndCompositePass::Process() {
   bool modified = false;
+  analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
   // Traverse through all the constant defining instructions. For Normal
   // Constants whose values are determined and do not depend on OpUndef
   // instructions, records their values in two internal maps: id_to_const_val_
@@ -62,8 +63,8 @@
     // used in OpSpecConstant{Composite|Op} instructions.
     // TODO(qining): If the constant or its type has decoration, we may need
     // to skip it.
-    if (context()->get_constant_mgr()->GetType(inst) &&
-        !context()->get_constant_mgr()->GetType(inst)->decoration_empty())
+    if (const_mgr->GetType(inst) &&
+        !const_mgr->GetType(inst)->decoration_empty())
       continue;
     switch (SpvOp opcode = inst->opcode()) {
       // Records the values of Normal Constants.
@@ -80,15 +81,14 @@
         // Constant will be turned in to a Normal Constant. In that case, a
         // Constant instance should also be created successfully and recorded
         // in the id_to_const_val_ and const_val_to_id_ mapps.
-        if (auto const_value =
-                context()->get_constant_mgr()->GetConstantFromInst(inst)) {
+        if (auto const_value = const_mgr->GetConstantFromInst(inst)) {
           // Need to replace the OpSpecConstantComposite instruction with a
           // corresponding OpConstantComposite instruction.
           if (opcode == SpvOp::SpvOpSpecConstantComposite) {
             inst->SetOpcode(SpvOp::SpvOpConstantComposite);
             modified = true;
           }
-          context()->get_constant_mgr()->MapConstantToInst(const_value, inst);
+          const_mgr->MapConstantToInst(const_value, inst);
         }
         break;
       }
@@ -115,7 +115,7 @@
   Instruction* folded_inst = nullptr;
   assert(inst->GetInOperand(0).type ==
              SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER &&
-         "The first in-operand of OpSpecContantOp instruction must be of "
+         "The first in-operand of OpSpecConstantOp instruction must be of "
          "SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER type");
 
   switch (static_cast<SpvOp>(inst->GetSingleWordInOperand(0))) {
@@ -144,17 +144,9 @@
   return true;
 }
 
-uint32_t FoldSpecConstantOpAndCompositePass::GetTypeComponent(
-    uint32_t typeId, uint32_t element) const {
-  Instruction* type = context()->get_def_use_mgr()->GetDef(typeId);
-  uint32_t subtype = type->GetTypeComponent(element);
-  assert(subtype != 0);
-
-  return subtype;
-}
-
 Instruction* FoldSpecConstantOpAndCompositePass::FoldWithInstructionFolder(
     Module::inst_iterator* inst_iter_ptr) {
+  analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
   // If one of operands to the instruction is not a
   // constant, then we cannot fold this spec constant.
   for (uint32_t i = 1; i < (*inst_iter_ptr)->NumInOperands(); i++) {
@@ -164,7 +156,7 @@
       continue;
     }
     uint32_t id = operand.words[0];
-    if (context()->get_constant_mgr()->FindDeclaredConstant(id) == nullptr) {
+    if (const_mgr->FindDeclaredConstant(id) == nullptr) {
       return nullptr;
     }
   }
@@ -211,89 +203,10 @@
     new_const_inst->InsertAfter(insert_pos);
     get_def_use_mgr()->AnalyzeInstDefUse(new_const_inst);
   }
+  const_mgr->MapInst(new_const_inst);
   return new_const_inst;
 }
 
-Instruction* FoldSpecConstantOpAndCompositePass::DoVectorShuffle(
-    Module::inst_iterator* pos) {
-  Instruction* inst = &**pos;
-  analysis::Vector* result_vec_type =
-      context()->get_constant_mgr()->GetType(inst)->AsVector();
-  assert(inst->NumInOperands() - 1 > 2 &&
-         "OpSpecConstantOp DoVectorShuffle instruction requires more than 2 "
-         "operands (2 vector ids and at least one literal operand");
-  assert(result_vec_type &&
-         "The result of VectorShuffle must be of type vector");
-
-  // A temporary null constants that can be used as the components of the result
-  // vector. This is needed when any one of the vector operands are null
-  // constant.
-  const analysis::Constant* null_component_constants = nullptr;
-
-  // Get a concatenated vector of scalar constants. The vector should be built
-  // with the components from the first and the second operand of VectorShuffle.
-  std::vector<const analysis::Constant*> concatenated_components;
-  // Note that for OpSpecConstantOp, the second in-operand is the first id
-  // operand. The first in-operand is the spec opcode.
-  for (uint32_t i : {1, 2}) {
-    assert(inst->GetInOperand(i).type == SPV_OPERAND_TYPE_ID &&
-           "The vector operand must have a SPV_OPERAND_TYPE_ID type");
-    uint32_t operand_id = inst->GetSingleWordInOperand(i);
-    auto operand_const =
-        context()->get_constant_mgr()->FindDeclaredConstant(operand_id);
-    if (!operand_const) return nullptr;
-    const analysis::Type* operand_type = operand_const->type();
-    assert(operand_type->AsVector() &&
-           "The first two operand of VectorShuffle must be of vector type");
-    if (auto vec_const = operand_const->AsVectorConstant()) {
-      // case 1: current operand is a non-null vector constant.
-      concatenated_components.insert(concatenated_components.end(),
-                                     vec_const->GetComponents().begin(),
-                                     vec_const->GetComponents().end());
-    } else if (operand_const->AsNullConstant()) {
-      // case 2: current operand is a null vector constant. Create a temporary
-      // null scalar constant as the component.
-      if (!null_component_constants) {
-        const analysis::Type* component_type =
-            operand_type->AsVector()->element_type();
-        null_component_constants =
-            context()->get_constant_mgr()->GetConstant(component_type, {});
-      }
-      // Append the null scalar consts to the concatenated components
-      // vector.
-      concatenated_components.insert(concatenated_components.end(),
-                                     operand_type->AsVector()->element_count(),
-                                     null_component_constants);
-    } else {
-      // no other valid cases
-      return nullptr;
-    }
-  }
-  // Create null component constants if there are any. The component constants
-  // must be added to the module before the dependee composite constants to
-  // satisfy SSA def-use dominance.
-  if (null_component_constants) {
-    context()->get_constant_mgr()->BuildInstructionAndAddToModule(
-        null_component_constants, pos);
-  }
-  // Create the new vector constant with the selected components.
-  std::vector<const analysis::Constant*> selected_components;
-  for (uint32_t i = 3; i < inst->NumInOperands(); i++) {
-    assert(inst->GetInOperand(i).type == SPV_OPERAND_TYPE_LITERAL_INTEGER &&
-           "The literal operand must of type SPV_OPERAND_TYPE_LITERAL_INTEGER");
-    uint32_t literal = inst->GetSingleWordInOperand(i);
-    assert(literal < concatenated_components.size() &&
-           "Literal index out of bound of the concatenated vector");
-    selected_components.push_back(concatenated_components[literal]);
-  }
-  auto new_vec_const = MakeUnique<analysis::VectorConstant>(
-      result_vec_type, selected_components);
-  auto reg_vec_const =
-      context()->get_constant_mgr()->RegisterConstant(std::move(new_vec_const));
-  return context()->get_constant_mgr()->BuildInstructionAndAddToModule(
-      reg_vec_const, pos);
-}
-
 namespace {
 // A helper function to check the type for component wise operations. Returns
 // true if the type:
@@ -374,8 +287,8 @@
 Instruction* FoldSpecConstantOpAndCompositePass::DoComponentWiseOperation(
     Module::inst_iterator* pos) {
   const Instruction* inst = &**pos;
-  const analysis::Type* result_type =
-      context()->get_constant_mgr()->GetType(inst);
+  analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
+  const analysis::Type* result_type = const_mgr->GetType(inst);
   SpvOp spec_opcode = static_cast<SpvOp>(inst->GetSingleWordInOperand(0));
   // Check and collect operands.
   std::vector<const analysis::Constant*> operands;
@@ -400,10 +313,9 @@
     // Scalar operation
     const uint32_t result_val =
         context()->get_instruction_folder().FoldScalars(spec_opcode, operands);
-    auto result_const = context()->get_constant_mgr()->GetConstant(
+    auto result_const = const_mgr->GetConstant(
         result_type, EncodeIntegerAsWords(*result_type, result_val));
-    return context()->get_constant_mgr()->BuildInstructionAndAddToModule(
-        result_const, pos);
+    return const_mgr->BuildInstructionAndAddToModule(result_const, pos);
   } else if (result_type->AsVector()) {
     // Vector operation
     const analysis::Type* element_type =
@@ -414,11 +326,10 @@
                                                         operands);
     std::vector<const analysis::Constant*> result_vector_components;
     for (const uint32_t r : result_vec) {
-      if (auto rc = context()->get_constant_mgr()->GetConstant(
+      if (auto rc = const_mgr->GetConstant(
               element_type, EncodeIntegerAsWords(*element_type, r))) {
         result_vector_components.push_back(rc);
-        if (!context()->get_constant_mgr()->BuildInstructionAndAddToModule(
-                rc, pos)) {
+        if (!const_mgr->BuildInstructionAndAddToModule(rc, pos)) {
           assert(false &&
                  "Failed to build and insert constant declaring instruction "
                  "for the given vector component constant");
@@ -429,10 +340,8 @@
     }
     auto new_vec_const = MakeUnique<analysis::VectorConstant>(
         result_type->AsVector(), result_vector_components);
-    auto reg_vec_const = context()->get_constant_mgr()->RegisterConstant(
-        std::move(new_vec_const));
-    return context()->get_constant_mgr()->BuildInstructionAndAddToModule(
-        reg_vec_const, pos);
+    auto reg_vec_const = const_mgr->RegisterConstant(std::move(new_vec_const));
+    return const_mgr->BuildInstructionAndAddToModule(reg_vec_const, pos);
   } else {
     // Cannot process invalid component wise operation. The result of component
     // wise operation must be of integer or bool scalar or vector of
diff --git a/source/opt/fold_spec_constant_op_and_composite_pass.h b/source/opt/fold_spec_constant_op_and_composite_pass.h
index 361d3ca..9a8fb40 100644
--- a/source/opt/fold_spec_constant_op_and_composite_pass.h
+++ b/source/opt/fold_spec_constant_op_and_composite_pass.h
@@ -58,22 +58,11 @@
   // |inst_iter_ptr| using the instruction folder.
   Instruction* FoldWithInstructionFolder(Module::inst_iterator* inst_iter_ptr);
 
-  // Try to fold the OpSpecConstantOp VectorShuffle instruction pointed by the
-  // given instruction iterator to a normal constant defining instruction.
-  // Returns the pointer to the new constant defining instruction if succeeded.
-  // Otherwise return nullptr.
-  Instruction* DoVectorShuffle(Module::inst_iterator* inst_iter_ptr);
-
   // Try to fold the OpSpecConstantOp <component wise operations> instruction
   // pointed by the given instruction iterator to a normal constant defining
   // instruction. Returns the pointer to the new constant defining instruction
   // if succeeded, otherwise return nullptr.
   Instruction* DoComponentWiseOperation(Module::inst_iterator* inst_iter_ptr);
-
-  // Returns the |element|'th subtype of |type|.
-  //
-  // |type| must be a composite type.
-  uint32_t GetTypeComponent(uint32_t type, uint32_t element) const;
 };
 
 }  // namespace opt
diff --git a/source/opt/folding_rules.cpp b/source/opt/folding_rules.cpp
index 4904f18..3d803ad 100644
--- a/source/opt/folding_rules.cpp
+++ b/source/opt/folding_rules.cpp
@@ -136,25 +136,28 @@
     const analysis::IntConstant* c) {
   assert(c != nullptr);
   uint32_t width = c->type()->AsInteger()->width();
-  assert(width == 32 || width == 64);
+  assert(width == 8 || width == 16 || width == 32 || width == 64);
   if (width == 64) {
     uint64_t uval = static_cast<uint64_t>(c->GetU64());
     return ExtractInts(uval);
   }
-  return {c->GetU32()};
+  // Section 2.2.1 of the SPIR-V spec guarantees that all integer types
+  // smaller than 32-bits are automatically zero or sign extended to 32-bits.
+  return {c->GetU32BitValue()};
 }
 
 std::vector<uint32_t> GetWordsFromScalarFloatConstant(
     const analysis::FloatConstant* c) {
   assert(c != nullptr);
   uint32_t width = c->type()->AsFloat()->width();
-  assert(width == 32 || width == 64);
+  assert(width == 16 || width == 32 || width == 64);
   if (width == 64) {
     utils::FloatProxy<double> result(c->GetDouble());
     return result.GetWords();
   }
-  utils::FloatProxy<float> result(c->GetFloat());
-  return result.GetWords();
+  // Section 2.2.1 of the SPIR-V spec guarantees that all floating-point types
+  // smaller than 32-bits are automatically zero extended to 32-bits.
+  return {c->GetU32BitValue()};
 }
 
 std::vector<uint32_t> GetWordsFromNumericScalarOrVectorConstant(
@@ -277,6 +280,11 @@
   uint32_t width = c->type()->AsFloat()->width();
   assert(width == 32 || width == 64);
   std::vector<uint32_t> words;
+
+  if (c->IsZero()) {
+    return 0;
+  }
+
   if (width == 64) {
     spvtools::utils::FloatProxy<double> result(1.0 / c->GetDouble());
     if (!IsValidResult(result.getAsFloat())) return 0;
@@ -1430,6 +1438,132 @@
   };
 }
 
+// Replaces |inst| inplace with an FMA instruction |(x*y)+a|.
+void ReplaceWithFma(Instruction* inst, uint32_t x, uint32_t y, uint32_t a) {
+  uint32_t ext =
+      inst->context()->get_feature_mgr()->GetExtInstImportId_GLSLstd450();
+
+  if (ext == 0) {
+    inst->context()->AddExtInstImport("GLSL.std.450");
+    ext = inst->context()->get_feature_mgr()->GetExtInstImportId_GLSLstd450();
+    assert(ext != 0 &&
+           "Could not add the GLSL.std.450 extended instruction set");
+  }
+
+  std::vector<Operand> operands;
+  operands.push_back({SPV_OPERAND_TYPE_ID, {ext}});
+  operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {GLSLstd450Fma}});
+  operands.push_back({SPV_OPERAND_TYPE_ID, {x}});
+  operands.push_back({SPV_OPERAND_TYPE_ID, {y}});
+  operands.push_back({SPV_OPERAND_TYPE_ID, {a}});
+
+  inst->SetOpcode(SpvOpExtInst);
+  inst->SetInOperands(std::move(operands));
+}
+
+// Folds a multiple and add into an Fma.
+//
+// Cases:
+// (x * y) + a = Fma x y a
+// a + (x * y) = Fma x y a
+bool MergeMulAddArithmetic(IRContext* context, Instruction* inst,
+                           const std::vector<const analysis::Constant*>&) {
+  assert(inst->opcode() == SpvOpFAdd);
+
+  if (!inst->IsFloatingPointFoldingAllowed()) {
+    return false;
+  }
+
+  analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
+  for (int i = 0; i < 2; i++) {
+    uint32_t op_id = inst->GetSingleWordInOperand(i);
+    Instruction* op_inst = def_use_mgr->GetDef(op_id);
+
+    if (op_inst->opcode() != SpvOpFMul) {
+      continue;
+    }
+
+    if (!op_inst->IsFloatingPointFoldingAllowed()) {
+      continue;
+    }
+
+    uint32_t x = op_inst->GetSingleWordInOperand(0);
+    uint32_t y = op_inst->GetSingleWordInOperand(1);
+    uint32_t a = inst->GetSingleWordInOperand((i + 1) % 2);
+    ReplaceWithFma(inst, x, y, a);
+    return true;
+  }
+  return false;
+}
+
+// Replaces |sub| inplace with an FMA instruction |(x*y)+a| where |a| first gets
+// negated if |negate_addition| is true, otherwise |x| gets negated.
+void ReplaceWithFmaAndNegate(Instruction* sub, uint32_t x, uint32_t y,
+                             uint32_t a, bool negate_addition) {
+  uint32_t ext =
+      sub->context()->get_feature_mgr()->GetExtInstImportId_GLSLstd450();
+
+  if (ext == 0) {
+    sub->context()->AddExtInstImport("GLSL.std.450");
+    ext = sub->context()->get_feature_mgr()->GetExtInstImportId_GLSLstd450();
+    assert(ext != 0 &&
+           "Could not add the GLSL.std.450 extended instruction set");
+  }
+
+  InstructionBuilder ir_builder(
+      sub->context(), sub,
+      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+
+  Instruction* neg = ir_builder.AddUnaryOp(sub->type_id(), SpvOpFNegate,
+                                           negate_addition ? a : x);
+  uint32_t neg_op = neg->result_id();  // -a : -x
+
+  std::vector<Operand> operands;
+  operands.push_back({SPV_OPERAND_TYPE_ID, {ext}});
+  operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {GLSLstd450Fma}});
+  operands.push_back({SPV_OPERAND_TYPE_ID, {negate_addition ? x : neg_op}});
+  operands.push_back({SPV_OPERAND_TYPE_ID, {y}});
+  operands.push_back({SPV_OPERAND_TYPE_ID, {negate_addition ? neg_op : a}});
+
+  sub->SetOpcode(SpvOpExtInst);
+  sub->SetInOperands(std::move(operands));
+}
+
+// Folds a multiply and subtract into an Fma and negation.
+//
+// Cases:
+// (x * y) - a = Fma x y -a
+// a - (x * y) = Fma -x y a
+bool MergeMulSubArithmetic(IRContext* context, Instruction* sub,
+                           const std::vector<const analysis::Constant*>&) {
+  assert(sub->opcode() == SpvOpFSub);
+
+  if (!sub->IsFloatingPointFoldingAllowed()) {
+    return false;
+  }
+
+  analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
+  for (int i = 0; i < 2; i++) {
+    uint32_t op_id = sub->GetSingleWordInOperand(i);
+    Instruction* mul = def_use_mgr->GetDef(op_id);
+
+    if (mul->opcode() != SpvOpFMul) {
+      continue;
+    }
+
+    if (!mul->IsFloatingPointFoldingAllowed()) {
+      continue;
+    }
+
+    uint32_t x = mul->GetSingleWordInOperand(0);
+    uint32_t y = mul->GetSingleWordInOperand(1);
+    uint32_t a = sub->GetSingleWordInOperand((i + 1) % 2);
+    ReplaceWithFmaAndNegate(sub, x, y, a, i == 0);
+    return true;
+  }
+  return false;
+}
+
 FoldingRule IntMultipleBy1() {
   return [](IRContext*, Instruction* inst,
             const std::vector<const analysis::Constant*>& constants) {
@@ -1573,6 +1707,57 @@
   return true;
 }
 
+// Walks the indexes chain from |start| to |end| of an OpCompositeInsert or
+// OpCompositeExtract instruction, and returns the type of the final element
+// being accessed.
+const analysis::Type* GetElementType(uint32_t type_id,
+                                     Instruction::iterator start,
+                                     Instruction::iterator end,
+                                     const analysis::TypeManager* type_mgr) {
+  const analysis::Type* type = type_mgr->GetType(type_id);
+  for (auto index : make_range(std::move(start), std::move(end))) {
+    assert(index.type == SPV_OPERAND_TYPE_LITERAL_INTEGER &&
+           index.words.size() == 1);
+    if (auto* array_type = type->AsArray()) {
+      type = array_type->element_type();
+    } else if (auto* matrix_type = type->AsMatrix()) {
+      type = matrix_type->element_type();
+    } else if (auto* struct_type = type->AsStruct()) {
+      type = struct_type->element_types()[index.words[0]];
+    } else {
+      type = nullptr;
+    }
+  }
+  return type;
+}
+
+// Returns true of |inst_1| and |inst_2| have the same indexes that will be used
+// to index into a composite object, excluding the last index.  The two
+// instructions must have the same opcode, and be either OpCompositeExtract or
+// OpCompositeInsert instructions.
+bool HaveSameIndexesExceptForLast(Instruction* inst_1, Instruction* inst_2) {
+  assert(inst_1->opcode() == inst_2->opcode() &&
+         "Expecting the opcodes to be the same.");
+  assert((inst_1->opcode() == SpvOpCompositeInsert ||
+          inst_1->opcode() == SpvOpCompositeExtract) &&
+         "Instructions must be OpCompositeInsert or OpCompositeExtract.");
+
+  if (inst_1->NumInOperands() != inst_2->NumInOperands()) {
+    return false;
+  }
+
+  uint32_t first_index_position =
+      (inst_1->opcode() == SpvOpCompositeInsert ? 2 : 1);
+  for (uint32_t i = first_index_position; i < inst_1->NumInOperands() - 1;
+       i++) {
+    if (inst_1->GetSingleWordInOperand(i) !=
+        inst_2->GetSingleWordInOperand(i)) {
+      return false;
+    }
+  }
+  return true;
+}
+
 // If the OpCompositeConstruct is simply putting back together elements that
 // where extracted from the same source, we can simply reuse the source.
 //
@@ -1595,19 +1780,24 @@
   // - extractions
   // - extracting the same position they are inserting
   // - all extract from the same id.
+  Instruction* first_element_inst = nullptr;
   for (uint32_t i = 0; i < inst->NumInOperands(); ++i) {
     const uint32_t element_id = inst->GetSingleWordInOperand(i);
     Instruction* element_inst = def_use_mgr->GetDef(element_id);
+    if (first_element_inst == nullptr) {
+      first_element_inst = element_inst;
+    }
 
     if (element_inst->opcode() != SpvOpCompositeExtract) {
       return false;
     }
 
-    if (element_inst->NumInOperands() != 2) {
+    if (!HaveSameIndexesExceptForLast(element_inst, first_element_inst)) {
       return false;
     }
 
-    if (element_inst->GetSingleWordInOperand(1) != i) {
+    if (element_inst->GetSingleWordInOperand(element_inst->NumInOperands() -
+                                             1) != i) {
       return false;
     }
 
@@ -1623,13 +1813,31 @@
   // The last check it to see that the object being extracted from is the
   // correct type.
   Instruction* original_inst = def_use_mgr->GetDef(original_id);
-  if (original_inst->type_id() != inst->type_id()) {
+  analysis::TypeManager* type_mgr = context->get_type_mgr();
+  const analysis::Type* original_type =
+      GetElementType(original_inst->type_id(), first_element_inst->begin() + 3,
+                     first_element_inst->end() - 1, type_mgr);
+
+  if (original_type == nullptr) {
     return false;
   }
 
-  // Simplify by using the original object.
-  inst->SetOpcode(SpvOpCopyObject);
-  inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {original_id}}});
+  if (inst->type_id() != type_mgr->GetId(original_type)) {
+    return false;
+  }
+
+  if (first_element_inst->NumInOperands() == 2) {
+    // Simplify by using the original object.
+    inst->SetOpcode(SpvOpCopyObject);
+    inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {original_id}}});
+    return true;
+  }
+
+  // Copies the original id and all indexes except for the last to the new
+  // extract instruction.
+  inst->SetOpcode(SpvOpCompositeExtract);
+  inst->SetInOperands(std::vector<Operand>(first_element_inst->begin() + 2,
+                                           first_element_inst->end() - 1));
   return true;
 }
 
@@ -1833,6 +2041,139 @@
   };
 }
 
+// Returns the number of elements in the composite type |type|.  Returns 0 if
+// |type| is a scalar value.
+uint32_t GetNumberOfElements(const analysis::Type* type) {
+  if (auto* vector_type = type->AsVector()) {
+    return vector_type->element_count();
+  }
+  if (auto* matrix_type = type->AsMatrix()) {
+    return matrix_type->element_count();
+  }
+  if (auto* struct_type = type->AsStruct()) {
+    return static_cast<uint32_t>(struct_type->element_types().size());
+  }
+  if (auto* array_type = type->AsArray()) {
+    return array_type->length_info().words[0];
+  }
+  return 0;
+}
+
+// Returns a map with the set of values that were inserted into an object by
+// the chain of OpCompositeInsertInstruction starting with |inst|.
+// The map will map the index to the value inserted at that index.
+std::map<uint32_t, uint32_t> GetInsertedValues(Instruction* inst) {
+  analysis::DefUseManager* def_use_mgr = inst->context()->get_def_use_mgr();
+  std::map<uint32_t, uint32_t> values_inserted;
+  Instruction* current_inst = inst;
+  while (current_inst->opcode() == SpvOpCompositeInsert) {
+    if (current_inst->NumInOperands() > inst->NumInOperands()) {
+      // This is the catch the case
+      //   %2 = OpCompositeInsert %m2x2int %v2int_1_0 %m2x2int_undef 0
+      //   %3 = OpCompositeInsert %m2x2int %int_4 %2 0 0
+      //   %4 = OpCompositeInsert %m2x2int %v2int_2_3 %3 1
+      // In this case we cannot do a single construct to get the matrix.
+      uint32_t partially_inserted_element_index =
+          current_inst->GetSingleWordInOperand(inst->NumInOperands() - 1);
+      if (values_inserted.count(partially_inserted_element_index) == 0)
+        return {};
+    }
+    if (HaveSameIndexesExceptForLast(inst, current_inst)) {
+      values_inserted.insert(
+          {current_inst->GetSingleWordInOperand(current_inst->NumInOperands() -
+                                                1),
+           current_inst->GetSingleWordInOperand(kInsertObjectIdInIdx)});
+    }
+    current_inst = def_use_mgr->GetDef(
+        current_inst->GetSingleWordInOperand(kInsertCompositeIdInIdx));
+  }
+  return values_inserted;
+}
+
+// Returns true of there is an entry in |values_inserted| for every element of
+// |Type|.
+bool DoInsertedValuesCoverEntireObject(
+    const analysis::Type* type, std::map<uint32_t, uint32_t>& values_inserted) {
+  uint32_t container_size = GetNumberOfElements(type);
+  if (container_size != values_inserted.size()) {
+    return false;
+  }
+
+  if (values_inserted.rbegin()->first >= container_size) {
+    return false;
+  }
+  return true;
+}
+
+// Returns the type of the element that immediately contains the element being
+// inserted by the OpCompositeInsert instruction |inst|.
+const analysis::Type* GetContainerType(Instruction* inst) {
+  assert(inst->opcode() == SpvOpCompositeInsert);
+  analysis::TypeManager* type_mgr = inst->context()->get_type_mgr();
+  return GetElementType(inst->type_id(), inst->begin() + 4, inst->end() - 1,
+                        type_mgr);
+}
+
+// Returns an OpCompositeConstruct instruction that build an object with
+// |type_id| out of the values in |values_inserted|.  Each value will be
+// placed at the index corresponding to the value.  The new instruction will
+// be placed before |insert_before|.
+Instruction* BuildCompositeConstruct(
+    uint32_t type_id, const std::map<uint32_t, uint32_t>& values_inserted,
+    Instruction* insert_before) {
+  InstructionBuilder ir_builder(
+      insert_before->context(), insert_before,
+      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+
+  std::vector<uint32_t> ids_in_order;
+  for (auto it : values_inserted) {
+    ids_in_order.push_back(it.second);
+  }
+  Instruction* construct =
+      ir_builder.AddCompositeConstruct(type_id, ids_in_order);
+  return construct;
+}
+
+// Replaces the OpCompositeInsert |inst| that inserts |construct| into the same
+// object as |inst| with final index removed.  If the resulting
+// OpCompositeInsert instruction would have no remaining indexes, the
+// instruction is replaced with an OpCopyObject instead.
+void InsertConstructedObject(Instruction* inst, const Instruction* construct) {
+  if (inst->NumInOperands() == 3) {
+    inst->SetOpcode(SpvOpCopyObject);
+    inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {construct->result_id()}}});
+  } else {
+    inst->SetInOperand(kInsertObjectIdInIdx, {construct->result_id()});
+    inst->RemoveOperand(inst->NumOperands() - 1);
+  }
+}
+
+// Replaces a series of |OpCompositeInsert| instruction that cover the entire
+// object with an |OpCompositeConstruct|.
+bool CompositeInsertToCompositeConstruct(
+    IRContext* context, Instruction* inst,
+    const std::vector<const analysis::Constant*>&) {
+  assert(inst->opcode() == SpvOpCompositeInsert &&
+         "Wrong opcode.  Should be OpCompositeInsert.");
+  if (inst->NumInOperands() < 3) return false;
+
+  std::map<uint32_t, uint32_t> values_inserted = GetInsertedValues(inst);
+  const analysis::Type* container_type = GetContainerType(inst);
+  if (container_type == nullptr) {
+    return false;
+  }
+
+  if (!DoInsertedValuesCoverEntireObject(container_type, values_inserted)) {
+    return false;
+  }
+
+  analysis::TypeManager* type_mgr = context->get_type_mgr();
+  Instruction* construct = BuildCompositeConstruct(
+      type_mgr->GetId(container_type), values_inserted, inst);
+  InsertConstructedObject(inst, construct);
+  return true;
+}
+
 FoldingRule RedundantPhi() {
   // An OpPhi instruction where all values are the same or the result of the phi
   // itself, can be replaced by the value itself.
@@ -2368,7 +2709,7 @@
             // fold.
             return false;
           }
-        } else {
+        } else if (component_index != undef_literal) {
           if (new_feeder_id == 0) {
             // First time through, save the id of the operand the element comes
             // from.
@@ -2382,7 +2723,7 @@
           component_index -= feeder_op0_length;
         }
 
-        if (!feeder_is_op0) {
+        if (!feeder_is_op0 && component_index != undef_literal) {
           component_index += op0_length;
         }
       }
@@ -2410,7 +2751,8 @@
 
       if (adjustment != 0) {
         for (uint32_t i = 2; i < new_operands.size(); i++) {
-          if (inst->GetSingleWordInOperand(i) >= op0_length) {
+          uint32_t operand = inst->GetSingleWordInOperand(i);
+          if (operand >= op0_length && operand != undef_literal) {
             new_operands[i].words[0] -= adjustment;
           }
         }
@@ -2533,6 +2875,8 @@
   rules_[SpvOpCompositeExtract].push_back(VectorShuffleFeedingExtract());
   rules_[SpvOpCompositeExtract].push_back(FMixFeedingExtract());
 
+  rules_[SpvOpCompositeInsert].push_back(CompositeInsertToCompositeConstruct);
+
   rules_[SpvOpDot].push_back(DotProductDoingExtract());
 
   rules_[SpvOpEntryPoint].push_back(RemoveRedundantOperands());
@@ -2543,6 +2887,7 @@
   rules_[SpvOpFAdd].push_back(MergeAddSubArithmetic());
   rules_[SpvOpFAdd].push_back(MergeGenericAddSubArithmetic());
   rules_[SpvOpFAdd].push_back(FactorAddMuls());
+  rules_[SpvOpFAdd].push_back(MergeMulAddArithmetic);
 
   rules_[SpvOpFDiv].push_back(RedundantFDiv());
   rules_[SpvOpFDiv].push_back(ReciprocalFDiv());
@@ -2563,6 +2908,7 @@
   rules_[SpvOpFSub].push_back(MergeSubNegateArithmetic());
   rules_[SpvOpFSub].push_back(MergeSubAddArithmetic());
   rules_[SpvOpFSub].push_back(MergeSubSubArithmetic());
+  rules_[SpvOpFSub].push_back(MergeMulSubArithmetic);
 
   rules_[SpvOpIAdd].push_back(RedundantIAdd());
   rules_[SpvOpIAdd].push_back(MergeAddNegateArithmetic());
diff --git a/source/opt/function.cpp b/source/opt/function.cpp
index 38c6695..bb51df3 100644
--- a/source/opt/function.cpp
+++ b/source/opt/function.cpp
@@ -270,5 +270,13 @@
   });
   return str.str();
 }
+
+void Function::ReorderBasicBlocksInStructuredOrder() {
+  std::list<BasicBlock*> order;
+  IRContext* context = this->def_inst_->context();
+  context->cfg()->ComputeStructuredOrder(this, blocks_[0].get(), &order);
+  ReorderBasicBlocks(order.begin(), order.end());
+}
+
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/function.h b/source/opt/function.h
index 917bf58..146cbe3 100644
--- a/source/opt/function.h
+++ b/source/opt/function.h
@@ -19,6 +19,7 @@
 #include <functional>
 #include <memory>
 #include <string>
+#include <unordered_set>
 #include <utility>
 #include <vector>
 
@@ -180,7 +181,19 @@
   // Returns true is a function declaration and not a function definition.
   bool IsDeclaration() { return begin() == end(); }
 
+  // Reorders the basic blocks in the function to match the structured order.
+  void ReorderBasicBlocksInStructuredOrder();
+
  private:
+  // Reorders the basic blocks in the function to match the order given by the
+  // range |{begin,end}|.  The range must contain every basic block in the
+  // function, and no extras.
+  template <class It>
+  void ReorderBasicBlocks(It begin, It end);
+
+  template <class It>
+  bool ContainsAllBlocksInTheFunction(It begin, It end);
+
   // The OpFunction instruction that begins the definition of this function.
   std::unique_ptr<Instruction> def_inst_;
   // All parameters to this function.
@@ -262,6 +275,34 @@
   non_semantic_.emplace_back(std::move(non_semantic));
 }
 
+template <class It>
+void Function::ReorderBasicBlocks(It begin, It end) {
+  // Asserts to make sure every node in the function is in new_order.
+  assert(ContainsAllBlocksInTheFunction(begin, end));
+
+  // We have a pointer to all the elements in order, so we can release all
+  // pointers in |block_|, and then create the new unique pointers from |{begin,
+  // end}|.
+  std::for_each(blocks_.begin(), blocks_.end(),
+                [](std::unique_ptr<BasicBlock>& bb) { bb.release(); });
+  std::transform(begin, end, blocks_.begin(), [](BasicBlock* bb) {
+    return std::unique_ptr<BasicBlock>(bb);
+  });
+}
+
+template <class It>
+bool Function::ContainsAllBlocksInTheFunction(It begin, It end) {
+  std::unordered_multiset<BasicBlock*> range(begin, end);
+  if (range.size() != blocks_.size()) {
+    return false;
+  }
+
+  for (auto& bb : blocks_) {
+    if (range.count(bb.get()) == 0) return false;
+  }
+  return true;
+}
+
 }  // namespace opt
 }  // namespace spvtools
 
diff --git a/source/opt/graphics_robust_access_pass.cpp b/source/opt/graphics_robust_access_pass.cpp
index 1b28f9b..4652d72 100644
--- a/source/opt/graphics_robust_access_pass.cpp
+++ b/source/opt/graphics_robust_access_pass.cpp
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 // This pass injects code in a graphics shader to implement guarantees
-// satisfying Vulkan's robustBufferAcces rules.  Robust access rules permit
+// satisfying Vulkan's robustBufferAccess rules.  Robust access rules permit
 // an out-of-bounds access to be redirected to an access of the same type
 // (load, store, etc.) but within the same root object.
 //
@@ -74,7 +74,7 @@
 //    Pointers are always (correctly) typed and so the address and number of
 //    consecutive locations are fully determined by the pointer.
 //
-//  - A pointer value orginates as one of few cases:
+//  - A pointer value originates as one of few cases:
 //
 //    - OpVariable for an interface object or an array of them: image,
 //      buffer (UBO or SSBO), sampler, sampled-image, push-constant, input
@@ -559,21 +559,17 @@
   if (module_status_.glsl_insts_id == 0) {
     // This string serves double-duty as raw data for a string and for a vector
     // of 32-bit words
-    const char glsl[] = "GLSL.std.450\0\0\0\0";
-    const size_t glsl_str_byte_len = 16;
+    const char glsl[] = "GLSL.std.450";
     // Use an existing import if we can.
     for (auto& inst : context()->module()->ext_inst_imports()) {
-      const auto& name_words = inst.GetInOperand(0).words;
-      if (0 == std::strncmp(reinterpret_cast<const char*>(name_words.data()),
-                            glsl, glsl_str_byte_len)) {
+      if (inst.GetInOperand(0).AsString() == glsl) {
         module_status_.glsl_insts_id = inst.result_id();
       }
     }
     if (module_status_.glsl_insts_id == 0) {
       // Make a new import instruction.
       module_status_.glsl_insts_id = TakeNextId();
-      std::vector<uint32_t> words(glsl_str_byte_len / sizeof(uint32_t));
-      std::memcpy(words.data(), glsl, glsl_str_byte_len);
+      std::vector<uint32_t> words = spvtools::utils::MakeVector(glsl);
       auto import_inst = MakeUnique<Instruction>(
           context(), SpvOpExtInstImport, 0, module_status_.glsl_insts_id,
           std::initializer_list<Operand>{
@@ -962,7 +958,7 @@
       constant_mgr->GetDefiningInstruction(component_0)->result_id();
 
   // If the image is a cube array, then the last component of the queried
-  // size is the layer count.  In the query, we have to accomodate folding
+  // size is the layer count.  In the query, we have to accommodate folding
   // in the face index ranging from 0 through 5. The inclusive upper bound
   // on the third coordinate therefore is multiplied by 6.
   auto* query_size_including_faces = query_size;
diff --git a/source/opt/graphics_robust_access_pass.h b/source/opt/graphics_robust_access_pass.h
index 6fc692c..8f4c9dc 100644
--- a/source/opt/graphics_robust_access_pass.h
+++ b/source/opt/graphics_robust_access_pass.h
@@ -111,7 +111,7 @@
                                    Instruction* max, Instruction* where);
 
   // Returns a new instruction which evaluates to the length the runtime array
-  // referenced by the access chain at the specfied index.  The instruction is
+  // referenced by the access chain at the specified index.  The instruction is
   // inserted before the access chain instruction.  Returns a null pointer in
   // some cases if assumptions are violated (rather than asserting out).
   opt::Instruction* MakeRuntimeArrayLengthInst(Instruction* access_chain,
diff --git a/source/opt/if_conversion.cpp b/source/opt/if_conversion.cpp
index 4920661..1232796 100644
--- a/source/opt/if_conversion.cpp
+++ b/source/opt/if_conversion.cpp
@@ -160,6 +160,11 @@
   BasicBlock* inc1 = context()->get_instr_block(preds[1]);
   if (dominators->Dominates(block, inc1)) return false;
 
+  if (inc0 == inc1) {
+    // If the predecessor blocks are the same, then there is only 1 value for
+    // the OpPhi.  Other transformation should be able to simplify that.
+    return false;
+  }
   // All phis will have the same common dominator, so cache the result
   // for this block. If there is no common dominator, then we cannot transform
   // any phi in this basic block.
@@ -169,6 +174,8 @@
   if (branch->opcode() != SpvOpBranchConditional) return false;
   auto merge = (*common)->GetMergeInst();
   if (!merge || merge->opcode() != SpvOpSelectionMerge) return false;
+  if (merge->GetSingleWordInOperand(1) == SpvSelectionControlDontFlattenMask)
+    return false;
   if ((*common)->MergeBlockIdIfAny() != block->id()) return false;
 
   return true;
diff --git a/source/opt/inline_pass.cpp b/source/opt/inline_pass.cpp
index 2cc3125..e14516f 100644
--- a/source/opt/inline_pass.cpp
+++ b/source/opt/inline_pass.cpp
@@ -508,6 +508,37 @@
   delete &*loop_merge_itr;
 }
 
+void InlinePass::UpdateSingleBlockLoopContinueTarget(
+    uint32_t new_id, std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+  auto& header = new_blocks->front();
+  auto* merge_inst = header->GetLoopMergeInst();
+
+  // The back-edge block is split at the branch to create a new back-edge
+  // block. The old block is modified to branch to the new block. The loop
+  // merge instruction is updated to declare the new block as the continue
+  // target. This has the effect of changing the loop from being a large
+  // continue construct and an empty loop construct to being a loop with a loop
+  // construct and a trivial continue construct. This change is made to satisfy
+  // structural dominance.
+
+  // Add the new basic block.
+  std::unique_ptr<BasicBlock> new_block =
+      MakeUnique<BasicBlock>(NewLabel(new_id));
+  auto& old_backedge = new_blocks->back();
+  auto old_branch = old_backedge->tail();
+
+  // Move the old back edge into the new block.
+  std::unique_ptr<Instruction> br(&*old_branch);
+  new_block->AddInstruction(std::move(br));
+
+  // Add a branch to the new block from the old back-edge block.
+  AddBranch(new_id, &old_backedge);
+  new_blocks->push_back(std::move(new_block));
+
+  // Update the loop's continue target to the new block.
+  merge_inst->SetInOperand(1u, {new_id});
+}
+
 bool InlinePass::GenInlineCode(
     std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
     std::vector<std::unique_ptr<Instruction>>* new_vars,
@@ -639,9 +670,19 @@
   // Finalize inline code.
   new_blocks->push_back(std::move(new_blk_ptr));
 
-  if (caller_is_loop_header && (new_blocks->size() > 1))
+  if (caller_is_loop_header && (new_blocks->size() > 1)) {
     MoveLoopMergeInstToFirstBlock(new_blocks);
 
+    // If the loop was a single basic block previously, update it's structure.
+    auto& header = new_blocks->front();
+    auto* merge_inst = header->GetLoopMergeInst();
+    if (merge_inst->GetSingleWordInOperand(1u) == header->id()) {
+      auto new_id = context()->TakeNextId();
+      if (new_id == 0) return false;
+      UpdateSingleBlockLoopContinueTarget(new_id, new_blocks);
+    }
+  }
+
   // Update block map given replacement blocks.
   for (auto& blk : *new_blocks) {
     id2block_[blk->id()] = &*blk;
@@ -753,22 +794,25 @@
     return false;
   }
 
-  // Do not inline functions with an OpKill if they are called from a continue
-  // construct. If it is inlined into a continue construct it will generate
-  // invalid code.
+  // Do not inline functions with an abort instruction if they are called from a
+  // continue construct. If it is inlined into a continue construct the backedge
+  // will no longer post-dominate the continue target, which is invalid.  An
+  // `OpUnreachable` is acceptable because it will not change post-dominance if
+  // it is statically unreachable.
   bool func_is_called_from_continue =
       funcs_called_from_continue_.count(func->result_id()) != 0;
 
-  if (func_is_called_from_continue && ContainsKillOrTerminateInvocation(func)) {
+  if (func_is_called_from_continue && ContainsAbortOtherThanUnreachable(func)) {
     return false;
   }
 
   return true;
 }
 
-bool InlinePass::ContainsKillOrTerminateInvocation(Function* func) const {
+bool InlinePass::ContainsAbortOtherThanUnreachable(Function* func) const {
   return !func->WhileEachInst([](Instruction* inst) {
-    return !spvOpcodeTerminatesExecution(inst->opcode());
+    return inst->opcode() == SpvOpUnreachable ||
+           !spvOpcodeIsAbort(inst->opcode());
   });
 }
 
diff --git a/source/opt/inline_pass.h b/source/opt/inline_pass.h
index 9a5429b..d29c1e0 100644
--- a/source/opt/inline_pass.h
+++ b/source/opt/inline_pass.h
@@ -139,9 +139,9 @@
   // Return true if |func| is a function that can be inlined.
   bool IsInlinableFunction(Function* func);
 
-  // Returns true if |func| contains an OpKill or OpTerminateInvocation
-  // instruction.
-  bool ContainsKillOrTerminateInvocation(Function* func) const;
+  // Returns true if |func| contains an abort instruction that is not an
+  // `OpUnreachable` instruction.
+  bool ContainsAbortOtherThanUnreachable(Function* func) const;
 
   // Update phis in succeeding blocks to point to new last block
   void UpdateSucceedingPhis(
@@ -235,6 +235,12 @@
   // Move the OpLoopMerge from the last block back to the first.
   void MoveLoopMergeInstToFirstBlock(
       std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+
+  // Update the structure of single block loops so that the inlined code ends
+  // up in the loop construct and a new continue target is added to satisfy
+  // structural dominance.
+  void UpdateSingleBlockLoopContinueTarget(
+      uint32_t new_id, std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
 };
 
 }  // namespace opt
diff --git a/source/opt/inst_bindless_check_pass.cpp b/source/opt/inst_bindless_check_pass.cpp
index 5607239..c2c5d6c 100644
--- a/source/opt/inst_bindless_check_pass.cpp
+++ b/source/opt/inst_bindless_check_pass.cpp
@@ -39,13 +39,6 @@
 static const int kSpvTypeImageSampled = 5;
 }  // anonymous namespace
 
-// Avoid unused variable warning/error on Linux
-#ifndef NDEBUG
-#define USE_ASSERT(x) assert(x)
-#else
-#define USE_ASSERT(x) ((void)(x))
-#endif
-
 namespace spvtools {
 namespace opt {
 
diff --git a/source/opt/inst_bindless_check_pass.h b/source/opt/inst_bindless_check_pass.h
index cd96180..e6e6ef4 100644
--- a/source/opt/inst_bindless_check_pass.h
+++ b/source/opt/inst_bindless_check_pass.h
@@ -147,11 +147,11 @@
   uint32_t GenLastByteIdx(RefAnalysis* ref, InstructionBuilder* builder);
 
   // Clone original image computation starting at |image_id| into |builder|.
-  // This may generate more than one instruction if neccessary.
+  // This may generate more than one instruction if necessary.
   uint32_t CloneOriginalImage(uint32_t image_id, InstructionBuilder* builder);
 
   // Clone original original reference encapsulated by |ref| into |builder|.
-  // This may generate more than one instruction if neccessary.
+  // This may generate more than one instruction if necessary.
   uint32_t CloneOriginalReference(RefAnalysis* ref,
                                   InstructionBuilder* builder);
 
diff --git a/source/opt/inst_buff_addr_check_pass.cpp b/source/opt/inst_buff_addr_check_pass.cpp
index e2336d3..3318f88 100644
--- a/source/opt/inst_buff_addr_check_pass.cpp
+++ b/source/opt/inst_buff_addr_check_pass.cpp
@@ -393,6 +393,8 @@
     get_def_use_mgr()->AnalyzeInstDefUse(&*func_end_inst);
     input_func->SetFunctionEnd(std::move(func_end_inst));
     context()->AddFunction(std::move(input_func));
+    context()->AddDebug2Inst(
+        NewGlobalName(search_test_func_id_, "search_and_test"));
   }
   return search_test_func_id_;
 }
diff --git a/source/opt/inst_buff_addr_check_pass.h b/source/opt/inst_buff_addr_check_pass.h
index a823223..fb43c39 100644
--- a/source/opt/inst_buff_addr_check_pass.h
+++ b/source/opt/inst_buff_addr_check_pass.h
@@ -39,7 +39,7 @@
   // See optimizer.hpp for pass user documentation.
   Status Process() override;
 
-  const char* name() const override { return "inst-bindless-check-pass"; }
+  const char* name() const override { return "inst-buff-addr-check-pass"; }
 
  private:
   // Return byte alignment of type |type_id|. Must be int, float, vector,
diff --git a/source/opt/inst_debug_printf_pass.cpp b/source/opt/inst_debug_printf_pass.cpp
index c0e6bc3..4218138 100644
--- a/source/opt/inst_debug_printf_pass.cpp
+++ b/source/opt/inst_debug_printf_pass.cpp
@@ -16,6 +16,7 @@
 
 #include "inst_debug_printf_pass.h"
 
+#include "source/util/string_utils.h"
 #include "spirv/unified1/NonSemanticDebugPrintf.h"
 
 namespace spvtools {
@@ -231,10 +232,8 @@
   bool non_sem_set_seen = false;
   for (auto c_itr = context()->module()->ext_inst_import_begin();
        c_itr != context()->module()->ext_inst_import_end(); ++c_itr) {
-    const char* set_name =
-        reinterpret_cast<const char*>(&c_itr->GetInOperand(0).words[0]);
-    const char* non_sem_str = "NonSemantic.";
-    if (!strncmp(set_name, non_sem_str, strlen(non_sem_str))) {
+    const std::string set_name = c_itr->GetInOperand(0).AsString();
+    if (spvtools::utils::starts_with(set_name, "NonSemantic.")) {
       non_sem_set_seen = true;
       break;
     }
@@ -242,9 +241,8 @@
   if (!non_sem_set_seen) {
     for (auto c_itr = context()->module()->extension_begin();
          c_itr != context()->module()->extension_end(); ++c_itr) {
-      const char* ext_name =
-          reinterpret_cast<const char*>(&c_itr->GetInOperand(0).words[0]);
-      if (!strcmp(ext_name, "SPV_KHR_non_semantic_info")) {
+      const std::string ext_name = c_itr->GetInOperand(0).AsString();
+      if (ext_name == "SPV_KHR_non_semantic_info") {
         context()->KillInst(&*c_itr);
         break;
       }
diff --git a/source/opt/instruction.cpp b/source/opt/instruction.cpp
index 2461e41..e775a99 100644
--- a/source/opt/instruction.cpp
+++ b/source/opt/instruction.cpp
@@ -76,10 +76,9 @@
       dbg_scope_(kNoDebugScope, kNoInlinedAt) {
   for (uint32_t i = 0; i < inst.num_operands; ++i) {
     const auto& current_payload = inst.operands[i];
-    std::vector<uint32_t> words(
-        inst.words + current_payload.offset,
+    operands_.emplace_back(
+        current_payload.type, inst.words + current_payload.offset,
         inst.words + current_payload.offset + current_payload.num_words);
-    operands_.emplace_back(current_payload.type, std::move(words));
   }
   assert((!IsLineInst() || dbg_line.empty()) &&
          "Op(No)Line attaching to Op(No)Line found");
@@ -96,10 +95,9 @@
       dbg_scope_(dbg_scope) {
   for (uint32_t i = 0; i < inst.num_operands; ++i) {
     const auto& current_payload = inst.operands[i];
-    std::vector<uint32_t> words(
-        inst.words + current_payload.offset,
+    operands_.emplace_back(
+        current_payload.type, inst.words + current_payload.offset,
         inst.words + current_payload.offset + current_payload.num_words);
-    operands_.emplace_back(current_payload.type, std::move(words));
   }
 }
 
@@ -506,26 +504,6 @@
   return storage_class == SpvStorageClassUniformConstant;
 }
 
-uint32_t Instruction::GetTypeComponent(uint32_t element) const {
-  uint32_t subtype = 0;
-  switch (opcode()) {
-    case SpvOpTypeStruct:
-      subtype = GetSingleWordInOperand(element);
-      break;
-    case SpvOpTypeArray:
-    case SpvOpTypeRuntimeArray:
-    case SpvOpTypeVector:
-    case SpvOpTypeMatrix:
-      // These types all have uniform subtypes.
-      subtype = GetSingleWordInOperand(0u);
-      break;
-    default:
-      break;
-  }
-
-  return subtype;
-}
-
 void Instruction::UpdateLexicalScope(uint32_t scope) {
   dbg_scope_.SetLexicalScope(scope);
   for (auto& i : dbg_line_insts_) {
@@ -695,8 +673,12 @@
     return NonSemanticShaderDebugInfo100InstructionsMax;
   }
 
-  return NonSemanticShaderDebugInfo100Instructions(
-      GetSingleWordInOperand(kExtInstInstructionInIdx));
+  uint32_t opcode = GetSingleWordInOperand(kExtInstInstructionInIdx);
+  if (opcode >= NonSemanticShaderDebugInfo100InstructionsMax) {
+    return NonSemanticShaderDebugInfo100InstructionsMax;
+  }
+
+  return NonSemanticShaderDebugInfo100Instructions(opcode);
 }
 
 CommonDebugInfoInstructions Instruction::GetCommonDebugOpcode() const {
diff --git a/source/opt/instruction.h b/source/opt/instruction.h
index ce568f6..e79c628 100644
--- a/source/opt/instruction.h
+++ b/source/opt/instruction.h
@@ -24,6 +24,7 @@
 
 #include "NonSemanticShaderDebugInfo100.h"
 #include "OpenCLDebugInfo100.h"
+#include "source/binary.h"
 #include "source/common_debug_info.h"
 #include "source/latest_version_glsl_std_450_header.h"
 #include "source/latest_version_spirv_header.h"
@@ -32,6 +33,7 @@
 #include "source/opt/reflect.h"
 #include "source/util/ilist_node.h"
 #include "source/util/small_vector.h"
+#include "source/util/string_utils.h"
 #include "spirv-tools/libspirv.h"
 
 const uint32_t kNoDebugScope = 0;
@@ -82,21 +84,32 @@
 
   Operand(spv_operand_type_t t, const OperandData& w) : type(t), words(w) {}
 
+  template <class InputIt>
+  Operand(spv_operand_type_t t, InputIt firstOperandData,
+          InputIt lastOperandData)
+      : type(t), words(firstOperandData, lastOperandData) {}
+
   spv_operand_type_t type;  // Type of this logical operand.
   OperandData words;        // Binary segments of this logical operand.
 
-  // Returns a string operand as a C-style string.
-  const char* AsCString() const {
-    assert(type == SPV_OPERAND_TYPE_LITERAL_STRING);
-    return reinterpret_cast<const char*>(words.data());
+  uint32_t AsId() const {
+    assert(spvIsIdType(type));
+    assert(words.size() == 1);
+    return words[0];
   }
 
   // Returns a string operand as a std::string.
-  std::string AsString() const { return AsCString(); }
+  std::string AsString() const {
+    assert(type == SPV_OPERAND_TYPE_LITERAL_STRING);
+    return spvtools::utils::MakeString(words);
+  }
 
   // Returns a literal integer operand as a uint64_t
   uint64_t AsLiteralUint64() const {
-    assert(type == SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER);
+    assert(type == SPV_OPERAND_TYPE_LITERAL_INTEGER ||
+           type == SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER ||
+           type == SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER ||
+           type == SPV_OPERAND_TYPE_OPTIONAL_TYPED_LITERAL_INTEGER);
     assert(1 <= words.size());
     assert(words.size() <= 2);
     uint64_t result = 0;
@@ -123,7 +136,7 @@
 }
 
 // This structure is used to represent a DebugScope instruction from
-// the OpenCL.100.DebugInfo extened instruction set. Note that we can
+// the OpenCL.100.DebugInfo extended instruction set. Note that we can
 // ignore the result id of DebugScope instruction because it is not
 // used for anything. We do not keep it to reduce the size of
 // structure.
@@ -295,6 +308,7 @@
   inline void SetInOperands(OperandList&& new_operands);
   // Sets the result type id.
   inline void SetResultType(uint32_t ty_id);
+  inline bool HasResultType() const { return has_type_id_; }
   // Sets the result id
   inline void SetResultId(uint32_t res_id);
   inline bool HasResultId() const { return has_result_id_; }
@@ -490,10 +504,6 @@
   // Returns true if this instruction exits this function or aborts execution.
   bool IsReturnOrAbort() const { return spvOpcodeIsReturnOrAbort(opcode()); }
 
-  // Returns the id for the |element|'th subtype. If the |this| is not a
-  // composite type, this function returns 0.
-  uint32_t GetTypeComponent(uint32_t element) const;
-
   // Returns true if this instruction is a basic block terminator.
   bool IsBlockTerminator() const {
     return spvOpcodeIsBlockTerminator(opcode());
diff --git a/source/opt/instrument_pass.cpp b/source/opt/instrument_pass.cpp
index ed34fb0..d143d59 100644
--- a/source/opt/instrument_pass.cpp
+++ b/source/opt/instrument_pass.cpp
@@ -88,6 +88,51 @@
   return newLabel;
 }
 
+std::unique_ptr<Instruction> InstrumentPass::NewName(
+    uint32_t id, const std::string& name_str) {
+  std::unique_ptr<Instruction> new_name(new Instruction(
+      context(), SpvOpName, 0, 0,
+      std::initializer_list<Operand>{
+          {SPV_OPERAND_TYPE_ID, {id}},
+          {SPV_OPERAND_TYPE_LITERAL_STRING, utils::MakeVector(name_str)}}));
+
+  return new_name;
+}
+
+std::unique_ptr<Instruction> InstrumentPass::NewGlobalName(
+    uint32_t id, const std::string& name_str) {
+  std::string prefixed_name;
+  switch (validation_id_) {
+    case kInstValidationIdBindless:
+      prefixed_name = "inst_bindless_";
+      break;
+    case kInstValidationIdBuffAddr:
+      prefixed_name = "inst_buff_addr_";
+      break;
+    case kInstValidationIdDebugPrintf:
+      prefixed_name = "inst_printf_";
+      break;
+    default:
+      assert(false);  // add new instrumentation pass here
+      prefixed_name = "inst_pass_";
+      break;
+  }
+  prefixed_name += name_str;
+  return NewName(id, prefixed_name);
+}
+
+std::unique_ptr<Instruction> InstrumentPass::NewMemberName(
+    uint32_t id, uint32_t member_index, const std::string& name_str) {
+  std::unique_ptr<Instruction> new_name(new Instruction(
+      context(), SpvOpMemberName, 0, 0,
+      std::initializer_list<Operand>{
+          {SPV_OPERAND_TYPE_ID, {id}},
+          {SPV_OPERAND_TYPE_LITERAL_INTEGER, {member_index}},
+          {SPV_OPERAND_TYPE_LITERAL_STRING, utils::MakeVector(name_str)}}));
+
+  return new_name;
+}
+
 uint32_t InstrumentPass::Gen32BitCvtCode(uint32_t val_id,
                                          InstructionBuilder* builder) {
   // Convert integer value to 32-bit if necessary
@@ -200,7 +245,9 @@
     } break;
     case SpvExecutionModelGLCompute:
     case SpvExecutionModelTaskNV:
-    case SpvExecutionModelMeshNV: {
+    case SpvExecutionModelMeshNV:
+    case SpvExecutionModelTaskEXT:
+    case SpvExecutionModelMeshEXT: {
       // Load and store GlobalInvocationId.
       uint32_t load_id = GenVarLoad(
           context()->GetBuiltinInputVarId(SpvBuiltInGlobalInvocationId),
@@ -525,6 +572,10 @@
         {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
           {SpvStorageClassStorageBuffer}}}));
     context()->AddGlobalValue(std::move(newVarOp));
+    context()->AddDebug2Inst(NewGlobalName(obufTyId, "OutputBuffer"));
+    context()->AddDebug2Inst(NewMemberName(obufTyId, 0, "written_count"));
+    context()->AddDebug2Inst(NewMemberName(obufTyId, 1, "data"));
+    context()->AddDebug2Inst(NewGlobalName(output_buffer_id_, "output_buffer"));
     deco_mgr->AddDecorationVal(output_buffer_id_, SpvDecorationDescriptorSet,
                                desc_set_);
     deco_mgr->AddDecorationVal(output_buffer_id_, SpvDecorationBinding,
@@ -569,6 +620,9 @@
         {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
           {SpvStorageClassStorageBuffer}}}));
     context()->AddGlobalValue(std::move(newVarOp));
+    context()->AddDebug2Inst(NewGlobalName(ibufTyId, "InputBuffer"));
+    context()->AddDebug2Inst(NewMemberName(ibufTyId, 0, "data"));
+    context()->AddDebug2Inst(NewGlobalName(input_buffer_id_, "input_buffer"));
     deco_mgr->AddDecorationVal(input_buffer_id_, SpvDecorationDescriptorSet,
                                desc_set_);
     deco_mgr->AddDecorationVal(input_buffer_id_, SpvDecorationBinding,
@@ -783,6 +837,12 @@
     get_def_use_mgr()->AnalyzeInstDefUse(&*func_end_inst);
     output_func->SetFunctionEnd(std::move(func_end_inst));
     context()->AddFunction(std::move(output_func));
+
+    std::string name("stream_write_");
+    name += std::to_string(param_cnt);
+
+    context()->AddDebug2Inst(
+        NewGlobalName(param2output_func_id_[param_cnt], name));
   }
   return param2output_func_id_[param_cnt];
 }
@@ -863,6 +923,11 @@
   get_def_use_mgr()->AnalyzeInstDefUse(&*func_end_inst);
   input_func->SetFunctionEnd(std::move(func_end_inst));
   context()->AddFunction(std::move(input_func));
+
+  std::string name("direct_read_");
+  name += std::to_string(param_cnt);
+  context()->AddDebug2Inst(NewGlobalName(func_id, name));
+
   param2input_func_id_[param_cnt] = func_id;
   return func_id;
 }
@@ -1001,7 +1066,8 @@
       stage != SpvExecutionModelAnyHitNV &&
       stage != SpvExecutionModelClosestHitNV &&
       stage != SpvExecutionModelMissNV &&
-      stage != SpvExecutionModelCallableNV) {
+      stage != SpvExecutionModelCallableNV &&
+      stage != SpvExecutionModelTaskEXT && stage != SpvExecutionModelMeshEXT) {
     if (consumer()) {
       std::string message = "Stage not supported by instrumentation";
       consumer()(SPV_MSG_ERROR, 0, {0, 0, 0}, message.c_str());
diff --git a/source/opt/instrument_pass.h b/source/opt/instrument_pass.h
index 12b939d..215b026 100644
--- a/source/opt/instrument_pass.h
+++ b/source/opt/instrument_pass.h
@@ -50,7 +50,7 @@
 // A validation pass may read or write multiple buffers. All such buffers
 // are located in a single debug descriptor set whose index is passed at the
 // creation of the instrumentation pass. The bindings of the buffers used by
-// a validation pass are permanantly assigned and fixed and documented by
+// a validation pass are permanently assigned and fixed and documented by
 // the kDebugOutput* static consts.
 
 namespace spvtools {
@@ -179,8 +179,8 @@
   // the error. Every stage will write a fixed number of words. Vertex shaders
   // will write the Vertex and Instance ID. Fragment shaders will write
   // FragCoord.xy. Compute shaders will write the GlobalInvocation ID.
-  // The tesselation eval shader will write the Primitive ID and TessCoords.uv.
-  // The tesselation control shader and geometry shader will write the
+  // The tessellation eval shader will write the Primitive ID and TessCoords.uv.
+  // The tessellation control shader and geometry shader will write the
   // Primitive ID and Invocation ID.
   //
   // The Validation Error Code specifies the exact error which has occurred.
@@ -224,6 +224,19 @@
   // Return new label.
   std::unique_ptr<Instruction> NewLabel(uint32_t label_id);
 
+  // Set the name function parameter or local variable
+  std::unique_ptr<Instruction> NewName(uint32_t id,
+                                       const std::string& name_str);
+
+  // Set the name for a function or global variable, names will be
+  // prefixed to identify which instrumentation pass generated them.
+  std::unique_ptr<Instruction> NewGlobalName(uint32_t id,
+                                             const std::string& name_str);
+
+  // Set the name for a structure member
+  std::unique_ptr<Instruction> NewMemberName(uint32_t id, uint32_t member_index,
+                                             const std::string& name_str);
+
   // Return id for 32-bit unsigned type
   uint32_t GetUintId();
 
diff --git a/source/opt/interface_var_sroa.cpp b/source/opt/interface_var_sroa.cpp
new file mode 100644
index 0000000..1b2cb36
--- /dev/null
+++ b/source/opt/interface_var_sroa.cpp
@@ -0,0 +1,968 @@
+// Copyright (c) 2022 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/interface_var_sroa.h"
+
+#include <iostream>
+
+#include "source/opt/decoration_manager.h"
+#include "source/opt/def_use_manager.h"
+#include "source/opt/function.h"
+#include "source/opt/log.h"
+#include "source/opt/type_manager.h"
+#include "source/util/make_unique.h"
+
+const static uint32_t kOpDecorateDecorationInOperandIndex = 1;
+const static uint32_t kOpDecorateLiteralInOperandIndex = 2;
+const static uint32_t kOpEntryPointInOperandInterface = 3;
+const static uint32_t kOpVariableStorageClassInOperandIndex = 0;
+const static uint32_t kOpTypeArrayElemTypeInOperandIndex = 0;
+const static uint32_t kOpTypeArrayLengthInOperandIndex = 1;
+const static uint32_t kOpTypeMatrixColCountInOperandIndex = 1;
+const static uint32_t kOpTypeMatrixColTypeInOperandIndex = 0;
+const static uint32_t kOpTypePtrTypeInOperandIndex = 1;
+const static uint32_t kOpConstantValueInOperandIndex = 0;
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+// Get the length of the OpTypeArray |array_type|.
+uint32_t GetArrayLength(analysis::DefUseManager* def_use_mgr,
+                        Instruction* array_type) {
+  assert(array_type->opcode() == SpvOpTypeArray);
+  uint32_t const_int_id =
+      array_type->GetSingleWordInOperand(kOpTypeArrayLengthInOperandIndex);
+  Instruction* array_length_inst = def_use_mgr->GetDef(const_int_id);
+  assert(array_length_inst->opcode() == SpvOpConstant);
+  return array_length_inst->GetSingleWordInOperand(
+      kOpConstantValueInOperandIndex);
+}
+
+// Get the element type instruction of the OpTypeArray |array_type|.
+Instruction* GetArrayElementType(analysis::DefUseManager* def_use_mgr,
+                                 Instruction* array_type) {
+  assert(array_type->opcode() == SpvOpTypeArray);
+  uint32_t elem_type_id =
+      array_type->GetSingleWordInOperand(kOpTypeArrayElemTypeInOperandIndex);
+  return def_use_mgr->GetDef(elem_type_id);
+}
+
+// Get the column type instruction of the OpTypeMatrix |matrix_type|.
+Instruction* GetMatrixColumnType(analysis::DefUseManager* def_use_mgr,
+                                 Instruction* matrix_type) {
+  assert(matrix_type->opcode() == SpvOpTypeMatrix);
+  uint32_t column_type_id =
+      matrix_type->GetSingleWordInOperand(kOpTypeMatrixColTypeInOperandIndex);
+  return def_use_mgr->GetDef(column_type_id);
+}
+
+// Traverses the component type of OpTypeArray or OpTypeMatrix. Repeats it
+// |depth_to_component| times recursively and returns the component type.
+// |type_id| is the result id of the OpTypeArray or OpTypeMatrix instruction.
+uint32_t GetComponentTypeOfArrayMatrix(analysis::DefUseManager* def_use_mgr,
+                                       uint32_t type_id,
+                                       uint32_t depth_to_component) {
+  if (depth_to_component == 0) return type_id;
+
+  Instruction* type_inst = def_use_mgr->GetDef(type_id);
+  if (type_inst->opcode() == SpvOpTypeArray) {
+    uint32_t elem_type_id =
+        type_inst->GetSingleWordInOperand(kOpTypeArrayElemTypeInOperandIndex);
+    return GetComponentTypeOfArrayMatrix(def_use_mgr, elem_type_id,
+                                         depth_to_component - 1);
+  }
+
+  assert(type_inst->opcode() == SpvOpTypeMatrix);
+  uint32_t column_type_id =
+      type_inst->GetSingleWordInOperand(kOpTypeMatrixColTypeInOperandIndex);
+  return GetComponentTypeOfArrayMatrix(def_use_mgr, column_type_id,
+                                       depth_to_component - 1);
+}
+
+// Creates an OpDecorate instruction whose Target is |var_id| and Decoration is
+// |decoration|. Adds |literal| as an extra operand of the instruction.
+void CreateDecoration(analysis::DecorationManager* decoration_mgr,
+                      uint32_t var_id, SpvDecoration decoration,
+                      uint32_t literal) {
+  std::vector<Operand> operands({
+      {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {var_id}},
+      {spv_operand_type_t::SPV_OPERAND_TYPE_DECORATION,
+       {static_cast<uint32_t>(decoration)}},
+      {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {literal}},
+  });
+  decoration_mgr->AddDecoration(SpvOpDecorate, std::move(operands));
+}
+
+// Replaces load instructions with composite construct instructions in all the
+// users of the loads. |loads_to_composites| is the mapping from each load to
+// its corresponding OpCompositeConstruct.
+void ReplaceLoadWithCompositeConstruct(
+    IRContext* context,
+    const std::unordered_map<Instruction*, Instruction*>& loads_to_composites) {
+  for (const auto& load_and_composite : loads_to_composites) {
+    Instruction* load = load_and_composite.first;
+    Instruction* composite_construct = load_and_composite.second;
+
+    std::vector<Instruction*> users;
+    context->get_def_use_mgr()->ForEachUse(
+        load, [&users, composite_construct](Instruction* user, uint32_t index) {
+          user->GetOperand(index).words[0] = composite_construct->result_id();
+          users.push_back(user);
+        });
+
+    for (Instruction* user : users)
+      context->get_def_use_mgr()->AnalyzeInstUse(user);
+  }
+}
+
+// Returns the storage class of the instruction |var|.
+SpvStorageClass GetStorageClass(Instruction* var) {
+  return static_cast<SpvStorageClass>(
+      var->GetSingleWordInOperand(kOpVariableStorageClassInOperandIndex));
+}
+
+}  // namespace
+
+bool InterfaceVariableScalarReplacement::HasExtraArrayness(
+    Instruction& entry_point, Instruction* var) {
+  SpvExecutionModel execution_model =
+      static_cast<SpvExecutionModel>(entry_point.GetSingleWordInOperand(0));
+  if (execution_model != SpvExecutionModelTessellationEvaluation &&
+      execution_model != SpvExecutionModelTessellationControl) {
+    return false;
+  }
+  if (!context()->get_decoration_mgr()->HasDecoration(var->result_id(),
+                                                      SpvDecorationPatch)) {
+    if (execution_model == SpvExecutionModelTessellationControl) return true;
+    return GetStorageClass(var) != SpvStorageClassOutput;
+  }
+  return false;
+}
+
+bool InterfaceVariableScalarReplacement::
+    CheckExtraArraynessConflictBetweenEntries(Instruction* interface_var,
+                                              bool has_extra_arrayness) {
+  if (has_extra_arrayness) {
+    return !ReportErrorIfHasNoExtraArraynessForOtherEntry(interface_var);
+  }
+  return !ReportErrorIfHasExtraArraynessForOtherEntry(interface_var);
+}
+
+bool InterfaceVariableScalarReplacement::GetVariableLocation(
+    Instruction* var, uint32_t* location) {
+  return !context()->get_decoration_mgr()->WhileEachDecoration(
+      var->result_id(), SpvDecorationLocation,
+      [location](const Instruction& inst) {
+        *location =
+            inst.GetSingleWordInOperand(kOpDecorateLiteralInOperandIndex);
+        return false;
+      });
+}
+
+bool InterfaceVariableScalarReplacement::GetVariableComponent(
+    Instruction* var, uint32_t* component) {
+  return !context()->get_decoration_mgr()->WhileEachDecoration(
+      var->result_id(), SpvDecorationComponent,
+      [component](const Instruction& inst) {
+        *component =
+            inst.GetSingleWordInOperand(kOpDecorateLiteralInOperandIndex);
+        return false;
+      });
+}
+
+std::vector<Instruction*>
+InterfaceVariableScalarReplacement::CollectInterfaceVariables(
+    Instruction& entry_point) {
+  std::vector<Instruction*> interface_vars;
+  for (uint32_t i = kOpEntryPointInOperandInterface;
+       i < entry_point.NumInOperands(); ++i) {
+    Instruction* interface_var = context()->get_def_use_mgr()->GetDef(
+        entry_point.GetSingleWordInOperand(i));
+    assert(interface_var->opcode() == SpvOpVariable);
+
+    SpvStorageClass storage_class = GetStorageClass(interface_var);
+    if (storage_class != SpvStorageClassInput &&
+        storage_class != SpvStorageClassOutput) {
+      continue;
+    }
+
+    interface_vars.push_back(interface_var);
+  }
+  return interface_vars;
+}
+
+void InterfaceVariableScalarReplacement::KillInstructionAndUsers(
+    Instruction* inst) {
+  if (inst->opcode() == SpvOpEntryPoint) {
+    return;
+  }
+  if (inst->opcode() != SpvOpAccessChain) {
+    context()->KillInst(inst);
+    return;
+  }
+  std::vector<Instruction*> users;
+  context()->get_def_use_mgr()->ForEachUser(
+      inst, [&users](Instruction* user) { users.push_back(user); });
+  for (auto user : users) {
+    context()->KillInst(user);
+  }
+  context()->KillInst(inst);
+}
+
+void InterfaceVariableScalarReplacement::KillInstructionsAndUsers(
+    const std::vector<Instruction*>& insts) {
+  for (Instruction* inst : insts) {
+    KillInstructionAndUsers(inst);
+  }
+}
+
+void InterfaceVariableScalarReplacement::KillLocationAndComponentDecorations(
+    uint32_t var_id) {
+  context()->get_decoration_mgr()->RemoveDecorationsFrom(
+      var_id, [](const Instruction& inst) {
+        uint32_t decoration =
+            inst.GetSingleWordInOperand(kOpDecorateDecorationInOperandIndex);
+        return decoration == SpvDecorationLocation ||
+               decoration == SpvDecorationComponent;
+      });
+}
+
+bool InterfaceVariableScalarReplacement::ReplaceInterfaceVariableWithScalars(
+    Instruction* interface_var, Instruction* interface_var_type,
+    uint32_t location, uint32_t component, uint32_t extra_array_length) {
+  NestedCompositeComponents scalar_interface_vars =
+      CreateScalarInterfaceVarsForReplacement(interface_var_type,
+                                              GetStorageClass(interface_var),
+                                              extra_array_length);
+
+  AddLocationAndComponentDecorations(scalar_interface_vars, &location,
+                                     component);
+  KillLocationAndComponentDecorations(interface_var->result_id());
+
+  if (!ReplaceInterfaceVarWith(interface_var, extra_array_length,
+                               scalar_interface_vars)) {
+    return false;
+  }
+
+  context()->KillInst(interface_var);
+  return true;
+}
+
+bool InterfaceVariableScalarReplacement::ReplaceInterfaceVarWith(
+    Instruction* interface_var, uint32_t extra_array_length,
+    const NestedCompositeComponents& scalar_interface_vars) {
+  std::vector<Instruction*> users;
+  context()->get_def_use_mgr()->ForEachUser(
+      interface_var, [&users](Instruction* user) { users.push_back(user); });
+
+  std::vector<uint32_t> interface_var_component_indices;
+  std::unordered_map<Instruction*, Instruction*> loads_to_composites;
+  std::unordered_map<Instruction*, Instruction*>
+      loads_for_access_chain_to_composites;
+  if (extra_array_length != 0) {
+    // Note that the extra arrayness is the first dimension of the array
+    // interface variable.
+    for (uint32_t index = 0; index < extra_array_length; ++index) {
+      std::unordered_map<Instruction*, Instruction*> loads_to_component_values;
+      if (!ReplaceComponentsOfInterfaceVarWith(
+              interface_var, users, scalar_interface_vars,
+              interface_var_component_indices, &index,
+              &loads_to_component_values,
+              &loads_for_access_chain_to_composites)) {
+        return false;
+      }
+      AddComponentsToCompositesForLoads(loads_to_component_values,
+                                        &loads_to_composites, 0);
+    }
+  } else if (!ReplaceComponentsOfInterfaceVarWith(
+                 interface_var, users, scalar_interface_vars,
+                 interface_var_component_indices, nullptr, &loads_to_composites,
+                 &loads_for_access_chain_to_composites)) {
+    return false;
+  }
+
+  ReplaceLoadWithCompositeConstruct(context(), loads_to_composites);
+  ReplaceLoadWithCompositeConstruct(context(),
+                                    loads_for_access_chain_to_composites);
+
+  KillInstructionsAndUsers(users);
+  return true;
+}
+
+void InterfaceVariableScalarReplacement::AddLocationAndComponentDecorations(
+    const NestedCompositeComponents& vars, uint32_t* location,
+    uint32_t component) {
+  if (!vars.HasMultipleComponents()) {
+    uint32_t var_id = vars.GetComponentVariable()->result_id();
+    CreateDecoration(context()->get_decoration_mgr(), var_id,
+                     SpvDecorationLocation, *location);
+    CreateDecoration(context()->get_decoration_mgr(), var_id,
+                     SpvDecorationComponent, component);
+    ++(*location);
+    return;
+  }
+  for (const auto& var : vars.GetComponents()) {
+    AddLocationAndComponentDecorations(var, location, component);
+  }
+}
+
+bool InterfaceVariableScalarReplacement::ReplaceComponentsOfInterfaceVarWith(
+    Instruction* interface_var,
+    const std::vector<Instruction*>& interface_var_users,
+    const NestedCompositeComponents& scalar_interface_vars,
+    std::vector<uint32_t>& interface_var_component_indices,
+    const uint32_t* extra_array_index,
+    std::unordered_map<Instruction*, Instruction*>* loads_to_composites,
+    std::unordered_map<Instruction*, Instruction*>*
+        loads_for_access_chain_to_composites) {
+  if (!scalar_interface_vars.HasMultipleComponents()) {
+    for (Instruction* interface_var_user : interface_var_users) {
+      if (!ReplaceComponentOfInterfaceVarWith(
+              interface_var, interface_var_user,
+              scalar_interface_vars.GetComponentVariable(),
+              interface_var_component_indices, extra_array_index,
+              loads_to_composites, loads_for_access_chain_to_composites)) {
+        return false;
+      }
+    }
+    return true;
+  }
+  return ReplaceMultipleComponentsOfInterfaceVarWith(
+      interface_var, interface_var_users, scalar_interface_vars.GetComponents(),
+      interface_var_component_indices, extra_array_index, loads_to_composites,
+      loads_for_access_chain_to_composites);
+}
+
+bool InterfaceVariableScalarReplacement::
+    ReplaceMultipleComponentsOfInterfaceVarWith(
+        Instruction* interface_var,
+        const std::vector<Instruction*>& interface_var_users,
+        const std::vector<NestedCompositeComponents>& components,
+        std::vector<uint32_t>& interface_var_component_indices,
+        const uint32_t* extra_array_index,
+        std::unordered_map<Instruction*, Instruction*>* loads_to_composites,
+        std::unordered_map<Instruction*, Instruction*>*
+            loads_for_access_chain_to_composites) {
+  for (uint32_t i = 0; i < components.size(); ++i) {
+    interface_var_component_indices.push_back(i);
+    std::unordered_map<Instruction*, Instruction*> loads_to_component_values;
+    std::unordered_map<Instruction*, Instruction*>
+        loads_for_access_chain_to_component_values;
+    if (!ReplaceComponentsOfInterfaceVarWith(
+            interface_var, interface_var_users, components[i],
+            interface_var_component_indices, extra_array_index,
+            &loads_to_component_values,
+            &loads_for_access_chain_to_component_values)) {
+      return false;
+    }
+    interface_var_component_indices.pop_back();
+
+    uint32_t depth_to_component =
+        static_cast<uint32_t>(interface_var_component_indices.size());
+    AddComponentsToCompositesForLoads(
+        loads_for_access_chain_to_component_values,
+        loads_for_access_chain_to_composites, depth_to_component);
+    if (extra_array_index) ++depth_to_component;
+    AddComponentsToCompositesForLoads(loads_to_component_values,
+                                      loads_to_composites, depth_to_component);
+  }
+  return true;
+}
+
+bool InterfaceVariableScalarReplacement::ReplaceComponentOfInterfaceVarWith(
+    Instruction* interface_var, Instruction* interface_var_user,
+    Instruction* scalar_var,
+    const std::vector<uint32_t>& interface_var_component_indices,
+    const uint32_t* extra_array_index,
+    std::unordered_map<Instruction*, Instruction*>* loads_to_component_values,
+    std::unordered_map<Instruction*, Instruction*>*
+        loads_for_access_chain_to_component_values) {
+  SpvOp opcode = interface_var_user->opcode();
+  if (opcode == SpvOpStore) {
+    uint32_t value_id = interface_var_user->GetSingleWordInOperand(1);
+    StoreComponentOfValueToScalarVar(value_id, interface_var_component_indices,
+                                     scalar_var, extra_array_index,
+                                     interface_var_user);
+    return true;
+  }
+  if (opcode == SpvOpLoad) {
+    Instruction* scalar_load =
+        LoadScalarVar(scalar_var, extra_array_index, interface_var_user);
+    loads_to_component_values->insert({interface_var_user, scalar_load});
+    return true;
+  }
+
+  // Copy OpName and annotation instructions only once. Therefore, we create
+  // them only for the first element of the extra array.
+  if (extra_array_index && *extra_array_index != 0) return true;
+
+  if (opcode == SpvOpDecorateId || opcode == SpvOpDecorateString ||
+      opcode == SpvOpDecorate) {
+    CloneAnnotationForVariable(interface_var_user, scalar_var->result_id());
+    return true;
+  }
+
+  if (opcode == SpvOpName) {
+    std::unique_ptr<Instruction> new_inst(interface_var_user->Clone(context()));
+    new_inst->SetInOperand(0, {scalar_var->result_id()});
+    context()->AddDebug2Inst(std::move(new_inst));
+    return true;
+  }
+
+  if (opcode == SpvOpEntryPoint) {
+    return ReplaceInterfaceVarInEntryPoint(interface_var, interface_var_user,
+                                           scalar_var->result_id());
+  }
+
+  if (opcode == SpvOpAccessChain) {
+    ReplaceAccessChainWith(interface_var_user, interface_var_component_indices,
+                           scalar_var,
+                           loads_for_access_chain_to_component_values);
+    return true;
+  }
+
+  std::string message("Unhandled instruction");
+  message += "\n  " + interface_var_user->PrettyPrint(
+                          SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+  message +=
+      "\nfor interface variable scalar replacement\n  " +
+      interface_var->PrettyPrint(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+  context()->consumer()(SPV_MSG_ERROR, "", {0, 0, 0}, message.c_str());
+  return false;
+}
+
+void InterfaceVariableScalarReplacement::UseBaseAccessChainForAccessChain(
+    Instruction* access_chain, Instruction* base_access_chain) {
+  assert(base_access_chain->opcode() == SpvOpAccessChain &&
+         access_chain->opcode() == SpvOpAccessChain &&
+         access_chain->GetSingleWordInOperand(0) ==
+             base_access_chain->result_id());
+  Instruction::OperandList new_operands;
+  for (uint32_t i = 0; i < base_access_chain->NumInOperands(); ++i) {
+    new_operands.emplace_back(base_access_chain->GetInOperand(i));
+  }
+  for (uint32_t i = 1; i < access_chain->NumInOperands(); ++i) {
+    new_operands.emplace_back(access_chain->GetInOperand(i));
+  }
+  access_chain->SetInOperands(std::move(new_operands));
+}
+
+Instruction* InterfaceVariableScalarReplacement::CreateAccessChainToVar(
+    uint32_t var_type_id, Instruction* var,
+    const std::vector<uint32_t>& index_ids, Instruction* insert_before,
+    uint32_t* component_type_id) {
+  analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
+  *component_type_id = GetComponentTypeOfArrayMatrix(
+      def_use_mgr, var_type_id, static_cast<uint32_t>(index_ids.size()));
+
+  uint32_t ptr_type_id =
+      GetPointerType(*component_type_id, GetStorageClass(var));
+
+  std::unique_ptr<Instruction> new_access_chain(
+      new Instruction(context(), SpvOpAccessChain, ptr_type_id, TakeNextId(),
+                      std::initializer_list<Operand>{
+                          {SPV_OPERAND_TYPE_ID, {var->result_id()}}}));
+  for (uint32_t index_id : index_ids) {
+    new_access_chain->AddOperand({SPV_OPERAND_TYPE_ID, {index_id}});
+  }
+
+  Instruction* inst = new_access_chain.get();
+  def_use_mgr->AnalyzeInstDefUse(inst);
+  insert_before->InsertBefore(std::move(new_access_chain));
+  return inst;
+}
+
+Instruction* InterfaceVariableScalarReplacement::CreateAccessChainWithIndex(
+    uint32_t component_type_id, Instruction* var, uint32_t index,
+    Instruction* insert_before) {
+  uint32_t ptr_type_id =
+      GetPointerType(component_type_id, GetStorageClass(var));
+  uint32_t index_id = context()->get_constant_mgr()->GetUIntConst(index);
+  std::unique_ptr<Instruction> new_access_chain(
+      new Instruction(context(), SpvOpAccessChain, ptr_type_id, TakeNextId(),
+                      std::initializer_list<Operand>{
+                          {SPV_OPERAND_TYPE_ID, {var->result_id()}},
+                          {SPV_OPERAND_TYPE_ID, {index_id}},
+                      }));
+  Instruction* inst = new_access_chain.get();
+  context()->get_def_use_mgr()->AnalyzeInstDefUse(inst);
+  insert_before->InsertBefore(std::move(new_access_chain));
+  return inst;
+}
+
+void InterfaceVariableScalarReplacement::ReplaceAccessChainWith(
+    Instruction* access_chain,
+    const std::vector<uint32_t>& interface_var_component_indices,
+    Instruction* scalar_var,
+    std::unordered_map<Instruction*, Instruction*>* loads_to_component_values) {
+  std::vector<uint32_t> indexes;
+  for (uint32_t i = 1; i < access_chain->NumInOperands(); ++i) {
+    indexes.push_back(access_chain->GetSingleWordInOperand(i));
+  }
+
+  // Note that we have a strong assumption that |access_chain| has only a single
+  // index that is for the extra arrayness.
+  context()->get_def_use_mgr()->ForEachUser(
+      access_chain,
+      [this, access_chain, &indexes, &interface_var_component_indices,
+       scalar_var, loads_to_component_values](Instruction* user) {
+        switch (user->opcode()) {
+          case SpvOpAccessChain: {
+            UseBaseAccessChainForAccessChain(user, access_chain);
+            ReplaceAccessChainWith(user, interface_var_component_indices,
+                                   scalar_var, loads_to_component_values);
+            return;
+          }
+          case SpvOpStore: {
+            uint32_t value_id = user->GetSingleWordInOperand(1);
+            StoreComponentOfValueToAccessChainToScalarVar(
+                value_id, interface_var_component_indices, scalar_var, indexes,
+                user);
+            return;
+          }
+          case SpvOpLoad: {
+            Instruction* value =
+                LoadAccessChainToVar(scalar_var, indexes, user);
+            loads_to_component_values->insert({user, value});
+            return;
+          }
+          default:
+            break;
+        }
+      });
+}
+
+void InterfaceVariableScalarReplacement::CloneAnnotationForVariable(
+    Instruction* annotation_inst, uint32_t var_id) {
+  assert(annotation_inst->opcode() == SpvOpDecorate ||
+         annotation_inst->opcode() == SpvOpDecorateId ||
+         annotation_inst->opcode() == SpvOpDecorateString);
+  std::unique_ptr<Instruction> new_inst(annotation_inst->Clone(context()));
+  new_inst->SetInOperand(0, {var_id});
+  context()->AddAnnotationInst(std::move(new_inst));
+}
+
+bool InterfaceVariableScalarReplacement::ReplaceInterfaceVarInEntryPoint(
+    Instruction* interface_var, Instruction* entry_point,
+    uint32_t scalar_var_id) {
+  analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
+  uint32_t interface_var_id = interface_var->result_id();
+  if (interface_vars_removed_from_entry_point_operands_.find(
+          interface_var_id) !=
+      interface_vars_removed_from_entry_point_operands_.end()) {
+    entry_point->AddOperand({SPV_OPERAND_TYPE_ID, {scalar_var_id}});
+    def_use_mgr->AnalyzeInstUse(entry_point);
+    return true;
+  }
+
+  bool success = !entry_point->WhileEachInId(
+      [&interface_var_id, &scalar_var_id](uint32_t* id) {
+        if (*id == interface_var_id) {
+          *id = scalar_var_id;
+          return false;
+        }
+        return true;
+      });
+  if (!success) {
+    std::string message(
+        "interface variable is not an operand of the entry point");
+    message += "\n  " + interface_var->PrettyPrint(
+                            SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+    message += "\n  " + entry_point->PrettyPrint(
+                            SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+    context()->consumer()(SPV_MSG_ERROR, "", {0, 0, 0}, message.c_str());
+    return false;
+  }
+
+  def_use_mgr->AnalyzeInstUse(entry_point);
+  interface_vars_removed_from_entry_point_operands_.insert(interface_var_id);
+  return true;
+}
+
+uint32_t InterfaceVariableScalarReplacement::GetPointeeTypeIdOfVar(
+    Instruction* var) {
+  assert(var->opcode() == SpvOpVariable);
+
+  uint32_t ptr_type_id = var->type_id();
+  analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
+  Instruction* ptr_type_inst = def_use_mgr->GetDef(ptr_type_id);
+
+  assert(ptr_type_inst->opcode() == SpvOpTypePointer &&
+         "Variable must have a pointer type.");
+  return ptr_type_inst->GetSingleWordInOperand(kOpTypePtrTypeInOperandIndex);
+}
+
+void InterfaceVariableScalarReplacement::StoreComponentOfValueToScalarVar(
+    uint32_t value_id, const std::vector<uint32_t>& component_indices,
+    Instruction* scalar_var, const uint32_t* extra_array_index,
+    Instruction* insert_before) {
+  uint32_t component_type_id = GetPointeeTypeIdOfVar(scalar_var);
+  Instruction* ptr = scalar_var;
+  if (extra_array_index) {
+    auto* ty_mgr = context()->get_type_mgr();
+    analysis::Array* array_type = ty_mgr->GetType(component_type_id)->AsArray();
+    assert(array_type != nullptr);
+    component_type_id = ty_mgr->GetTypeInstruction(array_type->element_type());
+    ptr = CreateAccessChainWithIndex(component_type_id, scalar_var,
+                                     *extra_array_index, insert_before);
+  }
+
+  StoreComponentOfValueTo(component_type_id, value_id, component_indices, ptr,
+                          extra_array_index, insert_before);
+}
+
+Instruction* InterfaceVariableScalarReplacement::LoadScalarVar(
+    Instruction* scalar_var, const uint32_t* extra_array_index,
+    Instruction* insert_before) {
+  uint32_t component_type_id = GetPointeeTypeIdOfVar(scalar_var);
+  Instruction* ptr = scalar_var;
+  if (extra_array_index) {
+    auto* ty_mgr = context()->get_type_mgr();
+    analysis::Array* array_type = ty_mgr->GetType(component_type_id)->AsArray();
+    assert(array_type != nullptr);
+    component_type_id = ty_mgr->GetTypeInstruction(array_type->element_type());
+    ptr = CreateAccessChainWithIndex(component_type_id, scalar_var,
+                                     *extra_array_index, insert_before);
+  }
+
+  return CreateLoad(component_type_id, ptr, insert_before);
+}
+
+Instruction* InterfaceVariableScalarReplacement::CreateLoad(
+    uint32_t type_id, Instruction* ptr, Instruction* insert_before) {
+  std::unique_ptr<Instruction> load(
+      new Instruction(context(), SpvOpLoad, type_id, TakeNextId(),
+                      std::initializer_list<Operand>{
+                          {SPV_OPERAND_TYPE_ID, {ptr->result_id()}}}));
+  Instruction* load_inst = load.get();
+  context()->get_def_use_mgr()->AnalyzeInstDefUse(load_inst);
+  insert_before->InsertBefore(std::move(load));
+  return load_inst;
+}
+
+void InterfaceVariableScalarReplacement::StoreComponentOfValueTo(
+    uint32_t component_type_id, uint32_t value_id,
+    const std::vector<uint32_t>& component_indices, Instruction* ptr,
+    const uint32_t* extra_array_index, Instruction* insert_before) {
+  std::unique_ptr<Instruction> composite_extract(CreateCompositeExtract(
+      component_type_id, value_id, component_indices, extra_array_index));
+
+  std::unique_ptr<Instruction> new_store(
+      new Instruction(context(), SpvOpStore));
+  new_store->AddOperand({SPV_OPERAND_TYPE_ID, {ptr->result_id()}});
+  new_store->AddOperand(
+      {SPV_OPERAND_TYPE_ID, {composite_extract->result_id()}});
+
+  analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
+  def_use_mgr->AnalyzeInstDefUse(composite_extract.get());
+  def_use_mgr->AnalyzeInstDefUse(new_store.get());
+
+  insert_before->InsertBefore(std::move(composite_extract));
+  insert_before->InsertBefore(std::move(new_store));
+}
+
+Instruction* InterfaceVariableScalarReplacement::CreateCompositeExtract(
+    uint32_t type_id, uint32_t composite_id,
+    const std::vector<uint32_t>& indexes, const uint32_t* extra_first_index) {
+  uint32_t component_id = TakeNextId();
+  Instruction* composite_extract = new Instruction(
+      context(), SpvOpCompositeExtract, type_id, component_id,
+      std::initializer_list<Operand>{{SPV_OPERAND_TYPE_ID, {composite_id}}});
+  if (extra_first_index) {
+    composite_extract->AddOperand(
+        {SPV_OPERAND_TYPE_LITERAL_INTEGER, {*extra_first_index}});
+  }
+  for (uint32_t index : indexes) {
+    composite_extract->AddOperand({SPV_OPERAND_TYPE_LITERAL_INTEGER, {index}});
+  }
+  return composite_extract;
+}
+
+void InterfaceVariableScalarReplacement::
+    StoreComponentOfValueToAccessChainToScalarVar(
+        uint32_t value_id, const std::vector<uint32_t>& component_indices,
+        Instruction* scalar_var,
+        const std::vector<uint32_t>& access_chain_indices,
+        Instruction* insert_before) {
+  uint32_t component_type_id = GetPointeeTypeIdOfVar(scalar_var);
+  Instruction* ptr = scalar_var;
+  if (!access_chain_indices.empty()) {
+    ptr = CreateAccessChainToVar(component_type_id, scalar_var,
+                                 access_chain_indices, insert_before,
+                                 &component_type_id);
+  }
+
+  StoreComponentOfValueTo(component_type_id, value_id, component_indices, ptr,
+                          nullptr, insert_before);
+}
+
+Instruction* InterfaceVariableScalarReplacement::LoadAccessChainToVar(
+    Instruction* var, const std::vector<uint32_t>& indexes,
+    Instruction* insert_before) {
+  uint32_t component_type_id = GetPointeeTypeIdOfVar(var);
+  Instruction* ptr = var;
+  if (!indexes.empty()) {
+    ptr = CreateAccessChainToVar(component_type_id, var, indexes, insert_before,
+                                 &component_type_id);
+  }
+
+  return CreateLoad(component_type_id, ptr, insert_before);
+}
+
+Instruction*
+InterfaceVariableScalarReplacement::CreateCompositeConstructForComponentOfLoad(
+    Instruction* load, uint32_t depth_to_component) {
+  analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
+  uint32_t type_id = load->type_id();
+  if (depth_to_component != 0) {
+    type_id = GetComponentTypeOfArrayMatrix(def_use_mgr, load->type_id(),
+                                            depth_to_component);
+  }
+  uint32_t new_id = context()->TakeNextId();
+  std::unique_ptr<Instruction> new_composite_construct(
+      new Instruction(context(), SpvOpCompositeConstruct, type_id, new_id, {}));
+  Instruction* composite_construct = new_composite_construct.get();
+  def_use_mgr->AnalyzeInstDefUse(composite_construct);
+
+  // Insert |new_composite_construct| after |load|. When there are multiple
+  // recursive composite construct instructions for a load, we have to place the
+  // composite construct with a lower depth later because it constructs the
+  // composite that contains other composites with lower depths.
+  auto* insert_before = load->NextNode();
+  while (true) {
+    auto itr =
+        composite_ids_to_component_depths.find(insert_before->result_id());
+    if (itr == composite_ids_to_component_depths.end()) break;
+    if (itr->second <= depth_to_component) break;
+    insert_before = insert_before->NextNode();
+  }
+  insert_before->InsertBefore(std::move(new_composite_construct));
+  composite_ids_to_component_depths.insert({new_id, depth_to_component});
+  return composite_construct;
+}
+
+void InterfaceVariableScalarReplacement::AddComponentsToCompositesForLoads(
+    const std::unordered_map<Instruction*, Instruction*>&
+        loads_to_component_values,
+    std::unordered_map<Instruction*, Instruction*>* loads_to_composites,
+    uint32_t depth_to_component) {
+  analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
+  for (auto& load_and_component_vale : loads_to_component_values) {
+    Instruction* load = load_and_component_vale.first;
+    Instruction* component_value = load_and_component_vale.second;
+    Instruction* composite_construct = nullptr;
+    auto itr = loads_to_composites->find(load);
+    if (itr == loads_to_composites->end()) {
+      composite_construct =
+          CreateCompositeConstructForComponentOfLoad(load, depth_to_component);
+      loads_to_composites->insert({load, composite_construct});
+    } else {
+      composite_construct = itr->second;
+    }
+    composite_construct->AddOperand(
+        {SPV_OPERAND_TYPE_ID, {component_value->result_id()}});
+    def_use_mgr->AnalyzeInstDefUse(composite_construct);
+  }
+}
+
+uint32_t InterfaceVariableScalarReplacement::GetArrayType(
+    uint32_t elem_type_id, uint32_t array_length) {
+  analysis::Type* elem_type = context()->get_type_mgr()->GetType(elem_type_id);
+  uint32_t array_length_id =
+      context()->get_constant_mgr()->GetUIntConst(array_length);
+  analysis::Array array_type(
+      elem_type,
+      analysis::Array::LengthInfo{array_length_id, {0, array_length}});
+  return context()->get_type_mgr()->GetTypeInstruction(&array_type);
+}
+
+uint32_t InterfaceVariableScalarReplacement::GetPointerType(
+    uint32_t type_id, SpvStorageClass storage_class) {
+  analysis::Type* type = context()->get_type_mgr()->GetType(type_id);
+  analysis::Pointer ptr_type(type, storage_class);
+  return context()->get_type_mgr()->GetTypeInstruction(&ptr_type);
+}
+
+InterfaceVariableScalarReplacement::NestedCompositeComponents
+InterfaceVariableScalarReplacement::CreateScalarInterfaceVarsForArray(
+    Instruction* interface_var_type, SpvStorageClass storage_class,
+    uint32_t extra_array_length) {
+  assert(interface_var_type->opcode() == SpvOpTypeArray);
+
+  analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
+  uint32_t array_length = GetArrayLength(def_use_mgr, interface_var_type);
+  Instruction* elem_type = GetArrayElementType(def_use_mgr, interface_var_type);
+
+  NestedCompositeComponents scalar_vars;
+  while (array_length > 0) {
+    NestedCompositeComponents scalar_vars_for_element =
+        CreateScalarInterfaceVarsForReplacement(elem_type, storage_class,
+                                                extra_array_length);
+    scalar_vars.AddComponent(scalar_vars_for_element);
+    --array_length;
+  }
+  return scalar_vars;
+}
+
+InterfaceVariableScalarReplacement::NestedCompositeComponents
+InterfaceVariableScalarReplacement::CreateScalarInterfaceVarsForMatrix(
+    Instruction* interface_var_type, SpvStorageClass storage_class,
+    uint32_t extra_array_length) {
+  assert(interface_var_type->opcode() == SpvOpTypeMatrix);
+
+  analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
+  uint32_t column_count = interface_var_type->GetSingleWordInOperand(
+      kOpTypeMatrixColCountInOperandIndex);
+  Instruction* column_type =
+      GetMatrixColumnType(def_use_mgr, interface_var_type);
+
+  NestedCompositeComponents scalar_vars;
+  while (column_count > 0) {
+    NestedCompositeComponents scalar_vars_for_column =
+        CreateScalarInterfaceVarsForReplacement(column_type, storage_class,
+                                                extra_array_length);
+    scalar_vars.AddComponent(scalar_vars_for_column);
+    --column_count;
+  }
+  return scalar_vars;
+}
+
+InterfaceVariableScalarReplacement::NestedCompositeComponents
+InterfaceVariableScalarReplacement::CreateScalarInterfaceVarsForReplacement(
+    Instruction* interface_var_type, SpvStorageClass storage_class,
+    uint32_t extra_array_length) {
+  // Handle array case.
+  if (interface_var_type->opcode() == SpvOpTypeArray) {
+    return CreateScalarInterfaceVarsForArray(interface_var_type, storage_class,
+                                             extra_array_length);
+  }
+
+  // Handle matrix case.
+  if (interface_var_type->opcode() == SpvOpTypeMatrix) {
+    return CreateScalarInterfaceVarsForMatrix(interface_var_type, storage_class,
+                                              extra_array_length);
+  }
+
+  // Handle scalar or vector case.
+  NestedCompositeComponents scalar_var;
+  uint32_t type_id = interface_var_type->result_id();
+  if (extra_array_length != 0) {
+    type_id = GetArrayType(type_id, extra_array_length);
+  }
+  uint32_t ptr_type_id =
+      context()->get_type_mgr()->FindPointerToType(type_id, storage_class);
+  uint32_t id = TakeNextId();
+  std::unique_ptr<Instruction> variable(
+      new Instruction(context(), SpvOpVariable, ptr_type_id, id,
+                      std::initializer_list<Operand>{
+                          {SPV_OPERAND_TYPE_STORAGE_CLASS,
+                           {static_cast<uint32_t>(storage_class)}}}));
+  scalar_var.SetSingleComponentVariable(variable.get());
+  context()->AddGlobalValue(std::move(variable));
+  return scalar_var;
+}
+
+Instruction* InterfaceVariableScalarReplacement::GetTypeOfVariable(
+    Instruction* var) {
+  uint32_t pointee_type_id = GetPointeeTypeIdOfVar(var);
+  analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
+  return def_use_mgr->GetDef(pointee_type_id);
+}
+
+Pass::Status InterfaceVariableScalarReplacement::Process() {
+  Pass::Status status = Status::SuccessWithoutChange;
+  for (Instruction& entry_point : get_module()->entry_points()) {
+    status =
+        CombineStatus(status, ReplaceInterfaceVarsWithScalars(entry_point));
+  }
+  return status;
+}
+
+bool InterfaceVariableScalarReplacement::
+    ReportErrorIfHasExtraArraynessForOtherEntry(Instruction* var) {
+  if (vars_with_extra_arrayness.find(var) == vars_with_extra_arrayness.end())
+    return false;
+
+  std::string message(
+      "A variable is arrayed for an entry point but it is not "
+      "arrayed for another entry point");
+  message +=
+      "\n  " + var->PrettyPrint(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+  context()->consumer()(SPV_MSG_ERROR, "", {0, 0, 0}, message.c_str());
+  return true;
+}
+
+bool InterfaceVariableScalarReplacement::
+    ReportErrorIfHasNoExtraArraynessForOtherEntry(Instruction* var) {
+  if (vars_without_extra_arrayness.find(var) ==
+      vars_without_extra_arrayness.end())
+    return false;
+
+  std::string message(
+      "A variable is not arrayed for an entry point but it is "
+      "arrayed for another entry point");
+  message +=
+      "\n  " + var->PrettyPrint(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+  context()->consumer()(SPV_MSG_ERROR, "", {0, 0, 0}, message.c_str());
+  return true;
+}
+
+Pass::Status
+InterfaceVariableScalarReplacement::ReplaceInterfaceVarsWithScalars(
+    Instruction& entry_point) {
+  std::vector<Instruction*> interface_vars =
+      CollectInterfaceVariables(entry_point);
+
+  Pass::Status status = Status::SuccessWithoutChange;
+  for (Instruction* interface_var : interface_vars) {
+    uint32_t location, component;
+    if (!GetVariableLocation(interface_var, &location)) continue;
+    if (!GetVariableComponent(interface_var, &component)) component = 0;
+
+    Instruction* interface_var_type = GetTypeOfVariable(interface_var);
+    uint32_t extra_array_length = 0;
+    if (HasExtraArrayness(entry_point, interface_var)) {
+      extra_array_length =
+          GetArrayLength(context()->get_def_use_mgr(), interface_var_type);
+      interface_var_type =
+          GetArrayElementType(context()->get_def_use_mgr(), interface_var_type);
+      vars_with_extra_arrayness.insert(interface_var);
+    } else {
+      vars_without_extra_arrayness.insert(interface_var);
+    }
+
+    if (!CheckExtraArraynessConflictBetweenEntries(interface_var,
+                                                   extra_array_length != 0)) {
+      return Pass::Status::Failure;
+    }
+
+    if (interface_var_type->opcode() != SpvOpTypeArray &&
+        interface_var_type->opcode() != SpvOpTypeMatrix) {
+      continue;
+    }
+
+    if (!ReplaceInterfaceVariableWithScalars(interface_var, interface_var_type,
+                                             location, component,
+                                             extra_array_length)) {
+      return Pass::Status::Failure;
+    }
+    status = Pass::Status::SuccessWithChange;
+  }
+
+  return status;
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/interface_var_sroa.h b/source/opt/interface_var_sroa.h
new file mode 100644
index 0000000..23baad0
--- /dev/null
+++ b/source/opt/interface_var_sroa.h
@@ -0,0 +1,401 @@
+// Copyright (c) 2022 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_INTERFACE_VAR_SROA_H_
+#define SOURCE_OPT_INTERFACE_VAR_SROA_H_
+
+#include <unordered_set>
+
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// See optimizer.hpp for documentation.
+//
+// Note that the current implementation of this pass covers only store, load,
+// access chain instructions for the interface variables. Supporting other types
+// of instructions is a future work.
+class InterfaceVariableScalarReplacement : public Pass {
+ public:
+  InterfaceVariableScalarReplacement() {}
+
+  const char* name() const override {
+    return "interface-variable-scalar-replacement";
+  }
+  Status Process() override;
+
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisDecorations | IRContext::kAnalysisDefUse |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
+  }
+
+ private:
+  // A struct containing components of a composite variable. If the composite
+  // consists of multiple or recursive components, |component_variable| is
+  // nullptr and |nested_composite_components| keeps the components. If it has a
+  // single component, |nested_composite_components| is empty and
+  // |component_variable| is the component. Note that each element of
+  // |nested_composite_components| has the NestedCompositeComponents struct as
+  // its type that can recursively keep the components.
+  struct NestedCompositeComponents {
+    NestedCompositeComponents() : component_variable(nullptr) {}
+
+    bool HasMultipleComponents() const {
+      return !nested_composite_components.empty();
+    }
+
+    const std::vector<NestedCompositeComponents>& GetComponents() const {
+      return nested_composite_components;
+    }
+
+    void AddComponent(const NestedCompositeComponents& component) {
+      nested_composite_components.push_back(component);
+    }
+
+    Instruction* GetComponentVariable() const { return component_variable; }
+
+    void SetSingleComponentVariable(Instruction* var) {
+      component_variable = var;
+    }
+
+   private:
+    std::vector<NestedCompositeComponents> nested_composite_components;
+    Instruction* component_variable;
+  };
+
+  // Collects all interface variables used by the |entry_point|.
+  std::vector<Instruction*> CollectInterfaceVariables(Instruction& entry_point);
+
+  // Returns whether |var| has the extra arrayness for the entry point
+  // |entry_point| or not.
+  bool HasExtraArrayness(Instruction& entry_point, Instruction* var);
+
+  // Finds a Location BuiltIn decoration of |var| and returns it via
+  // |location|. Returns true whether the location exists or not.
+  bool GetVariableLocation(Instruction* var, uint32_t* location);
+
+  // Finds a Component BuiltIn decoration of |var| and returns it via
+  // |component|. Returns true whether the component exists or not.
+  bool GetVariableComponent(Instruction* var, uint32_t* component);
+
+  // Returns the interface variable instruction whose result id is
+  // |interface_var_id|.
+  Instruction* GetInterfaceVariable(uint32_t interface_var_id);
+
+  // Returns the type of |var| as an instruction.
+  Instruction* GetTypeOfVariable(Instruction* var);
+
+  // Replaces an interface variable |interface_var| whose type is
+  // |interface_var_type| with scalars and returns whether it succeeds or not.
+  // |location| is the value of Location Decoration for |interface_var|.
+  // |component| is the value of Component Decoration for |interface_var|.
+  // If |extra_array_length| is 0, it means |interface_var| has a Patch
+  // decoration. Otherwise, |extra_array_length| denotes the length of the extra
+  // array of |interface_var|.
+  bool ReplaceInterfaceVariableWithScalars(Instruction* interface_var,
+                                           Instruction* interface_var_type,
+                                           uint32_t location,
+                                           uint32_t component,
+                                           uint32_t extra_array_length);
+
+  // Creates scalar variables with the storage classe |storage_class| to replace
+  // an interface variable whose type is |interface_var_type|. If
+  // |extra_array_length| is not zero, adds the extra arrayness to the created
+  // scalar variables.
+  NestedCompositeComponents CreateScalarInterfaceVarsForReplacement(
+      Instruction* interface_var_type, SpvStorageClass storage_class,
+      uint32_t extra_array_length);
+
+  // Creates scalar variables with the storage classe |storage_class| to replace
+  // the interface variable whose type is OpTypeArray |interface_var_type| with.
+  // If |extra_array_length| is not zero, adds the extra arrayness to all the
+  // scalar variables.
+  NestedCompositeComponents CreateScalarInterfaceVarsForArray(
+      Instruction* interface_var_type, SpvStorageClass storage_class,
+      uint32_t extra_array_length);
+
+  // Creates scalar variables with the storage classe |storage_class| to replace
+  // the interface variable whose type is OpTypeMatrix |interface_var_type|
+  // with. If |extra_array_length| is not zero, adds the extra arrayness to all
+  // the scalar variables.
+  NestedCompositeComponents CreateScalarInterfaceVarsForMatrix(
+      Instruction* interface_var_type, SpvStorageClass storage_class,
+      uint32_t extra_array_length);
+
+  // Recursively adds Location and Component decorations to variables in
+  // |vars| with |location| and |component|. Increases |location| by one after
+  // it actually adds Location and Component decorations for a variable.
+  void AddLocationAndComponentDecorations(const NestedCompositeComponents& vars,
+                                          uint32_t* location,
+                                          uint32_t component);
+
+  // Replaces the interface variable |interface_var| with
+  // |scalar_interface_vars| and returns whether it succeeds or not.
+  // |extra_arrayness| is the extra arrayness of the interface variable.
+  // |scalar_interface_vars| contains the nested variables to replace the
+  // interface variable with.
+  bool ReplaceInterfaceVarWith(
+      Instruction* interface_var, uint32_t extra_arrayness,
+      const NestedCompositeComponents& scalar_interface_vars);
+
+  // Replaces |interface_var| in the operands of instructions
+  // |interface_var_users| with |scalar_interface_vars|. This is a recursive
+  // method and |interface_var_component_indices| is used to specify which
+  // recursive component of |interface_var| is replaced. Returns composite
+  // construct instructions to be replaced with load instructions of
+  // |interface_var_users| via |loads_to_composites|. Returns composite
+  // construct instructions to be replaced with load instructions of access
+  // chain instructions in |interface_var_users| via
+  // |loads_for_access_chain_to_composites|.
+  bool ReplaceComponentsOfInterfaceVarWith(
+      Instruction* interface_var,
+      const std::vector<Instruction*>& interface_var_users,
+      const NestedCompositeComponents& scalar_interface_vars,
+      std::vector<uint32_t>& interface_var_component_indices,
+      const uint32_t* extra_array_index,
+      std::unordered_map<Instruction*, Instruction*>* loads_to_composites,
+      std::unordered_map<Instruction*, Instruction*>*
+          loads_for_access_chain_to_composites);
+
+  // Replaces |interface_var| in the operands of instructions
+  // |interface_var_users| with |components| that is a vector of components for
+  // the interface variable |interface_var|. This is a recursive method and
+  // |interface_var_component_indices| is used to specify which recursive
+  // component of |interface_var| is replaced. Returns composite construct
+  // instructions to be replaced with load instructions of |interface_var_users|
+  // via |loads_to_composites|. Returns composite construct instructions to be
+  // replaced with load instructions of access chain instructions in
+  // |interface_var_users| via |loads_for_access_chain_to_composites|.
+  bool ReplaceMultipleComponentsOfInterfaceVarWith(
+      Instruction* interface_var,
+      const std::vector<Instruction*>& interface_var_users,
+      const std::vector<NestedCompositeComponents>& components,
+      std::vector<uint32_t>& interface_var_component_indices,
+      const uint32_t* extra_array_index,
+      std::unordered_map<Instruction*, Instruction*>* loads_to_composites,
+      std::unordered_map<Instruction*, Instruction*>*
+          loads_for_access_chain_to_composites);
+
+  // Replaces a component of |interface_var| that is used as an operand of
+  // instruction |interface_var_user| with |scalar_var|.
+  // |interface_var_component_indices| is a vector of recursive indices for
+  // which recursive component of |interface_var| is replaced. If
+  // |interface_var_user| is a load, returns the component value via
+  // |loads_to_component_values|. If |interface_var_user| is an access chain,
+  // returns the component value for loads of |interface_var_user| via
+  // |loads_for_access_chain_to_component_values|.
+  bool ReplaceComponentOfInterfaceVarWith(
+      Instruction* interface_var, Instruction* interface_var_user,
+      Instruction* scalar_var,
+      const std::vector<uint32_t>& interface_var_component_indices,
+      const uint32_t* extra_array_index,
+      std::unordered_map<Instruction*, Instruction*>* loads_to_component_values,
+      std::unordered_map<Instruction*, Instruction*>*
+          loads_for_access_chain_to_component_values);
+
+  // Creates instructions to load |scalar_var| and inserts them before
+  // |insert_before|. If |extra_array_index| is not null, they load
+  // |extra_array_index| th component of |scalar_var| instead of |scalar_var|
+  // itself.
+  Instruction* LoadScalarVar(Instruction* scalar_var,
+                             const uint32_t* extra_array_index,
+                             Instruction* insert_before);
+
+  // Creates instructions to load an access chain to |var| and inserts them
+  // before |insert_before|. |Indexes| will be Indexes operand of the access
+  // chain.
+  Instruction* LoadAccessChainToVar(Instruction* var,
+                                    const std::vector<uint32_t>& indexes,
+                                    Instruction* insert_before);
+
+  // Creates instructions to store a component of an aggregate whose id is
+  // |value_id| to an access chain to |scalar_var| and inserts the created
+  // instructions before |insert_before|. To get the component, recursively
+  // traverses the aggregate with |component_indices| as indexes.
+  // Numbers in |access_chain_indices| are the Indexes operand of the access
+  // chain to |scalar_var|
+  void StoreComponentOfValueToAccessChainToScalarVar(
+      uint32_t value_id, const std::vector<uint32_t>& component_indices,
+      Instruction* scalar_var,
+      const std::vector<uint32_t>& access_chain_indices,
+      Instruction* insert_before);
+
+  // Creates instructions to store a component of an aggregate whose id is
+  // |value_id| to |scalar_var| and inserts the created instructions before
+  // |insert_before|. To get the component, recursively traverses the aggregate
+  // using |extra_array_index| and |component_indices| as indexes.
+  void StoreComponentOfValueToScalarVar(
+      uint32_t value_id, const std::vector<uint32_t>& component_indices,
+      Instruction* scalar_var, const uint32_t* extra_array_index,
+      Instruction* insert_before);
+
+  // Creates instructions to store a component of an aggregate whose id is
+  // |value_id| to |ptr| and inserts the created instructions before
+  // |insert_before|. To get the component, recursively traverses the aggregate
+  // using |extra_array_index| and |component_indices| as indexes.
+  // |component_type_id| is the id of the type instruction of the component.
+  void StoreComponentOfValueTo(uint32_t component_type_id, uint32_t value_id,
+                               const std::vector<uint32_t>& component_indices,
+                               Instruction* ptr,
+                               const uint32_t* extra_array_index,
+                               Instruction* insert_before);
+
+  // Creates new OpCompositeExtract with |type_id| for Result Type,
+  // |composite_id| for Composite operand, and |indexes| for Indexes operands.
+  // If |extra_first_index| is not nullptr, uses it as the first Indexes
+  // operand.
+  Instruction* CreateCompositeExtract(uint32_t type_id, uint32_t composite_id,
+                                      const std::vector<uint32_t>& indexes,
+                                      const uint32_t* extra_first_index);
+
+  // Creates a new OpLoad whose Result Type is |type_id| and Pointer operand is
+  // |ptr|. Inserts the new instruction before |insert_before|.
+  Instruction* CreateLoad(uint32_t type_id, Instruction* ptr,
+                          Instruction* insert_before);
+
+  // Clones an annotation instruction |annotation_inst| and sets the target
+  // operand of the new annotation instruction as |var_id|.
+  void CloneAnnotationForVariable(Instruction* annotation_inst,
+                                  uint32_t var_id);
+
+  // Replaces the interface variable |interface_var| in the operands of the
+  // entry point |entry_point| with |scalar_var_id|. If it cannot find
+  // |interface_var| from the operands of the entry point |entry_point|, adds
+  // |scalar_var_id| as an operand of the entry point |entry_point|.
+  bool ReplaceInterfaceVarInEntryPoint(Instruction* interface_var,
+                                       Instruction* entry_point,
+                                       uint32_t scalar_var_id);
+
+  // Creates an access chain instruction whose Base operand is |var| and Indexes
+  // operand is |index|. |component_type_id| is the id of the type instruction
+  // that is the type of component. Inserts the new access chain before
+  // |insert_before|.
+  Instruction* CreateAccessChainWithIndex(uint32_t component_type_id,
+                                          Instruction* var, uint32_t index,
+                                          Instruction* insert_before);
+
+  // Returns the pointee type of the type of variable |var|.
+  uint32_t GetPointeeTypeIdOfVar(Instruction* var);
+
+  // Replaces the access chain |access_chain| and its users with a new access
+  // chain that points |scalar_var| as the Base operand having
+  // |interface_var_component_indices| as Indexes operands and users of the new
+  // access chain. When some of the users are load instructions, returns the
+  // original load instruction to the new instruction that loads a component of
+  // the original load value via |loads_to_component_values|.
+  void ReplaceAccessChainWith(
+      Instruction* access_chain,
+      const std::vector<uint32_t>& interface_var_component_indices,
+      Instruction* scalar_var,
+      std::unordered_map<Instruction*, Instruction*>*
+          loads_to_component_values);
+
+  // Assuming that |access_chain| is an access chain instruction whose Base
+  // operand is |base_access_chain|, replaces the operands of |access_chain|
+  // with operands of |base_access_chain| and Indexes operands of
+  // |access_chain|.
+  void UseBaseAccessChainForAccessChain(Instruction* access_chain,
+                                        Instruction* base_access_chain);
+
+  // Creates composite construct instructions for load instructions that are the
+  // keys of |loads_to_component_values| if no such composite construct
+  // instructions exist. Adds a component of the composite as an operand of the
+  // created composite construct instruction. Each value of
+  // |loads_to_component_values| is the component. Returns the created composite
+  // construct instructions using |loads_to_composites|. |depth_to_component| is
+  // the number of recursive access steps to get the component from the
+  // composite.
+  void AddComponentsToCompositesForLoads(
+      const std::unordered_map<Instruction*, Instruction*>&
+          loads_to_component_values,
+      std::unordered_map<Instruction*, Instruction*>* loads_to_composites,
+      uint32_t depth_to_component);
+
+  // Creates a composite construct instruction for a component of the value of
+  // instruction |load| in |depth_to_component| th recursive depth and inserts
+  // it after |load|.
+  Instruction* CreateCompositeConstructForComponentOfLoad(
+      Instruction* load, uint32_t depth_to_component);
+
+  // Creates a new access chain instruction that points to variable |var| whose
+  // type is the instruction with |var_type_id| and inserts it before
+  // |insert_before|. The new access chain will have |index_ids| for Indexes
+  // operands. Returns the type id of the component that is pointed by the new
+  // access chain via |component_type_id|.
+  Instruction* CreateAccessChainToVar(uint32_t var_type_id, Instruction* var,
+                                      const std::vector<uint32_t>& index_ids,
+                                      Instruction* insert_before,
+                                      uint32_t* component_type_id);
+
+  // Returns the result id of OpTypeArray instrunction whose Element Type
+  // operand is |elem_type_id| and Length operand is |array_length|.
+  uint32_t GetArrayType(uint32_t elem_type_id, uint32_t array_length);
+
+  // Returns the result id of OpTypePointer instrunction whose Type
+  // operand is |type_id| and Storage Class operand is |storage_class|.
+  uint32_t GetPointerType(uint32_t type_id, SpvStorageClass storage_class);
+
+  // Kills an instrunction |inst| and its users.
+  void KillInstructionAndUsers(Instruction* inst);
+
+  // Kills a vector of instrunctions |insts| and their users.
+  void KillInstructionsAndUsers(const std::vector<Instruction*>& insts);
+
+  // Kills all OpDecorate instructions for Location and Component of the
+  // variable whose id is |var_id|.
+  void KillLocationAndComponentDecorations(uint32_t var_id);
+
+  // If |var| has the extra arrayness for an entry point, reports an error and
+  // returns true. Otherwise, returns false.
+  bool ReportErrorIfHasExtraArraynessForOtherEntry(Instruction* var);
+
+  // If |var| does not have the extra arrayness for an entry point, reports an
+  // error and returns true. Otherwise, returns false.
+  bool ReportErrorIfHasNoExtraArraynessForOtherEntry(Instruction* var);
+
+  // If |interface_var| has the extra arrayness for an entry point but it does
+  // not have one for another entry point, reports an error and returns false.
+  // Otherwise, returns true. |has_extra_arrayness| denotes whether it has an
+  // extra arrayness for an entry point or not.
+  bool CheckExtraArraynessConflictBetweenEntries(Instruction* interface_var,
+                                                 bool has_extra_arrayness);
+
+  // Conducts the scalar replacement for the interface variables used by the
+  // |entry_point|.
+  Pass::Status ReplaceInterfaceVarsWithScalars(Instruction& entry_point);
+
+  // A set of interface variable ids that were already removed from operands of
+  // the entry point.
+  std::unordered_set<uint32_t>
+      interface_vars_removed_from_entry_point_operands_;
+
+  // A mapping from ids of new composite construct instructions that load
+  // instructions are replaced with to the recursive depth of the component of
+  // load that the new component construct instruction is used for.
+  std::unordered_map<uint32_t, uint32_t> composite_ids_to_component_depths;
+
+  // A set of interface variables with the extra arrayness for any of the entry
+  // points.
+  std::unordered_set<Instruction*> vars_with_extra_arrayness;
+
+  // A set of interface variables without the extra arrayness for any of the
+  // entry points.
+  std::unordered_set<Instruction*> vars_without_extra_arrayness;
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_INTERFACE_VAR_SROA_H_
diff --git a/source/opt/interp_fixup_pass.cpp b/source/opt/interp_fixup_pass.cpp
index ad29e6a..e8cdd99 100644
--- a/source/opt/interp_fixup_pass.cpp
+++ b/source/opt/interp_fixup_pass.cpp
@@ -31,13 +31,6 @@
 // Input Operand Indices
 static const int kSpvVariableStorageClassInIdx = 0;
 
-// Avoid unused variable warning/error on Linux
-#ifndef NDEBUG
-#define USE_ASSERT(x) assert(x)
-#else
-#define USE_ASSERT(x) ((void)(x))
-#endif
-
 // Folding rule function which attempts to replace |op(OpLoad(a),...)|
 // by |op(a,...)|, where |op| is one of the GLSLstd450 InterpolateAt*
 // instructions. Returns true if replaced, false otherwise.
diff --git a/source/opt/ir_builder.h b/source/opt/ir_builder.h
index 4433cf0..9d4fa8f 100644
--- a/source/opt/ir_builder.h
+++ b/source/opt/ir_builder.h
@@ -487,6 +487,15 @@
     return AddInstruction(std::move(new_inst));
   }
 
+  Instruction* AddVariable(uint32_t type_id, uint32_t storage_class) {
+    std::vector<Operand> operands;
+    operands.push_back({SPV_OPERAND_TYPE_ID, {storage_class}});
+    std::unique_ptr<Instruction> new_inst(
+        new Instruction(GetContext(), SpvOpVariable, type_id,
+                        GetContext()->TakeNextId(), operands));
+    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}});
diff --git a/source/opt/ir_context.cpp b/source/opt/ir_context.cpp
index 612a831..c9c3f1b 100644
--- a/source/opt/ir_context.cpp
+++ b/source/opt/ir_context.cpp
@@ -41,6 +41,8 @@
 namespace opt {
 
 void IRContext::BuildInvalidAnalyses(IRContext::Analysis set) {
+  set = Analysis(set & ~valid_analyses_);
+
   if (set & kAnalysisDefUse) {
     BuildDefUseManager();
   }
@@ -106,7 +108,7 @@
     analyses_to_invalidate |= kAnalysisDebugInfo;
   }
 
-  // The dominator analysis hold the psuedo entry and exit nodes from the CFG.
+  // The dominator analysis hold the pseudo entry and exit nodes from the CFG.
   // Also if the CFG change the dominators many changed as well, so the
   // dominator analysis should be invalidated as well.
   if (analyses_to_invalidate & kAnalysisCFG) {
@@ -317,7 +319,7 @@
 #else
   if (AreAnalysesValid(kAnalysisDefUse)) {
     analysis::DefUseManager new_def_use(module());
-    if (*get_def_use_mgr() != new_def_use) {
+    if (!CompareAndPrintDifferences(*get_def_use_mgr(), new_def_use)) {
       return false;
     }
   }
@@ -623,9 +625,8 @@
 void IRContext::AddCombinatorsForExtension(Instruction* extension) {
   assert(extension->opcode() == SpvOpExtInstImport &&
          "Expecting an import of an extension's instruction set.");
-  const char* extension_name =
-      reinterpret_cast<const char*>(&extension->GetInOperand(0).words[0]);
-  if (!strcmp(extension_name, "GLSL.std.450")) {
+  const std::string extension_name = extension->GetInOperand(0).AsString();
+  if (extension_name == "GLSL.std.450") {
     combinator_ops_[extension->result_id()] = {GLSLstd450Round,
                                                GLSLstd450RoundEven,
                                                GLSLstd450Trunc,
@@ -925,6 +926,19 @@
   return modified;
 }
 
+void IRContext::CollectCallTreeFromRoots(unsigned entryId,
+                                         std::unordered_set<uint32_t>* funcs) {
+  std::queue<uint32_t> roots;
+  roots.push(entryId);
+  while (!roots.empty()) {
+    const uint32_t fi = roots.front();
+    roots.pop();
+    funcs->insert(fi);
+    Function* fn = GetFunction(fi);
+    AddCalls(fn, &roots);
+  }
+}
+
 void IRContext::EmitErrorMessage(std::string message, Instruction* inst) {
   if (!consumer()) {
     return;
@@ -944,11 +958,11 @@
 
   uint32_t line_number = 0;
   uint32_t col_number = 0;
-  char* source = nullptr;
+  std::string source;
   if (line_inst != nullptr) {
     Instruction* file_name =
         get_def_use_mgr()->GetDef(line_inst->GetSingleWordInOperand(0));
-    source = reinterpret_cast<char*>(&file_name->GetInOperand(0).words[0]);
+    source = file_name->GetInOperand(0).AsString();
 
     // Get the line number and column number.
     line_number = line_inst->GetSingleWordInOperand(1);
@@ -957,7 +971,7 @@
 
   message +=
       "\n  " + inst->PrettyPrint(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
-  consumer()(SPV_MSG_ERROR, source, {line_number, col_number, 0},
+  consumer()(SPV_MSG_ERROR, source.c_str(), {line_number, col_number, 0},
              message.c_str());
 }
 
diff --git a/source/opt/ir_context.h b/source/opt/ir_context.h
index 6585347..2f27942 100644
--- a/source/opt/ir_context.h
+++ b/source/opt/ir_context.h
@@ -43,6 +43,7 @@
 #include "source/opt/type_manager.h"
 #include "source/opt/value_number_table.h"
 #include "source/util/make_unique.h"
+#include "source/util/string_utils.h"
 
 namespace spvtools {
 namespace opt {
@@ -301,7 +302,7 @@
     }
   }
 
-  // Returns a pointer the decoration manager.  If the decoration manger is
+  // Returns a pointer the decoration manager.  If the decoration manager is
   // invalid, it is rebuilt first.
   analysis::DecorationManager* get_decoration_mgr() {
     if (!AreAnalysesValid(kAnalysisDecorations)) {
@@ -384,7 +385,7 @@
 
   // Deletes the instruction defining the given |id|. Returns true on
   // success, false if the given |id| is not defined at all. This method also
-  // erases the name, decorations, and defintion of |id|.
+  // erases the name, decorations, and definition of |id|.
   //
   // Pointers and iterators pointing to the deleted instructions become invalid.
   // However other pointers and iterators are still valid.
@@ -410,6 +411,10 @@
   void CollectNonSemanticTree(Instruction* inst,
                               std::unordered_set<Instruction*>* to_kill);
 
+  // Collect function reachable from |entryId|, returns |funcs|
+  void CollectCallTreeFromRoots(unsigned entryId,
+                                std::unordered_set<uint32_t>* funcs);
+
   // Returns true if all of the given analyses are valid.
   bool AreAnalysesValid(Analysis set) { return (set & valid_analyses_) == set; }
 
@@ -518,6 +523,18 @@
         std::string message = "ID overflow. Try running compact-ids.";
         consumer()(SPV_MSG_ERROR, "", {0, 0, 0}, message.c_str());
       }
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+      // If TakeNextId returns 0, it is very likely that execution will
+      // subsequently fail. Such failures are false alarms from a fuzzing point
+      // of view: they are due to the fact that too many ids were used, rather
+      // than being due to an actual bug. Thus, during a fuzzing build, it is
+      // preferable to bail out when ID overflow occurs.
+      //
+      // A zero exit code is returned here because a non-zero code would cause
+      // ClusterFuzz/OSS-Fuzz to regard the termination as a crash, and spurious
+      // crash reports is what this guard aims to avoid.
+      exit(0);
+#endif
     }
     return next_id;
   }
@@ -789,7 +806,7 @@
   // iterators to traverse instructions.
   std::unordered_map<uint32_t, Function*> id_to_func_;
 
-  // A bitset indicating which analyes are currently valid.
+  // A bitset indicating which analyzes are currently valid.
   Analysis valid_analyses_;
 
   // Opcodes of shader capability core executable instructions
@@ -854,8 +871,7 @@
 
 inline IRContext::Analysis& operator|=(IRContext::Analysis& lhs,
                                        IRContext::Analysis rhs) {
-  lhs = static_cast<IRContext::Analysis>(static_cast<int>(lhs) |
-                                         static_cast<int>(rhs));
+  lhs = lhs | rhs;
   return lhs;
 }
 
@@ -1020,11 +1036,7 @@
 }
 
 void IRContext::AddExtension(const std::string& ext_name) {
-  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);
+  std::vector<uint32_t> ext_words = spvtools::utils::MakeVector(ext_name);
   AddExtension(std::unique_ptr<Instruction>(
       new Instruction(this, SpvOpExtension, 0u, 0u,
                       {{SPV_OPERAND_TYPE_LITERAL_STRING, ext_words}})));
@@ -1041,11 +1053,7 @@
 }
 
 void IRContext::AddExtInstImport(const std::string& name) {
-  const auto num_chars = 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(), name.data(), num_chars);
+  std::vector<uint32_t> ext_words = spvtools::utils::MakeVector(name);
   AddExtInstImport(std::unique_ptr<Instruction>(
       new Instruction(this, SpvOpExtInstImport, 0u, TakeNextId(),
                       {{SPV_OPERAND_TYPE_LITERAL_STRING, ext_words}})));
@@ -1086,6 +1094,9 @@
       id_to_name_->insert({d->GetSingleWordInOperand(0), d.get()});
     }
   }
+  if (AreAnalysesValid(kAnalysisDefUse)) {
+    get_def_use_mgr()->AnalyzeInstDefUse(d.get());
+  }
   module()->AddDebug2Inst(std::move(d));
 }
 
diff --git a/source/opt/ir_loader.cpp b/source/opt/ir_loader.cpp
index a82b530..734ad55 100644
--- a/source/opt/ir_loader.cpp
+++ b/source/opt/ir_loader.cpp
@@ -187,9 +187,12 @@
         module_->AddExtInstImport(std::move(spv_inst));
       } else if (opcode == SpvOpMemoryModel) {
         module_->SetMemoryModel(std::move(spv_inst));
+      } else if (opcode == SpvOpSamplerImageAddressingModeNV) {
+        module_->SetSampledImageAddressMode(std::move(spv_inst));
       } else if (opcode == SpvOpEntryPoint) {
         module_->AddEntryPoint(std::move(spv_inst));
-      } else if (opcode == SpvOpExecutionMode) {
+      } else if (opcode == SpvOpExecutionMode ||
+                 opcode == SpvOpExecutionModeId) {
         module_->AddExecutionMode(std::move(spv_inst));
       } else if (IsDebug1Inst(opcode)) {
         module_->AddDebug1Inst(std::move(spv_inst));
diff --git a/source/opt/local_access_chain_convert_pass.cpp b/source/opt/local_access_chain_convert_pass.cpp
index da9ba8c..d11682f 100644
--- a/source/opt/local_access_chain_convert_pass.cpp
+++ b/source/opt/local_access_chain_convert_pass.cpp
@@ -19,6 +19,7 @@
 #include "ir_builder.h"
 #include "ir_context.h"
 #include "iterator.h"
+#include "source/util/string_utils.h"
 
 namespace spvtools {
 namespace opt {
@@ -27,8 +28,6 @@
 
 const uint32_t kStoreValIdInIdx = 1;
 const uint32_t kAccessChainPtrIdInIdx = 0;
-const uint32_t kConstantValueInIdx = 0;
-const uint32_t kTypeIntWidthInIdx = 0;
 
 }  // anonymous namespace
 
@@ -66,7 +65,19 @@
   ptrInst->ForEachInId([&iidIdx, &in_opnds, this](const uint32_t* iid) {
     if (iidIdx > 0) {
       const Instruction* cInst = get_def_use_mgr()->GetDef(*iid);
-      uint32_t val = cInst->GetSingleWordInOperand(kConstantValueInIdx);
+      const auto* constant_value =
+          context()->get_constant_mgr()->GetConstantFromInst(cInst);
+      assert(constant_value != nullptr &&
+             "Expecting the index to be a constant.");
+
+      // We take the sign extended value because OpAccessChain interprets the
+      // index as signed.
+      int64_t long_value = constant_value->GetSignExtendedValue();
+      assert(long_value <= UINT32_MAX && long_value >= 0 &&
+             "The index value is too large for a composite insert or extract "
+             "instruction.");
+
+      uint32_t val = static_cast<uint32_t>(long_value);
       in_opnds->push_back(
           {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {val}});
     }
@@ -168,13 +179,18 @@
   return true;
 }
 
-bool LocalAccessChainConvertPass::IsConstantIndexAccessChain(
+bool LocalAccessChainConvertPass::Is32BitConstantIndexAccessChain(
     const Instruction* acp) const {
   uint32_t inIdx = 0;
   return acp->WhileEachInId([&inIdx, this](const uint32_t* tid) {
     if (inIdx > 0) {
       Instruction* opInst = get_def_use_mgr()->GetDef(*tid);
       if (opInst->opcode() != SpvOpConstant) return false;
+      const auto* index =
+          context()->get_constant_mgr()->GetConstantFromInst(opInst);
+      int64_t index_value = index->GetSignExtendedValue();
+      if (index_value > UINT32_MAX) return false;
+      if (index_value < 0) return false;
     }
     ++inIdx;
     return true;
@@ -223,14 +239,21 @@
           }
           // Rule out variables with nested access chains
           // TODO(): Convert nested access chains
-          if (IsNonPtrAccessChain(op) && ptrInst->GetSingleWordInOperand(
+          bool is_non_ptr_access_chain = IsNonPtrAccessChain(op);
+          if (is_non_ptr_access_chain && ptrInst->GetSingleWordInOperand(
                                              kAccessChainPtrIdInIdx) != varId) {
             seen_non_target_vars_.insert(varId);
             seen_target_vars_.erase(varId);
             break;
           }
           // Rule out variables accessed with non-constant indices
-          if (!IsConstantIndexAccessChain(ptrInst)) {
+          if (!Is32BitConstantIndexAccessChain(ptrInst)) {
+            seen_non_target_vars_.insert(varId);
+            seen_target_vars_.erase(varId);
+            break;
+          }
+
+          if (is_non_ptr_access_chain && AnyIndexIsOutOfBounds(ptrInst)) {
             seen_non_target_vars_.insert(varId);
             seen_target_vars_.erase(varId);
             break;
@@ -328,8 +351,7 @@
     return false;
   // If any extension not in allowlist, return false
   for (auto& ei : get_module()->extensions()) {
-    const char* extName =
-        reinterpret_cast<const char*>(&ei.GetInOperand(0).words[0]);
+    const std::string extName = ei.GetInOperand(0).AsString();
     if (extensions_allowlist_.find(extName) == extensions_allowlist_.end())
       return false;
   }
@@ -339,11 +361,9 @@
   for (auto& inst : context()->module()->ext_inst_imports()) {
     assert(inst.opcode() == SpvOpExtInstImport &&
            "Expecting an import of an extension's instruction set.");
-    const char* extension_name =
-        reinterpret_cast<const char*>(&inst.GetInOperand(0).words[0]);
-    if (0 == std::strncmp(extension_name, "NonSemantic.", 12) &&
-        0 != std::strncmp(extension_name, "NonSemantic.Shader.DebugInfo.100",
-                          32)) {
+    const std::string extension_name = inst.GetInOperand(0).AsString();
+    if (spvtools::utils::starts_with(extension_name, "NonSemantic.") &&
+        extension_name != "NonSemantic.Shader.DebugInfo.100") {
       return false;
     }
   }
@@ -351,12 +371,6 @@
 }
 
 Pass::Status LocalAccessChainConvertPass::ProcessImpl() {
-  // If non-32-bit integer type in module, terminate processing
-  // TODO(): Handle non-32-bit integer constants in access chains
-  for (const Instruction& inst : get_module()->types_values())
-    if (inst.opcode() == SpvOpTypeInt &&
-        inst.GetSingleWordInOperand(kTypeIntWidthInIdx) != 32)
-      return Status::SuccessWithoutChange;
   // Do not process if module contains OpGroupDecorate. Additional
   // support required in KillNamesAndDecorates().
   // TODO(greg-lunarg): Add support for OpGroupDecorate
@@ -436,8 +450,47 @@
       "SPV_KHR_integer_dot_product",
       "SPV_EXT_shader_image_int64",
       "SPV_KHR_non_semantic_info",
+      "SPV_KHR_uniform_group_instructions",
+      "SPV_KHR_fragment_shader_barycentric",
   });
 }
 
+bool LocalAccessChainConvertPass::AnyIndexIsOutOfBounds(
+    const Instruction* access_chain_inst) {
+  assert(IsNonPtrAccessChain(access_chain_inst->opcode()));
+
+  analysis::TypeManager* type_mgr = context()->get_type_mgr();
+  analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
+  auto constants = const_mgr->GetOperandConstants(access_chain_inst);
+  uint32_t base_pointer_id = access_chain_inst->GetSingleWordInOperand(0);
+  Instruction* base_pointer = get_def_use_mgr()->GetDef(base_pointer_id);
+  const analysis::Pointer* base_pointer_type =
+      type_mgr->GetType(base_pointer->type_id())->AsPointer();
+  assert(base_pointer_type != nullptr &&
+         "The base of the access chain is not a pointer.");
+  const analysis::Type* current_type = base_pointer_type->pointee_type();
+  for (uint32_t i = 1; i < access_chain_inst->NumInOperands(); ++i) {
+    if (IsIndexOutOfBounds(constants[i], current_type)) {
+      return true;
+    }
+
+    uint32_t index =
+        (constants[i]
+             ? static_cast<uint32_t>(constants[i]->GetZeroExtendedValue())
+             : 0);
+    current_type = type_mgr->GetMemberType(current_type, {index});
+  }
+
+  return false;
+}
+
+bool LocalAccessChainConvertPass::IsIndexOutOfBounds(
+    const analysis::Constant* index, const analysis::Type* type) const {
+  if (index == nullptr) {
+    return false;
+  }
+  return index->GetZeroExtendedValue() >= type->NumberOfComponents();
+}
+
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/local_access_chain_convert_pass.h b/source/opt/local_access_chain_convert_pass.h
index 552062e..c3731b1 100644
--- a/source/opt/local_access_chain_convert_pass.h
+++ b/source/opt/local_access_chain_convert_pass.h
@@ -81,7 +81,7 @@
                               std::vector<Operand>* in_opnds);
 
   // Create a load/insert/store equivalent to a store of
-  // |valId| through (constant index) access chaing |ptrInst|.
+  // |valId| through (constant index) access chain |ptrInst|.
   // Append to |newInsts|.  Returns true if successful.
   bool GenAccessChainStoreReplacement(
       const Instruction* ptrInst, uint32_t valId,
@@ -94,8 +94,9 @@
   bool ReplaceAccessChainLoad(const Instruction* address_inst,
                               Instruction* original_load);
 
-  // Return true if all indices of access chain |acp| are OpConstant integers
-  bool IsConstantIndexAccessChain(const Instruction* acp) const;
+  // Return true if all indices of the access chain |acp| are OpConstant
+  // integers whose signed values can be represented as unsigned 32-bit values.
+  bool Is32BitConstantIndexAccessChain(const Instruction* acp) const;
 
   // Identify all function scope variables of target type which are
   // accessed only with loads, stores and access chains with constant
@@ -110,6 +111,17 @@
   // Returns a status to indicate success or failure, and change or no change.
   Status ConvertLocalAccessChains(Function* func);
 
+  // Returns true one of the indexes in the |access_chain_inst| is definitly out
+  // of bounds.  If the size of the type or the value of the index is unknown,
+  // then it will be considered in-bounds.
+  bool AnyIndexIsOutOfBounds(const Instruction* access_chain_inst);
+
+  // Returns true if getting element |index| from |type| would be out-of-bounds.
+  // If |index| is nullptr or the size of the type are unknown, then it will be
+  // considered in-bounds.
+  bool IsIndexOutOfBounds(const analysis::Constant* index,
+                          const analysis::Type* type) const;
+
   // Initialize extensions allowlist
   void InitExtensions();
 
diff --git a/source/opt/local_single_block_elim_pass.cpp b/source/opt/local_single_block_elim_pass.cpp
index 5fd4f65..a58e8e4 100644
--- a/source/opt/local_single_block_elim_pass.cpp
+++ b/source/opt/local_single_block_elim_pass.cpp
@@ -19,6 +19,7 @@
 #include <vector>
 
 #include "source/opt/iterator.h"
+#include "source/util/string_utils.h"
 
 namespace spvtools {
 namespace opt {
@@ -183,8 +184,7 @@
 bool LocalSingleBlockLoadStoreElimPass::AllExtensionsSupported() const {
   // If any extension not in allowlist, return false
   for (auto& ei : get_module()->extensions()) {
-    const char* extName =
-        reinterpret_cast<const char*>(&ei.GetInOperand(0).words[0]);
+    const std::string extName = ei.GetInOperand(0).AsString();
     if (extensions_allowlist_.find(extName) == extensions_allowlist_.end())
       return false;
   }
@@ -194,11 +194,9 @@
   for (auto& inst : context()->module()->ext_inst_imports()) {
     assert(inst.opcode() == SpvOpExtInstImport &&
            "Expecting an import of an extension's instruction set.");
-    const char* extension_name =
-        reinterpret_cast<const char*>(&inst.GetInOperand(0).words[0]);
-    if (0 == std::strncmp(extension_name, "NonSemantic.", 12) &&
-        0 != std::strncmp(extension_name, "NonSemantic.Shader.DebugInfo.100",
-                          32)) {
+    const std::string extension_name = inst.GetInOperand(0).AsString();
+    if (spvtools::utils::starts_with(extension_name, "NonSemantic.") &&
+        extension_name != "NonSemantic.Shader.DebugInfo.100") {
       return false;
     }
   }
@@ -288,6 +286,8 @@
       "SPV_KHR_integer_dot_product",
       "SPV_EXT_shader_image_int64",
       "SPV_KHR_non_semantic_info",
+      "SPV_KHR_uniform_group_instructions",
+      "SPV_KHR_fragment_shader_barycentric",
   });
 }
 
diff --git a/source/opt/local_single_store_elim_pass.cpp b/source/opt/local_single_store_elim_pass.cpp
index 051bcad..81648c7 100644
--- a/source/opt/local_single_store_elim_pass.cpp
+++ b/source/opt/local_single_store_elim_pass.cpp
@@ -19,6 +19,7 @@
 #include "source/cfa.h"
 #include "source/latest_version_glsl_std_450_header.h"
 #include "source/opt/iterator.h"
+#include "source/util/string_utils.h"
 
 namespace spvtools {
 namespace opt {
@@ -48,8 +49,7 @@
 bool LocalSingleStoreElimPass::AllExtensionsSupported() const {
   // If any extension not in allowlist, return false
   for (auto& ei : get_module()->extensions()) {
-    const char* extName =
-        reinterpret_cast<const char*>(&ei.GetInOperand(0).words[0]);
+    const std::string extName = ei.GetInOperand(0).AsString();
     if (extensions_allowlist_.find(extName) == extensions_allowlist_.end())
       return false;
   }
@@ -59,11 +59,9 @@
   for (auto& inst : context()->module()->ext_inst_imports()) {
     assert(inst.opcode() == SpvOpExtInstImport &&
            "Expecting an import of an extension's instruction set.");
-    const char* extension_name =
-        reinterpret_cast<const char*>(&inst.GetInOperand(0).words[0]);
-    if (0 == std::strncmp(extension_name, "NonSemantic.", 12) &&
-        0 != std::strncmp(extension_name, "NonSemantic.Shader.DebugInfo.100",
-                          32)) {
+    const std::string extension_name = inst.GetInOperand(0).AsString();
+    if (spvtools::utils::starts_with(extension_name, "NonSemantic.") &&
+        extension_name != "NonSemantic.Shader.DebugInfo.100") {
       return false;
     }
   }
@@ -141,6 +139,8 @@
       "SPV_KHR_integer_dot_product",
       "SPV_EXT_shader_image_int64",
       "SPV_KHR_non_semantic_info",
+      "SPV_KHR_uniform_group_instructions",
+      "SPV_KHR_fragment_shader_barycentric",
   });
 }
 bool LocalSingleStoreElimPass::ProcessVariable(Instruction* var_inst) {
@@ -175,29 +175,9 @@
 
 bool LocalSingleStoreElimPass::RewriteDebugDeclares(Instruction* store_inst,
                                                     uint32_t var_id) {
-  std::unordered_set<Instruction*> invisible_decls;
   uint32_t value_id = store_inst->GetSingleWordInOperand(1);
-  bool modified =
-      context()->get_debug_info_mgr()->AddDebugValueIfVarDeclIsVisible(
-          store_inst, var_id, value_id, store_inst, &invisible_decls);
-
-  // For cases like the argument passing for an inlined function, the value
-  // assignment is out of DebugDeclare's scope, but we have to preserve the
-  // value assignment information using DebugValue. Generally, we need
-  // ssa-rewrite analysis to decide a proper value assignment but at this point
-  // we confirm that |var_id| has a single store. We can safely add DebugValue.
-  if (!invisible_decls.empty()) {
-    BasicBlock* store_block = context()->get_instr_block(store_inst);
-    DominatorAnalysis* dominator_analysis =
-        context()->GetDominatorAnalysis(store_block->GetParent());
-    for (auto* decl : invisible_decls) {
-      if (dominator_analysis->Dominates(store_inst, decl)) {
-        context()->get_debug_info_mgr()->AddDebugValueForDecl(decl, value_id,
-                                                              decl, store_inst);
-        modified = true;
-      }
-    }
-  }
+  bool modified = context()->get_debug_info_mgr()->AddDebugValueForVariable(
+      store_inst, var_id, value_id, store_inst);
   modified |= context()->get_debug_info_mgr()->KillDebugDeclares(var_id);
   return modified;
 }
diff --git a/source/opt/loop_descriptor.cpp b/source/opt/loop_descriptor.cpp
index b5b5630..13982d1 100644
--- a/source/opt/loop_descriptor.cpp
+++ b/source/opt/loop_descriptor.cpp
@@ -497,7 +497,8 @@
     // continue blocks that must be copied to retain the structured order.
     // The structured order will include these.
     std::list<BasicBlock*> order;
-    cfg.ComputeStructuredOrder(loop_header_->GetParent(), loop_header_, &order);
+    cfg.ComputeStructuredOrder(loop_header_->GetParent(), loop_header_,
+                               loop_merge_, &order);
     for (BasicBlock* bb : order) {
       if (bb == GetMergeBlock()) {
         break;
@@ -719,7 +720,7 @@
     step_value = -step_value;
   }
 
-  // Find the inital value of the loop and make sure it is a constant integer.
+  // Find the initial value of the loop and make sure it is a constant integer.
   int64_t init_value = 0;
   if (!GetInductionInitValue(induction, &init_value)) return false;
 
@@ -751,9 +752,13 @@
 // We retrieve the number of iterations using the following formula, diff /
 // |step_value| where diff is calculated differently according to the
 // |condition| and uses the |condition_value| and |init_value|. If diff /
-// |step_value| is NOT cleanly divisable then we add one to the sum.
+// |step_value| is NOT cleanly divisible then we add one to the sum.
 int64_t Loop::GetIterations(SpvOp condition, int64_t condition_value,
                             int64_t init_value, int64_t step_value) const {
+  if (step_value == 0) {
+    return 0;
+  }
+
   int64_t diff = 0;
 
   switch (condition) {
@@ -795,7 +800,7 @@
       // If the condition is not met to begin with the loop will never iterate.
       if (!(init_value >= condition_value)) return 0;
 
-      // We subract one to make it the same as SpvOpGreaterThan as it is
+      // We subtract one to make it the same as SpvOpGreaterThan as it is
       // functionally equivalent.
       diff = init_value - (condition_value - 1);
 
diff --git a/source/opt/loop_descriptor.h b/source/opt/loop_descriptor.h
index 4b4f8bc..df01227 100644
--- a/source/opt/loop_descriptor.h
+++ b/source/opt/loop_descriptor.h
@@ -395,10 +395,11 @@
   // Sets |merge| as the loop merge block. No checks are performed here.
   inline void SetMergeBlockImpl(BasicBlock* merge) { loop_merge_ = merge; }
 
-  // Each differnt loop |condition| affects how we calculate the number of
+  // Each different loop |condition| affects how we calculate the number of
   // iterations using the |condition_value|, |init_value|, and |step_values| of
   // the induction variable. This method will return the number of iterations in
-  // a loop with those values for a given |condition|.
+  // a loop with those values for a given |condition|.  Returns 0 if the number
+  // of iterations could not be computed.
   int64_t GetIterations(SpvOp condition, int64_t condition_value,
                         int64_t init_value, int64_t step_value) const;
 
diff --git a/source/opt/loop_fission.cpp b/source/opt/loop_fission.cpp
index 0678113..b4df8c6 100644
--- a/source/opt/loop_fission.cpp
+++ b/source/opt/loop_fission.cpp
@@ -29,7 +29,7 @@
 // 2 - For each loop in the list, group each instruction into a set of related
 // instructions by traversing each instructions users and operands recursively.
 // We stop if we encounter an instruction we have seen before or an instruction
-// which we don't consider relevent (i.e OpLoopMerge). We then group these
+// which we don't consider relevant (i.e OpLoopMerge). We then group these
 // groups into two different sets, one for the first loop and one for the
 // second.
 //
@@ -453,7 +453,7 @@
   for (Function& f : *context()->module()) {
     // We collect all the inner most loops in the function and run the loop
     // splitting util on each. The reason we do this is to allow us to iterate
-    // over each, as creating new loops will invalidate the the loop iterator.
+    // over each, as creating new loops will invalidate the loop iterator.
     std::vector<Loop*> inner_most_loops{};
     LoopDescriptor& loop_descriptor = *context()->GetLoopDescriptor(&f);
     for (Loop& loop : loop_descriptor) {
diff --git a/source/opt/loop_fission.h b/source/opt/loop_fission.h
index e7a59c1..9bc12c0 100644
--- a/source/opt/loop_fission.h
+++ b/source/opt/loop_fission.h
@@ -33,7 +33,7 @@
 
 class LoopFissionPass : public Pass {
  public:
-  // Fuction used to determine if a given loop should be split. Takes register
+  // Function used to determine if a given loop should be split. Takes register
   // pressure region for that loop as a parameter and returns true if the loop
   // should be split.
   using FissionCriteriaFunction =
diff --git a/source/opt/loop_fusion.cpp b/source/opt/loop_fusion.cpp
index 07d171a..f3aab28 100644
--- a/source/opt/loop_fusion.cpp
+++ b/source/opt/loop_fusion.cpp
@@ -165,7 +165,7 @@
 
   // Check adjacency, |loop_0_| should come just before |loop_1_|.
   // There is always at least one block between loops, even if it's empty.
-  // We'll check at most 2 preceeding blocks.
+  // We'll check at most 2 preceding blocks.
 
   auto pre_header_1 = loop_1_->GetPreHeaderBlock();
 
@@ -712,7 +712,7 @@
 
   ld->RemoveLoop(loop_1_);
 
-  // Kill unnessecary instructions and remove all empty blocks.
+  // Kill unnecessary instructions and remove all empty blocks.
   for (auto inst : instr_to_delete) {
     context_->KillInst(inst);
   }
diff --git a/source/opt/loop_fusion.h b/source/opt/loop_fusion.h
index d61d678..769da5f 100644
--- a/source/opt/loop_fusion.h
+++ b/source/opt/loop_fusion.h
@@ -40,7 +40,7 @@
   // That means:
   //   * they both have one induction variable
   //   * they have the same upper and lower bounds
-  //     - same inital value
+  //     - same initial value
   //     - same condition
   //   * they have the same update step
   //   * they are adjacent, with |loop_0| appearing before |loop_1|
diff --git a/source/opt/loop_fusion_pass.h b/source/opt/loop_fusion_pass.h
index 3a0be60..9d5b7cc 100644
--- a/source/opt/loop_fusion_pass.h
+++ b/source/opt/loop_fusion_pass.h
@@ -33,7 +33,7 @@
 
   // Processes the given |module|. Returns Status::Failure if errors occur when
   // processing. Returns the corresponding Status::Success if processing is
-  // succesful to indicate whether changes have been made to the modue.
+  // successful to indicate whether changes have been made to the module.
   Status Process() override;
 
  private:
diff --git a/source/opt/loop_peeling.h b/source/opt/loop_peeling.h
index 413f896..2a55fe4 100644
--- a/source/opt/loop_peeling.h
+++ b/source/opt/loop_peeling.h
@@ -261,7 +261,7 @@
 
   // Processes the given |module|. Returns Status::Failure if errors occur when
   // processing. Returns the corresponding Status::Success if processing is
-  // succesful to indicate whether changes have been made to the modue.
+  // successful to indicate whether changes have been made to the module.
   Pass::Status Process() override;
 
  private:
diff --git a/source/opt/loop_unroller.cpp b/source/opt/loop_unroller.cpp
index aff191f..6f4e6f4 100644
--- a/source/opt/loop_unroller.cpp
+++ b/source/opt/loop_unroller.cpp
@@ -163,7 +163,7 @@
 };
 
 // This class implements the actual unrolling. It uses a LoopUnrollState to
-// maintain the state of the unrolling inbetween steps.
+// maintain the state of the unrolling in between steps.
 class LoopUnrollerUtilsImpl {
  public:
   using BasicBlockListTy = std::vector<std::unique_ptr<BasicBlock>>;
@@ -209,7 +209,7 @@
   // Add all blocks_to_add_ to function_ at the |insert_point|.
   void AddBlocksToFunction(const BasicBlock* insert_point);
 
-  // Duplicates the |old_loop|, cloning each body and remaping the ids without
+  // Duplicates the |old_loop|, cloning each body and remapping the ids without
   // removing instructions or changing relative structure. Result will be stored
   // in |new_loop|.
   void DuplicateLoop(Loop* old_loop, Loop* new_loop);
@@ -241,7 +241,7 @@
   // Remap all the in |basic_block| to new IDs and keep the mapping of new ids
   // to old
   // ids. |loop| is used to identify special loop blocks (header, continue,
-  // ect).
+  // etc).
   void AssignNewResultIds(BasicBlock* basic_block);
 
   // Using the map built by AssignNewResultIds, replace the uses in |inst|
@@ -320,7 +320,7 @@
   // and then be remapped at the end.
   std::vector<Instruction*> loop_phi_instructions_;
 
-  // The number of loop iterations that the loop would preform pre-unroll.
+  // The number of loop iterations that the loop would perform pre-unroll.
   size_t number_of_loop_iterations_;
 
   // The amount that the loop steps each iteration.
@@ -384,6 +384,7 @@
   std::unique_ptr<Instruction> new_label{new Instruction(
       context_, SpvOp::SpvOpLabel, 0, context_->TakeNextId(), {})};
   std::unique_ptr<BasicBlock> new_exit_bb{new BasicBlock(std::move(new_label))};
+  new_exit_bb->SetParent(&function_);
 
   // Save the id of the block before we move it.
   uint32_t new_merge_id = new_exit_bb->id();
@@ -839,7 +840,7 @@
   new_loop->SetMergeBlock(new_merge);
 }
 
-// Whenever the utility copies a block it stores it in a tempory buffer, this
+// Whenever the utility copies a block it stores it in a temporary buffer, this
 // function adds the buffer into the Function. The blocks will be inserted
 // after the block |insert_point|.
 void LoopUnrollerUtilsImpl::AddBlocksToFunction(
@@ -996,6 +997,20 @@
   if (!loop_->FindNumberOfIterations(induction, &*condition->ctail(), nullptr))
     return false;
 
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+  // ClusterFuzz/OSS-Fuzz is likely to yield examples with very high loop
+  // iteration counts. This can cause timeouts and memouts during fuzzing that
+  // are not classed as bugs. To avoid this noise, loop unrolling is not applied
+  // to loops with large iteration counts when fuzzing.
+  const size_t kFuzzerIterationLimit = 100;
+  size_t num_iterations;
+  loop_->FindNumberOfIterations(induction, &*condition->ctail(),
+                                &num_iterations);
+  if (num_iterations > kFuzzerIterationLimit) {
+    return false;
+  }
+#endif
+
   // Make sure the latch block is a unconditional branch to the header
   // block.
   const Instruction& branch = *loop_->GetLatchBlock()->ctail();
diff --git a/source/opt/loop_unswitch_pass.cpp b/source/opt/loop_unswitch_pass.cpp
index d805ecf..1ee7e5e 100644
--- a/source/opt/loop_unswitch_pass.cpp
+++ b/source/opt/loop_unswitch_pass.cpp
@@ -118,7 +118,7 @@
 
     // 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
+    // because this path will never be taken because 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) {
diff --git a/source/opt/loop_unswitch_pass.h b/source/opt/loop_unswitch_pass.h
index 3ecdd61..4f7295d 100644
--- a/source/opt/loop_unswitch_pass.h
+++ b/source/opt/loop_unswitch_pass.h
@@ -30,7 +30,7 @@
 
   // Processes the given |module|. Returns Status::Failure if errors occur when
   // processing. Returns the corresponding Status::Success if processing is
-  // succesful to indicate whether changes have been made to the modue.
+  // successful to indicate whether changes have been made to the module.
   Pass::Status Process() override;
 
  private:
diff --git a/source/opt/loop_utils.h b/source/opt/loop_utils.h
index a4e6190..70060fc 100644
--- a/source/opt/loop_utils.h
+++ b/source/opt/loop_utils.h
@@ -123,7 +123,7 @@
   // Clone the |loop_| and make the new loop branch to the second loop on exit.
   Loop* CloneAndAttachLoopToHeader(LoopCloningResult* cloning_result);
 
-  // Perfom a partial unroll of |loop| by given |factor|. This will copy the
+  // Perform a partial unroll of |loop| by given |factor|. This will copy the
   // body of the loop |factor| times. So a |factor| of one would give a new loop
   // with the original body plus one unrolled copy body.
   bool PartiallyUnroll(size_t factor);
@@ -139,7 +139,7 @@
   // 1. That the loop is in structured order.
   // 2. That the continue block is a branch to the header.
   // 3. That the only phi used in the loop is the induction variable.
-  //  TODO(stephen@codeplay.com): This is a temporary mesure, after the loop is
+  //  TODO(stephen@codeplay.com): This is a temporary measure, after the loop is
   //  converted into LCSAA form and has a single entry and exit we can rewrite
   //  the other phis.
   // 4. That this is an inner most loop, or that loops contained within this
diff --git a/source/opt/merge_return_pass.cpp b/source/opt/merge_return_pass.cpp
index a962a7c..7710dea 100644
--- a/source/opt/merge_return_pass.cpp
+++ b/source/opt/merge_return_pass.cpp
@@ -431,6 +431,7 @@
     std::list<BasicBlock*>* order, Instruction* break_merge_inst) {
   // 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()->InvalidateAnalyses(IRContext::kAnalysisCFG);
   context()->BuildInvalidAnalyses(IRContext::kAnalysisCFG);
 
   // When predicating, be aware of whether this block is a header block, a
diff --git a/source/opt/merge_return_pass.h b/source/opt/merge_return_pass.h
index 4096ce7..d15db2f 100644
--- a/source/opt/merge_return_pass.h
+++ b/source/opt/merge_return_pass.h
@@ -118,8 +118,6 @@
     StructuredControlState(Instruction* break_merge, Instruction* merge)
         : break_merge_(break_merge), current_merge_(merge) {}
 
-    StructuredControlState(const StructuredControlState&) = default;
-
     bool InBreakable() const { return break_merge_; }
     bool InStructuredFlow() const { return CurrentMergeId() != 0; }
 
@@ -247,7 +245,7 @@
 
   // Add new phi nodes for any id that no longer dominate all of it uses.  A phi
   // node is added to a block |bb| for an id if the id is defined between the
-  // original immediate dominator of |bb| and its new immidiate dominator.  It
+  // original immediate dominator of |bb| and its new immediate dominator.  It
   // is assumed that at this point there are no unreachable blocks in the
   // control flow graph.
   void AddNewPhiNodes();
@@ -273,7 +271,7 @@
   void InsertAfterElement(BasicBlock* element, BasicBlock* new_element,
                           std::list<BasicBlock*>* list);
 
-  // Creates a single case switch around all of the exectuable code of the
+  // Creates a single case switch around all of the executable code of the
   // current function where the switch and case value are both zero and the
   // default is the merge block. Returns after the switch is executed. Sets
   // |final_return_block_|.
diff --git a/source/opt/module.cpp b/source/opt/module.cpp
index f97defb..c98af8f 100644
--- a/source/opt/module.cpp
+++ b/source/opt/module.cpp
@@ -90,6 +90,8 @@
   DELEGATE(extensions_);
   DELEGATE(ext_inst_imports_);
   if (memory_model_) memory_model_->ForEachInst(f, run_on_debug_line_insts);
+  if (sampled_image_address_mode_)
+    sampled_image_address_mode_->ForEachInst(f, run_on_debug_line_insts);
   DELEGATE(entry_points_);
   DELEGATE(execution_modes_);
   DELEGATE(debugs1_);
@@ -114,6 +116,9 @@
   if (memory_model_)
     static_cast<const Instruction*>(memory_model_.get())
         ->ForEachInst(f, run_on_debug_line_insts);
+  if (sampled_image_address_mode_)
+    static_cast<const Instruction*>(sampled_image_address_mode_.get())
+        ->ForEachInst(f, run_on_debug_line_insts);
   for (auto& i : entry_points_) DELEGATE(i);
   for (auto& i : execution_modes_) DELEGATE(i);
   for (auto& i : debugs1_) DELEGATE(i);
@@ -139,7 +144,7 @@
   // TODO(antiagainst): should we change the generator number?
   binary->push_back(header_.generator);
   binary->push_back(header_.bound);
-  binary->push_back(header_.reserved);
+  binary->push_back(header_.schema);
 
   size_t bound_idx = binary->size() - 2;
   DebugScope last_scope(kNoDebugScope, kNoInlinedAt);
@@ -260,9 +265,7 @@
 
 uint32_t Module::GetExtInstImportId(const char* extstr) {
   for (auto& ei : ext_inst_imports_)
-    if (!strcmp(extstr,
-                reinterpret_cast<const char*>(&(ei.GetInOperand(0).words[0]))))
-      return ei.result_id();
+    if (!ei.GetInOperand(0).AsString().compare(extstr)) return ei.result_id();
   return 0;
 }
 
diff --git a/source/opt/module.h b/source/opt/module.h
index 0360b7d..7a6be46 100644
--- a/source/opt/module.h
+++ b/source/opt/module.h
@@ -36,7 +36,7 @@
   uint32_t version;
   uint32_t generator;
   uint32_t bound;
-  uint32_t reserved;
+  uint32_t schema;
 };
 
 // A SPIR-V module. It contains all the information for a SPIR-V module and
@@ -61,7 +61,7 @@
   }
 
   // Returns the Id bound.
-  uint32_t IdBound() { return header_.bound; }
+  uint32_t IdBound() const { return header_.bound; }
 
   // Returns the current Id bound and increases it to the next available value.
   // If the id bound has already reached its maximum value, then 0 is returned.
@@ -83,6 +83,9 @@
   // Set the memory model for this module.
   inline void SetMemoryModel(std::unique_ptr<Instruction> m);
 
+  // Set the sampled image addressing mode for this module.
+  inline void SetSampledImageAddressMode(std::unique_ptr<Instruction> m);
+
   // Appends an entry point instruction to this module.
   inline void AddEntryPoint(std::unique_ptr<Instruction> e);
 
@@ -141,6 +144,8 @@
   inline uint32_t id_bound() const { return header_.bound; }
 
   inline uint32_t version() const { return header_.version; }
+  inline uint32_t generator() const { return header_.generator; }
+  inline uint32_t schema() const { return header_.schema; }
 
   inline void set_version(uint32_t v) { header_.version = v; }
 
@@ -156,12 +161,20 @@
   inline IteratorRange<inst_iterator> ext_inst_imports();
   inline IteratorRange<const_inst_iterator> ext_inst_imports() const;
 
-  // Return the memory model instruction contained inthis module.
+  // Return the memory model instruction contained in this module.
   inline Instruction* GetMemoryModel() { return memory_model_.get(); }
   inline const Instruction* GetMemoryModel() const {
     return memory_model_.get();
   }
 
+  // Return the sampled image address mode instruction contained in this module.
+  inline Instruction* GetSampledImageAddressMode() {
+    return sampled_image_address_mode_.get();
+  }
+  inline const Instruction* GetSampledImageAddressMode() const {
+    return sampled_image_address_mode_.get();
+  }
+
   // There are several kinds of debug instructions, according to where they can
   // appear in the logical layout of a module:
   //  - Section 7a:  OpString, OpSourceExtension, OpSource, OpSourceContinued
@@ -286,6 +299,8 @@
   InstructionList ext_inst_imports_;
   // A module only has one memory model instruction.
   std::unique_ptr<Instruction> memory_model_;
+  // A module can only have one optional sampled image addressing mode
+  std::unique_ptr<Instruction> sampled_image_address_mode_;
   InstructionList entry_points_;
   InstructionList execution_modes_;
   InstructionList debugs1_;
@@ -324,6 +339,10 @@
   memory_model_ = std::move(m);
 }
 
+inline void Module::SetSampledImageAddressMode(std::unique_ptr<Instruction> m) {
+  sampled_image_address_mode_ = std::move(m);
+}
+
 inline void Module::AddEntryPoint(std::unique_ptr<Instruction> e) {
   entry_points_.push_back(std::move(e));
 }
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index e74db26..381589b 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -288,6 +288,8 @@
     RegisterPass(CreateStripDebugInfoPass());
   } else if (pass_name == "strip-reflect") {
     RegisterPass(CreateStripReflectInfoPass());
+  } else if (pass_name == "strip-nonsemantic") {
+    RegisterPass(CreateStripNonSemanticInfoPass());
   } else if (pass_name == "set-spec-const-default-value") {
     if (pass_args.size() > 0) {
       auto spec_ids_vals =
@@ -322,6 +324,8 @@
     RegisterPass(CreateLocalAccessChainConvertPass());
   } else if (pass_name == "replace-desc-array-access-using-var-index") {
     RegisterPass(CreateReplaceDescArrayAccessUsingVarIndexPass());
+  } else if (pass_name == "spread-volatile-semantics") {
+    RegisterPass(CreateSpreadVolatileSemanticsPass());
   } else if (pass_name == "descriptor-scalar-replacement") {
     RegisterPass(CreateDescriptorScalarReplacementPass());
   } else if (pass_name == "eliminate-dead-code-aggressive") {
@@ -517,6 +521,12 @@
     RegisterPass(CreateAmdExtToKhrPass());
   } else if (pass_name == "interpolate-fixup") {
     RegisterPass(CreateInterpolateFixupPass());
+  } else if (pass_name == "remove-dont-inline") {
+    RegisterPass(CreateRemoveDontInlinePass());
+  } else if (pass_name == "eliminate-dead-input-components") {
+    RegisterPass(CreateEliminateDeadInputComponentsPass());
+  } else if (pass_name == "fix-func-call-param") {
+    RegisterPass(CreateFixFuncCallArgumentsPass());
   } else if (pass_name == "convert-to-sampled-image") {
     if (pass_args.size() > 0) {
       auto descriptor_set_binding_pairs =
@@ -613,10 +623,16 @@
     assert(optimized_binary_with_nop.size() == original_binary_size &&
            "Binary size unexpectedly changed despite the optimizer saying "
            "there was no change");
-    assert(memcmp(optimized_binary_with_nop.data(), original_binary,
-                  original_binary_size) == 0 &&
-           "Binary content unexpectedly changed despite the optimizer saying "
-           "there was no change");
+
+    // Compare the magic number to make sure the binaries were encoded in the
+    // endianness.  If not, the contents of the binaries will be different, so
+    // do not check the contents.
+    if (optimized_binary_with_nop[0] == original_binary[0]) {
+      assert(memcmp(optimized_binary_with_nop.data(), original_binary,
+                    original_binary_size) == 0 &&
+             "Binary content unexpectedly changed despite the optimizer saying "
+             "there was no change");
+    }
   }
 #endif  // !NDEBUG
 
@@ -653,8 +669,12 @@
 }
 
 Optimizer::PassToken CreateStripReflectInfoPass() {
+  return CreateStripNonSemanticInfoPass();
+}
+
+Optimizer::PassToken CreateStripNonSemanticInfoPass() {
   return MakeUnique<Optimizer::PassToken::Impl>(
-      MakeUnique<opt::StripReflectInfoPass>());
+      MakeUnique<opt::StripNonSemanticInfoPass>());
 }
 
 Optimizer::PassToken CreateEliminateDeadFunctionsPass() {
@@ -764,6 +784,11 @@
       MakeUnique<opt::SSARewritePass>());
 }
 
+Optimizer::PassToken CreateAggressiveDCEPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::AggressiveDCEPass>(false));
+}
+
 Optimizer::PassToken CreateAggressiveDCEPass(bool preserve_interface) {
   return MakeUnique<Optimizer::PassToken::Impl>(
       MakeUnique<opt::AggressiveDCEPass>(preserve_interface));
@@ -965,6 +990,11 @@
       MakeUnique<opt::ReplaceDescArrayAccessUsingVarIndex>());
 }
 
+Optimizer::PassToken CreateSpreadVolatileSemanticsPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::SpreadVolatileSemantics>());
+}
+
 Optimizer::PassToken CreateDescriptorScalarReplacementPass() {
   return MakeUnique<Optimizer::PassToken::Impl>(
       MakeUnique<opt::DescriptorScalarReplacement>());
@@ -984,6 +1014,11 @@
       MakeUnique<opt::InterpFixupPass>());
 }
 
+Optimizer::PassToken CreateEliminateDeadInputComponentsPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::EliminateDeadInputComponentsPass>());
+}
+
 Optimizer::PassToken CreateConvertToSampledImagePass(
     const std::vector<opt::DescriptorSetAndBinding>&
         descriptor_set_binding_pairs) {
@@ -991,4 +1026,18 @@
       MakeUnique<opt::ConvertToSampledImagePass>(descriptor_set_binding_pairs));
 }
 
+Optimizer::PassToken CreateInterfaceVariableScalarReplacementPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::InterfaceVariableScalarReplacement>());
+}
+
+Optimizer::PassToken CreateRemoveDontInlinePass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::RemoveDontInline>());
+}
+
+Optimizer::PassToken CreateFixFuncCallArgumentsPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::FixFuncCallArgumentsPass>());
+}
 }  // namespace spvtools
diff --git a/source/opt/pass.h b/source/opt/pass.h
index a8c9c4b..b2303e2 100644
--- a/source/opt/pass.h
+++ b/source/opt/pass.h
@@ -28,6 +28,13 @@
 #include "spirv-tools/libspirv.hpp"
 #include "types.h"
 
+// Avoid unused variable warning/error on Linux
+#ifndef NDEBUG
+#define USE_ASSERT(x) assert(x)
+#else
+#define USE_ASSERT(x) ((void)(x))
+#endif
+
 namespace spvtools {
 namespace opt {
 
@@ -129,7 +136,7 @@
 
   // Processes the given |module|. Returns Status::Failure if errors occur when
   // processing. Returns the corresponding Status::Success if processing is
-  // succesful to indicate whether changes are made to the module.
+  // successful to indicate whether changes are made to the module.
   virtual Status Process() = 0;
 
   // Return the next available SSA id and increment it.
diff --git a/source/opt/pass_manager.cpp b/source/opt/pass_manager.cpp
index be53d34..d3c47e7 100644
--- a/source/opt/pass_manager.cpp
+++ b/source/opt/pass_manager.cpp
@@ -35,10 +35,18 @@
     if (print_all_stream_) {
       std::vector<uint32_t> binary;
       context->module()->ToBinary(&binary, false);
-      SpirvTools t(SPV_ENV_UNIVERSAL_1_2);
+      SpirvTools t(target_env_);
+      t.SetMessageConsumer(consumer());
       std::string disassembly;
-      t.Disassemble(binary, &disassembly, 0);
-      *print_all_stream_ << preamble << (pass ? pass->name() : "") << "\n"
+      std::string pass_name = (pass ? pass->name() : "");
+      if (!t.Disassemble(binary, &disassembly)) {
+        std::string msg = "Disassembly failed before pass ";
+        msg += pass_name + "\n";
+        spv_position_t null_pos{0, 0, 0};
+        consumer()(SPV_MSG_WARNING, "", null_pos, msg.c_str());
+        return;
+      }
+      *print_all_stream_ << preamble << pass_name << "\n"
                          << disassembly << std::endl;
     }
   };
diff --git a/source/opt/pass_manager.h b/source/opt/pass_manager.h
index 9686ddd..11961a3 100644
--- a/source/opt/pass_manager.h
+++ b/source/opt/pass_manager.h
@@ -54,7 +54,7 @@
   // Adds an externally constructed pass.
   void AddPass(std::unique_ptr<Pass> pass);
   // Uses the argument |args| to construct a pass instance of type |T|, and adds
-  // the pass instance to this pass manger. The pass added will use this pass
+  // the pass instance to this pass manager. The pass added will use this pass
   // manager's message consumer.
   template <typename T, typename... Args>
   void AddPass(Args&&... args);
@@ -70,7 +70,7 @@
   // Runs all passes on the given |module|. Returns Status::Failure if errors
   // occur when processing using one of the registered passes. All passes
   // registered after the error-reporting pass will be skipped. Returns the
-  // corresponding Status::Success if processing is succesful to indicate
+  // corresponding Status::Success if processing is successful to indicate
   // whether changes are made to the module.
   //
   // After running all the passes, they are removed from the list.
diff --git a/source/opt/passes.h b/source/opt/passes.h
index f3c30d5..21354c7 100644
--- a/source/opt/passes.h
+++ b/source/opt/passes.h
@@ -34,8 +34,10 @@
 #include "source/opt/desc_sroa.h"
 #include "source/opt/eliminate_dead_constant_pass.h"
 #include "source/opt/eliminate_dead_functions_pass.h"
+#include "source/opt/eliminate_dead_input_components_pass.h"
 #include "source/opt/eliminate_dead_members_pass.h"
 #include "source/opt/empty_pass.h"
+#include "source/opt/fix_func_call_arguments.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"
@@ -47,6 +49,7 @@
 #include "source/opt/inst_bindless_check_pass.h"
 #include "source/opt/inst_buff_addr_check_pass.h"
 #include "source/opt/inst_debug_printf_pass.h"
+#include "source/opt/interface_var_sroa.h"
 #include "source/opt/interp_fixup_pass.h"
 #include "source/opt/licm_pass.h"
 #include "source/opt/local_access_chain_convert_pass.h"
@@ -64,6 +67,7 @@
 #include "source/opt/reduce_load_size.h"
 #include "source/opt/redundancy_elimination.h"
 #include "source/opt/relax_float_ops_pass.h"
+#include "source/opt/remove_dontinline_pass.h"
 #include "source/opt/remove_duplicates_pass.h"
 #include "source/opt/remove_unused_interface_variables_pass.h"
 #include "source/opt/replace_desc_array_access_using_var_index.h"
@@ -71,10 +75,11 @@
 #include "source/opt/scalar_replacement_pass.h"
 #include "source/opt/set_spec_constant_default_value_pass.h"
 #include "source/opt/simplification_pass.h"
+#include "source/opt/spread_volatile_semantics.h"
 #include "source/opt/ssa_rewrite_pass.h"
 #include "source/opt/strength_reduction_pass.h"
 #include "source/opt/strip_debug_info_pass.h"
-#include "source/opt/strip_reflect_info_pass.h"
+#include "source/opt/strip_nonsemantic_info_pass.h"
 #include "source/opt/unify_const_pass.h"
 #include "source/opt/upgrade_memory_model.h"
 #include "source/opt/vector_dce.h"
diff --git a/source/opt/private_to_local_pass.h b/source/opt/private_to_local_pass.h
index c6127d6..e96a965 100644
--- a/source/opt/private_to_local_pass.h
+++ b/source/opt/private_to_local_pass.h
@@ -44,7 +44,7 @@
   // class of |function|.  Returns false if the variable could not be moved.
   bool MoveVariable(Instruction* variable, Function* function);
 
-  // |inst| is an instruction declaring a varible.  If that variable is
+  // |inst| is an instruction declaring a variable.  If that variable is
   // referenced in a single function and all of uses are valid as defined by
   // |IsValidUse|, then that function is returned.  Otherwise, the return
   // value is |nullptr|.
diff --git a/source/opt/reduce_load_size.cpp b/source/opt/reduce_load_size.cpp
index e9b8087..56491b2 100644
--- a/source/opt/reduce_load_size.cpp
+++ b/source/opt/reduce_load_size.cpp
@@ -161,8 +161,15 @@
       case analysis::Type::kArray: {
         const analysis::Constant* size_const =
             const_mgr->FindDeclaredConstant(load_type->AsArray()->LengthId());
-        assert(size_const->AsIntConstant());
-        total_size = size_const->GetU32();
+
+        if (size_const) {
+          assert(size_const->AsIntConstant());
+          total_size = size_const->GetU32();
+        } else {
+          // The size is spec constant, so it is unknown at this time.  Assume
+          // it is very large.
+          total_size = UINT32_MAX;
+        }
       } break;
       case analysis::Type::kStruct:
         total_size = static_cast<uint32_t>(
diff --git a/source/opt/redundancy_elimination.h b/source/opt/redundancy_elimination.h
index 91809b5..40451f4 100644
--- a/source/opt/redundancy_elimination.h
+++ b/source/opt/redundancy_elimination.h
@@ -41,7 +41,7 @@
   // in the function containing |bb|.
   //
   // |value_to_ids| is a map from value number to ids.  If {vn, id} is in
-  // |value_to_ids| then vn is the value number of id, and the defintion of id
+  // |value_to_ids| then vn is the value number of id, and the definition of id
   // dominates |bb|.
   //
   // Returns true if at least one instruction is deleted.
diff --git a/source/opt/register_pressure.cpp b/source/opt/register_pressure.cpp
index 5750c6d..1ad3373 100644
--- a/source/opt/register_pressure.cpp
+++ b/source/opt/register_pressure.cpp
@@ -378,7 +378,7 @@
   // The loop fusion is injecting the l1 before the l2, the latch of l1 will be
   // connected to the header of l2.
   // To compute the register usage, we inject the loop live-in (union of l1 and
-  // l2 live-in header blocks) into the the live in/out of each basic block of
+  // l2 live-in header blocks) into the live in/out of each basic block of
   // l1 to get the peak register usage. We then repeat the operation to for l2
   // basic blocks but in this case we inject the live-out of the latch of l1.
   auto live_loop = MakeFilterIteratorRange(
diff --git a/source/opt/remove_dontinline_pass.cpp b/source/opt/remove_dontinline_pass.cpp
new file mode 100644
index 0000000..4dd1cd4
--- /dev/null
+++ b/source/opt/remove_dontinline_pass.cpp
@@ -0,0 +1,49 @@
+// Copyright (c) 2022 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/remove_dontinline_pass.h"
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status RemoveDontInline::Process() {
+  bool modified = false;
+  modified = ClearDontInlineFunctionControl();
+  return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
+}
+
+bool RemoveDontInline::ClearDontInlineFunctionControl() {
+  bool modified = false;
+  for (auto& func : *get_module()) {
+    ClearDontInlineFunctionControl(&func);
+  }
+  return modified;
+}
+
+bool RemoveDontInline::ClearDontInlineFunctionControl(Function* function) {
+  constexpr uint32_t kFunctionControlInOperandIdx = 0;
+  Instruction* function_inst = &function->DefInst();
+  uint32_t function_control =
+      function_inst->GetSingleWordInOperand(kFunctionControlInOperandIdx);
+
+  if ((function_control & SpvFunctionControlDontInlineMask) == 0) {
+    return false;
+  }
+  function_control &= ~SpvFunctionControlDontInlineMask;
+  function_inst->SetInOperand(kFunctionControlInOperandIdx, {function_control});
+  return true;
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/remove_dontinline_pass.h b/source/opt/remove_dontinline_pass.h
new file mode 100644
index 0000000..1624319
--- /dev/null
+++ b/source/opt/remove_dontinline_pass.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2022 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_REMOVE_DONTINLINE_PASS_H_
+#define SOURCE_OPT_REMOVE_DONTINLINE_PASS_H_
+
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// See optimizer.hpp for documentation.
+class RemoveDontInline : public Pass {
+ public:
+  const char* name() const override { return "remove-dont-inline"; }
+  Status Process() override;
+
+ private:
+  // Clears the DontInline function control from every function in the module.
+  // Returns true of a change was made.
+  bool ClearDontInlineFunctionControl();
+
+  // Clears the DontInline function control from |function|.
+  // Returns true of a change was made.
+  bool ClearDontInlineFunctionControl(Function* function);
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_REMOVE_DONTINLINE_PASS_H_
diff --git a/source/opt/remove_duplicates_pass.cpp b/source/opt/remove_duplicates_pass.cpp
index 0e65cc8..1ed8e2a 100644
--- a/source/opt/remove_duplicates_pass.cpp
+++ b/source/opt/remove_duplicates_pass.cpp
@@ -72,9 +72,8 @@
 
   std::unordered_map<std::string, SpvId> ext_inst_imports;
   for (auto* i = &*context()->ext_inst_import_begin(); i;) {
-    auto res = ext_inst_imports.emplace(
-        reinterpret_cast<const char*>(i->GetInOperand(0u).words.data()),
-        i->result_id());
+    auto res = ext_inst_imports.emplace(i->GetInOperand(0u).AsString(),
+                                        i->result_id());
     if (res.second) {
       // Never seen before, keep it.
       i = i->NextNode();
diff --git a/source/opt/replace_desc_array_access_using_var_index.cpp b/source/opt/replace_desc_array_access_using_var_index.cpp
index 1082e67..e97593e 100644
--- a/source/opt/replace_desc_array_access_using_var_index.cpp
+++ b/source/opt/replace_desc_array_access_using_var_index.cpp
@@ -95,7 +95,7 @@
   CollectRecursiveUsersWithConcreteType(access_chain, &final_users);
   for (auto* inst : final_users) {
     std::deque<Instruction*> insts_to_be_cloned =
-        CollectRequiredImageInsts(inst);
+        CollectRequiredImageAndAccessInsts(inst);
     ReplaceNonUniformAccessWithSwitchCase(
         inst, access_chain, number_of_elements, insts_to_be_cloned);
   }
@@ -121,8 +121,8 @@
 }
 
 std::deque<Instruction*>
-ReplaceDescArrayAccessUsingVarIndex::CollectRequiredImageInsts(
-    Instruction* user_of_image_insts) const {
+ReplaceDescArrayAccessUsingVarIndex::CollectRequiredImageAndAccessInsts(
+    Instruction* user) const {
   std::unordered_set<uint32_t> seen_inst_ids;
   std::queue<Instruction*> work_list;
 
@@ -131,21 +131,23 @@
     if (!seen_inst_ids.insert(*idp).second) return;
     Instruction* operand = get_def_use_mgr()->GetDef(*idp);
     if (context()->get_instr_block(operand) != nullptr &&
-        HasImageOrImagePtrType(operand)) {
+        (HasImageOrImagePtrType(operand) ||
+         operand->opcode() == SpvOpAccessChain ||
+         operand->opcode() == SpvOpInBoundsAccessChain)) {
       work_list.push(operand);
     }
   };
 
-  std::deque<Instruction*> required_image_insts;
-  required_image_insts.push_front(user_of_image_insts);
-  user_of_image_insts->ForEachInId(decision_to_include_operand);
+  std::deque<Instruction*> required_insts;
+  required_insts.push_front(user);
+  user->ForEachInId(decision_to_include_operand);
   while (!work_list.empty()) {
     auto* inst_from_work_list = work_list.front();
     work_list.pop();
-    required_image_insts.push_front(inst_from_work_list);
+    required_insts.push_front(inst_from_work_list);
     inst_from_work_list->ForEachInId(decision_to_include_operand);
   }
-  return required_image_insts;
+  return required_insts;
 }
 
 bool ReplaceDescArrayAccessUsingVarIndex::HasImageOrImagePtrType(
@@ -253,8 +255,12 @@
     Instruction* access_chain_final_user, Instruction* access_chain,
     uint32_t number_of_elements,
     const std::deque<Instruction*>& insts_to_be_cloned) const {
-  // Create merge block and add terminator
   auto* block = context()->get_instr_block(access_chain_final_user);
+  // If the instruction does not belong to a block (i.e. in the case of
+  // OpDecorate), no replacement is needed.
+  if (!block) return;
+
+  // Create merge block and add terminator
   auto* merge_block = SeparateInstructionsIntoNewBlock(
       block, access_chain_final_user->NextNode());
 
diff --git a/source/opt/replace_desc_array_access_using_var_index.h b/source/opt/replace_desc_array_access_using_var_index.h
index e18222c..51817c1 100644
--- a/source/opt/replace_desc_array_access_using_var_index.h
+++ b/source/opt/replace_desc_array_access_using_var_index.h
@@ -47,7 +47,7 @@
   }
 
  private:
-  // Replaces all acceses to |var| using variable indices with constant
+  // Replaces all accesses to |var| using variable indices with constant
   // elements of the array |var|. Creates switch-case statements to determine
   // the value of the variable index for all the possible cases. Returns
   // whether replacement is done or not.
@@ -76,11 +76,12 @@
   void CollectRecursiveUsersWithConcreteType(
       Instruction* access_chain, std::vector<Instruction*>* final_users) const;
 
-  // Recursively collects the operands of |user_of_image_insts| (and operands
-  // of the operands) whose result types are images/samplers or pointers/array/
-  // struct of them and returns them.
-  std::deque<Instruction*> CollectRequiredImageInsts(
-      Instruction* user_of_image_insts) const;
+  // Recursively collects the operands of |user| (and operands of the operands)
+  // whose result types are images/samplers (or pointers/arrays/ structs of
+  // them) and access chains instructions and returns them. The returned
+  // collection includes |user|.
+  std::deque<Instruction*> CollectRequiredImageAndAccessInsts(
+      Instruction* user) const;
 
   // Returns whether result type of |inst| is an image/sampler/pointer of image
   // or sampler or not.
@@ -170,7 +171,7 @@
   // Creates and adds an OpSwitch used for the selection of OpAccessChain whose
   // first Indexes operand is |access_chain_index_var_id|. The OpSwitch will be
   // added at the end of |parent_block|. It will jump to |default_id| for the
-  // default case and jumps to one of case blocks whoes ids are |case_block_ids|
+  // default case and jumps to one of case blocks whose ids are |case_block_ids|
   // if |access_chain_index_var_id| matches the case number. |merge_id| is the
   // merge block id.
   void AddSwitchForAccessChain(
diff --git a/source/opt/replace_invalid_opc.cpp b/source/opt/replace_invalid_opc.cpp
index e3b9d3e..1dcd06f 100644
--- a/source/opt/replace_invalid_opc.cpp
+++ b/source/opt/replace_invalid_opc.cpp
@@ -112,8 +112,7 @@
             }
             Instruction* file_name =
                 context()->get_def_use_mgr()->GetDef(file_name_id);
-            const char* source = reinterpret_cast<const char*>(
-                &file_name->GetInOperand(0).words[0]);
+            const std::string source = file_name->GetInOperand(0).AsString();
 
             // Get the line number and column number.
             uint32_t line_number =
@@ -121,7 +120,7 @@
             uint32_t col_number = last_line_dbg_inst->GetSingleWordInOperand(2);
 
             // Replace the instruction.
-            ReplaceInstruction(inst, source, line_number, col_number);
+            ReplaceInstruction(inst, source.c_str(), line_number, col_number);
           }
         }
       },
diff --git a/source/opt/scalar_analysis.cpp b/source/opt/scalar_analysis.cpp
index 38555e6..2b0a824 100644
--- a/source/opt/scalar_analysis.cpp
+++ b/source/opt/scalar_analysis.cpp
@@ -581,7 +581,7 @@
 
 // Implements the hashing of SENodes.
 size_t SENodeHash::operator()(const SENode* node) const {
-  // Concatinate the terms into a string which we can hash.
+  // Concatenate the terms into a string which we can hash.
   std::u32string hash_string{};
 
   // Hashing the type as a string is safer than hashing the enum as the enum is
diff --git a/source/opt/scalar_analysis_nodes.h b/source/opt/scalar_analysis_nodes.h
index b0e3fef..91ce446 100644
--- a/source/opt/scalar_analysis_nodes.h
+++ b/source/opt/scalar_analysis_nodes.h
@@ -167,7 +167,7 @@
   const ChildContainerType& GetChildren() const { return children_; }
   ChildContainerType& GetChildren() { return children_; }
 
-  // Return true if this node is a cant compute node.
+  // Return true if this node is a can't compute node.
   bool IsCantCompute() const { return GetType() == CanNotCompute; }
 
 // Implements a casting method for each type.
diff --git a/source/opt/scalar_analysis_simplification.cpp b/source/opt/scalar_analysis_simplification.cpp
index 52f2d6a..3c1ecc0 100644
--- a/source/opt/scalar_analysis_simplification.cpp
+++ b/source/opt/scalar_analysis_simplification.cpp
@@ -88,7 +88,7 @@
 
  private:
   // Recursively descend through the graph to build up the accumulator objects
-  // which are used to flatten the graph. |child| is the node currenty being
+  // which are used to flatten the graph. |child| is the node currently being
   // traversed and the |negation| flag is used to signify that this operation
   // was preceded by a unary negative operation and as such the result should be
   // negated.
@@ -134,7 +134,7 @@
   // offset.
   SENode* EliminateZeroCoefficientRecurrents(SENode* node);
 
-  // A reference the the analysis which requested the simplification.
+  // A reference the analysis which requested the simplification.
   ScalarEvolutionAnalysis& analysis_;
 
   // The node being simplified.
diff --git a/source/opt/scalar_replacement_pass.cpp b/source/opt/scalar_replacement_pass.cpp
index 4d6a7aa..e27c828 100644
--- a/source/opt/scalar_replacement_pass.cpp
+++ b/source/opt/scalar_replacement_pass.cpp
@@ -24,6 +24,7 @@
 #include "source/opt/reflect.h"
 #include "source/opt/types.h"
 #include "source/util/make_unique.h"
+#include "types.h"
 
 static const uint32_t kDebugValueOperandValueIndex = 5;
 static const uint32_t kDebugValueOperandExpressionIndex = 6;
@@ -395,7 +396,7 @@
             if (!components_used || components_used->count(elem)) {
               CreateVariable(*id, inst, elem, replacements);
             } else {
-              replacements->push_back(CreateNullConstant(*id));
+              replacements->push_back(GetUndef(*id));
             }
             elem++;
           });
@@ -406,8 +407,8 @@
           CreateVariable(type->GetSingleWordInOperand(0u), inst, i,
                          replacements);
         } else {
-          replacements->push_back(
-              CreateNullConstant(type->GetSingleWordInOperand(0u)));
+          uint32_t element_type_id = type->GetSingleWordInOperand(0);
+          replacements->push_back(GetUndef(element_type_id));
         }
       }
       break;
@@ -429,6 +430,10 @@
          replacements->end();
 }
 
+Instruction* ScalarReplacementPass::GetUndef(uint32_t type_id) {
+  return get_def_use_mgr()->GetDef(Type2Undef(type_id));
+}
+
 void ScalarReplacementPass::TransferAnnotations(
     const Instruction* source, std::vector<Instruction*>* replacements) {
   // Only transfer invariant and restrict decorations on the variable. There are
@@ -981,20 +986,6 @@
   return result;
 }
 
-Instruction* ScalarReplacementPass::CreateNullConstant(uint32_t type_id) {
-  analysis::TypeManager* type_mgr = context()->get_type_mgr();
-  analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
-
-  const analysis::Type* type = type_mgr->GetType(type_id);
-  const analysis::Constant* null_const = const_mgr->GetConstant(type, {});
-  Instruction* null_inst =
-      const_mgr->GetDefiningInstruction(null_const, type_id);
-  if (null_inst != nullptr) {
-    context()->UpdateDefUse(null_inst);
-  }
-  return null_inst;
-}
-
 uint64_t ScalarReplacementPass::GetMaxLegalIndex(
     const Instruction* var_inst) const {
   assert(var_inst->opcode() == SpvOpVariable &&
diff --git a/source/opt/scalar_replacement_pass.h b/source/opt/scalar_replacement_pass.h
index 0928830..6a66dfb 100644
--- a/source/opt/scalar_replacement_pass.h
+++ b/source/opt/scalar_replacement_pass.h
@@ -15,6 +15,7 @@
 #ifndef SOURCE_OPT_SCALAR_REPLACEMENT_PASS_H_
 #define SOURCE_OPT_SCALAR_REPLACEMENT_PASS_H_
 
+#include <cassert>
 #include <cstdio>
 #include <memory>
 #include <queue>
@@ -23,23 +24,34 @@
 #include <vector>
 
 #include "source/opt/function.h"
-#include "source/opt/pass.h"
+#include "source/opt/mem_pass.h"
 #include "source/opt/type_manager.h"
 
 namespace spvtools {
 namespace opt {
 
 // Documented in optimizer.hpp
-class ScalarReplacementPass : public Pass {
+class ScalarReplacementPass : public MemPass {
  private:
   static const uint32_t kDefaultLimit = 100;
 
  public:
   ScalarReplacementPass(uint32_t limit = kDefaultLimit)
       : max_num_elements_(limit) {
-    name_[0] = '\0';
-    strcat(name_, "scalar-replacement=");
-    sprintf(&name_[strlen(name_)], "%d", max_num_elements_);
+    const auto num_to_write = snprintf(
+        name_, sizeof(name_), "scalar-replacement=%u", max_num_elements_);
+    assert(size_t(num_to_write) < sizeof(name_));
+    (void)num_to_write;  // Mark as unused
+
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+    // ClusterFuzz/OSS-Fuzz is likely to yield examples with very large arrays.
+    // This can cause timeouts and memouts during fuzzing that
+    // are not classed as bugs. To avoid this noise, we set the
+    // max_num_elements_ to a smaller value for fuzzing.
+    max_num_elements_ =
+        (max_num_elements_ > 0 && max_num_elements_ < 100 ? max_num_elements_
+                                                          : 100);
+#endif
   }
 
   const char* name() const override { return name_; }
@@ -234,10 +246,8 @@
   std::unique_ptr<std::unordered_set<int64_t>> GetUsedComponents(
       Instruction* inst);
 
-  // Returns an instruction defining a null constant with type |type_id|.  If
-  // one already exists, it is returned.  Otherwise a new one is created.
-  // Returns |nullptr| if the new constant could not be created.
-  Instruction* CreateNullConstant(uint32_t type_id);
+  // Returns an instruction defining an undefined value type |type_id|.
+  Instruction* GetUndef(uint32_t type_id);
 
   // Maps storage type to a pointer type enclosing that type.
   std::unordered_map<uint32_t, uint32_t> pointee_to_pointer_;
@@ -255,7 +265,10 @@
   // Limit on the number of members in an object that will be replaced.
   // 0 means there is no limit.
   uint32_t max_num_elements_;
-  char name_[55];
+  // This has to be big enough to fit "scalar-replacement=" followed by a
+  // uint32_t number written in decimal (so 10 digits), and then a
+  // terminating nul.
+  char name_[30];
 };
 
 }  // namespace opt
diff --git a/source/opt/spread_volatile_semantics.cpp b/source/opt/spread_volatile_semantics.cpp
new file mode 100644
index 0000000..b61fd0f
--- /dev/null
+++ b/source/opt/spread_volatile_semantics.cpp
@@ -0,0 +1,298 @@
+// Copyright (c) 2022 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/spread_volatile_semantics.h"
+
+#include "source/opt/decoration_manager.h"
+#include "source/opt/ir_builder.h"
+#include "source/spirv_constant.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+const uint32_t kOpDecorateInOperandBuiltinDecoration = 2u;
+const uint32_t kOpLoadInOperandMemoryOperands = 1u;
+const uint32_t kOpEntryPointInOperandEntryPoint = 1u;
+const uint32_t kOpEntryPointInOperandInterface = 3u;
+
+bool HasBuiltinDecoration(analysis::DecorationManager* decoration_manager,
+                          uint32_t var_id, uint32_t built_in) {
+  return decoration_manager->FindDecoration(
+      var_id, SpvDecorationBuiltIn, [built_in](const Instruction& inst) {
+        return built_in == inst.GetSingleWordInOperand(
+                               kOpDecorateInOperandBuiltinDecoration);
+      });
+}
+
+bool IsBuiltInForRayTracingVolatileSemantics(uint32_t built_in) {
+  switch (built_in) {
+    case SpvBuiltInSMIDNV:
+    case SpvBuiltInWarpIDNV:
+    case SpvBuiltInSubgroupSize:
+    case SpvBuiltInSubgroupLocalInvocationId:
+    case SpvBuiltInSubgroupEqMask:
+    case SpvBuiltInSubgroupGeMask:
+    case SpvBuiltInSubgroupGtMask:
+    case SpvBuiltInSubgroupLeMask:
+    case SpvBuiltInSubgroupLtMask:
+      return true;
+    default:
+      return false;
+  }
+}
+
+bool HasBuiltinForRayTracingVolatileSemantics(
+    analysis::DecorationManager* decoration_manager, uint32_t var_id) {
+  return decoration_manager->FindDecoration(
+      var_id, SpvDecorationBuiltIn, [](const Instruction& inst) {
+        uint32_t built_in =
+            inst.GetSingleWordInOperand(kOpDecorateInOperandBuiltinDecoration);
+        return IsBuiltInForRayTracingVolatileSemantics(built_in);
+      });
+}
+
+bool HasVolatileDecoration(analysis::DecorationManager* decoration_manager,
+                           uint32_t var_id) {
+  return decoration_manager->HasDecoration(var_id, SpvDecorationVolatile);
+}
+
+}  // namespace
+
+Pass::Status SpreadVolatileSemantics::Process() {
+  if (HasNoExecutionModel()) {
+    return Status::SuccessWithoutChange;
+  }
+  const bool is_vk_memory_model_enabled =
+      context()->get_feature_mgr()->HasCapability(
+          SpvCapabilityVulkanMemoryModel);
+  CollectTargetsForVolatileSemantics(is_vk_memory_model_enabled);
+
+  // If VulkanMemoryModel capability is not enabled, we have to set Volatile
+  // decoration for interface variables instead of setting Volatile for load
+  // instructions. If an interface (or pointers to it) is used by two load
+  // instructions in two entry points and one must be volatile while another
+  // is not, we have to report an error for the conflict.
+  if (!is_vk_memory_model_enabled &&
+      HasInterfaceInConflictOfVolatileSemantics()) {
+    return Status::Failure;
+  }
+
+  return SpreadVolatileSemanticsToVariables(is_vk_memory_model_enabled);
+}
+
+Pass::Status SpreadVolatileSemantics::SpreadVolatileSemanticsToVariables(
+    const bool is_vk_memory_model_enabled) {
+  Status status = Status::SuccessWithoutChange;
+  for (Instruction& var : context()->types_values()) {
+    auto entry_function_ids =
+        EntryFunctionsToSpreadVolatileSemanticsForVar(var.result_id());
+    if (entry_function_ids.empty()) {
+      continue;
+    }
+
+    if (is_vk_memory_model_enabled) {
+      SetVolatileForLoadsInEntries(&var, entry_function_ids);
+    } else {
+      DecorateVarWithVolatile(&var);
+    }
+    status = Status::SuccessWithChange;
+  }
+  return status;
+}
+
+bool SpreadVolatileSemantics::IsTargetUsedByNonVolatileLoadInEntryPoint(
+    uint32_t var_id, Instruction* entry_point) {
+  uint32_t entry_function_id =
+      entry_point->GetSingleWordInOperand(kOpEntryPointInOperandEntryPoint);
+  std::unordered_set<uint32_t> funcs;
+  context()->CollectCallTreeFromRoots(entry_function_id, &funcs);
+  return !VisitLoadsOfPointersToVariableInEntries(
+      var_id,
+      [](Instruction* load) {
+        // If it has a load without volatile memory operand, finish traversal
+        // and return false.
+        if (load->NumInOperands() <= kOpLoadInOperandMemoryOperands) {
+          return false;
+        }
+        uint32_t memory_operands =
+            load->GetSingleWordInOperand(kOpLoadInOperandMemoryOperands);
+        return (memory_operands & SpvMemoryAccessVolatileMask) != 0;
+      },
+      funcs);
+}
+
+bool SpreadVolatileSemantics::HasInterfaceInConflictOfVolatileSemantics() {
+  for (Instruction& entry_point : get_module()->entry_points()) {
+    SpvExecutionModel execution_model =
+        static_cast<SpvExecutionModel>(entry_point.GetSingleWordInOperand(0));
+    for (uint32_t operand_index = kOpEntryPointInOperandInterface;
+         operand_index < entry_point.NumInOperands(); ++operand_index) {
+      uint32_t var_id = entry_point.GetSingleWordInOperand(operand_index);
+      if (!EntryFunctionsToSpreadVolatileSemanticsForVar(var_id).empty() &&
+          !IsTargetForVolatileSemantics(var_id, execution_model) &&
+          IsTargetUsedByNonVolatileLoadInEntryPoint(var_id, &entry_point)) {
+        Instruction* inst = context()->get_def_use_mgr()->GetDef(var_id);
+        context()->EmitErrorMessage(
+            "Variable is a target for Volatile semantics for an entry point, "
+            "but it is not for another entry point",
+            inst);
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+void SpreadVolatileSemantics::MarkVolatileSemanticsForVariable(
+    uint32_t var_id, Instruction* entry_point) {
+  uint32_t entry_function_id =
+      entry_point->GetSingleWordInOperand(kOpEntryPointInOperandEntryPoint);
+  auto itr = var_ids_to_entry_fn_for_volatile_semantics_.find(var_id);
+  if (itr == var_ids_to_entry_fn_for_volatile_semantics_.end()) {
+    var_ids_to_entry_fn_for_volatile_semantics_[var_id] = {entry_function_id};
+    return;
+  }
+  itr->second.insert(entry_function_id);
+}
+
+void SpreadVolatileSemantics::CollectTargetsForVolatileSemantics(
+    const bool is_vk_memory_model_enabled) {
+  for (Instruction& entry_point : get_module()->entry_points()) {
+    SpvExecutionModel execution_model =
+        static_cast<SpvExecutionModel>(entry_point.GetSingleWordInOperand(0));
+    for (uint32_t operand_index = kOpEntryPointInOperandInterface;
+         operand_index < entry_point.NumInOperands(); ++operand_index) {
+      uint32_t var_id = entry_point.GetSingleWordInOperand(operand_index);
+      if (!IsTargetForVolatileSemantics(var_id, execution_model)) {
+        continue;
+      }
+      if (is_vk_memory_model_enabled ||
+          IsTargetUsedByNonVolatileLoadInEntryPoint(var_id, &entry_point)) {
+        MarkVolatileSemanticsForVariable(var_id, &entry_point);
+      }
+    }
+  }
+}
+
+void SpreadVolatileSemantics::DecorateVarWithVolatile(Instruction* var) {
+  analysis::DecorationManager* decoration_manager =
+      context()->get_decoration_mgr();
+  uint32_t var_id = var->result_id();
+  if (HasVolatileDecoration(decoration_manager, var_id)) {
+    return;
+  }
+  get_decoration_mgr()->AddDecoration(
+      SpvOpDecorate, {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {var_id}},
+                      {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
+                       {SpvDecorationVolatile}}});
+}
+
+bool SpreadVolatileSemantics::VisitLoadsOfPointersToVariableInEntries(
+    uint32_t var_id, const std::function<bool(Instruction*)>& handle_load,
+    const std::unordered_set<uint32_t>& function_ids) {
+  std::vector<uint32_t> worklist({var_id});
+  auto* def_use_mgr = context()->get_def_use_mgr();
+  while (!worklist.empty()) {
+    uint32_t ptr_id = worklist.back();
+    worklist.pop_back();
+    bool finish_traversal = !def_use_mgr->WhileEachUser(
+        ptr_id, [this, &worklist, &ptr_id, handle_load,
+                 &function_ids](Instruction* user) {
+          BasicBlock* block = context()->get_instr_block(user);
+          if (block == nullptr ||
+              function_ids.find(block->GetParent()->result_id()) ==
+                  function_ids.end()) {
+            return true;
+          }
+
+          if (user->opcode() == SpvOpAccessChain ||
+              user->opcode() == SpvOpInBoundsAccessChain ||
+              user->opcode() == SpvOpPtrAccessChain ||
+              user->opcode() == SpvOpInBoundsPtrAccessChain ||
+              user->opcode() == SpvOpCopyObject) {
+            if (ptr_id == user->GetSingleWordInOperand(0))
+              worklist.push_back(user->result_id());
+            return true;
+          }
+
+          if (user->opcode() != SpvOpLoad) {
+            return true;
+          }
+
+          return handle_load(user);
+        });
+    if (finish_traversal) return false;
+  }
+  return true;
+}
+
+void SpreadVolatileSemantics::SetVolatileForLoadsInEntries(
+    Instruction* var, const std::unordered_set<uint32_t>& entry_function_ids) {
+  // Set Volatile memory operand for all load instructions if they do not have
+  // it.
+  for (auto entry_id : entry_function_ids) {
+    std::unordered_set<uint32_t> funcs;
+    context()->CollectCallTreeFromRoots(entry_id, &funcs);
+    VisitLoadsOfPointersToVariableInEntries(
+        var->result_id(),
+        [](Instruction* load) {
+          if (load->NumInOperands() <= kOpLoadInOperandMemoryOperands) {
+            load->AddOperand({SPV_OPERAND_TYPE_MEMORY_ACCESS,
+                              {SpvMemoryAccessVolatileMask}});
+            return true;
+          }
+          uint32_t memory_operands =
+              load->GetSingleWordInOperand(kOpLoadInOperandMemoryOperands);
+          memory_operands |= SpvMemoryAccessVolatileMask;
+          load->SetInOperand(kOpLoadInOperandMemoryOperands, {memory_operands});
+          return true;
+        },
+        funcs);
+  }
+}
+
+bool SpreadVolatileSemantics::IsTargetForVolatileSemantics(
+    uint32_t var_id, SpvExecutionModel execution_model) {
+  analysis::DecorationManager* decoration_manager =
+      context()->get_decoration_mgr();
+  if (execution_model == SpvExecutionModelFragment) {
+    return get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 6) &&
+           HasBuiltinDecoration(decoration_manager, var_id,
+                                SpvBuiltInHelperInvocation);
+  }
+
+  if (execution_model == SpvExecutionModelIntersectionKHR ||
+      execution_model == SpvExecutionModelIntersectionNV) {
+    if (HasBuiltinDecoration(decoration_manager, var_id,
+                             SpvBuiltInRayTmaxKHR)) {
+      return true;
+    }
+  }
+
+  switch (execution_model) {
+    case SpvExecutionModelRayGenerationKHR:
+    case SpvExecutionModelClosestHitKHR:
+    case SpvExecutionModelMissKHR:
+    case SpvExecutionModelCallableKHR:
+    case SpvExecutionModelIntersectionKHR:
+      return HasBuiltinForRayTracingVolatileSemantics(decoration_manager,
+                                                      var_id);
+    default:
+      return false;
+  }
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/spread_volatile_semantics.h b/source/opt/spread_volatile_semantics.h
new file mode 100644
index 0000000..014858d
--- /dev/null
+++ b/source/opt/spread_volatile_semantics.h
@@ -0,0 +1,116 @@
+// Copyright (c) 2022 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_SPREAD_VOLATILE_SEMANTICS_H_
+#define SOURCE_OPT_SPREAD_VOLATILE_SEMANTICS_H_
+
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// See optimizer.hpp for documentation.
+class SpreadVolatileSemantics : public Pass {
+ public:
+  SpreadVolatileSemantics() {}
+
+  const char* name() const override { return "spread-volatile-semantics"; }
+
+  Status Process() override;
+
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisDefUse | IRContext::kAnalysisDecorations |
+           IRContext::kAnalysisInstrToBlockMapping;
+  }
+
+ private:
+  // Returns true if it does not have an execution model. Linkage shaders do not
+  // have an execution model.
+  bool HasNoExecutionModel() {
+    return get_module()->entry_points().empty() &&
+           context()->get_feature_mgr()->HasCapability(SpvCapabilityLinkage);
+  }
+
+  // Iterates interface variables and spreads the Volatile semantics if it has
+  // load instructions for the Volatile semantics.
+  Pass::Status SpreadVolatileSemanticsToVariables(
+      const bool is_vk_memory_model_enabled);
+
+  // Returns whether |var_id| is the result id of a target builtin variable for
+  // the volatile semantics for |execution_model| based on the Vulkan spec
+  // VUID-StandaloneSpirv-VulkanMemoryModel-04678 or
+  // VUID-StandaloneSpirv-VulkanMemoryModel-04679.
+  bool IsTargetForVolatileSemantics(uint32_t var_id,
+                                    SpvExecutionModel execution_model);
+
+  // Collects interface variables that need the volatile semantics.
+  // |is_vk_memory_model_enabled| is true if VulkanMemoryModel capability is
+  // enabled.
+  void CollectTargetsForVolatileSemantics(
+      const bool is_vk_memory_model_enabled);
+
+  // Reports an error if an interface variable is used by two entry points and
+  // it needs the Volatile decoration for one but not for another. Returns true
+  // if the error must be reported.
+  bool HasInterfaceInConflictOfVolatileSemantics();
+
+  // Returns whether the variable whose result is |var_id| is used by a
+  // non-volatile load or a pointer to it is used by a non-volatile load in
+  // |entry_point| or not.
+  bool IsTargetUsedByNonVolatileLoadInEntryPoint(uint32_t var_id,
+                                                 Instruction* entry_point);
+
+  // Visits load instructions of pointers to variable whose result id is
+  // |var_id| if the load instructions are in reachable functions from entry
+  // points. |handle_load| is a function to do some actions for the load
+  // instructions. Finishes the traversal and returns false if |handle_load|
+  // returns false for a load instruction. Otherwise, returns true after running
+  // |handle_load| for all the load instructions.
+  bool VisitLoadsOfPointersToVariableInEntries(
+      uint32_t var_id, const std::function<bool(Instruction*)>& handle_load,
+      const std::unordered_set<uint32_t>& function_ids);
+
+  // Sets Memory Operands of OpLoad instructions that load |var| or pointers
+  // of |var| as Volatile if the function id of the OpLoad instruction is
+  // included in |entry_function_ids|.
+  void SetVolatileForLoadsInEntries(
+      Instruction* var, const std::unordered_set<uint32_t>& entry_function_ids);
+
+  // Adds OpDecorate Volatile for |var| if it does not exist.
+  void DecorateVarWithVolatile(Instruction* var);
+
+  // Returns a set of entry function ids to spread the volatile semantics for
+  // the variable with the result id |var_id|.
+  std::unordered_set<uint32_t> EntryFunctionsToSpreadVolatileSemanticsForVar(
+      uint32_t var_id) {
+    auto itr = var_ids_to_entry_fn_for_volatile_semantics_.find(var_id);
+    if (itr == var_ids_to_entry_fn_for_volatile_semantics_.end()) return {};
+    return itr->second;
+  }
+
+  // Specifies that we have to spread the volatile semantics for the
+  // variable with the result id |var_id| for the entry point |entry_point|.
+  void MarkVolatileSemanticsForVariable(uint32_t var_id,
+                                        Instruction* entry_point);
+
+  // Result ids of variables to entry function ids for the volatile semantics
+  // spread.
+  std::unordered_map<uint32_t, std::unordered_set<uint32_t>>
+      var_ids_to_entry_fn_for_volatile_semantics_;
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_SPREAD_VOLATILE_SEMANTICS_H_
diff --git a/source/opt/ssa_rewrite_pass.cpp b/source/opt/ssa_rewrite_pass.cpp
index 29ab612..22d8110 100644
--- a/source/opt/ssa_rewrite_pass.cpp
+++ b/source/opt/ssa_rewrite_pass.cpp
@@ -67,7 +67,6 @@
 namespace {
 const uint32_t kStoreValIdInIdx = 1;
 const uint32_t kVariableInitIdInIdx = 1;
-const uint32_t kDebugDeclareOperandVariableIdx = 5;
 }  // namespace
 
 std::string SSARewriter::PhiCandidate::PrettyPrint(const CFG* cfg) const {
@@ -315,8 +314,8 @@
   }
   if (pass_->IsTargetVar(var_id)) {
     WriteVariable(var_id, bb, val_id);
-    pass_->context()->get_debug_info_mgr()->AddDebugValueIfVarDeclIsVisible(
-        inst, var_id, val_id, inst, &decls_invisible_to_value_assignment_);
+    pass_->context()->get_debug_info_mgr()->AddDebugValueForVariable(
+        inst, var_id, val_id, inst);
 
 #if SSA_REWRITE_DEBUGGING_LEVEL > 1
     std::cerr << "\tFound store '%" << var_id << " = %" << val_id << "': "
@@ -559,9 +558,9 @@
 
     // Add DebugValue for the new OpPhi instruction.
     insert_it->SetDebugScope(local_var->GetDebugScope());
-    pass_->context()->get_debug_info_mgr()->AddDebugValueIfVarDeclIsVisible(
+    pass_->context()->get_debug_info_mgr()->AddDebugValueForVariable(
         &*insert_it, phi_candidate->var_id(), phi_candidate->result_id(),
-        &*insert_it, &decls_invisible_to_value_assignment_);
+        &*insert_it);
 
     modified = true;
   }
@@ -650,62 +649,6 @@
   }
 }
 
-Pass::Status SSARewriter::AddDebugValuesForInvisibleDebugDecls(Function* fp) {
-  // For the cases the value assignment is invisible to DebugDeclare e.g.,
-  // the argument passing for an inlined function.
-  //
-  // Before inlining foo(int x):
-  //   a = 3;
-  //   foo(3);
-  // After inlining:
-  //   a = 3;
-  //   foo and x disappeared but we want to specify "DebugValue: %x = %int_3".
-  //
-  // We want to specify the value for the variable using |defs_at_block_[bb]|,
-  // where |bb| is the basic block contains the decl.
-  DominatorAnalysis* dom_tree = pass_->context()->GetDominatorAnalysis(fp);
-  Pass::Status status = Pass::Status::SuccessWithoutChange;
-  for (auto* decl : decls_invisible_to_value_assignment_) {
-    uint32_t var_id =
-        decl->GetSingleWordOperand(kDebugDeclareOperandVariableIdx);
-    auto* var = pass_->get_def_use_mgr()->GetDef(var_id);
-    if (var->opcode() == SpvOpFunctionParameter) continue;
-
-    auto* bb = pass_->context()->get_instr_block(decl);
-    uint32_t value_id = GetValueAtBlock(var_id, bb);
-    Instruction* value = nullptr;
-    if (value_id) value = pass_->get_def_use_mgr()->GetDef(value_id);
-
-    // If |value| is defined before the function body, it dominates |decl|.
-    // If |value| dominates |decl|, we can set it as DebugValue.
-    if (value && (pass_->context()->get_instr_block(value) == nullptr ||
-                  dom_tree->Dominates(value, decl))) {
-      if (pass_->context()->get_debug_info_mgr()->AddDebugValueForDecl(
-              decl, value->result_id(), decl, value) == nullptr) {
-        return Pass::Status::Failure;
-      }
-    } else {
-      // If |value| in the same basic block does not dominate |decl|, we can
-      // assign the value in the immediate dominator.
-      value_id = GetValueAtBlock(var_id, dom_tree->ImmediateDominator(bb));
-      if (value_id) value = pass_->get_def_use_mgr()->GetDef(value_id);
-      if (value_id &&
-          pass_->context()->get_debug_info_mgr()->AddDebugValueForDecl(
-              decl, value_id, decl, value) == nullptr) {
-        return Pass::Status::Failure;
-      }
-    }
-
-    // DebugDeclares of target variables will be removed by
-    // SSARewritePass::Process().
-    if (!pass_->IsTargetVar(var_id)) {
-      pass_->context()->get_debug_info_mgr()->KillDebugDeclares(var_id);
-    }
-    status = Pass::Status::SuccessWithChange;
-  }
-  return status;
-}
-
 Pass::Status SSARewriter::RewriteFunctionIntoSSA(Function* fp) {
 #if SSA_REWRITE_DEBUGGING_LEVEL > 0
   std::cerr << "Function before SSA rewrite:\n"
@@ -735,12 +678,6 @@
   // Finally, apply all the replacements in the IR.
   bool modified = ApplyReplacements();
 
-  auto status = AddDebugValuesForInvisibleDebugDecls(fp);
-  if (status == Pass::Status::SuccessWithChange ||
-      status == Pass::Status::Failure) {
-    return status;
-  }
-
 #if SSA_REWRITE_DEBUGGING_LEVEL > 0
   std::cerr << "\n\n\nFunction after SSA rewrite:\n"
             << fp->PrettyPrint(0) << "\n";
diff --git a/source/opt/ssa_rewrite_pass.h b/source/opt/ssa_rewrite_pass.h
index 1f4cd24..2470f85 100644
--- a/source/opt/ssa_rewrite_pass.h
+++ b/source/opt/ssa_rewrite_pass.h
@@ -253,11 +253,6 @@
   // candidates.
   void FinalizePhiCandidates();
 
-  // Adds DebugValues for DebugDeclares in
-  // |decls_invisible_to_value_assignment_|. Returns whether the function was
-  // modified or not, and whether or not the conversion was successful.
-  Pass::Status AddDebugValuesForInvisibleDebugDecls(Function* fp);
-
   // Prints the table of Phi candidates to std::cerr.
   void PrintPhiCandidates() const;
 
@@ -295,10 +290,6 @@
 
   // Memory pass requesting the SSA rewriter.
   MemPass* pass_;
-
-  // Set of DebugDeclare instructions that are not added as DebugValue because
-  // they are invisible to the store or phi instructions.
-  std::unordered_set<Instruction*> decls_invisible_to_value_assignment_;
 };
 
 class SSARewritePass : public MemPass {
diff --git a/source/opt/strength_reduction_pass.h b/source/opt/strength_reduction_pass.h
index 8dfeb30..1cbbbcc 100644
--- a/source/opt/strength_reduction_pass.h
+++ b/source/opt/strength_reduction_pass.h
@@ -34,7 +34,7 @@
   // Returns true if something changed.
   bool ReplaceMultiplyByPowerOf2(BasicBlock::iterator*);
 
-  // Scan the types and constants in the module looking for the the integer
+  // Scan the types and constants in the module looking for the integer
   // types that we are
   // interested in.  The shift operation needs a small unsigned integer.  We
   // need to find
diff --git a/source/opt/strip_debug_info_pass.cpp b/source/opt/strip_debug_info_pass.cpp
index c86ce57..6a0ebf2 100644
--- a/source/opt/strip_debug_info_pass.cpp
+++ b/source/opt/strip_debug_info_pass.cpp
@@ -14,6 +14,7 @@
 
 #include "source/opt/strip_debug_info_pass.h"
 #include "source/opt/ir_context.h"
+#include "source/util/string_utils.h"
 
 namespace spvtools {
 namespace opt {
@@ -21,9 +22,8 @@
 Pass::Status StripDebugInfoPass::Process() {
   bool uses_non_semantic_info = false;
   for (auto& inst : context()->module()->extensions()) {
-    const char* ext_name =
-        reinterpret_cast<const char*>(&inst.GetInOperand(0).words[0]);
-    if (0 == std::strcmp(ext_name, "SPV_KHR_non_semantic_info")) {
+    const std::string ext_name = inst.GetInOperand(0).AsString();
+    if (ext_name == "SPV_KHR_non_semantic_info") {
       uses_non_semantic_info = true;
     }
   }
@@ -46,9 +46,10 @@
                 if (use->opcode() == SpvOpExtInst) {
                   auto ext_inst_set =
                       def_use->GetDef(use->GetSingleWordInOperand(0u));
-                  const char* extension_name = reinterpret_cast<const char*>(
-                      &ext_inst_set->GetInOperand(0).words[0]);
-                  if (0 == std::strncmp(extension_name, "NonSemantic.", 12)) {
+                  const std::string extension_name =
+                      ext_inst_set->GetInOperand(0).AsString();
+                  if (spvtools::utils::starts_with(extension_name,
+                                                   "NonSemantic.")) {
                     // found a non-semantic use, return false as we cannot
                     // remove this OpString
                     return false;
diff --git a/source/opt/strip_reflect_info_pass.cpp b/source/opt/strip_nonsemantic_info_pass.cpp
similarity index 65%
rename from source/opt/strip_reflect_info_pass.cpp
rename to source/opt/strip_nonsemantic_info_pass.cpp
index 8b0f2db..cd1fbb6 100644
--- a/source/opt/strip_reflect_info_pass.cpp
+++ b/source/opt/strip_nonsemantic_info_pass.cpp
@@ -12,18 +12,19 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "source/opt/strip_reflect_info_pass.h"
+#include "source/opt/strip_nonsemantic_info_pass.h"
 
 #include <cstring>
 #include <vector>
 
 #include "source/opt/instruction.h"
 #include "source/opt/ir_context.h"
+#include "source/util/string_utils.h"
 
 namespace spvtools {
 namespace opt {
 
-Pass::Status StripReflectInfoPass::Process() {
+Pass::Status StripNonSemanticInfoPass::Process() {
   bool modified = false;
 
   std::vector<Instruction*> to_remove;
@@ -32,7 +33,8 @@
   for (auto& inst : context()->module()->annotations()) {
     switch (inst.opcode()) {
       case SpvOpDecorateStringGOOGLE:
-        if (inst.GetSingleWordInOperand(1) == SpvDecorationHlslSemanticGOOGLE) {
+        if (inst.GetSingleWordInOperand(1) == SpvDecorationHlslSemanticGOOGLE ||
+            inst.GetSingleWordInOperand(1) == SpvDecorationUserTypeGOOGLE) {
           to_remove.push_back(&inst);
         } else {
           other_uses_for_decorate_string = true;
@@ -40,7 +42,8 @@
         break;
 
       case SpvOpMemberDecorateStringGOOGLE:
-        if (inst.GetSingleWordInOperand(2) == SpvDecorationHlslSemanticGOOGLE) {
+        if (inst.GetSingleWordInOperand(2) == SpvDecorationHlslSemanticGOOGLE ||
+            inst.GetSingleWordInOperand(2) == SpvDecorationUserTypeGOOGLE) {
           to_remove.push_back(&inst);
         } else {
           other_uses_for_decorate_string = true;
@@ -60,33 +63,26 @@
   }
 
   for (auto& inst : context()->module()->extensions()) {
-    const char* ext_name =
-        reinterpret_cast<const char*>(&inst.GetInOperand(0).words[0]);
-    if (0 == std::strcmp(ext_name, "SPV_GOOGLE_hlsl_functionality1")) {
+    const std::string ext_name = inst.GetInOperand(0).AsString();
+    if (ext_name == "SPV_GOOGLE_hlsl_functionality1") {
+      to_remove.push_back(&inst);
+    } else if (ext_name == "SPV_GOOGLE_user_type") {
       to_remove.push_back(&inst);
     } else if (!other_uses_for_decorate_string &&
-               0 == std::strcmp(ext_name, "SPV_GOOGLE_decorate_string")) {
+               ext_name == "SPV_GOOGLE_decorate_string") {
       to_remove.push_back(&inst);
-    } else if (0 == std::strcmp(ext_name, "SPV_KHR_non_semantic_info")) {
+    } else if (ext_name == "SPV_KHR_non_semantic_info") {
       to_remove.push_back(&inst);
     }
   }
 
-  // clear all debug data now if it hasn't been cleared already, to remove any
-  // remaining OpString that may have been referenced by non-semantic extinsts
-  for (auto& dbg : context()->debugs1()) to_remove.push_back(&dbg);
-  for (auto& dbg : context()->debugs2()) to_remove.push_back(&dbg);
-  for (auto& dbg : context()->debugs3()) to_remove.push_back(&dbg);
-  for (auto& dbg : context()->ext_inst_debuginfo()) to_remove.push_back(&dbg);
-
   // remove any extended inst imports that are non semantic
   std::unordered_set<uint32_t> non_semantic_sets;
   for (auto& inst : context()->module()->ext_inst_imports()) {
     assert(inst.opcode() == SpvOpExtInstImport &&
            "Expecting an import of an extension's instruction set.");
-    const char* extension_name =
-        reinterpret_cast<const char*>(&inst.GetInOperand(0).words[0]);
-    if (0 == std::strncmp(extension_name, "NonSemantic.", 12)) {
+    const std::string extension_name = inst.GetInOperand(0).AsString();
+    if (spvtools::utils::starts_with(extension_name, "NonSemantic.")) {
       non_semantic_sets.insert(inst.result_id());
       to_remove.push_back(&inst);
     }
@@ -103,19 +99,10 @@
               to_remove.push_back(inst);
             }
           }
-        });
+        },
+        true);
   }
 
-  // OpName must come first, since they may refer to other debug instructions.
-  // If they are after the instructions that refer to, then they will be killed
-  // when that instruction is killed, which will lead to a double kill.
-  std::sort(to_remove.begin(), to_remove.end(),
-            [](Instruction* lhs, Instruction* rhs) -> bool {
-              if (lhs->opcode() == SpvOpName && rhs->opcode() != SpvOpName)
-                return true;
-              return false;
-            });
-
   for (auto* inst : to_remove) {
     modified = true;
     context()->KillInst(inst);
diff --git a/source/opt/strip_reflect_info_pass.h b/source/opt/strip_nonsemantic_info_pass.h
similarity index 82%
rename from source/opt/strip_reflect_info_pass.h
rename to source/opt/strip_nonsemantic_info_pass.h
index 4e1999e..ff4e2e1 100644
--- a/source/opt/strip_reflect_info_pass.h
+++ b/source/opt/strip_nonsemantic_info_pass.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SOURCE_OPT_STRIP_REFLECT_INFO_PASS_H_
-#define SOURCE_OPT_STRIP_REFLECT_INFO_PASS_H_
+#ifndef SOURCE_OPT_STRIP_NONSEMANTIC_INFO_PASS_H_
+#define SOURCE_OPT_STRIP_NONSEMANTIC_INFO_PASS_H_
 
 #include "source/opt/ir_context.h"
 #include "source/opt/module.h"
@@ -23,9 +23,9 @@
 namespace opt {
 
 // See optimizer.hpp for documentation.
-class StripReflectInfoPass : public Pass {
+class StripNonSemanticInfoPass : public Pass {
  public:
-  const char* name() const override { return "strip-reflect"; }
+  const char* name() const override { return "strip-nonsemantic"; }
   Status Process() override;
 
   // Return the mask of preserved Analyses.
@@ -41,4 +41,4 @@
 }  // namespace opt
 }  // namespace spvtools
 
-#endif  // SOURCE_OPT_STRIP_REFLECT_INFO_PASS_H_
+#endif  // SOURCE_OPT_STRIP_NONSEMANTIC_INFO_PASS_H_
diff --git a/source/opt/type_manager.cpp b/source/opt/type_manager.cpp
index 7935ad3..a0006f5 100644
--- a/source/opt/type_manager.cpp
+++ b/source/opt/type_manager.cpp
@@ -23,6 +23,7 @@
 #include "source/opt/log.h"
 #include "source/opt/reflect.h"
 #include "source/util/make_unique.h"
+#include "source/util/string_utils.h"
 
 namespace spvtools {
 namespace opt {
@@ -234,6 +235,7 @@
     DefineParameterlessCase(PipeStorage);
     DefineParameterlessCase(NamedBarrier);
     DefineParameterlessCase(AccelerationStructureNV);
+    DefineParameterlessCase(RayQueryKHR);
 #undef DefineParameterlessCase
     case Type::kInteger:
       typeInst = MakeUnique<Instruction>(
@@ -349,11 +351,8 @@
     }
     case Type::kOpaque: {
       const Opaque* opaque = type->AsOpaque();
-      size_t size = opaque->name().size();
       // Convert to null-terminated packed UTF-8 string.
-      std::vector<uint32_t> words(size / 4 + 1, 0);
-      char* dst = reinterpret_cast<char*>(words.data());
-      strncpy(dst, opaque->name().c_str(), size);
+      std::vector<uint32_t> words = spvtools::utils::MakeVector(opaque->name());
       typeInst = MakeUnique<Instruction>(
           context(), SpvOpTypeOpaque, 0, id,
           std::initializer_list<Operand>{
@@ -529,6 +528,7 @@
     DefineNoSubtypeCase(PipeStorage);
     DefineNoSubtypeCase(NamedBarrier);
     DefineNoSubtypeCase(AccelerationStructureNV);
+    DefineNoSubtypeCase(RayQueryKHR);
 #undef DefineNoSubtypeCase
     case Type::kVector: {
       const Vector* vec_ty = type.AsVector();
@@ -781,8 +781,7 @@
       }
     } break;
     case SpvOpTypeOpaque: {
-      const uint32_t* data = inst.GetInOperand(0).words.data();
-      type = new Opaque(reinterpret_cast<const char*>(data));
+      type = new Opaque(inst.GetInOperand(0).AsString());
     } break;
     case SpvOpTypePointer: {
       uint32_t pointee_type_id = inst.GetSingleWordInOperand(1);
diff --git a/source/opt/type_manager.h b/source/opt/type_manager.h
index ce9d83d..72e37f4 100644
--- a/source/opt/type_manager.h
+++ b/source/opt/type_manager.h
@@ -160,6 +160,13 @@
 
   uint32_t GetFloatTypeId() { return GetTypeInstruction(GetFloatType()); }
 
+  Type* GetDoubleType() {
+    Float float_type(64);
+    return GetRegisteredType(&float_type);
+  }
+
+  uint32_t GetDoubleTypeId() { return GetTypeInstruction(GetDoubleType()); }
+
   Type* GetUIntVectorType(uint32_t size) {
     Vector vec_type(GetUIntType(), size);
     return GetRegisteredType(&vec_type);
diff --git a/source/opt/types.cpp b/source/opt/types.cpp
index b1eb3a5..056aceb 100644
--- a/source/opt/types.cpp
+++ b/source/opt/types.cpp
@@ -16,11 +16,13 @@
 
 #include <algorithm>
 #include <cassert>
+#include <climits>
 #include <cstdint>
 #include <sstream>
 #include <string>
 #include <unordered_set>
 
+#include "source/util/hash_combine.h"
 #include "source/util/make_unique.h"
 #include "spirv/unified1/spirv.h"
 
@@ -28,6 +30,7 @@
 namespace opt {
 namespace analysis {
 
+using spvtools::utils::hash_combine;
 using U32VecVec = std::vector<std::vector<uint32_t>>;
 
 namespace {
@@ -182,23 +185,26 @@
   }
 }
 
-void Type::GetHashWords(std::vector<uint32_t>* words,
-                        std::unordered_set<const Type*>* seen) const {
-  if (!seen->insert(this).second) {
-    return;
+size_t Type::ComputeHashValue(size_t hash, SeenTypes* seen) const {
+  // Linear search through a dense, cache coherent vector is faster than O(log
+  // n) search in a complex data structure (eg std::set) for the generally small
+  // number of nodes.  It also skips the overhead of an new/delete per Type
+  // (when inserting/removing from a set).
+  if (std::find(seen->begin(), seen->end(), this) != seen->end()) {
+    return hash;
   }
 
-  words->push_back(kind_);
+  seen->push_back(this);
+
+  hash = hash_combine(hash, uint32_t(kind_));
   for (const auto& d : decorations_) {
-    for (auto w : d) {
-      words->push_back(w);
-    }
+    hash = hash_combine(hash, d);
   }
 
   switch (kind_) {
-#define DeclareKindCase(type)                   \
-  case k##type:                                 \
-    As##type()->GetExtraHashWords(words, seen); \
+#define DeclareKindCase(type)                             \
+  case k##type:                                           \
+    hash = As##type()->ComputeExtraStateHash(hash, seen); \
     break
     DeclareKindCase(Void);
     DeclareKindCase(Bool);
@@ -232,18 +238,42 @@
       break;
   }
 
-  seen->erase(this);
+  seen->pop_back();
+  return hash;
 }
 
 size_t Type::HashValue() const {
-  std::u32string h;
-  std::vector<uint32_t> words;
-  GetHashWords(&words);
-  for (auto w : words) {
-    h.push_back(w);
-  }
+  SeenTypes seen;
+  return ComputeHashValue(0, &seen);
+}
 
-  return std::hash<std::u32string>()(h);
+uint64_t Type::NumberOfComponents() const {
+  switch (kind()) {
+    case kVector:
+      return AsVector()->element_count();
+    case kMatrix:
+      return AsMatrix()->element_count();
+    case kArray: {
+      Array::LengthInfo length_info = AsArray()->length_info();
+      if (length_info.words[0] != Array::LengthInfo::kConstant) {
+        return UINT64_MAX;
+      }
+      assert(length_info.words.size() <= 3 &&
+             "The size of the array could not fit size_t.");
+      uint64_t length = 0;
+      length |= length_info.words[1];
+      if (length_info.words.size() > 2) {
+        length |= static_cast<uint64_t>(length_info.words[2]) << 32;
+      }
+      return length;
+    }
+    case kRuntimeArray:
+      return UINT64_MAX;
+    case kStruct:
+      return AsStruct()->element_types().size();
+    default:
+      return 0;
+  }
 }
 
 bool Integer::IsSameImpl(const Type* that, IsSameCache*) const {
@@ -258,10 +288,8 @@
   return oss.str();
 }
 
-void Integer::GetExtraHashWords(std::vector<uint32_t>* words,
-                                std::unordered_set<const Type*>*) const {
-  words->push_back(width_);
-  words->push_back(signed_);
+size_t Integer::ComputeExtraStateHash(size_t hash, SeenTypes*) const {
+  return hash_combine(hash, width_, signed_);
 }
 
 bool Float::IsSameImpl(const Type* that, IsSameCache*) const {
@@ -275,9 +303,8 @@
   return oss.str();
 }
 
-void Float::GetExtraHashWords(std::vector<uint32_t>* words,
-                              std::unordered_set<const Type*>*) const {
-  words->push_back(width_);
+size_t Float::ComputeExtraStateHash(size_t hash, SeenTypes*) const {
+  return hash_combine(hash, width_);
 }
 
 Vector::Vector(const Type* type, uint32_t count)
@@ -299,10 +326,11 @@
   return oss.str();
 }
 
-void Vector::GetExtraHashWords(std::vector<uint32_t>* words,
-                               std::unordered_set<const Type*>* seen) const {
-  element_type_->GetHashWords(words, seen);
-  words->push_back(count_);
+size_t Vector::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
+  // prefer form that doesn't require push/pop from stack: add state and
+  // make tail call.
+  hash = hash_combine(hash, count_);
+  return element_type_->ComputeHashValue(hash, seen);
 }
 
 Matrix::Matrix(const Type* type, uint32_t count)
@@ -324,10 +352,9 @@
   return oss.str();
 }
 
-void Matrix::GetExtraHashWords(std::vector<uint32_t>* words,
-                               std::unordered_set<const Type*>* seen) const {
-  element_type_->GetHashWords(words, seen);
-  words->push_back(count_);
+size_t Matrix::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
+  hash = hash_combine(hash, count_);
+  return element_type_->ComputeHashValue(hash, seen);
 }
 
 Image::Image(Type* type, SpvDim dimen, uint32_t d, bool array, bool multisample,
@@ -362,16 +389,10 @@
   return oss.str();
 }
 
-void Image::GetExtraHashWords(std::vector<uint32_t>* words,
-                              std::unordered_set<const Type*>* seen) const {
-  sampled_type_->GetHashWords(words, seen);
-  words->push_back(dim_);
-  words->push_back(depth_);
-  words->push_back(arrayed_);
-  words->push_back(ms_);
-  words->push_back(sampled_);
-  words->push_back(format_);
-  words->push_back(access_qualifier_);
+size_t Image::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
+  hash = hash_combine(hash, uint32_t(dim_), depth_, arrayed_, ms_, sampled_,
+                      uint32_t(format_), uint32_t(access_qualifier_));
+  return sampled_type_->ComputeHashValue(hash, seen);
 }
 
 bool SampledImage::IsSameImpl(const Type* that, IsSameCache* seen) const {
@@ -387,9 +408,8 @@
   return oss.str();
 }
 
-void SampledImage::GetExtraHashWords(
-    std::vector<uint32_t>* words, std::unordered_set<const Type*>* seen) const {
-  image_type_->GetHashWords(words, seen);
+size_t SampledImage::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
+  return image_type_->ComputeHashValue(hash, seen);
 }
 
 Array::Array(const Type* type, const Array::LengthInfo& length_info_arg)
@@ -422,16 +442,19 @@
   return oss.str();
 }
 
-void Array::GetExtraHashWords(std::vector<uint32_t>* words,
-                              std::unordered_set<const Type*>* seen) const {
-  element_type_->GetHashWords(words, seen);
-  // This should mirror the logic in IsSameImpl
-  words->insert(words->end(), length_info_.words.begin(),
-                length_info_.words.end());
+size_t Array::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
+  hash = hash_combine(hash, length_info_.words);
+  return element_type_->ComputeHashValue(hash, seen);
 }
 
 void Array::ReplaceElementType(const Type* type) { element_type_ = type; }
 
+Array::LengthInfo Array::GetConstantLengthInfo(uint32_t const_id,
+                                               uint32_t length) const {
+  std::vector<uint32_t> extra_words{LengthInfo::Case::kConstant, length};
+  return {const_id, extra_words};
+}
+
 RuntimeArray::RuntimeArray(const Type* type)
     : Type(kRuntimeArray), element_type_(type) {
   assert(!type->AsVoid());
@@ -450,9 +473,8 @@
   return oss.str();
 }
 
-void RuntimeArray::GetExtraHashWords(
-    std::vector<uint32_t>* words, std::unordered_set<const Type*>* seen) const {
-  element_type_->GetHashWords(words, seen);
+size_t RuntimeArray::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
+  return element_type_->ComputeHashValue(hash, seen);
 }
 
 void RuntimeArray::ReplaceElementType(const Type* type) {
@@ -509,19 +531,14 @@
   return oss.str();
 }
 
-void Struct::GetExtraHashWords(std::vector<uint32_t>* words,
-                               std::unordered_set<const Type*>* seen) const {
+size_t Struct::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
   for (auto* t : element_types_) {
-    t->GetHashWords(words, seen);
+    hash = t->ComputeHashValue(hash, seen);
   }
   for (const auto& pair : element_decorations_) {
-    words->push_back(pair.first);
-    for (const auto& d : pair.second) {
-      for (auto w : d) {
-        words->push_back(w);
-      }
-    }
+    hash = hash_combine(hash, pair.first, pair.second);
   }
+  return hash;
 }
 
 bool Opaque::IsSameImpl(const Type* that, IsSameCache*) const {
@@ -536,11 +553,8 @@
   return oss.str();
 }
 
-void Opaque::GetExtraHashWords(std::vector<uint32_t>* words,
-                               std::unordered_set<const Type*>*) const {
-  for (auto c : name_) {
-    words->push_back(static_cast<char32_t>(c));
-  }
+size_t Opaque::ComputeExtraStateHash(size_t hash, SeenTypes*) const {
+  return hash_combine(hash, name_);
 }
 
 Pointer::Pointer(const Type* type, SpvStorageClass sc)
@@ -569,10 +583,9 @@
   return os.str();
 }
 
-void Pointer::GetExtraHashWords(std::vector<uint32_t>* words,
-                                std::unordered_set<const Type*>* seen) const {
-  pointee_type_->GetHashWords(words, seen);
-  words->push_back(storage_class_);
+size_t Pointer::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
+  hash = hash_combine(hash, uint32_t(storage_class_));
+  return pointee_type_->ComputeHashValue(hash, seen);
 }
 
 void Pointer::SetPointeeType(const Type* type) { pointee_type_ = type; }
@@ -606,12 +619,11 @@
   return oss.str();
 }
 
-void Function::GetExtraHashWords(std::vector<uint32_t>* words,
-                                 std::unordered_set<const Type*>* seen) const {
-  return_type_->GetHashWords(words, seen);
+size_t Function::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
   for (const auto* t : param_types_) {
-    t->GetHashWords(words, seen);
+    hash = t->ComputeHashValue(hash, seen);
   }
+  return return_type_->ComputeHashValue(hash, seen);
 }
 
 void Function::SetReturnType(const Type* type) { return_type_ = type; }
@@ -628,9 +640,8 @@
   return oss.str();
 }
 
-void Pipe::GetExtraHashWords(std::vector<uint32_t>* words,
-                             std::unordered_set<const Type*>*) const {
-  words->push_back(access_qualifier_);
+size_t Pipe::ComputeExtraStateHash(size_t hash, SeenTypes*) const {
+  return hash_combine(hash, uint32_t(access_qualifier_));
 }
 
 bool ForwardPointer::IsSameImpl(const Type* that, IsSameCache*) const {
@@ -653,11 +664,11 @@
   return oss.str();
 }
 
-void ForwardPointer::GetExtraHashWords(
-    std::vector<uint32_t>* words, std::unordered_set<const Type*>* seen) const {
-  words->push_back(target_id_);
-  words->push_back(storage_class_);
-  if (pointer_) pointer_->GetHashWords(words, seen);
+size_t ForwardPointer::ComputeExtraStateHash(size_t hash,
+                                             SeenTypes* seen) const {
+  hash = hash_combine(hash, target_id_, uint32_t(storage_class_));
+  if (pointer_) hash = pointer_->ComputeHashValue(hash, seen);
+  return hash;
 }
 
 CooperativeMatrixNV::CooperativeMatrixNV(const Type* type, const uint32_t scope,
@@ -681,12 +692,10 @@
   return oss.str();
 }
 
-void CooperativeMatrixNV::GetExtraHashWords(
-    std::vector<uint32_t>* words, std::unordered_set<const Type*>* pSet) const {
-  component_type_->GetHashWords(words, pSet);
-  words->push_back(scope_id_);
-  words->push_back(rows_id_);
-  words->push_back(columns_id_);
+size_t CooperativeMatrixNV::ComputeExtraStateHash(size_t hash,
+                                                  SeenTypes* seen) const {
+  hash = hash_combine(hash, scope_id_, rows_id_, columns_id_);
+  return component_type_->ComputeHashValue(hash, seen);
 }
 
 bool CooperativeMatrixNV::IsSameImpl(const Type* that,
diff --git a/source/opt/types.h b/source/opt/types.h
index 9ecd41a..a92669e 100644
--- a/source/opt/types.h
+++ b/source/opt/types.h
@@ -28,6 +28,7 @@
 
 #include "source/latest_version_spirv_header.h"
 #include "source/opt/instruction.h"
+#include "source/util/small_vector.h"
 #include "spirv-tools/libspirv.h"
 
 namespace spvtools {
@@ -67,6 +68,8 @@
  public:
   typedef std::set<std::pair<const Pointer*, const Pointer*>> IsSameCache;
 
+  using SeenTypes = spvtools::utils::SmallVector<const Type*, 8>;
+
   // Available subtypes.
   //
   // When adding a new derived class of Type, please add an entry to the enum.
@@ -96,7 +99,8 @@
     kNamedBarrier,
     kAccelerationStructureNV,
     kCooperativeMatrixNV,
-    kRayQueryKHR
+    kRayQueryKHR,
+    kLast
   };
 
   Type(Kind k) : kind_(k) {}
@@ -154,21 +158,11 @@
   // Returns the hash value of this type.
   size_t HashValue() const;
 
-  // Adds the necessary words to compute a hash value of this type to |words|.
-  void GetHashWords(std::vector<uint32_t>* words) const {
-    std::unordered_set<const Type*> seen;
-    GetHashWords(words, &seen);
-  }
+  size_t ComputeHashValue(size_t hash, SeenTypes* seen) const;
 
-  // Adds the necessary words to compute a hash value of this type to |words|.
-  void GetHashWords(std::vector<uint32_t>* words,
-                    std::unordered_set<const Type*>* seen) const;
-
-  // Adds necessary extra words for a subtype to calculate a hash value into
-  // |words|.
-  virtual void GetExtraHashWords(
-      std::vector<uint32_t>* words,
-      std::unordered_set<const Type*>* pSet) const = 0;
+  // Returns the number of components in a composite type.  Returns 0 for a
+  // non-composite type.
+  uint64_t NumberOfComponents() const;
 
 // A bunch of methods for casting this type to a given type. Returns this if the
 // cast can be done, nullptr otherwise.
@@ -204,6 +198,10 @@
   DeclareCastMethod(RayQueryKHR)
 #undef DeclareCastMethod
 
+protected:
+  // Add any type-specific state to |hash| and returns new hash.
+  virtual size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const = 0;
+
  protected:
   // Decorations attached to this type. Each decoration is encoded as a vector
   // of uint32_t numbers. The first uint32_t number is the decoration value,
@@ -232,8 +230,7 @@
   uint32_t width() const { return width_; }
   bool IsSigned() const { return signed_; }
 
-  void GetExtraHashWords(std::vector<uint32_t>* words,
-                         std::unordered_set<const Type*>* pSet) const override;
+  size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
 
  private:
   bool IsSameImpl(const Type* that, IsSameCache*) const override;
@@ -253,8 +250,7 @@
   const Float* AsFloat() const override { return this; }
   uint32_t width() const { return width_; }
 
-  void GetExtraHashWords(std::vector<uint32_t>* words,
-                         std::unordered_set<const Type*>* pSet) const override;
+  size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
 
  private:
   bool IsSameImpl(const Type* that, IsSameCache*) const override;
@@ -274,8 +270,7 @@
   Vector* AsVector() override { return this; }
   const Vector* AsVector() const override { return this; }
 
-  void GetExtraHashWords(std::vector<uint32_t>* words,
-                         std::unordered_set<const Type*>* pSet) const override;
+  size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
 
  private:
   bool IsSameImpl(const Type* that, IsSameCache*) const override;
@@ -296,8 +291,7 @@
   Matrix* AsMatrix() override { return this; }
   const Matrix* AsMatrix() const override { return this; }
 
-  void GetExtraHashWords(std::vector<uint32_t>* words,
-                         std::unordered_set<const Type*>* pSet) const override;
+  size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
 
  private:
   bool IsSameImpl(const Type* that, IsSameCache*) const override;
@@ -327,8 +321,7 @@
   SpvImageFormat format() const { return format_; }
   SpvAccessQualifier access_qualifier() const { return access_qualifier_; }
 
-  void GetExtraHashWords(std::vector<uint32_t>* words,
-                         std::unordered_set<const Type*>* pSet) const override;
+  size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
 
  private:
   bool IsSameImpl(const Type* that, IsSameCache*) const override;
@@ -355,8 +348,7 @@
 
   const Type* image_type() const { return image_type_; }
 
-  void GetExtraHashWords(std::vector<uint32_t>* words,
-                         std::unordered_set<const Type*>* pSet) const override;
+  size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
 
  private:
   bool IsSameImpl(const Type* that, IsSameCache*) const override;
@@ -399,10 +391,10 @@
   Array* AsArray() override { return this; }
   const Array* AsArray() const override { return this; }
 
-  void GetExtraHashWords(std::vector<uint32_t>* words,
-                         std::unordered_set<const Type*>* pSet) const override;
+  size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
 
   void ReplaceElementType(const Type* element_type);
+  LengthInfo GetConstantLengthInfo(uint32_t const_id, uint32_t length) const;
 
  private:
   bool IsSameImpl(const Type* that, IsSameCache*) const override;
@@ -422,8 +414,7 @@
   RuntimeArray* AsRuntimeArray() override { return this; }
   const RuntimeArray* AsRuntimeArray() const override { return this; }
 
-  void GetExtraHashWords(std::vector<uint32_t>* words,
-                         std::unordered_set<const Type*>* pSet) const override;
+  size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
 
   void ReplaceElementType(const Type* element_type);
 
@@ -459,8 +450,7 @@
   Struct* AsStruct() override { return this; }
   const Struct* AsStruct() const override { return this; }
 
-  void GetExtraHashWords(std::vector<uint32_t>* words,
-                         std::unordered_set<const Type*>* pSet) const override;
+  size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
 
  private:
   bool IsSameImpl(const Type* that, IsSameCache*) const override;
@@ -491,8 +481,7 @@
 
   const std::string& name() const { return name_; }
 
-  void GetExtraHashWords(std::vector<uint32_t>* words,
-                         std::unordered_set<const Type*>* pSet) const override;
+  size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
 
  private:
   bool IsSameImpl(const Type* that, IsSameCache*) const override;
@@ -512,8 +501,7 @@
   Pointer* AsPointer() override { return this; }
   const Pointer* AsPointer() const override { return this; }
 
-  void GetExtraHashWords(std::vector<uint32_t>* words,
-                         std::unordered_set<const Type*>* pSet) const override;
+  size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
 
   void SetPointeeType(const Type* type);
 
@@ -539,8 +527,7 @@
   const std::vector<const Type*>& param_types() const { return param_types_; }
   std::vector<const Type*>& param_types() { return param_types_; }
 
-  void GetExtraHashWords(std::vector<uint32_t>* words,
-                         std::unordered_set<const Type*>*) const override;
+  size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
 
   void SetReturnType(const Type* type);
 
@@ -564,8 +551,7 @@
 
   SpvAccessQualifier access_qualifier() const { return access_qualifier_; }
 
-  void GetExtraHashWords(std::vector<uint32_t>* words,
-                         std::unordered_set<const Type*>* pSet) const override;
+  size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
 
  private:
   bool IsSameImpl(const Type* that, IsSameCache*) const override;
@@ -592,8 +578,7 @@
   ForwardPointer* AsForwardPointer() override { return this; }
   const ForwardPointer* AsForwardPointer() const override { return this; }
 
-  void GetExtraHashWords(std::vector<uint32_t>* words,
-                         std::unordered_set<const Type*>* pSet) const override;
+  size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
 
  private:
   bool IsSameImpl(const Type* that, IsSameCache*) const override;
@@ -616,8 +601,7 @@
     return this;
   }
 
-  void GetExtraHashWords(std::vector<uint32_t>*,
-                         std::unordered_set<const Type*>*) const override;
+  size_t ComputeExtraStateHash(size_t hash, SeenTypes* seen) const override;
 
   const Type* component_type() const { return component_type_; }
   uint32_t scope_id() const { return scope_id_; }
@@ -633,24 +617,25 @@
   const uint32_t columns_id_;
 };
 
-#define DefineParameterlessType(type, name)                                    \
-  class type : public Type {                                                   \
-   public:                                                                     \
-    type() : Type(k##type) {}                                                  \
-    type(const type&) = default;                                               \
-                                                                               \
-    std::string str() const override { return #name; }                         \
-                                                                               \
-    type* As##type() override { return this; }                                 \
-    const type* As##type() const override { return this; }                     \
-                                                                               \
-    void GetExtraHashWords(std::vector<uint32_t>*,                             \
-                           std::unordered_set<const Type*>*) const override {} \
-                                                                               \
-   private:                                                                    \
-    bool IsSameImpl(const Type* that, IsSameCache*) const override {           \
-      return that->As##type() && HasSameDecorations(that);                     \
-    }                                                                          \
+#define DefineParameterlessType(type, name)                                \
+  class type : public Type {                                               \
+   public:                                                                 \
+    type() : Type(k##type) {}                                              \
+    type(const type&) = default;                                           \
+                                                                           \
+    std::string str() const override { return #name; }                     \
+                                                                           \
+    type* As##type() override { return this; }                             \
+    const type* As##type() const override { return this; }                 \
+                                                                           \
+    size_t ComputeExtraStateHash(size_t hash, SeenTypes*) const override { \
+      return hash;                                                         \
+    }                                                                      \
+                                                                           \
+   private:                                                                \
+    bool IsSameImpl(const Type* that, IsSameCache*) const override {       \
+      return that->As##type() && HasSameDecorations(that);                 \
+    }                                                                      \
   }
 DefineParameterlessType(Void, void);
 DefineParameterlessType(Bool, bool);
diff --git a/source/opt/unify_const_pass.cpp b/source/opt/unify_const_pass.cpp
index 227fd61..6bfa11a 100644
--- a/source/opt/unify_const_pass.cpp
+++ b/source/opt/unify_const_pass.cpp
@@ -151,7 +151,7 @@
       // 'SpecId' decoration and all of them should be treated as unique.
       // 'SpecId' is not applicable to SpecConstants defined with
       // OpSpecConstant{Op|Composite}, their values are not necessary to be
-      // unique. When all the operands/compoents are the same between two
+      // unique. When all the operands/components are the same between two
       // OpSpecConstant{Op|Composite} results, their result values must be the
       // same so are unifiable.
       case SpvOp::SpvOpSpecConstantOp:
diff --git a/source/opt/upgrade_memory_model.cpp b/source/opt/upgrade_memory_model.cpp
index ab25205..9d6a5bc 100644
--- a/source/opt/upgrade_memory_model.cpp
+++ b/source/opt/upgrade_memory_model.cpp
@@ -20,6 +20,7 @@
 #include "source/opt/ir_context.h"
 #include "source/spirv_constant.h"
 #include "source/util/make_unique.h"
+#include "source/util/string_utils.h"
 
 namespace spvtools {
 namespace opt {
@@ -58,9 +59,7 @@
       std::initializer_list<Operand>{
           {SPV_OPERAND_TYPE_CAPABILITY, {SpvCapabilityVulkanMemoryModelKHR}}}));
   const std::string extension = "SPV_KHR_vulkan_memory_model";
-  std::vector<uint32_t> words(extension.size() / 4 + 1, 0);
-  char* dst = reinterpret_cast<char*>(words.data());
-  strncpy(dst, extension.c_str(), extension.size());
+  std::vector<uint32_t> words = spvtools::utils::MakeVector(extension);
   context()->AddExtension(
       MakeUnique<Instruction>(context(), SpvOpExtension, 0, 0,
                               std::initializer_list<Operand>{
@@ -85,8 +84,7 @@
         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")) {
+          if (import->GetInOperand(0u).AsString() == "GLSL.std.450") {
             UpgradeExtInst(inst);
           }
         }
diff --git a/source/opt/vector_dce.h b/source/opt/vector_dce.h
index 4d30b92..a55bda6 100644
--- a/source/opt/vector_dce.h
+++ b/source/opt/vector_dce.h
@@ -73,7 +73,7 @@
   bool RewriteInstructions(Function* function,
                            const LiveComponentMap& live_components);
 
-  // Makrs all DebugValue instructions that use |composite| for their values as
+  // Makes all DebugValue instructions that use |composite| for their values as
   // dead instructions by putting them into |dead_dbg_value|.
   void MarkDebugValueUsesAsDead(Instruction* composite,
                                 std::vector<Instruction*>* dead_dbg_value);
diff --git a/source/parsed_operand.cpp b/source/parsed_operand.cpp
index 7ad369c..5f8e94d 100644
--- a/source/parsed_operand.cpp
+++ b/source/parsed_operand.cpp
@@ -24,7 +24,9 @@
 void EmitNumericLiteral(std::ostream* out, const spv_parsed_instruction_t& inst,
                         const spv_parsed_operand_t& operand) {
   if (operand.type != SPV_OPERAND_TYPE_LITERAL_INTEGER &&
-      operand.type != SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER)
+      operand.type != SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER &&
+      operand.type != SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER &&
+      operand.type != SPV_OPERAND_TYPE_OPTIONAL_TYPED_LITERAL_INTEGER)
     return;
   if (operand.num_words < 1) return;
   // TODO(dneto): Support more than 64-bits at a time.
diff --git a/source/print.cpp b/source/print.cpp
index 2418c5b..6c94e2b 100644
--- a/source/print.cpp
+++ b/source/print.cpp
@@ -16,7 +16,8 @@
 
 #if defined(SPIRV_ANDROID) || defined(SPIRV_LINUX) || defined(SPIRV_MAC) || \
     defined(SPIRV_IOS) || defined(SPIRV_TVOS) || defined(SPIRV_FREEBSD) ||  \
-    defined(SPIRV_EMSCRIPTEN) || defined(SPIRV_FUCHSIA)
+    defined(SPIRV_OPENBSD) || defined(SPIRV_EMSCRIPTEN) ||                  \
+    defined(SPIRV_FUCHSIA) || defined(SPIRV_GNU)
 namespace spvtools {
 
 clr::reset::operator const char*() { return "\x1b[0m"; }
diff --git a/source/reduce/remove_struct_member_reduction_opportunity.cpp b/source/reduce/remove_struct_member_reduction_opportunity.cpp
index da096e1..e72ed35 100644
--- a/source/reduce/remove_struct_member_reduction_opportunity.cpp
+++ b/source/reduce/remove_struct_member_reduction_opportunity.cpp
@@ -153,7 +153,7 @@
         next_type = type_inst->GetSingleWordInOperand(0);
         break;
       case SpvOpTypeStruct: {
-        // Struct types are special becuase (a) we may need to adjust the index
+        // Struct types are special because (a) we may need to adjust the index
         // being used, if the struct type is the one from which we are removing
         // a member, and (b) the type encountered by following the current index
         // is dependent on the value of the index.
diff --git a/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp b/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp
index e72be62..cd0c4e4 100644
--- a/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp
+++ b/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp
@@ -136,9 +136,9 @@
     }
   }
 
-  // We now know those struct indices that are unsed, and we make a reduction
+  // We now know those struct indices that are unused, and we make a reduction
   // opportunity for each of them. By mapping each relevant member index to the
-  // structs in which it is unsed, we will group all opportunities to remove
+  // structs in which it is unused, we will group all opportunities to remove
   // member k of a struct (for some k) together.  This reduces the likelihood
   // that opportunities to remove members from the same struct will be adjacent,
   // which is good because such opportunities mutually disable one another.
diff --git a/source/reduce/structured_construct_to_block_reduction_opportunity_finder.cpp b/source/reduce/structured_construct_to_block_reduction_opportunity_finder.cpp
index dc20f68..29fbe55 100644
--- a/source/reduce/structured_construct_to_block_reduction_opportunity_finder.cpp
+++ b/source/reduce/structured_construct_to_block_reduction_opportunity_finder.cpp
@@ -96,7 +96,7 @@
         // This also means that we don't add a region.
         continue;
       }
-      // We have a reachable header block with a rechable merge that
+      // We have a reachable header block with a reachable merge that
       // postdominates the header: this means we have a new region.
       regions.emplace(&block, std::unordered_set<opt::BasicBlock*>());
     }
@@ -128,7 +128,7 @@
     if (!block->WhileEachInst(
             [context, &header, &region](opt::Instruction* inst) -> bool {
               if (inst->result_id() == 0) {
-                // The instruction does not genreate a result id, thus it cannot
+                // The instruction does not generate a result id, thus it cannot
                 // be referred to outside the region - this is fine.
                 return true;
               }
diff --git a/source/spirv_definition.h b/source/spirv_definition.h
index 63a4ef0..5dbd6ab 100644
--- a/source/spirv_definition.h
+++ b/source/spirv_definition.h
@@ -27,7 +27,7 @@
   uint32_t generator;
   uint32_t bound;
   uint32_t schema;               // NOTE: Reserved
-  const uint32_t* instructions;  // NOTE: Unfixed pointer to instruciton stream
+  const uint32_t* instructions;  // NOTE: Unfixed pointer to instruction stream
 } spv_header_t;
 
 #endif  // SOURCE_SPIRV_DEFINITION_H_
diff --git a/source/spirv_endian.h b/source/spirv_endian.h
index c2540be..b4927f3 100644
--- a/source/spirv_endian.h
+++ b/source/spirv_endian.h
@@ -31,7 +31,7 @@
 spv_result_t spvBinaryEndianness(const spv_const_binary binary,
                                  spv_endianness_t* endian);
 
-// Returns true if the given endianness matches the host's native endiannes.
+// Returns true if the given endianness matches the host's native endianness.
 bool spvIsHostEndian(spv_endianness_t endian);
 
 #endif  // SOURCE_SPIRV_ENDIAN_H_
diff --git a/source/spirv_target_env.cpp b/source/spirv_target_env.cpp
index 187ab61..9a03817 100644
--- a/source/spirv_target_env.cpp
+++ b/source/spirv_target_env.cpp
@@ -72,6 +72,10 @@
       return "SPIR-V 1.5";
     case SPV_ENV_VULKAN_1_2:
       return "SPIR-V 1.5 (under Vulkan 1.2 semantics)";
+    case SPV_ENV_UNIVERSAL_1_6:
+      return "SPIR-V 1.6";
+    case SPV_ENV_VULKAN_1_3:
+      return "SPIR-V 1.6 (under Vulkan 1.3 semantics)";
     case SPV_ENV_MAX:
       assert(false && "Invalid target environment value.");
       break;
@@ -113,6 +117,9 @@
     case SPV_ENV_UNIVERSAL_1_5:
     case SPV_ENV_VULKAN_1_2:
       return SPV_SPIRV_VERSION_WORD(1, 5);
+    case SPV_ENV_UNIVERSAL_1_6:
+    case SPV_ENV_VULKAN_1_3:
+      return SPV_SPIRV_VERSION_WORD(1, 6);
     case SPV_ENV_MAX:
       assert(false && "Invalid target environment value.");
       break;
@@ -125,12 +132,14 @@
     {"vulkan1.0", SPV_ENV_VULKAN_1_0},
     {"vulkan1.1", SPV_ENV_VULKAN_1_1},
     {"vulkan1.2", SPV_ENV_VULKAN_1_2},
+    {"vulkan1.3", SPV_ENV_VULKAN_1_3},
     {"spv1.0", SPV_ENV_UNIVERSAL_1_0},
     {"spv1.1", SPV_ENV_UNIVERSAL_1_1},
     {"spv1.2", SPV_ENV_UNIVERSAL_1_2},
     {"spv1.3", SPV_ENV_UNIVERSAL_1_3},
     {"spv1.4", SPV_ENV_UNIVERSAL_1_4},
     {"spv1.5", SPV_ENV_UNIVERSAL_1_5},
+    {"spv1.6", SPV_ENV_UNIVERSAL_1_6},
     {"opencl1.2embedded", SPV_ENV_OPENCL_EMBEDDED_1_2},
     {"opencl1.2", SPV_ENV_OPENCL_1_2},
     {"opencl2.0embedded", SPV_ENV_OPENCL_EMBEDDED_2_0},
@@ -177,7 +186,8 @@
     {SPV_ENV_VULKAN_1_0, VULKAN_VER(1, 0), SPIRV_VER(1, 0)},
     {SPV_ENV_VULKAN_1_1, VULKAN_VER(1, 1), SPIRV_VER(1, 3)},
     {SPV_ENV_VULKAN_1_1_SPIRV_1_4, VULKAN_VER(1, 1), SPIRV_VER(1, 4)},
-    {SPV_ENV_VULKAN_1_2, VULKAN_VER(1, 2), SPIRV_VER(1, 5)}};
+    {SPV_ENV_VULKAN_1_2, VULKAN_VER(1, 2), SPIRV_VER(1, 5)},
+    {SPV_ENV_VULKAN_1_3, VULKAN_VER(1, 3), SPIRV_VER(1, 6)}};
 
 bool spvParseVulkanEnv(uint32_t vulkan_ver, uint32_t spirv_ver,
                        spv_target_env* env) {
@@ -211,11 +221,13 @@
     case SPV_ENV_UNIVERSAL_1_3:
     case SPV_ENV_UNIVERSAL_1_4:
     case SPV_ENV_UNIVERSAL_1_5:
+    case SPV_ENV_UNIVERSAL_1_6:
       return false;
     case SPV_ENV_VULKAN_1_0:
     case SPV_ENV_VULKAN_1_1:
     case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
     case SPV_ENV_VULKAN_1_2:
+    case SPV_ENV_VULKAN_1_3:
       return true;
     case SPV_ENV_WEBGPU_0:
       assert(false && "Deprecated target environment value.");
@@ -244,6 +256,8 @@
     case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
     case SPV_ENV_UNIVERSAL_1_5:
     case SPV_ENV_VULKAN_1_2:
+    case SPV_ENV_UNIVERSAL_1_6:
+    case SPV_ENV_VULKAN_1_3:
       return false;
     case SPV_ENV_OPENCL_1_2:
     case SPV_ENV_OPENCL_EMBEDDED_1_2:
@@ -284,6 +298,8 @@
     case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
     case SPV_ENV_UNIVERSAL_1_5:
     case SPV_ENV_VULKAN_1_2:
+    case SPV_ENV_UNIVERSAL_1_6:
+    case SPV_ENV_VULKAN_1_3:
       return false;
     case SPV_ENV_OPENGL_4_0:
     case SPV_ENV_OPENGL_4_1:
@@ -321,6 +337,8 @@
     case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
     case SPV_ENV_UNIVERSAL_1_5:
     case SPV_ENV_VULKAN_1_2:
+    case SPV_ENV_UNIVERSAL_1_6:
+    case SPV_ENV_VULKAN_1_3:
     case SPV_ENV_OPENGL_4_0:
     case SPV_ENV_OPENGL_4_1:
     case SPV_ENV_OPENGL_4_2:
@@ -355,16 +373,18 @@
     }
     case SPV_ENV_VULKAN_1_0:
     case SPV_ENV_VULKAN_1_1:
-    case SPV_ENV_VULKAN_1_1_SPIRV_1_4: {
-      case SPV_ENV_VULKAN_1_2:
-        return "Vulkan";
+    case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
+    case SPV_ENV_VULKAN_1_2:
+    case SPV_ENV_VULKAN_1_3: {
+      return "Vulkan";
     }
     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:
-    case SPV_ENV_UNIVERSAL_1_5: {
+    case SPV_ENV_UNIVERSAL_1_5:
+    case SPV_ENV_UNIVERSAL_1_6: {
       return "Universal";
     }
     case SPV_ENV_WEBGPU_0:
diff --git a/source/spirv_target_env.h b/source/spirv_target_env.h
index cc06dec..f3b0c2f 100644
--- a/source/spirv_target_env.h
+++ b/source/spirv_target_env.h
@@ -40,7 +40,7 @@
 
 // Returns a formatted list of all SPIR-V target environment names that
 // can be parsed by spvParseTargetEnv.
-// |pad| is the number of space characters that the begining of each line
+// |pad| is the number of space characters that the beginning of each line
 //       except the first one will be padded with.
 // |wrap| is the max length of lines the user desires. Word-wrapping will
 //        occur to satisfy this limit.
diff --git a/source/spirv_validator_options.cpp b/source/spirv_validator_options.cpp
index e5b1eec..b72a644 100644
--- a/source/spirv_validator_options.cpp
+++ b/source/spirv_validator_options.cpp
@@ -125,3 +125,8 @@
                                             bool val) {
   options->allow_localsizeid = val;
 }
+
+void spvValidatorOptionsSetFriendlyNames(spv_validator_options options,
+                                         bool val) {
+  options->use_friendly_names = val;
+}
diff --git a/source/spirv_validator_options.h b/source/spirv_validator_options.h
index a357c03..0145048 100644
--- a/source/spirv_validator_options.h
+++ b/source/spirv_validator_options.h
@@ -48,7 +48,8 @@
         workgroup_scalar_block_layout(false),
         skip_block_layout(false),
         allow_localsizeid(false),
-        before_hlsl_legalization(false) {}
+        before_hlsl_legalization(false),
+        use_friendly_names(true) {}
 
   validator_universal_limits_t universal_limits_;
   bool relax_struct_store;
@@ -60,6 +61,7 @@
   bool skip_block_layout;
   bool allow_localsizeid;
   bool before_hlsl_legalization;
+  bool use_friendly_names;
 };
 
 #endif  // SOURCE_SPIRV_VALIDATOR_OPTIONS_H_
diff --git a/source/table.cpp b/source/table.cpp
index d4a2d7e..822cefe 100644
--- a/source/table.cpp
+++ b/source/table.cpp
@@ -41,6 +41,8 @@
     case SPV_ENV_UNIVERSAL_1_4:
     case SPV_ENV_UNIVERSAL_1_5:
     case SPV_ENV_VULKAN_1_2:
+    case SPV_ENV_UNIVERSAL_1_6:
+    case SPV_ENV_VULKAN_1_3:
       break;
     default:
       return nullptr;
diff --git a/source/text.cpp b/source/text.cpp
index 88a8e8f..90f69c5 100644
--- a/source/text.cpp
+++ b/source/text.cpp
@@ -403,9 +403,10 @@
     case SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS:
     case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_INFO_FLAGS: {
       uint32_t value;
-      if (grammar.parseMaskOperand(type, textValue, &value)) {
-        return context->diagnostic() << "Invalid " << spvOperandTypeStr(type)
-                                     << " operand '" << textValue << "'.";
+      if (auto error = grammar.parseMaskOperand(type, textValue, &value)) {
+        return context->diagnostic(error)
+               << "Invalid " << spvOperandTypeStr(type) << " operand '"
+               << textValue << "'.";
       }
       if (auto error = context->binaryEncodeU32(value, pInst)) return error;
       // Prepare to parse the operands for this logical operand.
@@ -622,7 +623,8 @@
           break;
         } else {
           return context->diagnostic()
-                 << "Expected operand, found end of stream.";
+                 << "Expected operand for " << opcodeName
+                 << " instruction, but found the end of the stream.";
         }
       }
       assert(error == SPV_SUCCESS && "Somebody added another way to fail");
@@ -632,7 +634,8 @@
           break;
         } else {
           return context->diagnostic()
-                 << "Expected operand, found next instruction instead.";
+                 << "Expected operand for " << opcodeName
+                 << " instruction, but found the next instruction instead.";
         }
       }
 
@@ -666,7 +669,7 @@
 
   if (pInst->words.size() > SPV_LIMIT_INSTRUCTION_WORD_COUNT_MAX) {
     return context->diagnostic()
-           << "Instruction too long: " << pInst->words.size()
+           << opcodeName << " Instruction too long: " << pInst->words.size()
            << " words, but the limit is "
            << SPV_LIMIT_INSTRUCTION_WORD_COUNT_MAX;
   }
@@ -715,6 +718,12 @@
   while (context.hasText()) {
     spv_instruction_t inst;
 
+    // Operand parsing sometimes involves knowing the opcode of the instruction
+    // being parsed. A malformed input might feature such an operand *before*
+    // the opcode is known. To guard against accessing an uninitialized opcode,
+    // the instruction's opcode is initialized to a default value.
+    inst.opcode = SpvOpMax;
+
     if (spvTextEncodeOpcode(grammar, &context, &inst)) {
       return SPV_ERROR_INVALID_TEXT;
     }
@@ -763,8 +772,8 @@
     instructions.push_back({});
     spv_instruction_t& inst = instructions.back();
 
-    if (spvTextEncodeOpcode(grammar, &context, &inst)) {
-      return SPV_ERROR_INVALID_TEXT;
+    if (auto error = spvTextEncodeOpcode(grammar, &context, &inst)) {
+      return error;
     }
 
     if (context.advance()) break;
diff --git a/source/text_handler.cpp b/source/text_handler.cpp
index 46b9845..15c1741 100644
--- a/source/text_handler.cpp
+++ b/source/text_handler.cpp
@@ -29,6 +29,7 @@
 #include "source/util/bitutils.h"
 #include "source/util/hex_float.h"
 #include "source/util/parse_number.h"
+#include "source/util/string_utils.h"
 
 namespace spvtools {
 namespace {
@@ -61,28 +62,29 @@
 // parameters, its the users responsibility to ensure these are non null.
 spv_result_t advance(spv_text text, spv_position position) {
   // NOTE: Consume white space, otherwise don't advance.
-  if (position->index >= text->length) return SPV_END_OF_STREAM;
-  switch (text->str[position->index]) {
-    case '\0':
-      return SPV_END_OF_STREAM;
-    case ';':
-      if (spv_result_t error = advanceLine(text, position)) return error;
-      return advance(text, position);
-    case ' ':
-    case '\t':
-    case '\r':
-      position->column++;
-      position->index++;
-      return advance(text, position);
-    case '\n':
-      position->column = 0;
-      position->line++;
-      position->index++;
-      return advance(text, position);
-    default:
-      break;
+  while (true) {
+    if (position->index >= text->length) return SPV_END_OF_STREAM;
+    switch (text->str[position->index]) {
+      case '\0':
+        return SPV_END_OF_STREAM;
+      case ';':
+        if (spv_result_t error = advanceLine(text, position)) return error;
+        continue;
+      case ' ':
+      case '\t':
+      case '\r':
+        position->column++;
+        position->index++;
+        continue;
+      case '\n':
+        position->column = 0;
+        position->line++;
+        position->index++;
+        continue;
+      default:
+        return SPV_SUCCESS;
+    }
   }
-  return SPV_SUCCESS;
 }
 
 // Fetches the next word from the given text stream starting from the given
@@ -307,14 +309,8 @@
                         << SPV_LIMIT_INSTRUCTION_WORD_COUNT_MAX << " words.";
   }
 
-  pInst->words.resize(newWordCount);
-
-  // Make sure all the bytes in the last word are 0, in case we only
-  // write a partial word at the end.
-  pInst->words.back() = 0;
-
-  char* dest = (char*)&pInst->words[oldWordCount];
-  strncpy(dest, value, length + 1);
+  pInst->words.reserve(newWordCount);
+  spvtools::utils::AppendToVector(value, &pInst->words);
 
   return SPV_SUCCESS;
 }
diff --git a/source/util/bit_vector.h b/source/util/bit_vector.h
index 3e189cb..826d62f 100644
--- a/source/util/bit_vector.h
+++ b/source/util/bit_vector.h
@@ -32,7 +32,7 @@
   enum { kInitialNumBits = 1024 };
 
  public:
-  // Creates a bit vector contianing 0s.
+  // Creates a bit vector containing 0s.
   BitVector(uint32_t reserved_size = kInitialNumBits)
       : bits_((reserved_size - 1) / kBitContainerSize + 1, 0) {}
 
diff --git a/source/util/hash_combine.h b/source/util/hash_combine.h
new file mode 100644
index 0000000..1a2dbc3
--- /dev/null
+++ b/source/util/hash_combine.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2022 The Khronos Group 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_UTIL_HASH_COMBINE_H_
+#define SOURCE_UTIL_HASH_COMBINE_H_
+
+#include <cstddef>
+#include <functional>
+#include <vector>
+
+namespace spvtools {
+namespace utils {
+
+// Helpers for incrementally computing hashes.
+// For reference, see
+// http://open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3876.pdf
+
+template <typename T>
+inline size_t hash_combine(std::size_t seed, const T& val) {
+  return seed ^ (std::hash<T>()(val) + 0x9e3779b9 + (seed << 6) + (seed >> 2));
+}
+
+template <typename T>
+inline size_t hash_combine(std::size_t hash, const std::vector<T>& vals) {
+  for (const T& val : vals) {
+    hash = hash_combine(hash, val);
+  }
+  return hash;
+}
+
+inline size_t hash_combine(std::size_t hash) { return hash; }
+
+template <typename T, typename... Types>
+inline size_t hash_combine(std::size_t hash, const T& val,
+                           const Types&... args) {
+  return hash_combine(hash_combine(hash, val), args...);
+}
+
+}  // namespace utils
+}  // namespace spvtools
+
+#endif  // SOURCE_UTIL_HASH_COMBINE_H_
diff --git a/source/util/hex_float.h b/source/util/hex_float.h
index be28eae..06e3c57 100644
--- a/source/util/hex_float.h
+++ b/source/util/hex_float.h
@@ -199,7 +199,7 @@
 // Reads a FloatProxy value as a normal float from a stream.
 template <typename T>
 std::istream& operator>>(std::istream& is, FloatProxy<T>& value) {
-  T float_val;
+  T float_val = static_cast<T>(0.0);
   is >> float_val;
   value = FloatProxy<T>(float_val);
   return is;
@@ -209,9 +209,10 @@
 // be the default for any non-specialized type.
 template <typename T>
 struct HexFloatTraits {
-  // Integer type that can store this hex-float.
+  // Integer type that can store the bit representation of this hex-float.
   using uint_type = void;
-  // Signed integer type that can store this hex-float.
+  // Signed integer type that can store the bit representation of this
+  // hex-float.
   using int_type = void;
   // The numerical type that this HexFloat represents.
   using underlying_type = void;
@@ -958,9 +959,15 @@
   // This "looks" like a hex-float so treat it as one.
   bool seen_p = false;
   bool seen_dot = false;
+
+  // The mantissa bits, without the most significant 1 bit, and with the
+  // the most recently read bits in the least significant positions.
+  uint_type fraction = 0;
+  // The number of mantissa bits that have been read, including the leading 1
+  // bit that is not written into 'fraction'.
   uint_type fraction_index = 0;
 
-  uint_type fraction = 0;
+  // TODO(dneto): handle overflow and underflow
   int_type exponent = HF::exponent_bias;
 
   // Strip off leading zeros so we don't have to special-case them later.
@@ -968,11 +975,13 @@
     is.get();
   }
 
-  bool is_denorm =
-      true;  // Assume denorm "representation" until we hear otherwise.
-             // NB: This does not mean the value is actually denorm,
-             // it just means that it was written 0.
+  // Does the mantissa, as written, have non-zero digits to the left of
+  // the decimal point.  Assume no until proven otherwise.
+  bool has_integer_part = false;
   bool bits_written = false;  // Stays false until we write a bit.
+
+  // Scan the mantissa hex digits until we see a '.' or the 'p' that
+  // starts the exponent.
   while (!seen_p && !seen_dot) {
     // Handle characters that are left of the fractional part.
     if (next_char == '.') {
@@ -980,9 +989,8 @@
     } else if (next_char == 'p') {
       seen_p = true;
     } else if (::isxdigit(next_char)) {
-      // We know this is not denormalized since we have stripped all leading
-      // zeroes and we are not a ".".
-      is_denorm = false;
+      // We have stripped all leading zeroes and we have not yet seen a ".".
+      has_integer_part = true;
       int number = get_nibble_from_character(next_char);
       for (int i = 0; i < 4; ++i, number <<= 1) {
         uint_type write_bit = (number & 0x8) ? 0x1 : 0x0;
@@ -993,8 +1001,12 @@
               fraction |
               static_cast<uint_type>(
                   write_bit << (HF::top_bit_left_shift - fraction_index++)));
+          // TODO(dneto): Avoid overflow. Testing would require
+          // parameterization.
           exponent = static_cast<int_type>(exponent + 1);
         }
+        // Since this updated after setting fraction bits, this effectively
+        // drops the leading 1 bit.
         bits_written |= write_bit != 0;
       }
     } else {
@@ -1018,10 +1030,12 @@
       for (int i = 0; i < 4; ++i, number <<= 1) {
         uint_type write_bit = (number & 0x8) ? 0x01 : 0x00;
         bits_written |= write_bit != 0;
-        if (is_denorm && !bits_written) {
+        if ((!has_integer_part) && !bits_written) {
           // Handle modifying the exponent here this way we can handle
           // an arbitrary number of hex values without overflowing our
           // integer.
+          // TODO(dneto): Handle underflow. Testing would require extra
+          // parameterization.
           exponent = static_cast<int_type>(exponent - 1);
         } else {
           fraction = static_cast<uint_type>(
@@ -1043,25 +1057,40 @@
   // Finished reading the part preceding 'p'.
   // In hex floats syntax, the binary exponent is required.
 
-  bool seen_sign = false;
+  bool seen_exponent_sign = false;
   int8_t exponent_sign = 1;
   bool seen_written_exponent_digits = false;
+  // The magnitude of the exponent, as written, or the sentinel value to signal
+  // overflow.
   int_type written_exponent = 0;
+  // A sentinel value signalling overflow of the magnitude of the written
+  // exponent.  We'll assume that -written_exponent_overflow is valid for the
+  // type. Later we may add 1 or subtract 1 from the adjusted exponent, so leave
+  // room for an extra 1.
+  const int_type written_exponent_overflow =
+      std::numeric_limits<int_type>::max() - 1;
   while (true) {
     if (!seen_written_exponent_digits &&
         (next_char == '-' || next_char == '+')) {
-      if (seen_sign) {
+      if (seen_exponent_sign) {
         is.setstate(std::ios::failbit);
         return is;
       }
-      seen_sign = true;
+      seen_exponent_sign = true;
       exponent_sign = (next_char == '-') ? -1 : 1;
     } else if (::isdigit(next_char)) {
       seen_written_exponent_digits = true;
       // Hex-floats express their exponent as decimal.
-      written_exponent = static_cast<int_type>(written_exponent * 10);
-      written_exponent =
-          static_cast<int_type>(written_exponent + (next_char - '0'));
+      int_type digit =
+          static_cast<int_type>(static_cast<int_type>(next_char) - '0');
+      if (written_exponent >= (written_exponent_overflow - digit) / 10) {
+        // The exponent is very big. Saturate rather than overflow the exponent.
+        // signed integer, which would be undefined behaviour.
+        written_exponent = written_exponent_overflow;
+      } else {
+        written_exponent = static_cast<int_type>(
+            static_cast<int_type>(written_exponent * 10) + digit);
+      }
     } else {
       break;
     }
@@ -1075,10 +1104,29 @@
   }
 
   written_exponent = static_cast<int_type>(written_exponent * exponent_sign);
-  exponent = static_cast<int_type>(exponent + written_exponent);
+  // Now fold in the exponent bias into the written exponent, updating exponent.
+  // But avoid undefined behaviour that would result from overflowing int_type.
+  if (written_exponent >= 0 && exponent >= 0) {
+    // Saturate up to written_exponent_overflow.
+    if (written_exponent_overflow - exponent > written_exponent) {
+      exponent = static_cast<int_type>(written_exponent + exponent);
+    } else {
+      exponent = written_exponent_overflow;
+    }
+  } else if (written_exponent < 0 && exponent < 0) {
+    // Saturate down to -written_exponent_overflow.
+    if (written_exponent_overflow + exponent > -written_exponent) {
+      exponent = static_cast<int_type>(written_exponent + exponent);
+    } else {
+      exponent = static_cast<int_type>(-written_exponent_overflow);
+    }
+  } else {
+    // They're of opposing sign, so it's safe to add.
+    exponent = static_cast<int_type>(written_exponent + exponent);
+  }
 
-  bool is_zero = is_denorm && (fraction == 0);
-  if (is_denorm && !is_zero) {
+  bool is_zero = (!has_integer_part) && (fraction == 0);
+  if ((!has_integer_part) && !is_zero) {
     fraction = static_cast<uint_type>(fraction << 1);
     exponent = static_cast<int_type>(exponent - 1);
   } else if (is_zero) {
@@ -1095,7 +1143,7 @@
   const int_type max_exponent =
       SetBits<uint_type, 0, HF::num_exponent_bits>::get;
 
-  // Handle actual denorm numbers
+  // Handle denorm numbers
   while (exponent < 0 && !is_zero) {
     fraction = static_cast<uint_type>(fraction >> 1);
     exponent = static_cast<int_type>(exponent + 1);
diff --git a/source/util/ilist.h b/source/util/ilist.h
index 9837b09..42d5e62 100644
--- a/source/util/ilist.h
+++ b/source/util/ilist.h
@@ -59,7 +59,7 @@
   // Moves the contents of the given list to the list being constructed.
   IntrusiveList(IntrusiveList&&);
 
-  // Destorys the list.  Note that the elements of the list will not be deleted,
+  // Destroys the list.  Note that the elements of the list will not be deleted,
   // but they will be removed from the list.
   virtual ~IntrusiveList();
 
@@ -348,6 +348,7 @@
     p = p->next_node_;
   } while (p != start);
   assert(sentinel_count == 1 && "List should have exactly 1 sentinel node.");
+  (void)sentinel_count;
 
   p = start;
   do {
diff --git a/source/util/parse_number.h b/source/util/parse_number.h
index 729aac5..d0f2a09 100644
--- a/source/util/parse_number.h
+++ b/source/util/parse_number.h
@@ -220,7 +220,7 @@
     std::function<void(uint32_t)> emit, std::string* error_msg);
 
 // Parses a floating point value of a given |type| from the given |text| and
-// encodes the number by the given |emit| funciton. On success, returns
+// encodes the number by the given |emit| function. On success, returns
 // EncodeNumberStatus::kSuccess and the parsed number will be consumed by the
 // given |emit| function word by word (least significant word first). On
 // failure, this function returns the error code of the encoding status and
diff --git a/source/util/small_vector.h b/source/util/small_vector.h
index 8f56268..648a348 100644
--- a/source/util/small_vector.h
+++ b/source/util/small_vector.h
@@ -64,6 +64,11 @@
     }
   }
 
+  template <class InputIt>
+  SmallVector(InputIt first, InputIt last) : SmallVector() {
+    insert(end(), first, last);
+  }
+
   SmallVector(std::vector<T>&& vec) : SmallVector() {
     if (vec.size() > small_size) {
       large_data_ = MakeUnique<std::vector<T>>(std::move(vec));
@@ -328,6 +333,15 @@
     ++size_;
   }
 
+  void pop_back() {
+    if (large_data_) {
+      large_data_->pop_back();
+    } else {
+      --size_;
+      small_data_[size_].~T();
+    }
+  }
+
   template <class InputIt>
   iterator insert(iterator pos, InputIt first, InputIt last) {
     size_t element_idx = (pos - begin());
@@ -366,7 +380,7 @@
       }
     }
 
-    // Upate the size.
+    // Update the size.
     size_ += num_of_new_elements;
     return pos;
   }
@@ -452,7 +466,7 @@
   T* small_data_;
 
   // The actual data used to store the array elements.  It must never be used
-  // directly, but must only be accesed through |small_data_|.
+  // directly, but must only be accessed through |small_data_|.
   typename std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type
       buffer[small_size];
 
diff --git a/source/util/string_utils.h b/source/util/string_utils.h
index 4282aa9..03e20b3 100644
--- a/source/util/string_utils.h
+++ b/source/util/string_utils.h
@@ -16,6 +16,8 @@
 #define SOURCE_UTIL_STRING_UTILS_H_
 
 #include <assert.h>
+
+#include <cstring>
 #include <sstream>
 #include <string>
 #include <vector>
@@ -44,9 +46,10 @@
 // string will be empty.
 std::pair<std::string, std::string> SplitFlagArgs(const std::string& flag);
 
-// Encodes a string as a sequence of words, using the SPIR-V encoding.
-inline std::vector<uint32_t> MakeVector(std::string input) {
-  std::vector<uint32_t> result;
+// Encodes a string as a sequence of words, using the SPIR-V encoding, appending
+// to an existing vector.
+inline void AppendToVector(const std::string& input,
+                           std::vector<uint32_t>* result) {
   uint32_t word = 0;
   size_t num_bytes = input.size();
   // SPIR-V strings are null-terminated.  The byte_index == num_bytes
@@ -56,24 +59,36 @@
         (byte_index < num_bytes ? uint8_t(input[byte_index]) : uint8_t(0));
     word |= (new_byte << (8 * (byte_index % sizeof(uint32_t))));
     if (3 == (byte_index % sizeof(uint32_t))) {
-      result.push_back(word);
+      result->push_back(word);
       word = 0;
     }
   }
   // Emit a trailing partial word.
   if ((num_bytes + 1) % sizeof(uint32_t)) {
-    result.push_back(word);
+    result->push_back(word);
   }
+}
+
+// Encodes a string as a sequence of words, using the SPIR-V encoding.
+inline std::vector<uint32_t> MakeVector(const std::string& input) {
+  std::vector<uint32_t> result;
+  AppendToVector(input, &result);
   return result;
 }
 
-// Decode a string from a sequence of words, using the SPIR-V encoding.
-template <class VectorType>
-inline std::string MakeString(const VectorType& words) {
+// Decode a string from a sequence of words between first and last, using the
+// SPIR-V encoding. Assert that a terminating 0-byte was found (unless
+// assert_found_terminating_null is passed as false).
+template <class InputIt>
+inline std::string MakeString(InputIt first, InputIt last,
+                              bool assert_found_terminating_null = true) {
   std::string result;
+  constexpr size_t kCharsPerWord = sizeof(*first);
+  static_assert(kCharsPerWord == 4, "expect 4-byte word");
 
-  for (uint32_t word : words) {
-    for (int byte_index = 0; byte_index < 4; byte_index++) {
+  for (InputIt pos = first; pos != last; ++pos) {
+    uint32_t word = *pos;
+    for (size_t byte_index = 0; byte_index < kCharsPerWord; byte_index++) {
       uint32_t extracted_word = (word >> (8 * byte_index)) & 0xFF;
       char c = static_cast<char>(extracted_word);
       if (c == 0) {
@@ -82,9 +97,33 @@
       result += c;
     }
   }
-  assert(false && "Did not find terminating null for the string.");
+  assert(!assert_found_terminating_null &&
+         "Did not find terminating null for the string.");
+  (void)assert_found_terminating_null; /* No unused parameters in release
+                                          builds. */
   return result;
-}  // namespace utils
+}
+
+// Decode a string from a sequence of words in a vector, using the SPIR-V
+// encoding.
+template <class VectorType>
+inline std::string MakeString(const VectorType& words,
+                              bool assert_found_terminating_null = true) {
+  return MakeString(words.cbegin(), words.cend(),
+                    assert_found_terminating_null);
+}
+
+// Decode a string from array words, consuming up to count words, using the
+// SPIR-V encoding.
+inline std::string MakeString(const uint32_t* words, size_t num_words,
+                              bool assert_found_terminating_null = true) {
+  return MakeString(words, words + num_words, assert_found_terminating_null);
+}
+
+// Check if str starts with prefix (only included since C++20)
+inline bool starts_with(const std::string& str, const char* prefix) {
+  return 0 == str.compare(0, std::strlen(prefix), prefix);
+}
 
 }  // namespace utils
 }  // namespace spvtools
diff --git a/source/util/timer.h b/source/util/timer.h
index fc4b747..0808311 100644
--- a/source/util/timer.h
+++ b/source/util/timer.h
@@ -206,16 +206,16 @@
 
   // Variable to save the result of clock_gettime(CLOCK_PROCESS_CPUTIME_ID) when
   // Timer::Stop() is called. It is used as the last status of CPU time. The
-  // resouce usage is measured by subtracting |cpu_before_| from it.
+  // resource usage is measured by subtracting |cpu_before_| from it.
   timespec cpu_after_;
 
   // Variable to save the result of clock_gettime(CLOCK_MONOTONIC) when
   // Timer::Stop() is called. It is used as the last status of WALL time. The
-  // resouce usage is measured by subtracting |wall_before_| from it.
+  // resource usage is measured by subtracting |wall_before_| from it.
   timespec wall_after_;
 
   // Variable to save the result of getrusage() when Timer::Stop() is called. It
-  // is used as the last status of USR time, SYS time, and RSS. Those resouce
+  // is used as the last status of USR time, SYS time, and RSS. Those resource
   // usages are measured by subtracting |usage_before_| from it.
   rusage usage_after_;
 
diff --git a/source/val/basic_block.cpp b/source/val/basic_block.cpp
index b2a8793..da05db3 100644
--- a/source/val/basic_block.cpp
+++ b/source/val/basic_block.cpp
@@ -24,11 +24,13 @@
 BasicBlock::BasicBlock(uint32_t label_id)
     : id_(label_id),
       immediate_dominator_(nullptr),
-      immediate_post_dominator_(nullptr),
+      immediate_structural_dominator_(nullptr),
+      immediate_structural_post_dominator_(nullptr),
       predecessors_(),
       successors_(),
       type_(0),
       reachable_(false),
+      structurally_reachable_(false),
       label_(nullptr),
       terminator_(nullptr) {}
 
@@ -36,21 +38,32 @@
   immediate_dominator_ = dom_block;
 }
 
-void BasicBlock::SetImmediatePostDominator(BasicBlock* pdom_block) {
-  immediate_post_dominator_ = pdom_block;
+void BasicBlock::SetImmediateStructuralDominator(BasicBlock* dom_block) {
+  immediate_structural_dominator_ = dom_block;
+}
+
+void BasicBlock::SetImmediateStructuralPostDominator(BasicBlock* pdom_block) {
+  immediate_structural_post_dominator_ = pdom_block;
 }
 
 const BasicBlock* BasicBlock::immediate_dominator() const {
   return immediate_dominator_;
 }
 
-const BasicBlock* BasicBlock::immediate_post_dominator() const {
-  return immediate_post_dominator_;
+const BasicBlock* BasicBlock::immediate_structural_dominator() const {
+  return immediate_structural_dominator_;
+}
+
+const BasicBlock* BasicBlock::immediate_structural_post_dominator() const {
+  return immediate_structural_post_dominator_;
 }
 
 BasicBlock* BasicBlock::immediate_dominator() { return immediate_dominator_; }
-BasicBlock* BasicBlock::immediate_post_dominator() {
-  return immediate_post_dominator_;
+BasicBlock* BasicBlock::immediate_structural_dominator() {
+  return immediate_structural_dominator_;
+}
+BasicBlock* BasicBlock::immediate_structural_post_dominator() {
+  return immediate_structural_post_dominator_;
 }
 
 void BasicBlock::RegisterSuccessors(
@@ -58,6 +71,10 @@
   for (auto& block : next_blocks) {
     block->predecessors_.push_back(this);
     successors_.push_back(block);
+
+    // Register structural successors/predecessors too.
+    block->structural_predecessors_.push_back(this);
+    structural_successors_.push_back(block);
   }
 }
 
@@ -67,10 +84,16 @@
            std::find(other.dom_begin(), other.dom_end(), this));
 }
 
-bool BasicBlock::postdominates(const BasicBlock& other) const {
-  return (this == &other) ||
-         !(other.pdom_end() ==
-           std::find(other.pdom_begin(), other.pdom_end(), this));
+bool BasicBlock::structurally_dominates(const BasicBlock& other) const {
+  return (this == &other) || !(other.structural_dom_end() ==
+                               std::find(other.structural_dom_begin(),
+                                         other.structural_dom_end(), this));
+}
+
+bool BasicBlock::structurally_postdominates(const BasicBlock& other) const {
+  return (this == &other) || !(other.structural_pdom_end() ==
+                               std::find(other.structural_pdom_begin(),
+                                         other.structural_pdom_end(), this));
 }
 
 BasicBlock::DominatorIterator::DominatorIterator() : current_(nullptr) {}
@@ -107,21 +130,43 @@
   return DominatorIterator();
 }
 
-const BasicBlock::DominatorIterator BasicBlock::pdom_begin() const {
-  return DominatorIterator(
-      this, [](const BasicBlock* b) { return b->immediate_post_dominator(); });
+const BasicBlock::DominatorIterator BasicBlock::structural_dom_begin() const {
+  return DominatorIterator(this, [](const BasicBlock* b) {
+    return b->immediate_structural_dominator();
+  });
 }
 
-BasicBlock::DominatorIterator BasicBlock::pdom_begin() {
-  return DominatorIterator(
-      this, [](const BasicBlock* b) { return b->immediate_post_dominator(); });
+BasicBlock::DominatorIterator BasicBlock::structural_dom_begin() {
+  return DominatorIterator(this, [](const BasicBlock* b) {
+    return b->immediate_structural_dominator();
+  });
 }
 
-const BasicBlock::DominatorIterator BasicBlock::pdom_end() const {
+const BasicBlock::DominatorIterator BasicBlock::structural_dom_end() const {
   return DominatorIterator();
 }
 
-BasicBlock::DominatorIterator BasicBlock::pdom_end() {
+BasicBlock::DominatorIterator BasicBlock::structural_dom_end() {
+  return DominatorIterator();
+}
+
+const BasicBlock::DominatorIterator BasicBlock::structural_pdom_begin() const {
+  return DominatorIterator(this, [](const BasicBlock* b) {
+    return b->immediate_structural_post_dominator();
+  });
+}
+
+BasicBlock::DominatorIterator BasicBlock::structural_pdom_begin() {
+  return DominatorIterator(this, [](const BasicBlock* b) {
+    return b->immediate_structural_post_dominator();
+  });
+}
+
+const BasicBlock::DominatorIterator BasicBlock::structural_pdom_end() const {
+  return DominatorIterator();
+}
+
+BasicBlock::DominatorIterator BasicBlock::structural_pdom_end() {
   return DominatorIterator();
 }
 
diff --git a/source/val/basic_block.h b/source/val/basic_block.h
index 5af4b9e..be5657e 100644
--- a/source/val/basic_block.h
+++ b/source/val/basic_block.h
@@ -64,9 +64,32 @@
   /// Returns the successors of the BasicBlock
   std::vector<BasicBlock*>* successors() { return &successors_; }
 
-  /// Returns true if the block is reachable in the CFG
+  /// Returns the structural successors of the BasicBlock
+  std::vector<BasicBlock*>* structural_predecessors() {
+    return &structural_predecessors_;
+  }
+
+  /// Returns the structural predecessors of the BasicBlock
+  const std::vector<BasicBlock*>* structural_predecessors() const {
+    return &structural_predecessors_;
+  }
+
+  /// Returns the structural successors of the BasicBlock
+  std::vector<BasicBlock*>* structural_successors() {
+    return &structural_successors_;
+  }
+
+  /// Returns the structural predecessors of the BasicBlock
+  const std::vector<BasicBlock*>* structural_successors() const {
+    return &structural_successors_;
+  }
+
+  /// Returns true if the block is reachable in the CFG.
   bool reachable() const { return reachable_; }
 
+  /// Returns true if the block is structurally reachable in the CFG.
+  bool structurally_reachable() const { return structurally_reachable_; }
+
   /// Returns true if BasicBlock is of the given type
   bool is_type(BlockType type) const {
     if (type == kBlockTypeUndefined) return type_.none();
@@ -76,6 +99,11 @@
   /// Sets the reachability of the basic block in the CFG
   void set_reachable(bool reachability) { reachable_ = reachability; }
 
+  /// Sets the structural reachability of the basic block in the CFG
+  void set_structurally_reachable(bool reachability) {
+    structurally_reachable_ = reachability;
+  }
+
   /// Sets the type of the BasicBlock
   void set_type(BlockType type) {
     if (type == kBlockTypeUndefined)
@@ -84,27 +112,38 @@
       type_.set(type);
   }
 
-  /// Sets the immedate dominator of this basic block
+  /// Sets the immediate dominator of this basic block
   ///
   /// @param[in] dom_block The dominator block
   void SetImmediateDominator(BasicBlock* dom_block);
 
-  /// Sets the immedate post dominator of this basic block
+  /// Sets the immediate dominator of this basic block
+  ///
+  /// @param[in] dom_block The dominator block
+  void SetImmediateStructuralDominator(BasicBlock* dom_block);
+
+  /// Sets the immediate post dominator of this basic block
   ///
   /// @param[in] pdom_block The post dominator block
-  void SetImmediatePostDominator(BasicBlock* pdom_block);
+  void SetImmediateStructuralPostDominator(BasicBlock* pdom_block);
 
-  /// Returns the immedate dominator of this basic block
+  /// Returns the immediate dominator of this basic block
   BasicBlock* immediate_dominator();
 
-  /// Returns the immedate dominator of this basic block
+  /// Returns the immediate dominator of this basic block
   const BasicBlock* immediate_dominator() const;
 
-  /// Returns the immedate post dominator of this basic block
-  BasicBlock* immediate_post_dominator();
+  /// Returns the immediate dominator of this basic block
+  BasicBlock* immediate_structural_dominator();
 
-  /// Returns the immedate post dominator of this basic block
-  const BasicBlock* immediate_post_dominator() const;
+  /// Returns the immediate dominator of this basic block
+  const BasicBlock* immediate_structural_dominator() const;
+
+  /// Returns the immediate post dominator of this basic block
+  BasicBlock* immediate_structural_post_dominator();
+
+  /// Returns the immediate post dominator of this basic block
+  const BasicBlock* immediate_structural_post_dominator() const;
 
   /// Returns the label instruction for the block, or nullptr if not set.
   const Instruction* label() const { return label_; }
@@ -132,9 +171,18 @@
   /// Assumes dominators have been computed.
   bool dominates(const BasicBlock& other) const;
 
-  /// Returns true if this block postdominates the other block.
-  /// Assumes dominators have been computed.
-  bool postdominates(const BasicBlock& other) const;
+  /// Returns true if this block structurally dominates the other block.
+  /// Assumes structural dominators have been computed.
+  bool structurally_dominates(const BasicBlock& other) const;
+
+  /// Returns true if this block structurally postdominates the other block.
+  /// Assumes structural dominators have been computed.
+  bool structurally_postdominates(const BasicBlock& other) const;
+
+  void RegisterStructuralSuccessor(BasicBlock* block) {
+    block->structural_predecessors_.push_back(this);
+    structural_successors_.push_back(block);
+  }
 
   /// @brief A BasicBlock dominator iterator class
   ///
@@ -191,18 +239,32 @@
   /// block
   DominatorIterator dom_end();
 
+  /// Returns a dominator iterator which points to the current block
+  const DominatorIterator structural_dom_begin() const;
+
+  /// Returns a dominator iterator which points to the current block
+  DominatorIterator structural_dom_begin();
+
+  /// Returns a dominator iterator which points to one element past the first
+  /// block
+  const DominatorIterator structural_dom_end() const;
+
+  /// Returns a dominator iterator which points to one element past the first
+  /// block
+  DominatorIterator structural_dom_end();
+
   /// Returns a post dominator iterator which points to the current block
-  const DominatorIterator pdom_begin() const;
+  const DominatorIterator structural_pdom_begin() const;
   /// Returns a post dominator iterator which points to the current block
-  DominatorIterator pdom_begin();
+  DominatorIterator structural_pdom_begin();
 
   /// Returns a post dominator iterator which points to one element past the
   /// last block
-  const DominatorIterator pdom_end() const;
+  const DominatorIterator structural_pdom_end() const;
 
   /// Returns a post dominator iterator which points to one element past the
   /// last block
-  DominatorIterator pdom_end();
+  DominatorIterator structural_pdom_end();
 
  private:
   /// Id of the BasicBlock
@@ -211,8 +273,11 @@
   /// Pointer to the immediate dominator of the BasicBlock
   BasicBlock* immediate_dominator_;
 
-  /// Pointer to the immediate dominator of the BasicBlock
-  BasicBlock* immediate_post_dominator_;
+  /// Pointer to the immediate structural dominator of the BasicBlock
+  BasicBlock* immediate_structural_dominator_;
+
+  /// Pointer to the immediate structural post dominator of the BasicBlock
+  BasicBlock* immediate_structural_post_dominator_;
 
   /// The set of predecessors of the BasicBlock
   std::vector<BasicBlock*> predecessors_;
@@ -226,11 +291,17 @@
   /// True if the block is reachable in the CFG
   bool reachable_;
 
+  /// True if the block is structurally reachable in the CFG
+  bool structurally_reachable_;
+
   /// label of this block, if any.
   const Instruction* label_;
 
   /// Terminator of this block.
   const Instruction* terminator_;
+
+  std::vector<BasicBlock*> structural_predecessors_;
+  std::vector<BasicBlock*> structural_successors_;
 };
 
 /// @brief Returns true if the iterators point to the same element or if both
diff --git a/source/val/construct.cpp b/source/val/construct.cpp
index 251e2bb..52e61d5 100644
--- a/source/val/construct.cpp
+++ b/source/val/construct.cpp
@@ -70,60 +70,45 @@
 
 void Construct::set_exit(BasicBlock* block) { exit_block_ = block; }
 
-Construct::ConstructBlockSet Construct::blocks(Function* function) const {
-  auto header = entry_block();
-  auto merge = exit_block();
-  assert(header);
-  int header_depth = function->GetBlockDepth(const_cast<BasicBlock*>(header));
-  ConstructBlockSet construct_blocks;
-  std::unordered_set<BasicBlock*> corresponding_headers;
-  for (auto& other : corresponding_constructs()) {
-    // The corresponding header can be the same block as this construct's
-    // header for loops with no loop construct. In those cases, don't add the
-    // loop header as it prevents finding any blocks in the construct.
-    if (type() != ConstructType::kContinue || other->entry_block() != header) {
-      corresponding_headers.insert(other->entry_block());
-    }
+Construct::ConstructBlockSet Construct::blocks(Function* /*function*/) const {
+  const auto header = entry_block();
+  const auto exit = exit_block();
+  const bool is_continue = type() == ConstructType::kContinue;
+  const bool is_loop = type() == ConstructType::kLoop;
+  const BasicBlock* continue_header = nullptr;
+  if (is_loop) {
+    // The only corresponding construct for a loop is the continue.
+    continue_header = (*corresponding_constructs().begin())->entry_block();
   }
   std::vector<BasicBlock*> stack;
   stack.push_back(const_cast<BasicBlock*>(header));
+  ConstructBlockSet construct_blocks;
   while (!stack.empty()) {
-    BasicBlock* block = stack.back();
+    auto* block = stack.back();
     stack.pop_back();
 
-    if (merge == block && ExitBlockIsMergeBlock()) {
-      // Merge block is not part of the construct.
-      continue;
-    }
+    if (header->structurally_dominates(*block)) {
+      bool include = false;
+      if (is_continue && exit->structurally_postdominates(*block)) {
+        // Continue construct include blocks dominated by the continue target
+        // and post-dominated by the back-edge block.
+        include = true;
+      } else if (!exit->structurally_dominates(*block)) {
+        // Selection and loop constructs include blocks dominated by the header
+        // and not dominated by the merge.
+        include = true;
+        if (is_loop && continue_header->structurally_dominates(*block)) {
+          // Loop constructs have an additional constraint that they do not
+          // include blocks dominated by the continue construct. Since all
+          // blocks in the continue construct are dominated by the continue
+          // target, we just test for dominance by continue target.
+          include = false;
+        }
+      }
+      if (include) {
+        if (!construct_blocks.insert(block).second) continue;
 
-    if (corresponding_headers.count(block)) {
-      // Entered a corresponding construct.
-      continue;
-    }
-
-    int block_depth = function->GetBlockDepth(block);
-    if (block_depth < header_depth) {
-      // Broke to outer construct.
-      continue;
-    }
-
-    // In a loop, the continue target is at a depth of the loop construct + 1.
-    // A selection construct nested directly within the loop construct is also
-    // at the same depth. It is valid, however, to branch directly to the
-    // continue target from within the selection construct.
-    if (block != header && block_depth == header_depth &&
-        type() == ConstructType::kSelection &&
-        block->is_type(kBlockTypeContinue)) {
-      // Continued to outer construct.
-      continue;
-    }
-
-    if (!construct_blocks.insert(block).second) continue;
-
-    if (merge != block) {
-      for (auto succ : *block->successors()) {
-        // All blocks in the construct must be dominated by the header.
-        if (header->dominates(*succ)) {
+        for (auto succ : *block->structural_successors()) {
           stack.push_back(succ);
         }
       }
@@ -181,11 +166,12 @@
       for (auto& use : block->label()->uses()) {
         if ((use.first->opcode() == SpvOpLoopMerge ||
              use.first->opcode() == SpvOpSelectionMerge) &&
-            use.second == 1 && use.first->block()->dominates(*block)) {
+            use.second == 1 &&
+            use.first->block()->structurally_dominates(*block)) {
           return use.first->block();
         }
       }
-      return block->immediate_dominator();
+      return block->immediate_structural_dominator();
     };
 
     bool seen_switch = false;
@@ -201,7 +187,7 @@
            terminator->opcode() == SpvOpSwitch)) {
         auto merge_target = merge_inst->GetOperandAs<uint32_t>(0u);
         auto merge_block = merge_inst->function()->GetBlock(merge_target).first;
-        if (merge_block->dominates(*header)) {
+        if (merge_block->structurally_dominates(*header)) {
           block = NextBlock(block);
           continue;
         }
diff --git a/source/val/decoration.h b/source/val/decoration.h
index ed3320f..4f53f20 100644
--- a/source/val/decoration.h
+++ b/source/val/decoration.h
@@ -69,6 +69,15 @@
   std::vector<uint32_t>& params() { return params_; }
   const std::vector<uint32_t>& params() const { return params_; }
 
+  inline bool operator<(const Decoration& rhs) const {
+    // Note: Sort by struct_member_index_ first, then type, so look up can be
+    // efficient using lower_bound() and upper_bound().
+    if (struct_member_index_ < rhs.struct_member_index_) return true;
+    if (rhs.struct_member_index_ < struct_member_index_) return false;
+    if (dec_type_ < rhs.dec_type_) return true;
+    if (rhs.dec_type_ < dec_type_) return false;
+    return params_ < rhs.params_;
+  }
   inline bool operator==(const Decoration& rhs) const {
     return (dec_type_ == rhs.dec_type_ && params_ == rhs.params_ &&
             struct_member_index_ == rhs.struct_member_index_);
diff --git a/source/val/function.cpp b/source/val/function.cpp
index 9ad68e8..fc7ccd0 100644
--- a/source/val/function.cpp
+++ b/source/val/function.cpp
@@ -57,7 +57,7 @@
                                                  uint32_t type_id) {
   assert(current_block_ == nullptr &&
          "RegisterFunctionParameter can only be called when parsing the binary "
-         "ouside of a block");
+         "outside of a block");
   // TODO(umar): Validate function parameter type order and count
   // TODO(umar): Use these variables to validate parameter type
   (void)parameter_id;
@@ -73,6 +73,8 @@
   BasicBlock& continue_target_block = blocks_.at(continue_id);
   assert(current_block_ &&
          "RegisterLoopMerge must be called when called within a block");
+  current_block_->RegisterStructuralSuccessor(&merge_block);
+  current_block_->RegisterStructuralSuccessor(&continue_target_block);
 
   current_block_->set_type(kBlockTypeLoop);
   merge_block.set_type(kBlockTypeMerge);
@@ -101,6 +103,7 @@
   current_block_->set_type(kBlockTypeSelection);
   merge_block.set_type(kBlockTypeMerge);
   merge_block_header_[&merge_block] = current_block_;
+  current_block_->RegisterStructuralSuccessor(&merge_block);
 
   AddConstruct({ConstructType::kSelection, current_block(), &merge_block});
 
@@ -130,7 +133,7 @@
     undefined_blocks_.erase(block_id);
     current_block_ = &inserted_block->second;
     ordered_blocks_.push_back(current_block_);
-  } else if (success) {  // Block doesn't exsist but this is not a definition
+  } else if (success) {  // Block doesn't exist but this is not a definition
     undefined_blocks_.insert(block_id);
   }
 
@@ -251,16 +254,6 @@
   };
 }
 
-Function::GetBlocksFunction
-Function::AugmentedCFGSuccessorsFunctionIncludingHeaderToContinueEdge() const {
-  return [this](const BasicBlock* block) {
-    auto where = loop_header_successors_plus_continue_target_map_.find(block);
-    return where == loop_header_successors_plus_continue_target_map_.end()
-               ? AugmentedCFGSuccessorsFunction()(block)
-               : &(*where).second;
-  };
-}
-
 Function::GetBlocksFunction Function::AugmentedCFGPredecessorsFunction() const {
   return [this](const BasicBlock* block) {
     auto where = augmented_predecessors_map_.find(block);
@@ -269,11 +262,35 @@
   };
 }
 
+Function::GetBlocksFunction Function::AugmentedStructuralCFGSuccessorsFunction()
+    const {
+  return [this](const BasicBlock* block) {
+    auto where = augmented_successors_map_.find(block);
+    return where == augmented_successors_map_.end()
+               ? block->structural_successors()
+               : &(*where).second;
+  };
+}
+
+Function::GetBlocksFunction
+Function::AugmentedStructuralCFGPredecessorsFunction() const {
+  return [this](const BasicBlock* block) {
+    auto where = augmented_predecessors_map_.find(block);
+    return where == augmented_predecessors_map_.end()
+               ? block->structural_predecessors()
+               : &(*where).second;
+  };
+}
+
 void Function::ComputeAugmentedCFG() {
   // Compute the successors of the pseudo-entry block, and
   // the predecessors of the pseudo exit block.
-  auto succ_func = [](const BasicBlock* b) { return b->successors(); };
-  auto pred_func = [](const BasicBlock* b) { return b->predecessors(); };
+  auto succ_func = [](const BasicBlock* b) {
+    return b->structural_successors();
+  };
+  auto pred_func = [](const BasicBlock* b) {
+    return b->structural_predecessors();
+  };
   CFA<BasicBlock>::ComputeAugmentedCFG(
       ordered_blocks_, &pseudo_entry_block_, &pseudo_exit_block_,
       &augmented_successors_map_, &augmented_predecessors_map_, succ_func,
diff --git a/source/val/function.h b/source/val/function.h
index 400bb63..126b1dc 100644
--- a/source/val/function.h
+++ b/source/val/function.h
@@ -73,8 +73,8 @@
 
   /// Registers a variable in the current block
   ///
-  /// @param[in] type_id The type ID of the varaible
-  /// @param[in] id      The ID of the varaible
+  /// @param[in] type_id The type ID of the variable
+  /// @param[in] id      The ID of the variable
   /// @param[in] storage The storage of the variable
   /// @param[in] init_id The initializer ID of the variable
   ///
@@ -184,12 +184,12 @@
       std::function<const std::vector<BasicBlock*>*(const BasicBlock*)>;
   /// Returns the block successors function for the augmented CFG.
   GetBlocksFunction AugmentedCFGSuccessorsFunction() const;
-  /// Like AugmentedCFGSuccessorsFunction, but also includes a forward edge from
-  /// a loop header block to its continue target, if they are different blocks.
-  GetBlocksFunction
-  AugmentedCFGSuccessorsFunctionIncludingHeaderToContinueEdge() const;
   /// Returns the block predecessors function for the augmented CFG.
   GetBlocksFunction AugmentedCFGPredecessorsFunction() const;
+  /// Returns the block structural successors function for the augmented CFG.
+  GetBlocksFunction AugmentedStructuralCFGSuccessorsFunction() const;
+  /// Returns the block structural predecessors function for the augmented CFG.
+  GetBlocksFunction AugmentedStructuralCFGPredecessorsFunction() const;
 
   /// Returns the control flow nesting depth of the given basic block.
   /// This function only works when you have structured control flow.
@@ -197,10 +197,10 @@
   /// been identified and dominators have been computed.
   int GetBlockDepth(BasicBlock* bb);
 
-  /// Prints a GraphViz digraph of the CFG of the current funciton
+  /// Prints a GraphViz digraph of the CFG of the current function
   void PrintDotGraph() const;
 
-  /// Prints a directed graph of the CFG of the current funciton
+  /// Prints a directed graph of the CFG of the current function
   void PrintBlocks() const;
 
   /// Registers execution model limitation such as "Feature X is only available
@@ -285,7 +285,7 @@
   /// The type of the return value
   uint32_t result_type_id_;
 
-  /// The control fo the funciton
+  /// The control fo the function
   SpvFunctionControlMask function_control_;
 
   /// The type of declaration of each function
diff --git a/source/val/instruction.cpp b/source/val/instruction.cpp
index b915589..f16fcd7 100644
--- a/source/val/instruction.cpp
+++ b/source/val/instruction.cpp
@@ -16,6 +16,9 @@
 
 #include <utility>
 
+#include "source/binary.h"
+#include "source/util/string_utils.h"
+
 namespace spvtools {
 namespace val {
 
@@ -41,5 +44,12 @@
   return lhs.id() == rhs;
 }
 
+template <>
+std::string Instruction::GetOperandAs<std::string>(size_t index) const {
+  const spv_parsed_operand_t& o = operands_.at(index);
+  assert(o.offset + o.num_words <= inst_.num_words);
+  return spvtools::utils::MakeString(words_.data() + o.offset, o.num_words);
+}
+
 }  // namespace val
 }  // namespace spvtools
diff --git a/source/val/instruction.h b/source/val/instruction.h
index 617cb06..6d1f9f4 100644
--- a/source/val/instruction.h
+++ b/source/val/instruction.h
@@ -133,6 +133,9 @@
 bool operator==(const Instruction& lhs, const Instruction& rhs);
 bool operator==(const Instruction& lhs, uint32_t rhs);
 
+template <>
+std::string Instruction::GetOperandAs<std::string>(size_t index) const;
+
 }  // namespace val
 }  // namespace spvtools
 
diff --git a/source/val/validate.cpp b/source/val/validate.cpp
index 45b6a46..efb9225 100644
--- a/source/val/validate.cpp
+++ b/source/val/validate.cpp
@@ -202,13 +202,15 @@
                  /* diagnostic = */ nullptr);
 
   // Parse the module and perform inline validation checks. These checks do
-  // not require the the knowledge of the whole module.
+  // not require the knowledge of the whole module.
   if (auto error = spvBinaryParse(&context, vstate, words, num_words,
                                   /*parsed_header =*/nullptr,
                                   ProcessInstruction, pDiagnostic)) {
     return error;
   }
 
+  bool has_mask_task_nv = false;
+  bool has_mask_task_ext = false;
   std::vector<Instruction*> visited_entry_points;
   for (auto& instruction : vstate->ordered_instructions()) {
     {
@@ -219,9 +221,7 @@
       if (inst->opcode() == SpvOpEntryPoint) {
         const auto entry_point = inst->GetOperandAs<uint32_t>(1);
         const auto execution_model = inst->GetOperandAs<SpvExecutionModel>(0);
-        const char* str = reinterpret_cast<const char*>(
-            inst->words().data() + inst->operand(2).offset);
-        const std::string desc_name(str);
+        const std::string desc_name = inst->GetOperandAs<std::string>(2);
 
         ValidationState_t::EntryPointDescription desc;
         desc.name = desc_name;
@@ -237,9 +237,8 @@
           for (const Instruction* check_inst : visited_entry_points) {
             const auto check_execution_model =
                 check_inst->GetOperandAs<SpvExecutionModel>(0);
-            const char* check_str = reinterpret_cast<const char*>(
-                check_inst->words().data() + inst->operand(2).offset);
-            const std::string check_name(check_str);
+            const std::string check_name =
+                check_inst->GetOperandAs<std::string>(2);
 
             if (desc_name == check_name &&
                 execution_model == check_execution_model) {
@@ -250,6 +249,11 @@
           }
         }
         visited_entry_points.push_back(inst);
+
+        has_mask_task_nv |= (execution_model == SpvExecutionModelTaskNV ||
+                             execution_model == SpvExecutionModelMeshNV);
+        has_mask_task_ext |= (execution_model == SpvExecutionModelTaskEXT ||
+                              execution_model == SpvExecutionModelMeshEXT);
       }
       if (inst->opcode() == SpvOpFunctionCall) {
         if (!vstate->in_function_body()) {
@@ -296,6 +300,17 @@
     return vstate->diag(SPV_ERROR_INVALID_LAYOUT, nullptr)
            << "Missing OpFunctionEnd at end of module.";
 
+  if (vstate->HasCapability(SpvCapabilityBindlessTextureNV) &&
+      !vstate->has_samplerimage_variable_address_mode_specified())
+    return vstate->diag(SPV_ERROR_INVALID_LAYOUT, nullptr)
+           << "Missing required OpSamplerImageAddressingModeNV instruction.";
+
+  if (has_mask_task_ext && has_mask_task_nv)
+    return vstate->diag(SPV_ERROR_INVALID_LAYOUT, nullptr)
+           << vstate->VkErrorID(7102)
+           << "Module can't mix MeshEXT/TaskEXT with MeshNV/TaskNV Execution "
+              "Model.";
+
   // Catch undefined forward references before performing further checks.
   if (auto error = ValidateForwardDecls(*vstate)) return error;
 
@@ -348,10 +363,13 @@
     if (auto error = NonUniformPass(*vstate, &instruction)) return error;
 
     if (auto error = LiteralsPass(*vstate, &instruction)) return error;
+    if (auto error = RayQueryPass(*vstate, &instruction)) return error;
+    if (auto error = RayTracingPass(*vstate, &instruction)) return error;
+    if (auto error = MeshShadingPass(*vstate, &instruction)) return error;
   }
 
   // Validate the preconditions involving adjacent instructions. e.g. SpvOpPhi
-  // must only be preceeded by SpvOpLabel, SpvOpPhi, or SpvOpLine.
+  // must only be preceded by SpvOpLabel, SpvOpPhi, or SpvOpLine.
   if (auto error = ValidateAdjacency(*vstate)) return error;
 
   if (auto error = ValidateEntryPoints(*vstate)) return error;
diff --git a/source/val/validate.h b/source/val/validate.h
index 3fc183d..4b953ba 100644
--- a/source/val/validate.h
+++ b/source/val/validate.h
@@ -70,7 +70,7 @@
 ///
 /// This function will iterate over all instructions and check for any required
 /// predecessor and/or successor instructions. e.g. SpvOpPhi must only be
-/// preceeded by SpvOpLabel, SpvOpPhi, or SpvOpLine.
+/// preceded by SpvOpLabel, SpvOpPhi, or SpvOpLine.
 ///
 /// @param[in] _ the validation state of the module
 ///
@@ -197,6 +197,15 @@
 /// Validates correctness of miscellaneous instructions.
 spv_result_t MiscPass(ValidationState_t& _, const Instruction* inst);
 
+/// Validates correctness of ray query instructions.
+spv_result_t RayQueryPass(ValidationState_t& _, const Instruction* inst);
+
+/// Validates correctness of ray tracing instructions.
+spv_result_t RayTracingPass(ValidationState_t& _, const Instruction* inst);
+
+/// Validates correctness of mesh shading instructions.
+spv_result_t MeshShadingPass(ValidationState_t& _, const Instruction* inst);
+
 /// Calculates the reachability of basic blocks.
 void ReachabilityPass(ValidationState_t& _);
 
diff --git a/source/val/validate_annotation.cpp b/source/val/validate_annotation.cpp
index 3a77552..21f999b 100644
--- a/source/val/validate_annotation.cpp
+++ b/source/val/validate_annotation.cpp
@@ -22,138 +22,6 @@
 namespace val {
 namespace {
 
-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 SpvDecorationNonUniform:
-      return "NonUniform";
-    case SpvDecorationRestrictPointer:
-      return "RestrictPointer";
-    case SpvDecorationAliasedPointer:
-      return "AliasedPointer";
-    case SpvDecorationCounterBuffer:
-      return "CounterBuffer";
-    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(SpvDecoration type) {
@@ -230,17 +98,17 @@
 spv_result_t ValidateDecorationTarget(ValidationState_t& _, SpvDecoration dec,
                                       const Instruction* inst,
                                       const Instruction* target) {
-  auto fail = [&_, dec, inst, target](uint32_t vuid = 0) -> DiagnosticStream {
+  auto fail = [&_, dec, inst, target](uint32_t vuid) -> DiagnosticStream {
     DiagnosticStream ds = std::move(
         _.diag(SPV_ERROR_INVALID_ID, inst)
-        << _.VkErrorID(vuid) << LogStringForDecoration(dec)
-        << " decoration on target <id> '" << _.getIdName(target->id()) << "' ");
+        << _.VkErrorID(vuid) << _.SpvDecorationString(dec)
+        << " decoration on target <id> " << _.getIdName(target->id()) << " ");
     return ds;
   };
   switch (dec) {
     case SpvDecorationSpecId:
       if (!spvOpcodeIsScalarSpecConstant(target->opcode())) {
-        return fail() << "must be a scalar specialization constant";
+        return fail(0) << "must be a scalar specialization constant";
       }
       break;
     case SpvDecorationBlock:
@@ -249,14 +117,14 @@
     case SpvDecorationGLSLPacked:
     case SpvDecorationCPacked:
       if (target->opcode() != SpvOpTypeStruct) {
-        return fail() << "must be a structure type";
+        return fail(0) << "must be a structure type";
       }
       break;
     case SpvDecorationArrayStride:
       if (target->opcode() != SpvOpTypeArray &&
           target->opcode() != SpvOpTypeRuntimeArray &&
           target->opcode() != SpvOpTypePointer) {
-        return fail() << "must be an array or pointer type";
+        return fail(0) << "must be an array or pointer type";
       }
       break;
     case SpvDecorationBuiltIn:
@@ -269,10 +137,10 @@
       if (_.HasCapability(SpvCapabilityShader) &&
           inst->GetOperandAs<SpvBuiltIn>(2) == SpvBuiltInWorkgroupSize) {
         if (!spvOpcodeIsConstant(target->opcode())) {
-          return fail() << "must be a constant for WorkgroupSize";
+          return fail(0) << "must be a constant for WorkgroupSize";
         }
       } else if (target->opcode() != SpvOpVariable) {
-        return fail() << "must be a variable";
+        return fail(0) << "must be a variable";
       }
       break;
     case SpvDecorationNoPerspective:
@@ -294,10 +162,10 @@
     case SpvDecorationAliasedPointer:
       if (target->opcode() != SpvOpVariable &&
           target->opcode() != SpvOpFunctionParameter) {
-        return fail() << "must be a memory object declaration";
+        return fail(0) << "must be a memory object declaration";
       }
       if (_.GetIdOpcode(target->type_id()) != SpvOpTypePointer) {
-        return fail() << "must be a pointer type";
+        return fail(0) << "must be a pointer type";
       }
       break;
     case SpvDecorationInvariant:
@@ -308,7 +176,7 @@
     case SpvDecorationDescriptorSet:
     case SpvDecorationInputAttachmentIndex:
       if (target->opcode() != SpvOpVariable) {
-        return fail() << "must be a variable";
+        return fail(0) << "must be a variable";
       }
       break;
     default:
@@ -326,19 +194,22 @@
       case SpvDecorationLocation:
       case SpvDecorationComponent:
         // Location is used for input, output and ray tracing stages.
-        if (sc == SpvStorageClassStorageBuffer ||
-            sc == SpvStorageClassUniform ||
-            sc == SpvStorageClassUniformConstant ||
-            sc == SpvStorageClassWorkgroup || sc == SpvStorageClassPrivate ||
-            sc == SpvStorageClassFunction) {
+        if (sc != SpvStorageClassInput && sc != SpvStorageClassOutput &&
+            sc != SpvStorageClassRayPayloadKHR &&
+            sc != SpvStorageClassIncomingRayPayloadKHR &&
+            sc != SpvStorageClassHitAttributeKHR &&
+            sc != SpvStorageClassCallableDataKHR &&
+            sc != SpvStorageClassIncomingCallableDataKHR &&
+            sc != SpvStorageClassShaderRecordBufferKHR) {
           return _.diag(SPV_ERROR_INVALID_ID, target)
-                 << LogStringForDecoration(dec)
+                 << _.VkErrorID(6672) << _.SpvDecorationString(dec)
                  << " decoration must not be applied to this storage class";
         }
         break;
       case SpvDecorationIndex:
+        // Langauge from SPIR-V definition of Index
         if (sc != SpvStorageClassOutput) {
-          return fail() << "must be in the Output storage class";
+          return fail(0) << "must be in the Output storage class";
         }
         break;
       case SpvDecorationBinding:
@@ -346,13 +217,13 @@
         if (sc != SpvStorageClassStorageBuffer &&
             sc != SpvStorageClassUniform &&
             sc != SpvStorageClassUniformConstant) {
-          return fail() << "must be in the StorageBuffer, Uniform, or "
-                           "UniformConstant storage class";
+          return fail(6491) << "must be in the StorageBuffer, Uniform, or "
+                               "UniformConstant storage class";
         }
         break;
       case SpvDecorationInputAttachmentIndex:
         if (sc != SpvStorageClassUniformConstant) {
-          return fail() << "must be in the UniformConstant storage class";
+          return fail(6678) << "must be in the UniformConstant storage class";
         }
         break;
       case SpvDecorationFlat:
@@ -363,6 +234,11 @@
           return fail(4670) << "storage class must be Input or Output";
         }
         break;
+      case SpvDecorationPerVertexKHR:
+        if (sc != SpvStorageClassInput) {
+          return fail(6777) << "storage class must be Input";
+        }
+        break;
       default:
         break;
     }
@@ -383,7 +259,7 @@
         (decoration == SpvDecorationGLSLPacked)) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
              << _.VkErrorID(4669) << "OpDecorate decoration '"
-             << LogStringForDecoration(decoration)
+             << _.SpvDecorationString(decoration)
              << "' is not valid for the Vulkan execution environment.";
     }
   }
@@ -397,7 +273,7 @@
   if (target->opcode() != SpvOpDecorationGroup) {
     if (IsMemberDecorationOnly(decoration)) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
-             << LogStringForDecoration(decoration)
+             << _.SpvDecorationString(decoration)
              << " can only be applied to structure members";
     }
 
@@ -432,8 +308,8 @@
   const auto struct_type = _.FindDef(struct_type_id);
   if (!struct_type || SpvOpTypeStruct != struct_type->opcode()) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpMemberDecorate Structure type <id> '"
-           << _.getIdName(struct_type_id) << "' is not a struct type.";
+           << "OpMemberDecorate Structure type <id> "
+           << _.getIdName(struct_type_id) << " is not a struct type.";
   }
   const auto member = inst->GetOperandAs<uint32_t>(1);
   const auto member_count =
@@ -450,7 +326,7 @@
   const auto decoration = inst->GetOperandAs<SpvDecoration>(2);
   if (IsNotMemberDecoration(decoration)) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << LogStringForDecoration(decoration)
+           << _.SpvDecorationString(decoration)
            << " cannot be applied to structure members";
   }
 
@@ -482,17 +358,16 @@
   auto decoration_group = _.FindDef(decoration_group_id);
   if (!decoration_group || SpvOpDecorationGroup != decoration_group->opcode()) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpGroupDecorate Decoration group <id> '"
-           << _.getIdName(decoration_group_id)
-           << "' is not a decoration group.";
+           << "OpGroupDecorate Decoration group <id> "
+           << _.getIdName(decoration_group_id) << " is not a decoration group.";
   }
   for (unsigned i = 1; i < inst->operands().size(); ++i) {
     auto target_id = inst->GetOperandAs<uint32_t>(i);
     auto target = _.FindDef(target_id);
     if (!target || target->opcode() == SpvOpDecorationGroup) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
-             << "OpGroupDecorate may not target OpDecorationGroup <id> '"
-             << _.getIdName(target_id) << "'";
+             << "OpGroupDecorate may not target OpDecorationGroup <id> "
+             << _.getIdName(target_id);
     }
   }
   return SPV_SUCCESS;
@@ -504,9 +379,8 @@
   const auto decoration_group = _.FindDef(decoration_group_id);
   if (!decoration_group || SpvOpDecorationGroup != decoration_group->opcode()) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpGroupMemberDecorate Decoration group <id> '"
-           << _.getIdName(decoration_group_id)
-           << "' is not a decoration group.";
+           << "OpGroupMemberDecorate Decoration group <id> "
+           << _.getIdName(decoration_group_id) << " is not a decoration group.";
   }
   // Grammar checks ensures that the number of arguments to this instruction
   // is an odd number: 1 decoration group + (id,literal) pairs.
@@ -516,8 +390,8 @@
     auto struct_instr = _.FindDef(struct_id);
     if (!struct_instr || SpvOpTypeStruct != struct_instr->opcode()) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
-             << "OpGroupMemberDecorate Structure type <id> '"
-             << _.getIdName(struct_id) << "' is not a struct type.";
+             << "OpGroupMemberDecorate Structure type <id> "
+             << _.getIdName(struct_id) << " is not a struct type.";
     }
     const uint32_t num_struct_members =
         static_cast<uint32_t>(struct_instr->words().size() - 2);
@@ -573,7 +447,7 @@
       // Word 1 is the group <id>. All subsequent words are target <id>s that
       // are going to be decorated with the decorations.
       const uint32_t decoration_group_id = inst->word(1);
-      std::vector<Decoration>& group_decorations =
+      std::set<Decoration>& group_decorations =
           _.id_decorations(decoration_group_id);
       for (size_t i = 2; i < inst->words().size(); ++i) {
         const uint32_t target_id = inst->word(i);
@@ -587,7 +461,7 @@
       // pairs. All decorations of the group should be applied to all the struct
       // members that are specified in the instructions.
       const uint32_t decoration_group_id = inst->word(1);
-      std::vector<Decoration>& group_decorations =
+      std::set<Decoration>& group_decorations =
           _.id_decorations(decoration_group_id);
       // Grammar checks ensures that the number of arguments to this instruction
       // is an odd number: 1 decoration group + (id,literal) pairs.
diff --git a/source/val/validate_arithmetics.cpp b/source/val/validate_arithmetics.cpp
index 433330d..bae9b5d 100644
--- a/source/val/validate_arithmetics.cpp
+++ b/source/val/validate_arithmetics.cpp
@@ -155,7 +155,7 @@
           first_vector_num_components = num_components;
         } else if (num_components != first_vector_num_components) {
           return _.diag(SPV_ERROR_INVALID_DATA, inst)
-                 << "Expected operands to have the same number of componenets: "
+                 << "Expected operands to have the same number of components: "
                  << spvOpcodeString(opcode);
         }
       }
diff --git a/source/val/validate_atomics.cpp b/source/val/validate_atomics.cpp
index cfa15d9..bf565c3 100644
--- a/source/val/validate_atomics.cpp
+++ b/source/val/validate_atomics.cpp
@@ -39,7 +39,8 @@
     case SpvStorageClassAtomicCounter:
     case SpvStorageClassImage:
     case SpvStorageClassFunction:
-    case SpvStorageClassPhysicalStorageBufferEXT:
+    case SpvStorageClassPhysicalStorageBuffer:
+    case SpvStorageClassTaskPayloadWorkgroupEXT:
       return true;
       break;
     default:
@@ -206,12 +207,13 @@
               (storage_class != SpvStorageClassStorageBuffer) &&
               (storage_class != SpvStorageClassWorkgroup) &&
               (storage_class != SpvStorageClassImage) &&
-              (storage_class != SpvStorageClassPhysicalStorageBuffer)) {
+              (storage_class != SpvStorageClassPhysicalStorageBuffer) &&
+              (storage_class != SpvStorageClassTaskPayloadWorkgroupEXT)) {
             return _.diag(SPV_ERROR_INVALID_DATA, inst)
                    << _.VkErrorID(4686) << spvOpcodeString(opcode)
                    << ": Vulkan spec only allows storage classes for atomic to "
-                      "be: Uniform, Workgroup, Image, StorageBuffer, or "
-                      "PhysicalStorageBuffer.";
+                      "be: Uniform, Workgroup, Image, StorageBuffer, "
+                      "PhysicalStorageBuffer or TaskPayloadWorkgroupEXT.";
           }
         } else if (storage_class == SpvStorageClassFunction) {
           return _.diag(SPV_ERROR_INVALID_DATA, inst)
diff --git a/source/val/validate_barriers.cpp b/source/val/validate_barriers.cpp
index 3a9e3e7..03225d8 100644
--- a/source/val/validate_barriers.cpp
+++ b/source/val/validate_barriers.cpp
@@ -50,7 +50,8 @@
                       *message =
                           "OpControlBarrier requires one of the following "
                           "Execution "
-                          "Models: TessellationControl, GLCompute or Kernel";
+                          "Models: TessellationControl, GLCompute, Kernel, "
+                          "MeshNV or TaskNV";
                     }
                     return false;
                   }
diff --git a/source/val/validate_bitwise.cpp b/source/val/validate_bitwise.cpp
index d46b3fc..e6e97c4 100644
--- a/source/val/validate_bitwise.cpp
+++ b/source/val/validate_bitwise.cpp
@@ -14,16 +14,48 @@
 
 // Validates correctness of bitwise instructions.
 
-#include "source/val/validate.h"
-
 #include "source/diagnostic.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 {
 namespace val {
 
+// Validates when base and result need to be the same type
+spv_result_t ValidateBaseType(ValidationState_t& _, const Instruction* inst,
+                              const uint32_t base_type) {
+  const SpvOp opcode = inst->opcode();
+
+  if (!_.IsIntScalarType(base_type) && !_.IsIntVectorType(base_type)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << _.VkErrorID(4781)
+           << "Expected int scalar or vector type for Base operand: "
+           << spvOpcodeString(opcode);
+  }
+
+  // Vulkan has a restriction to 32 bit for base
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    if (_.GetBitWidth(base_type) != 32) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << _.VkErrorID(4781)
+             << "Expected 32-bit int type for Base operand: "
+             << spvOpcodeString(opcode);
+    }
+  }
+
+  // OpBitCount just needs same number of components
+  if (base_type != inst->type_id() && opcode != SpvOpBitCount) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Expected Base Type to be equal to Result Type: "
+           << spvOpcodeString(opcode);
+  }
+
+  return SPV_SUCCESS;
+}
+
 // Validates correctness of bitwise instructions.
 spv_result_t BitwisePass(ValidationState_t& _, const Instruction* inst) {
   const SpvOp opcode = inst->opcode();
@@ -109,20 +141,14 @@
     }
 
     case SpvOpBitFieldInsert: {
-      if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type))
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Expected int scalar or vector type as Result Type: "
-               << spvOpcodeString(opcode);
-
       const uint32_t base_type = _.GetOperandTypeId(inst, 2);
       const uint32_t insert_type = _.GetOperandTypeId(inst, 3);
       const uint32_t offset_type = _.GetOperandTypeId(inst, 4);
       const uint32_t count_type = _.GetOperandTypeId(inst, 5);
 
-      if (base_type != result_type)
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Expected Base Type to be equal to Result Type: "
-               << spvOpcodeString(opcode);
+      if (spv_result_t error = ValidateBaseType(_, inst, base_type)) {
+        return error;
+      }
 
       if (insert_type != result_type)
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
@@ -143,19 +169,13 @@
 
     case SpvOpBitFieldSExtract:
     case SpvOpBitFieldUExtract: {
-      if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type))
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Expected int scalar or vector type as Result Type: "
-               << spvOpcodeString(opcode);
-
       const uint32_t base_type = _.GetOperandTypeId(inst, 2);
       const uint32_t offset_type = _.GetOperandTypeId(inst, 3);
       const uint32_t count_type = _.GetOperandTypeId(inst, 4);
 
-      if (base_type != result_type)
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Expected Base Type to be equal to Result Type: "
-               << spvOpcodeString(opcode);
+      if (spv_result_t error = ValidateBaseType(_, inst, base_type)) {
+        return error;
+      }
 
       if (!offset_type || !_.IsIntScalarType(offset_type))
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
@@ -170,17 +190,12 @@
     }
 
     case SpvOpBitReverse: {
-      if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type))
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Expected int scalar or vector type as Result Type: "
-               << spvOpcodeString(opcode);
-
       const uint32_t base_type = _.GetOperandTypeId(inst, 2);
 
-      if (base_type != result_type)
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Expected Base Type to be equal to Result Type: "
-               << spvOpcodeString(opcode);
+      if (spv_result_t error = ValidateBaseType(_, inst, base_type)) {
+        return error;
+      }
+
       break;
     }
 
@@ -191,15 +206,13 @@
                << spvOpcodeString(opcode);
 
       const uint32_t base_type = _.GetOperandTypeId(inst, 2);
-      if (!base_type ||
-          (!_.IsIntScalarType(base_type) && !_.IsIntVectorType(base_type)))
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Expected Base Type to be int scalar or vector: "
-               << spvOpcodeString(opcode);
-
       const uint32_t base_dimension = _.GetDimension(base_type);
       const uint32_t result_dimension = _.GetDimension(result_type);
 
+      if (spv_result_t error = ValidateBaseType(_, inst, base_type)) {
+        return error;
+      }
+
       if (base_dimension != result_dimension)
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
                << "Expected Base dimension to be equal to Result Type "
diff --git a/source/val/validate_builtins.cpp b/source/val/validate_builtins.cpp
index 57dde8a..6f4b0f9 100644
--- a/source/val/validate_builtins.cpp
+++ b/source/val/validate_builtins.cpp
@@ -120,7 +120,7 @@
   VUIDErrorMax,
 } VUIDError;
 
-const static uint32_t NumVUIDBuiltins = 33;
+const static uint32_t NumVUIDBuiltins = 36;
 
 typedef struct {
   SpvBuiltIn builtIn;
@@ -162,6 +162,9 @@
     {SpvBuiltInFragSizeEXT,               {4220, 4221, 4222}},
     {SpvBuiltInFragStencilRefEXT,         {4223, 4224, 4225}},
     {SpvBuiltInFullyCoveredEXT,           {4232, 4233, 4234}},
+    {SpvBuiltInCullMaskKHR,               {6735, 6736, 6737}},
+    {SpvBuiltInBaryCoordKHR,              {4154, 4155, 4156}},
+    {SpvBuiltInBaryCoordNoPerspKHR,       {4160, 4161, 4162}},
     // clang-format off
 } };
 
@@ -208,6 +211,7 @@
     case SpvBuiltInRayTmaxKHR:
     case SpvBuiltInWorldRayDirectionKHR:
     case SpvBuiltInWorldRayOriginKHR:
+    case SpvBuiltInCullMaskKHR:
       switch (stage) {
         case SpvExecutionModelIntersectionKHR:
         case SpvExecutionModelAnyHitKHR:
@@ -329,9 +333,11 @@
   // Used for GlobalInvocationId, LocalInvocationId, NumWorkgroups, WorkgroupId.
   spv_result_t ValidateComputeShaderI32Vec3InputAtDefinition(
       const Decoration& decoration, const Instruction& inst);
-  spv_result_t ValidateSMBuiltinsAtDefinition(const Decoration& decoration,
+  spv_result_t ValidateNVSMOrARMCoreBuiltinsAtDefinition(const Decoration& decoration,
                                               const Instruction& inst);
-
+  // Used for BaryCoord, BaryCoordNoPersp.
+  spv_result_t ValidateFragmentShaderF32Vec3InputAtDefinition(
+      const Decoration& decoration, const Instruction& inst);
   // Used for SubgroupEqMask, SubgroupGeMask, SubgroupGtMask, SubgroupLtMask,
   // SubgroupLeMask.
   spv_result_t ValidateI32Vec4InputAtDefinition(const Decoration& decoration,
@@ -509,13 +515,20 @@
       const Decoration& decoration, const Instruction& built_in_inst,
       const Instruction& referenced_inst,
       const Instruction& referenced_from_inst);
+
+  // Used for BaryCoord, BaryCoordNoPersp.
+  spv_result_t ValidateFragmentShaderF32Vec3InputAtReference(
+      const Decoration& decoration, const Instruction& built_in_inst,
+      const Instruction& referenced_inst,
+      const Instruction& referenced_from_inst);
+
   // Used for SubgroupId and NumSubgroups.
   spv_result_t ValidateComputeI32InputAtReference(
       const Decoration& decoration, const Instruction& built_in_inst,
       const Instruction& referenced_inst,
       const Instruction& referenced_from_inst);
 
-  spv_result_t ValidateSMBuiltinsAtReference(
+  spv_result_t ValidateNVSMOrARMCoreBuiltinsAtReference(
       const Decoration& decoration, const Instruction& built_in_inst,
       const Instruction& referenced_inst,
       const Instruction& referenced_from_inst);
@@ -1166,9 +1179,16 @@
           &BuiltInsValidator::ValidateNotCalledWithExecutionModel, this, vuid,
           "Vulkan spec doesn't allow BuiltIn ClipDistance/CullDistance to be "
           "used for variables with Input storage class if execution model is "
-          "Vertex.",
+          "MeshNV.",
           SpvExecutionModelMeshNV, decoration, built_in_inst,
           referenced_from_inst, std::placeholders::_1));
+      id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind(
+          &BuiltInsValidator::ValidateNotCalledWithExecutionModel, this, vuid,
+          "Vulkan spec doesn't allow BuiltIn ClipDistance/CullDistance to be "
+          "used for variables with Input storage class if execution model is "
+          "MeshEXT.",
+          SpvExecutionModelMeshEXT, decoration, built_in_inst,
+          referenced_from_inst, std::placeholders::_1));
     }
 
     if (storage_class == SpvStorageClassOutput) {
@@ -1211,7 +1231,8 @@
         case SpvExecutionModelTessellationControl:
         case SpvExecutionModelTessellationEvaluation:
         case SpvExecutionModelGeometry:
-        case SpvExecutionModelMeshNV: {
+        case SpvExecutionModelMeshNV:
+        case SpvExecutionModelMeshEXT: {
           if (decoration.struct_member_index() != Decoration::kInvalidMember) {
             // The outer level of array is applied on the variable.
             if (spv_result_t error = ValidateF32Arr(
@@ -1843,7 +1864,8 @@
         case SpvExecutionModelTessellationControl:
         case SpvExecutionModelTessellationEvaluation:
         case SpvExecutionModelGeometry:
-        case SpvExecutionModelMeshNV: {
+        case SpvExecutionModelMeshNV:
+        case SpvExecutionModelMeshEXT: {
           // PointSize can be a per-vertex variable for tessellation control,
           // tessellation evaluation and geometry shader stages. In such cases
           // variables will have an array of 32-bit floats.
@@ -1944,6 +1966,13 @@
           "with Input storage class if execution model is MeshNV.",
           SpvExecutionModelMeshNV, decoration, built_in_inst,
           referenced_from_inst, std::placeholders::_1));
+      id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind(
+          &BuiltInsValidator::ValidateNotCalledWithExecutionModel, this, 4319,
+          "Vulkan spec doesn't allow BuiltIn Position to be used "
+          "for variables "
+          "with Input storage class if execution model is MeshEXT.",
+          SpvExecutionModelMeshEXT, decoration, built_in_inst,
+          referenced_from_inst, std::placeholders::_1));
     }
 
     for (const SpvExecutionModel execution_model : execution_models_) {
@@ -1967,7 +1996,8 @@
         case SpvExecutionModelGeometry:
         case SpvExecutionModelTessellationControl:
         case SpvExecutionModelTessellationEvaluation:
-        case SpvExecutionModelMeshNV: {
+        case SpvExecutionModelMeshNV:
+        case SpvExecutionModelMeshEXT: {
           // Position can be a per-vertex variable for tessellation control,
           // tessellation evaluation, geometry and mesh shader stages. In such
           // cases variables will have an array of 4-component 32-bit float
@@ -2138,6 +2168,7 @@
         case SpvExecutionModelTessellationEvaluation:
         case SpvExecutionModelGeometry:
         case SpvExecutionModelMeshNV:
+        case SpvExecutionModelMeshEXT:
         case SpvExecutionModelIntersectionKHR:
         case SpvExecutionModelAnyHitKHR:
         case SpvExecutionModelClosestHitKHR: {
@@ -2150,9 +2181,8 @@
                  << _.VkErrorID(4330)
                  << "Vulkan spec allows BuiltIn PrimitiveId to be used only "
                     "with Fragment, TessellationControl, "
-                    "TessellationEvaluation, Geometry, MeshNV, "
-                    "IntersectionKHR, "
-                    "AnyHitKHR, and ClosestHitKHR execution models. "
+                    "TessellationEvaluation, Geometry, MeshNV, MeshEXT, "
+                    "IntersectionKHR, AnyHitKHR, and ClosestHitKHR execution models. "
                  << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                      referenced_from_inst, execution_model);
         }
@@ -2700,7 +2730,8 @@
       assert(function_id_ == 0);
       for (const auto em :
            {SpvExecutionModelVertex, SpvExecutionModelTessellationEvaluation,
-            SpvExecutionModelGeometry, SpvExecutionModelMeshNV}) {
+            SpvExecutionModelGeometry, SpvExecutionModelMeshNV,
+            SpvExecutionModelMeshEXT}) {
         id_to_at_reference_checks_[referenced_from_inst.id()].push_back(
             std::bind(&BuiltInsValidator::ValidateNotCalledWithExecutionModel,
                       this, ((operand == SpvBuiltInLayer) ? 4274 : 4406),
@@ -2708,7 +2739,7 @@
                       "ViewportIndex to be "
                       "used for variables with Input storage class if "
                       "execution model is Vertex, TessellationEvaluation, "
-                      "Geometry, or MeshNV.",
+                      "Geometry, MeshNV or MeshEXT.",
                       em, decoration, built_in_inst, referenced_from_inst,
                       std::placeholders::_1));
       }
@@ -2733,6 +2764,7 @@
         case SpvExecutionModelGeometry:
         case SpvExecutionModelFragment:
         case SpvExecutionModelMeshNV:
+        case SpvExecutionModelMeshEXT:
           // Ok.
           break;
         case SpvExecutionModelVertex:
@@ -2788,6 +2820,80 @@
   return SPV_SUCCESS;
 }
 
+spv_result_t BuiltInsValidator::ValidateFragmentShaderF32Vec3InputAtDefinition(
+    const Decoration& decoration, const Instruction& inst) {
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    const SpvBuiltIn builtin = SpvBuiltIn(decoration.params()[0]);
+    if (spv_result_t error = ValidateF32Vec(
+            decoration, inst, 3,
+            [this, &inst, builtin](const std::string& message) -> spv_result_t {
+              uint32_t vuid = GetVUIDForBuiltin(builtin, VUIDErrorType);
+              return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << _.VkErrorID(vuid) << "According to the "
+                     << spvLogStringForEnv(_.context()->target_env)
+                     << " spec BuiltIn "
+                     << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                                      builtin)
+                     << " variable needs to be a 3-component 32-bit float "
+                        "vector. "
+                     << message;
+            })) {
+      return error;
+    }
+  }
+
+  // Seed at reference checks with this built-in.
+  return ValidateFragmentShaderF32Vec3InputAtReference(decoration, inst, inst,
+                                                      inst);
+}
+
+spv_result_t BuiltInsValidator::ValidateFragmentShaderF32Vec3InputAtReference(
+    const Decoration& decoration, const Instruction& built_in_inst,
+    const Instruction& referenced_inst,
+    const Instruction& referenced_from_inst) {
+
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    const SpvBuiltIn builtin = SpvBuiltIn(decoration.params()[0]);
+    const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
+    if (storage_class != SpvStorageClassMax &&
+        storage_class != SpvStorageClassInput) {
+      uint32_t vuid = GetVUIDForBuiltin(builtin, VUIDErrorStorageClass);
+      return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << _.VkErrorID(vuid) << spvLogStringForEnv(_.context()->target_env)
+             << " spec allows BuiltIn "
+             << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, builtin)
+             << " 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 != SpvExecutionModelFragment) {
+        uint32_t vuid = GetVUIDForBuiltin(builtin, VUIDErrorExecutionModel);
+        return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+               << _.VkErrorID(vuid)
+               << spvLogStringForEnv(_.context()->target_env)
+               << " spec allows BuiltIn "
+               << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, builtin)
+               << " to be used only with Fragment 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::ValidateFragmentShaderF32Vec3InputAtReference, this,
+        decoration, built_in_inst, referenced_from_inst,
+        std::placeholders::_1));
+  }
+
+  return SPV_SUCCESS;
+}
+
 spv_result_t BuiltInsValidator::ValidateComputeShaderI32Vec3InputAtDefinition(
     const Decoration& decoration, const Instruction& inst) {
   if (spvIsVulkanEnv(_.context()->target_env)) {
@@ -2838,7 +2944,10 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       bool has_vulkan_model = execution_model == SpvExecutionModelGLCompute ||
                               execution_model == SpvExecutionModelTaskNV ||
-                              execution_model == SpvExecutionModelMeshNV;
+                              execution_model == SpvExecutionModelMeshNV ||
+                              execution_model == SpvExecutionModelTaskEXT ||
+                              execution_model == SpvExecutionModelMeshEXT;
+
       if (spvIsVulkanEnv(_.context()->target_env) && !has_vulkan_model) {
         uint32_t vuid = GetVUIDForBuiltin(builtin, VUIDErrorExecutionModel);
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
@@ -2846,7 +2955,8 @@
                << spvLogStringForEnv(_.context()->target_env)
                << " spec allows BuiltIn "
                << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, builtin)
-               << " to be used only with GLCompute, MeshNV, or TaskNV execution model. "
+               << " to be used only with GLCompute, MeshNV, TaskNV, MeshEXT or"
+               << " TaskEXT execution model. "
                << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                    referenced_from_inst, execution_model);
       }
@@ -2920,7 +3030,9 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       bool has_vulkan_model = execution_model == SpvExecutionModelGLCompute ||
                               execution_model == SpvExecutionModelTaskNV ||
-                              execution_model == SpvExecutionModelMeshNV;
+                              execution_model == SpvExecutionModelMeshNV ||
+                              execution_model == SpvExecutionModelTaskEXT ||
+                              execution_model == SpvExecutionModelMeshEXT;
       if (spvIsVulkanEnv(_.context()->target_env) && !has_vulkan_model) {
         uint32_t vuid = GetVUIDForBuiltin(builtin, VUIDErrorExecutionModel);
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
@@ -2928,7 +3040,8 @@
                << spvLogStringForEnv(_.context()->target_env)
                << " spec allows BuiltIn "
                << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, builtin)
-               << " to be used only with GLCompute, MeshNV, or TaskNV execution model. "
+               << " to be used only with GLCompute, MeshNV, TaskNV, MeshEXT or "
+               << "TaskEXT execution model. "
                << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                    referenced_from_inst, execution_model);
       }
@@ -3072,14 +3185,17 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelGLCompute &&
           execution_model != SpvExecutionModelTaskNV &&
-          execution_model != SpvExecutionModelMeshNV) {
+          execution_model != SpvExecutionModelMeshNV &&
+          execution_model != SpvExecutionModelTaskEXT &&
+          execution_model != SpvExecutionModelMeshEXT) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
                << _.VkErrorID(4425)
                << spvLogStringForEnv(_.context()->target_env)
                << " spec allows BuiltIn "
                << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
                                                 decoration.params()[0])
-               << " to be used only with GLCompute, MeshNV, or TaskNV execution model. "
+               << " to be used only with GLCompute, MeshNV, TaskNV, MeshEXT or "
+               << "TaskEXT execution model. "
                << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                    referenced_from_inst, execution_model);
       }
@@ -3210,12 +3326,15 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelVertex &&
           execution_model != SpvExecutionModelMeshNV &&
-          execution_model != SpvExecutionModelTaskNV) {
+          execution_model != SpvExecutionModelTaskNV &&
+          execution_model != SpvExecutionModelMeshEXT &&
+          execution_model != SpvExecutionModelTaskEXT) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
                << _.VkErrorID(4207) << "Vulkan spec allows BuiltIn "
                << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
                                                 operand)
-               << " to be used only with Vertex, MeshNV, or TaskNV execution "
+               << " to be used only with Vertex, MeshNV, TaskNV , MeshEXT or"
+               << " TaskEXT execution "
                   "model. "
                << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                    referenced_from_inst, execution_model);
@@ -3630,7 +3749,7 @@
   return SPV_SUCCESS;
 }
 
-spv_result_t BuiltInsValidator::ValidateSMBuiltinsAtDefinition(
+spv_result_t BuiltInsValidator::ValidateNVSMOrARMCoreBuiltinsAtDefinition(
     const Decoration& decoration, const Instruction& inst) {
   if (spvIsVulkanEnv(_.context()->target_env)) {
     if (spv_result_t error = ValidateI32(
@@ -3651,10 +3770,10 @@
   }
 
   // Seed at reference checks with this built-in.
-  return ValidateSMBuiltinsAtReference(decoration, inst, inst, inst);
+  return ValidateNVSMOrARMCoreBuiltinsAtReference(decoration, inst, inst, inst);
 }
 
-spv_result_t BuiltInsValidator::ValidateSMBuiltinsAtReference(
+spv_result_t BuiltInsValidator::ValidateNVSMOrARMCoreBuiltinsAtReference(
     const Decoration& decoration, const Instruction& built_in_inst,
     const Instruction& referenced_inst,
     const Instruction& referenced_from_inst) {
@@ -3678,7 +3797,7 @@
   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::ValidateSMBuiltinsAtReference, this, decoration,
+        &BuiltInsValidator::ValidateNVSMOrARMCoreBuiltinsAtReference, this, decoration,
         built_in_inst, referenced_from_inst, std::placeholders::_1));
   }
 
@@ -3731,6 +3850,7 @@
         case SpvExecutionModelVertex:
         case SpvExecutionModelGeometry:
         case SpvExecutionModelMeshNV:
+        case SpvExecutionModelMeshEXT:
           break;
         default: {
           return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
@@ -3851,6 +3971,7 @@
       case SpvBuiltInInstanceId:
       case SpvBuiltInRayGeometryIndexKHR:
       case SpvBuiltInIncomingRayFlagsKHR:
+      case SpvBuiltInCullMaskKHR:
         // i32 scalar
         if (spv_result_t error = ValidateI32(
                 decoration, inst,
@@ -4027,6 +4148,10 @@
     case SpvBuiltInWorkgroupId: {
       return ValidateComputeShaderI32Vec3InputAtDefinition(decoration, inst);
     }
+    case SpvBuiltInBaryCoordKHR:
+    case SpvBuiltInBaryCoordNoPerspKHR: {
+      return ValidateFragmentShaderF32Vec3InputAtDefinition(decoration, inst);
+    }
     case SpvBuiltInHelperInvocation: {
       return ValidateHelperInvocationAtDefinition(decoration, inst);
     }
@@ -4100,11 +4225,16 @@
     case SpvBuiltInLocalInvocationIndex: {
       return ValidateLocalInvocationIndexAtDefinition(decoration, inst);
     }
+    case SpvBuiltInCoreIDARM:
+    case SpvBuiltInCoreCountARM:
+    case SpvBuiltInCoreMaxIDARM:
+    case SpvBuiltInWarpIDARM:
+    case SpvBuiltInWarpMaxIDARM:
     case SpvBuiltInWarpsPerSMNV:
     case SpvBuiltInSMCountNV:
     case SpvBuiltInWarpIDNV:
     case SpvBuiltInSMIDNV: {
-      return ValidateSMBuiltinsAtDefinition(decoration, inst);
+      return ValidateNVSMOrARMCoreBuiltinsAtDefinition(decoration, inst);
     }
     case SpvBuiltInBaseInstance:
     case SpvBuiltInBaseVertex: {
@@ -4151,49 +4281,19 @@
     case SpvBuiltInObjectToWorldKHR:        // alias SpvBuiltInObjectToWorldNV
     case SpvBuiltInWorldToObjectKHR:        // alias SpvBuiltInWorldToObjectNV
     case SpvBuiltInIncomingRayFlagsKHR:    // alias SpvBuiltInIncomingRayFlagsNV
-    case SpvBuiltInRayGeometryIndexKHR: {  // NOT present in NV
+    case SpvBuiltInRayGeometryIndexKHR:    // NOT present in NV
+    case SpvBuiltInCullMaskKHR: {
       return ValidateRayTracingBuiltinsAtDefinition(decoration, inst);
     }
-    case SpvBuiltInWorkDim:
-    case SpvBuiltInGlobalSize:
-    case SpvBuiltInEnqueuedWorkgroupSize:
-    case SpvBuiltInGlobalOffset:
-    case SpvBuiltInGlobalLinearId:
-    case SpvBuiltInSubgroupMaxSize:
-    case SpvBuiltInNumEnqueuedSubgroups:
-    case SpvBuiltInBaryCoordNoPerspAMD:
-    case SpvBuiltInBaryCoordNoPerspCentroidAMD:
-    case SpvBuiltInBaryCoordNoPerspSampleAMD:
-    case SpvBuiltInBaryCoordSmoothAMD:
-    case SpvBuiltInBaryCoordSmoothCentroidAMD:
-    case SpvBuiltInBaryCoordSmoothSampleAMD:
-    case SpvBuiltInBaryCoordPullModelAMD:
-    case SpvBuiltInViewportMaskNV:
-    case SpvBuiltInSecondaryPositionNV:
-    case SpvBuiltInSecondaryViewportMaskNV:
-    case SpvBuiltInPositionPerViewNV:
-    case SpvBuiltInViewportMaskPerViewNV:
-    case SpvBuiltInMax:
-    case SpvBuiltInTaskCountNV:
-    case SpvBuiltInPrimitiveCountNV:
-    case SpvBuiltInPrimitiveIndicesNV:
-    case SpvBuiltInClipDistancePerViewNV:
-    case SpvBuiltInCullDistancePerViewNV:
-    case SpvBuiltInLayerPerViewNV:
-    case SpvBuiltInMeshViewCountNV:
-    case SpvBuiltInMeshViewIndicesNV:
-    case SpvBuiltInBaryCoordNV:
-    case SpvBuiltInBaryCoordNoPerspNV:
-    case SpvBuiltInCurrentRayTimeNV:
-      // No validation rules (for the moment).
-      break;
-
     case SpvBuiltInPrimitiveShadingRateKHR: {
       return ValidatePrimitiveShadingRateAtDefinition(decoration, inst);
     }
     case SpvBuiltInShadingRateKHR: {
       return ValidateShadingRateAtDefinition(decoration, inst);
     }
+    default:
+      // No validation rules (for the moment).
+      break;
   }
   return SPV_SUCCESS;
 }
diff --git a/source/val/validate_cfg.cpp b/source/val/validate_cfg.cpp
index 7842e56..cc0b999 100644
--- a/source/val/validate_cfg.cpp
+++ b/source/val/validate_cfg.cpp
@@ -27,6 +27,7 @@
 
 #include "source/cfa.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/basic_block.h"
@@ -54,8 +55,7 @@
   }
   if (_.IsPointerType(inst->type_id()) &&
       _.addressing_model() == SpvAddressingModelLogical) {
-    if (!_.features().variable_pointers &&
-        !_.features().variable_pointers_storage_buffer) {
+    if (!_.features().variable_pointers) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << "Using pointers with OpPhi requires capability "
              << "VariablePointers or VariablePointersStorageBuffer";
@@ -66,7 +66,8 @@
   assert(type_inst);
   const SpvOp type_opcode = type_inst->opcode();
 
-  if (!_.options()->before_hlsl_legalization) {
+  if (!_.options()->before_hlsl_legalization &&
+      !_.HasCapability(SpvCapabilityBindlessTextureNV)) {
     if (type_opcode == SpvOpTypeSampledImage ||
         (_.HasCapability(SpvCapabilityShader) &&
          (type_opcode == SpvOpTypeImage || type_opcode == SpvOpTypeSampler))) {
@@ -191,6 +192,12 @@
               "ID of an OpLabel instruction";
   }
 
+  if (_.version() >= SPV_SPIRV_VERSION_WORD(1, 6) && true_id == false_id) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "In SPIR-V 1.6 or later, True Label and False Label must be "
+              "different labels";
+  }
+
   return SPV_SUCCESS;
 }
 
@@ -232,27 +239,23 @@
   const auto value = _.FindDef(value_id);
   if (!value || !value->type_id()) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpReturnValue Value <id> '" << _.getIdName(value_id)
-           << "' does not represent a value.";
+           << "OpReturnValue Value <id> " << _.getIdName(value_id)
+           << " does not represent a value.";
   }
   auto value_type = _.FindDef(value->type_id());
   if (!value_type || SpvOpTypeVoid == value_type->opcode()) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpReturnValue value's type <id> '"
-           << _.getIdName(value->type_id()) << "' is missing or void.";
+           << "OpReturnValue value's type <id> "
+           << _.getIdName(value->type_id()) << " is missing or void.";
   }
 
-  const bool uses_variable_pointer =
-      _.features().variable_pointers ||
-      _.features().variable_pointers_storage_buffer;
-
   if (_.addressing_model() == SpvAddressingModelLogical &&
-      SpvOpTypePointer == value_type->opcode() && !uses_variable_pointer &&
-      !_.options()->relax_logical_pointer) {
+      SpvOpTypePointer == value_type->opcode() &&
+      !_.features().variable_pointers && !_.options()->relax_logical_pointer) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpReturnValue value's type <id> '"
+           << "OpReturnValue value's type <id> "
            << _.getIdName(value->type_id())
-           << "' is a pointer, which is invalid in the Logical addressing "
+           << " is a pointer, which is invalid in the Logical addressing "
               "model.";
   }
 
@@ -260,8 +263,8 @@
   const auto return_type = _.FindDef(function->GetResultTypeId());
   if (!return_type || return_type->id() != value_type->id()) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpReturnValue Value <id> '" << _.getIdName(value_id)
-           << "'s type does not match OpFunction's return type.";
+           << "OpReturnValue Value <id> " << _.getIdName(value_id)
+           << "s type does not match OpFunction's return type.";
   }
 
   return SPV_SUCCESS;
@@ -464,7 +467,7 @@
   std::vector<BasicBlock*> stack;
   stack.push_back(target_block);
   std::unordered_set<const BasicBlock*> visited;
-  bool target_reachable = target_block->reachable();
+  bool target_reachable = target_block->structurally_reachable();
   int target_depth = function->GetBlockDepth(target_block);
   while (!stack.empty()) {
     auto block = stack.back();
@@ -474,8 +477,8 @@
 
     if (!visited.insert(block).second) continue;
 
-    if (target_reachable && block->reachable() &&
-        target_block->dominates(*block)) {
+    if (target_reachable && block->structurally_reachable() &&
+        target_block->structurally_dominates(*block)) {
       // Still in the case construct.
       for (auto successor : *block->successors()) {
         stack.push_back(successor);
@@ -547,11 +550,12 @@
     if (seen_iter == seen_to_fall_through.end()) {
       const auto target_block = function->GetBlock(target).first;
       // OpSwitch must dominate all its case constructs.
-      if (header->reachable() && target_block->reachable() &&
-          !header->dominates(*target_block)) {
+      if (header->structurally_reachable() &&
+          target_block->structurally_reachable() &&
+          !header->structurally_dominates(*target_block)) {
         return _.diag(SPV_ERROR_INVALID_CFG, header->label())
                << "Selection header " << _.getIdName(header->id())
-               << " does not dominate its case construct "
+               << " does not structurally dominate its case construct "
                << _.getIdName(target);
       }
 
@@ -651,7 +655,7 @@
     }
 
     // Skip unreachable blocks.
-    if (!block->reachable()) continue;
+    if (!block->structurally_reachable()) continue;
 
     if (terminator->opcode() == SpvOpBranchConditional) {
       const auto true_label = terminator->GetOperandAs<uint32_t>(1);
@@ -668,7 +672,7 @@
     } else if (terminator->opcode() == SpvOpSwitch) {
       if (!merge) {
         return _.diag(SPV_ERROR_INVALID_CFG, terminator)
-               << "OpSwitch must be preceeded by an OpSelectionMerge "
+               << "OpSwitch must be preceded by an OpSelectionMerge "
                   "instruction";
       }
       // Mark the targets as seen.
@@ -706,7 +710,7 @@
 
   // Check the loop headers have exactly one back-edge branching to it
   for (BasicBlock* loop_header : function->ordered_blocks()) {
-    if (!loop_header->reachable()) continue;
+    if (!loop_header->structurally_reachable()) continue;
     if (!loop_header->is_type(kBlockTypeLoop)) continue;
     auto loop_header_id = loop_header->id();
     auto num_latch_blocks = loop_latch_blocks[loop_header_id].size();
@@ -721,9 +725,10 @@
   // Check construct rules
   for (const Construct& construct : function->constructs()) {
     auto header = construct.entry_block();
+    if (!header->structurally_reachable()) continue;
     auto merge = construct.exit_block();
 
-    if (header->reachable() && !merge) {
+    if (!merge) {
       std::string construct_name, header_name, exit_name;
       std::tie(construct_name, header_name, exit_name) =
           ConstructNames(construct.type());
@@ -733,32 +738,31 @@
                     exit_name + ". This may be a bug in the validator.";
     }
 
-    // If the exit block is reachable then it's dominated by the
-    // header.
-    if (merge && merge->reachable()) {
-      if (!header->dominates(*merge)) {
-        return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(merge->id()))
-               << ConstructErrorString(construct, _.getIdName(header->id()),
-                                       _.getIdName(merge->id()),
-                                       "does not dominate");
-      }
-      // If it's really a merge block for a selection or loop, then it must be
-      // *strictly* dominated by the header.
-      if (construct.ExitBlockIsMergeBlock() && (header == merge)) {
-        return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(merge->id()))
-               << ConstructErrorString(construct, _.getIdName(header->id()),
-                                       _.getIdName(merge->id()),
-                                       "does not strictly dominate");
-      }
+    // If the header is reachable, the merge is guaranteed to be structurally
+    // reachable.
+    if (!header->structurally_dominates(*merge)) {
+      return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(merge->id()))
+             << ConstructErrorString(construct, _.getIdName(header->id()),
+                                     _.getIdName(merge->id()),
+                                     "does not structurally dominate");
     }
+    // If it's really a merge block for a selection or loop, then it must be
+    // *strictly* structrually dominated by the header.
+    if (construct.ExitBlockIsMergeBlock() && (header == merge)) {
+      return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(merge->id()))
+             << ConstructErrorString(construct, _.getIdName(header->id()),
+                                     _.getIdName(merge->id()),
+                                     "does not strictly structurally dominate");
+    }
+
     // Check post-dominance for continue constructs.  But dominance and
     // post-dominance only make sense when the construct is reachable.
-    if (header->reachable() && construct.type() == ConstructType::kContinue) {
-      if (!merge->postdominates(*header)) {
+    if (construct.type() == ConstructType::kContinue) {
+      if (!merge->structurally_postdominates(*header)) {
         return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(merge->id()))
                << ConstructErrorString(construct, _.getIdName(header->id()),
                                        _.getIdName(merge->id()),
-                                       "is not post dominated by");
+                                       "is not structurally post dominated by");
       }
     }
 
@@ -769,7 +773,7 @@
     for (auto block : construct_blocks) {
       // Check that all exits from the construct are via structured exits.
       for (auto succ : *block->successors()) {
-        if (block->reachable() && !construct_blocks.count(succ) &&
+        if (!construct_blocks.count(succ) &&
             !construct.IsStructuredExit(_, succ)) {
           return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
                  << "block <ID> " << _.getIdName(block->id()) << " exits the "
@@ -782,7 +786,7 @@
       // Check that for all non-header blocks, all predecessors are within this
       // construct.
       for (auto pred : *block->predecessors()) {
-        if (pred->reachable() && !construct_blocks.count(pred)) {
+        if (pred->structurally_reachable() && !construct_blocks.count(pred)) {
           return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(pred->id()))
                  << "block <ID> " << pred->id() << " branches to the "
                  << construct_name << " construct, but not to the "
@@ -798,7 +802,7 @@
             merge_inst.opcode() == SpvOpLoopMerge) {
           uint32_t merge_id = merge_inst.GetOperandAs<uint32_t>(0);
           auto merge_block = function->GetBlock(merge_id).first;
-          if (merge_block->reachable() &&
+          if (merge_block->structurally_reachable() &&
               !construct_blocks.count(merge_block)) {
             return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
                    << "Header block " << _.getIdName(block->id())
@@ -811,6 +815,43 @@
       }
     }
 
+    if (construct.type() == ConstructType::kLoop) {
+      // If the continue target differs from the loop header, then check that
+      // all edges into the continue construct come from within the loop.
+      const auto index = header->terminator() - &_.ordered_instructions()[0];
+      const auto& merge_inst = _.ordered_instructions()[index - 1];
+      const auto continue_id = merge_inst.GetOperandAs<uint32_t>(1);
+      const auto* continue_inst = _.FindDef(continue_id);
+      // OpLabel instructions aren't stored as part of the basic block for
+      // legacy reaasons. Grab the next instruction and use it's block pointer
+      // instead.
+      const auto next_index =
+          (continue_inst - &_.ordered_instructions()[0]) + 1;
+      const auto& next_inst = _.ordered_instructions()[next_index];
+      const auto* continue_target = next_inst.block();
+      if (header->id() != continue_id) {
+        for (auto pred : *continue_target->predecessors()) {
+          // Ignore back-edges from within the continue construct.
+          bool is_back_edge = false;
+          for (auto back_edge : back_edges) {
+            uint32_t back_edge_block;
+            uint32_t header_block;
+            std::tie(back_edge_block, header_block) = back_edge;
+            if (header_block == continue_id && back_edge_block == pred->id())
+              is_back_edge = true;
+          }
+          if (!construct_blocks.count(pred) && !is_back_edge) {
+            return _.diag(SPV_ERROR_INVALID_CFG, pred->terminator())
+                   << "Block " << _.getIdName(pred->id())
+                   << " branches to the loop continue target "
+                   << _.getIdName(continue_id)
+                   << ", but is not contained in the associated loop construct "
+                   << _.getIdName(header->id());
+          }
+        }
+      }
+    }
+
     // Checks rules for case constructs.
     if (construct.type() == ConstructType::kSelection &&
         header->terminator()->opcode() == SpvOpSwitch) {
@@ -848,52 +889,27 @@
              << _.getIdName(function.id());
     }
 
-    // Set each block's immediate dominator and immediate postdominator,
-    // and find all back-edges.
+    // Set each block's immediate dominator.
     //
     // We want to analyze all the blocks in the function, even in degenerate
     // control flow cases including unreachable blocks.  So use the augmented
     // CFG to ensure we cover all the blocks.
     std::vector<const BasicBlock*> postorder;
-    std::vector<const BasicBlock*> postdom_postorder;
-    std::vector<std::pair<uint32_t, uint32_t>> back_edges;
     auto ignore_block = [](const BasicBlock*) {};
-    auto ignore_edge = [](const BasicBlock*, const BasicBlock*) {};
+    auto no_terminal_blocks = [](const BasicBlock*) { return false; };
     if (!function.ordered_blocks().empty()) {
       /// calculate dominators
       CFA<BasicBlock>::DepthFirstTraversal(
           function.first_block(), function.AugmentedCFGSuccessorsFunction(),
           ignore_block, [&](const BasicBlock* b) { postorder.push_back(b); },
-          ignore_edge);
+          no_terminal_blocks);
       auto edges = CFA<BasicBlock>::CalculateDominators(
           postorder, function.AugmentedCFGPredecessorsFunction());
       for (auto edge : edges) {
         if (edge.first != edge.second)
           edge.first->SetImmediateDominator(edge.second);
       }
-
-      /// calculate post dominators
-      CFA<BasicBlock>::DepthFirstTraversal(
-          function.pseudo_exit_block(),
-          function.AugmentedCFGPredecessorsFunction(), ignore_block,
-          [&](const BasicBlock* b) { postdom_postorder.push_back(b); },
-          ignore_edge);
-      auto postdom_edges = CFA<BasicBlock>::CalculateDominators(
-          postdom_postorder, function.AugmentedCFGSuccessorsFunction());
-      for (auto edge : postdom_edges) {
-        edge.first->SetImmediatePostDominator(edge.second);
-      }
-      /// calculate back edges.
-      CFA<BasicBlock>::DepthFirstTraversal(
-          function.pseudo_entry_block(),
-          function
-              .AugmentedCFGSuccessorsFunctionIncludingHeaderToContinueEdge(),
-          ignore_block, ignore_block,
-          [&](const BasicBlock* from, const BasicBlock* to) {
-            back_edges.emplace_back(from->id(), to->id());
-          });
     }
-    UpdateContinueConstructExitBlocks(function, back_edges);
 
     auto& blocks = function.ordered_blocks();
     if (!blocks.empty()) {
@@ -910,7 +926,7 @@
           }
         }
       }
-      // If we have structed control flow, check that no block has a control
+      // If we have structured control flow, check that no block has a control
       // flow nesting depth larger than the limit.
       if (_.HasCapability(SpvCapabilityShader)) {
         const int control_flow_nesting_depth_limit =
@@ -927,6 +943,52 @@
 
     /// Structured control flow checks are only required for shader capabilities
     if (_.HasCapability(SpvCapabilityShader)) {
+      // Calculate structural dominance.
+      postorder.clear();
+      std::vector<const BasicBlock*> postdom_postorder;
+      std::vector<std::pair<uint32_t, uint32_t>> back_edges;
+      if (!function.ordered_blocks().empty()) {
+        /// calculate dominators
+        CFA<BasicBlock>::DepthFirstTraversal(
+            function.first_block(),
+            function.AugmentedStructuralCFGSuccessorsFunction(), ignore_block,
+            [&](const BasicBlock* b) { postorder.push_back(b); },
+            no_terminal_blocks);
+        auto edges = CFA<BasicBlock>::CalculateDominators(
+            postorder, function.AugmentedStructuralCFGPredecessorsFunction());
+        for (auto edge : edges) {
+          if (edge.first != edge.second)
+            edge.first->SetImmediateStructuralDominator(edge.second);
+        }
+
+        /// calculate post dominators
+        CFA<BasicBlock>::DepthFirstTraversal(
+            function.pseudo_exit_block(),
+            function.AugmentedStructuralCFGPredecessorsFunction(), ignore_block,
+            [&](const BasicBlock* b) { postdom_postorder.push_back(b); },
+            no_terminal_blocks);
+        auto postdom_edges = CFA<BasicBlock>::CalculateDominators(
+            postdom_postorder,
+            function.AugmentedStructuralCFGSuccessorsFunction());
+        for (auto edge : postdom_edges) {
+          edge.first->SetImmediateStructuralPostDominator(edge.second);
+        }
+        /// calculate back edges.
+        CFA<BasicBlock>::DepthFirstTraversal(
+            function.pseudo_entry_block(),
+            function.AugmentedStructuralCFGSuccessorsFunction(), ignore_block,
+            ignore_block,
+            [&](const BasicBlock* from, const BasicBlock* to) {
+              // A back edge must be a real edge. Since the augmented successors
+              // contain structural edges, filter those from consideration.
+              for (const auto* succ : *(from->successors())) {
+                if (succ == to) back_edges.emplace_back(from->id(), to->id());
+              }
+            },
+            no_terminal_blocks);
+      }
+      UpdateContinueConstructExitBlocks(function, back_edges);
+
       if (auto error =
               StructuredControlFlowChecks(_, &function, back_edges, postorder))
         return error;
@@ -1004,7 +1066,9 @@
     case SpvOpTerminateInvocation:
     case SpvOpIgnoreIntersectionKHR:
     case SpvOpTerminateRayKHR:
+    case SpvOpEmitMeshTasksEXT:
       _.current_function().RegisterBlockEnd(std::vector<uint32_t>());
+      // Ops with dedicated passes check for the Execution Model there
       if (opcode == SpvOpKill) {
         _.current_function().RegisterExecutionModelLimitation(
             SpvExecutionModelFragment,
@@ -1018,12 +1082,12 @@
       if (opcode == SpvOpIgnoreIntersectionKHR) {
         _.current_function().RegisterExecutionModelLimitation(
             SpvExecutionModelAnyHitKHR,
-            "OpIgnoreIntersectionKHR requires AnyHit execution model");
+            "OpIgnoreIntersectionKHR requires AnyHitKHR execution model");
       }
       if (opcode == SpvOpTerminateRayKHR) {
         _.current_function().RegisterExecutionModelLimitation(
             SpvExecutionModelAnyHitKHR,
-            "OpTerminateRayKHR requires AnyHit execution model");
+            "OpTerminateRayKHR requires AnyHitKHR execution model");
       }
 
       break;
@@ -1052,6 +1116,26 @@
       }
     }
   }
+
+  // Repeat for structural reachability.
+  for (auto& f : _.functions()) {
+    std::vector<BasicBlock*> stack;
+    auto entry = f.first_block();
+    // Skip function declarations.
+    if (entry) stack.push_back(entry);
+
+    while (!stack.empty()) {
+      auto block = stack.back();
+      stack.pop_back();
+
+      if (block->structurally_reachable()) continue;
+
+      block->set_structurally_reachable(true);
+      for (auto succ : *block->structural_successors()) {
+        stack.push_back(succ);
+      }
+    }
+  }
 }
 
 spv_result_t ControlFlowPass(ValidationState_t& _, const Instruction* inst) {
diff --git a/source/val/validate_composites.cpp b/source/val/validate_composites.cpp
index 5d6c5e3..c3d948d 100644
--- a/source/val/validate_composites.cpp
+++ b/source/val/validate_composites.cpp
@@ -505,8 +505,8 @@
   if (componentCount != resultVectorDimension) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
            << "OpVectorShuffle component literals count does not match "
-              "Result Type <id> '"
-           << _.getIdName(resultType->id()) << "'s vector component count.";
+              "Result Type <id> "
+           << _.getIdName(resultType->id()) << "s vector component count.";
   }
 
   // Vector 1 and Vector 2 must both have vector types, with the same Component
diff --git a/source/val/validate_constants.cpp b/source/val/validate_constants.cpp
index dea95c8..fdfaea5 100644
--- a/source/val/validate_constants.cpp
+++ b/source/val/validate_constants.cpp
@@ -26,8 +26,8 @@
   auto type = _.FindDef(inst->type_id());
   if (!type || type->opcode() != SpvOpTypeBool) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "Op" << spvOpcodeString(inst->opcode()) << " Result Type <id> '"
-           << _.getIdName(inst->type_id()) << "' is not a boolean type.";
+           << "Op" << spvOpcodeString(inst->opcode()) << " Result Type <id> "
+           << _.getIdName(inst->type_id()) << " is not a boolean type.";
   }
 
   return SPV_SUCCESS;
@@ -40,8 +40,8 @@
   const auto result_type = _.FindDef(inst->type_id());
   if (!result_type || !spvOpcodeIsComposite(result_type->opcode())) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << opcode_name << " Result Type <id> '"
-           << _.getIdName(inst->type_id()) << "' is not a composite type.";
+           << opcode_name << " Result Type <id> "
+           << _.getIdName(inst->type_id()) << " is not a composite type.";
   }
 
   const auto constituent_count = inst->words().size() - 3;
@@ -53,9 +53,8 @@
         return _.diag(SPV_ERROR_INVALID_ID, inst)
                << opcode_name
                << " Constituent <id> count does not match "
-                  "Result Type <id> '"
-               << _.getIdName(result_type->id())
-               << "'s vector component count.";
+                  "Result Type <id> "
+               << _.getIdName(result_type->id()) << "s vector component count.";
       }
       const auto component_type =
           _.FindDef(result_type->GetOperandAs<uint32_t>(1));
@@ -71,18 +70,18 @@
         if (!constituent ||
             !spvOpcodeIsConstantOrUndef(constituent->opcode())) {
           return _.diag(SPV_ERROR_INVALID_ID, inst)
-                 << opcode_name << " Constituent <id> '"
+                 << opcode_name << " Constituent <id> "
                  << _.getIdName(constituent_id)
-                 << "' is not a constant or undef.";
+                 << " is not a constant or undef.";
         }
         const auto constituent_result_type = _.FindDef(constituent->type_id());
         if (!constituent_result_type ||
             component_type->opcode() != constituent_result_type->opcode()) {
           return _.diag(SPV_ERROR_INVALID_ID, inst)
-                 << opcode_name << " Constituent <id> '"
+                 << opcode_name << " Constituent <id> "
                  << _.getIdName(constituent_id)
-                 << "'s type does not match Result Type <id> '"
-                 << _.getIdName(result_type->id()) << "'s vector element type.";
+                 << "s type does not match Result Type <id> "
+                 << _.getIdName(result_type->id()) << "s vector element type.";
         }
       }
     } break;
@@ -93,8 +92,8 @@
         return _.diag(SPV_ERROR_INVALID_ID, inst)
                << opcode_name
                << " Constituent <id> count does not match "
-                  "Result Type <id> '"
-               << _.getIdName(result_type->id()) << "'s matrix column count.";
+                  "Result Type <id> "
+               << _.getIdName(result_type->id()) << "s matrix column count.";
       }
 
       const auto column_type = _.FindDef(result_type->words()[2]);
@@ -120,9 +119,9 @@
           // The message says "... or undef" because the spec does not say
           // undef is a constant.
           return _.diag(SPV_ERROR_INVALID_ID, inst)
-                 << opcode_name << " Constituent <id> '"
+                 << opcode_name << " Constituent <id> "
                  << _.getIdName(constituent_id)
-                 << "' is not a constant or undef.";
+                 << " is not a constant or undef.";
         }
         const auto vector = _.FindDef(constituent->type_id());
         if (!vector) {
@@ -131,28 +130,28 @@
         }
         if (column_type->opcode() != vector->opcode()) {
           return _.diag(SPV_ERROR_INVALID_ID, inst)
-                 << opcode_name << " Constituent <id> '"
+                 << opcode_name << " Constituent <id> "
                  << _.getIdName(constituent_id)
-                 << "' type does not match Result Type <id> '"
-                 << _.getIdName(result_type->id()) << "'s matrix column type.";
+                 << " type does not match Result Type <id> "
+                 << _.getIdName(result_type->id()) << "s matrix column type.";
         }
         const auto vector_component_type =
             _.FindDef(vector->GetOperandAs<uint32_t>(1));
         if (component_type->id() != vector_component_type->id()) {
           return _.diag(SPV_ERROR_INVALID_ID, inst)
-                 << opcode_name << " Constituent <id> '"
+                 << opcode_name << " Constituent <id> "
                  << _.getIdName(constituent_id)
-                 << "' component type does not match Result Type <id> '"
+                 << " component type does not match Result Type <id> "
                  << _.getIdName(result_type->id())
-                 << "'s matrix column component type.";
+                 << "s matrix column component type.";
         }
         if (component_count != vector->words()[3]) {
           return _.diag(SPV_ERROR_INVALID_ID, inst)
-                 << opcode_name << " Constituent <id> '"
+                 << opcode_name << " Constituent <id> "
                  << _.getIdName(constituent_id)
-                 << "' vector component count does not match Result Type <id> '"
+                 << " vector component count does not match Result Type <id> "
                  << _.getIdName(result_type->id())
-                 << "'s vector component count.";
+                 << "s vector component count.";
         }
       }
     } break;
@@ -175,8 +174,8 @@
         return _.diag(SPV_ERROR_INVALID_ID, inst)
                << opcode_name
                << " Constituent count does not match "
-                  "Result Type <id> '"
-               << _.getIdName(result_type->id()) << "'s array length.";
+                  "Result Type <id> "
+               << _.getIdName(result_type->id()) << "s array length.";
       }
       for (size_t constituent_index = 2;
            constituent_index < inst->operands().size(); constituent_index++) {
@@ -186,9 +185,9 @@
         if (!constituent ||
             !spvOpcodeIsConstantOrUndef(constituent->opcode())) {
           return _.diag(SPV_ERROR_INVALID_ID, inst)
-                 << opcode_name << " Constituent <id> '"
+                 << opcode_name << " Constituent <id> "
                  << _.getIdName(constituent_id)
-                 << "' is not a constant or undef.";
+                 << " is not a constant or undef.";
         }
         const auto constituent_type = _.FindDef(constituent->type_id());
         if (!constituent_type) {
@@ -197,10 +196,10 @@
         }
         if (element_type->id() != constituent_type->id()) {
           return _.diag(SPV_ERROR_INVALID_ID, inst)
-                 << opcode_name << " Constituent <id> '"
+                 << opcode_name << " Constituent <id> "
                  << _.getIdName(constituent_id)
-                 << "'s type does not match Result Type <id> '"
-                 << _.getIdName(result_type->id()) << "'s array element type.";
+                 << "s type does not match Result Type <id> "
+                 << _.getIdName(result_type->id()) << "s array element type.";
         }
       }
     } break;
@@ -208,10 +207,10 @@
       const auto member_count = result_type->words().size() - 2;
       if (member_count != constituent_count) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
-               << opcode_name << " Constituent <id> '"
+               << opcode_name << " Constituent <id> "
                << _.getIdName(inst->type_id())
-               << "' count does not match Result Type <id> '"
-               << _.getIdName(result_type->id()) << "'s struct member count.";
+               << " count does not match Result Type <id> "
+               << _.getIdName(result_type->id()) << "s struct member count.";
       }
       for (uint32_t constituent_index = 2, member_index = 1;
            constituent_index < inst->operands().size();
@@ -222,9 +221,9 @@
         if (!constituent ||
             !spvOpcodeIsConstantOrUndef(constituent->opcode())) {
           return _.diag(SPV_ERROR_INVALID_ID, inst)
-                 << opcode_name << " Constituent <id> '"
+                 << opcode_name << " Constituent <id> "
                  << _.getIdName(constituent_id)
-                 << "' is not a constant or undef.";
+                 << " is not a constant or undef.";
         }
         const auto constituent_type = _.FindDef(constituent->type_id());
         if (!constituent_type) {
@@ -237,26 +236,25 @@
         const auto member_type = _.FindDef(member_type_id);
         if (!member_type || member_type->id() != constituent_type->id()) {
           return _.diag(SPV_ERROR_INVALID_ID, inst)
-                 << opcode_name << " Constituent <id> '"
+                 << opcode_name << " Constituent <id> "
                  << _.getIdName(constituent_id)
-                 << "' type does not match the Result Type <id> '"
-                 << _.getIdName(result_type->id()) << "'s member type.";
+                 << " type does not match the Result Type <id> "
+                 << _.getIdName(result_type->id()) << "s member type.";
         }
       }
     } 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.";
+               << 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.";
+               << opcode_name << " Constituent <id> "
+               << _.getIdName(constituent_id) << " is not a constant or undef.";
       }
       const auto constituent_type = _.FindDef(constituent->type_id());
       if (!constituent_type) {
@@ -268,10 +266,10 @@
       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> '"
+               << opcode_name << " Constituent <id> "
                << _.getIdName(constituent_id)
-               << "' type does not match the Result Type <id> '"
-               << _.getIdName(result_type->id()) << "'s component type.";
+               << " type does not match the Result Type <id> "
+               << _.getIdName(result_type->id()) << "s component type.";
       }
     } break;
     default:
@@ -285,8 +283,8 @@
   const auto result_type = _.FindDef(inst->type_id());
   if (!result_type || result_type->opcode() != SpvOpTypeSampler) {
     return _.diag(SPV_ERROR_INVALID_ID, result_type)
-           << "OpConstantSampler Result Type <id> '"
-           << _.getIdName(inst->type_id()) << "' is not a sampler type.";
+           << "OpConstantSampler Result Type <id> "
+           << _.getIdName(inst->type_id()) << " is not a sampler type.";
   }
 
   return SPV_SUCCESS;
@@ -339,8 +337,8 @@
   const auto result_type = _.FindDef(inst->type_id());
   if (!result_type || !IsTypeNullable(result_type->words(), _)) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpConstantNull Result Type <id> '"
-           << _.getIdName(inst->type_id()) << "' cannot have a null value.";
+           << "OpConstantNull Result Type <id> " << _.getIdName(inst->type_id())
+           << " cannot have a null value.";
   }
 
   return SPV_SUCCESS;
diff --git a/source/val/validate_conversion.cpp b/source/val/validate_conversion.cpp
index b4e39cf..dc6b151 100644
--- a/source/val/validate_conversion.cpp
+++ b/source/val/validate_conversion.cpp
@@ -534,6 +534,24 @@
       break;
     }
 
+    case SpvOpConvertUToAccelerationStructureKHR: {
+      if (!_.IsAccelerationStructureType(result_type)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Expected Result Type to be a Acceleration Structure: "
+               << spvOpcodeString(opcode);
+      }
+
+      const uint32_t input_type = _.GetOperandTypeId(inst, 2);
+      if (!input_type || !_.IsUnsigned64BitHandle(input_type)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Expected 64-bit uint scalar or 2-component 32-bit uint "
+                  "vector as input: "
+               << spvOpcodeString(opcode);
+      }
+
+      break;
+    }
+
     default:
       break;
   }
diff --git a/source/val/validate_debug.cpp b/source/val/validate_debug.cpp
index 0a25d8a..7ab597a 100644
--- a/source/val/validate_debug.cpp
+++ b/source/val/validate_debug.cpp
@@ -28,16 +28,16 @@
   const auto type = _.FindDef(type_id);
   if (!type || SpvOpTypeStruct != type->opcode()) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpMemberName Type <id> '" << _.getIdName(type_id)
-           << "' is not a struct type.";
+           << "OpMemberName Type <id> " << _.getIdName(type_id)
+           << " is not a struct type.";
   }
   const auto member_id = inst->GetOperandAs<uint32_t>(1);
   const auto member_count = (uint32_t)(type->words().size() - 2);
   if (member_count <= member_id) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpMemberName Member <id> '" << _.getIdName(member_id)
-           << "' index is larger than Type <id> '" << _.getIdName(type->id())
-           << "'s member count.";
+           << "OpMemberName Member <id> " << _.getIdName(member_id)
+           << " index is larger than Type <id> " << _.getIdName(type->id())
+           << "s member count.";
   }
   return SPV_SUCCESS;
 }
@@ -47,8 +47,8 @@
   const auto file = _.FindDef(file_id);
   if (!file || SpvOpString != file->opcode()) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpLine Target <id> '" << _.getIdName(file_id)
-           << "' is not an OpString.";
+           << "OpLine Target <id> " << _.getIdName(file_id)
+           << " is not an OpString.";
   }
   return SPV_SUCCESS;
 }
diff --git a/source/val/validate_decorations.cpp b/source/val/validate_decorations.cpp
index 3cdb471..7505850 100644
--- a/source/val/validate_decorations.cpp
+++ b/source/val/validate_decorations.cpp
@@ -21,11 +21,13 @@
 #include <utility>
 #include <vector>
 
+#include "source/binary.h"
 #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/util/string_utils.h"
 #include "source/val/validate_scopes.h"
 #include "source/val/validation_state.h"
 
@@ -96,6 +98,14 @@
       });
 }
 
+// Returns true if the given structure type has a Block decoration.
+bool isBlock(uint32_t struct_id, ValidationState_t& vstate) {
+  const auto& decorations = vstate.id_decorations(struct_id);
+  return std::any_of(
+      decorations.begin(), decorations.end(),
+      [](const Decoration& d) { return SpvDecorationBlock == d.dec_type(); });
+}
+
 // Returns true if the given ID has the Import LinkageAttributes decoration.
 bool hasImportLinkageAttribute(uint32_t id, ValidationState_t& vstate) {
   const auto& decorations = vstate.id_decorations(id);
@@ -169,8 +179,9 @@
 }
 
 // Returns base alignment of struct member. If |roundUp| is true, also
-// ensure that structs and arrays are aligned at least to a multiple of 16
-// bytes.
+// ensure that structs, arrays, and matrices are aligned at least to a
+// multiple of 16 bytes.  (That is, when roundUp is true, this function
+// returns the *extended* alignment as it's called by the Vulkan spec.)
 uint32_t getBaseAlignment(uint32_t member_id, bool roundUp,
                           const LayoutConstraints& inherited,
                           MemberConstraints& constraints,
@@ -180,6 +191,13 @@
   // Minimal alignment is byte-aligned.
   uint32_t baseAlignment = 1;
   switch (inst->opcode()) {
+    case SpvOpTypeSampledImage:
+    case SpvOpTypeSampler:
+    case SpvOpTypeImage:
+      if (vstate.HasCapability(SpvCapabilityBindlessTextureNV))
+        return baseAlignment = vstate.samplerimage_variable_address_mode() / 8;
+      assert(0);
+      return 0;
     case SpvOpTypeInt:
     case SpvOpTypeFloat:
       baseAlignment = words[2] / 8;
@@ -209,6 +227,7 @@
         baseAlignment =
             componentAlignment * (num_columns == 3 ? 4 : num_columns);
       }
+      if (roundUp) baseAlignment = align(baseAlignment, 16u);
     } break;
     case SpvOpTypeArray:
     case SpvOpTypeRuntimeArray:
@@ -246,6 +265,13 @@
   const auto inst = vstate.FindDef(type_id);
   const auto& words = inst->words();
   switch (inst->opcode()) {
+    case SpvOpTypeSampledImage:
+    case SpvOpTypeSampler:
+    case SpvOpTypeImage:
+      if (vstate.HasCapability(SpvCapabilityBindlessTextureNV))
+        return vstate.samplerimage_variable_address_mode() / 8;
+      assert(0);
+      return 0;
     case SpvOpTypeInt:
     case SpvOpTypeFloat:
       return words[2] / 8;
@@ -286,6 +312,13 @@
   const auto inst = vstate.FindDef(member_id);
   const auto& words = inst->words();
   switch (inst->opcode()) {
+    case SpvOpTypeSampledImage:
+    case SpvOpTypeSampler:
+    case SpvOpTypeImage:
+      if (vstate.HasCapability(SpvCapabilityBindlessTextureNV))
+        return vstate.samplerimage_variable_address_mode() / 8;
+      assert(0);
+      return 0;
     case SpvOpTypeInt:
     case SpvOpTypeFloat:
       return words[2] / 8;
@@ -336,10 +369,13 @@
       const auto& lastMember = members.back();
       uint32_t offset = 0xffffffff;
       // Find the offset of the last element and add the size.
-      for (auto& decoration : vstate.id_decorations(member_id)) {
-        if (SpvDecorationOffset == decoration.dec_type() &&
-            decoration.struct_member_index() == (int)lastIdx) {
-          offset = decoration.params()[0];
+      auto member_decorations =
+          vstate.id_member_decorations(member_id, lastIdx);
+      for (auto decoration = member_decorations.begin;
+           decoration != member_decorations.end; ++decoration) {
+        assert(decoration->struct_member_index() == (int)lastIdx);
+        if (SpvDecorationOffset == decoration->dec_type()) {
+          offset = decoration->params()[0];
         }
       }
       // This check depends on the fact that all members have offsets.  This
@@ -435,15 +471,17 @@
   for (uint32_t memberIdx = 0, numMembers = uint32_t(members.size());
        memberIdx < numMembers; memberIdx++) {
     uint32_t offset = 0xffffffff;
-    for (auto& decoration : vstate.id_decorations(struct_id)) {
-      if (decoration.struct_member_index() == (int)memberIdx) {
-        switch (decoration.dec_type()) {
-          case SpvDecorationOffset:
-            offset = decoration.params()[0];
-            break;
-          default:
-            break;
-        }
+    auto member_decorations =
+        vstate.id_member_decorations(struct_id, memberIdx);
+    for (auto decoration = member_decorations.begin;
+         decoration != member_decorations.end; ++decoration) {
+      assert(decoration->struct_member_index() == (int)memberIdx);
+      switch (decoration->dec_type()) {
+        case SpvDecorationOffset:
+          offset = decoration->params()[0];
+          break;
+        default:
+          break;
       }
     }
     member_offsets.push_back(
@@ -455,7 +493,7 @@
         return lhs.offset < rhs.offset;
       });
 
-  // Now scan from lowest offest to highest offset.
+  // Now scan from lowest offset to highest offset.
   uint32_t nextValidOffset = 0;
   for (size_t ordered_member_idx = 0;
        ordered_member_idx < member_offsets.size(); ordered_member_idx++) {
@@ -623,7 +661,8 @@
 }
 
 // Returns true if all ids of given type have a specified decoration.
-bool checkForRequiredDecoration(uint32_t struct_id, SpvDecoration decoration,
+bool checkForRequiredDecoration(uint32_t struct_id,
+                                std::function<bool(SpvDecoration)> checker,
                                 SpvOp type, ValidationState_t& vstate) {
   const auto& members = getStructMembers(struct_id, vstate);
   for (size_t memberIdx = 0; memberIdx < members.size(); memberIdx++) {
@@ -631,10 +670,10 @@
     if (type != vstate.FindDef(id)->opcode()) continue;
     bool found = false;
     for (auto& dec : vstate.id_decorations(id)) {
-      if (decoration == dec.dec_type()) found = true;
+      if (checker(dec.dec_type())) found = true;
     }
     for (auto& dec : vstate.id_decorations(struct_id)) {
-      if (decoration == dec.dec_type() &&
+      if (checker(dec.dec_type()) &&
           (int)memberIdx == dec.struct_member_index()) {
         found = true;
       }
@@ -644,7 +683,7 @@
     }
   }
   for (auto id : getStructMembers(struct_id, SpvOpTypeStruct, vstate)) {
-    if (!checkForRequiredDecoration(id, decoration, type, vstate)) {
+    if (!checkForRequiredDecoration(id, checker, type, vstate)) {
       return false;
     }
   }
@@ -702,7 +741,7 @@
       if (d.dec_type() == SpvDecorationLocation ||
           d.dec_type() == SpvDecorationComponent) {
         return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
-               << "A BuiltIn variable (id " << var_id
+               << vstate.VkErrorID(4915) << "A BuiltIn variable (id " << var_id
                << ") cannot have any Location or Component decorations";
       }
     }
@@ -710,12 +749,12 @@
   return SPV_SUCCESS;
 }
 
-// Checks whether proper decorations have been appied to the entry points.
+// Checks whether proper decorations have been applied to the entry points.
 spv_result_t CheckDecorationsOfEntryPoints(ValidationState_t& vstate) {
   for (uint32_t entry_point : vstate.entry_points()) {
     const auto& descs = vstate.entry_point_descriptions(entry_point);
-    int num_builtin_inputs = 0;
-    int num_builtin_outputs = 0;
+    int num_builtin_block_inputs = 0;
+    int num_builtin_block_outputs = 0;
     int num_workgroup_variables = 0;
     int num_workgroup_variables_with_block = 0;
     int num_workgroup_variables_with_aliased = 0;
@@ -766,9 +805,20 @@
         Instruction* type_instr = vstate.FindDef(type_id);
         if (type_instr && SpvOpTypeStruct == type_instr->opcode() &&
             isBuiltInStruct(type_id, vstate)) {
-          if (storage_class == SpvStorageClassInput) ++num_builtin_inputs;
-          if (storage_class == SpvStorageClassOutput) ++num_builtin_outputs;
-          if (num_builtin_inputs > 1 || num_builtin_outputs > 1) break;
+          if (!isBlock(type_id, vstate)) {
+            return vstate.diag(SPV_ERROR_INVALID_DATA, vstate.FindDef(type_id))
+                   << vstate.VkErrorID(4919)
+                   << "Interface struct has no Block decoration but has "
+                      "BuiltIn members. "
+                      "Location decorations must be used on each member of "
+                      "OpVariable with a structure type that is a block not "
+                      "decorated with Location.";
+          }
+          if (storage_class == SpvStorageClassInput) ++num_builtin_block_inputs;
+          if (storage_class == SpvStorageClassOutput)
+            ++num_builtin_block_outputs;
+          if (num_builtin_block_inputs > 1 || num_builtin_block_outputs > 1)
+            break;
           if (auto error = CheckBuiltInVariable(interface, vstate))
             return error;
         } else if (isBuiltInVar(interface, vstate)) {
@@ -785,8 +835,58 @@
               ++num_workgroup_variables_with_aliased;
           }
         }
+
+        if (spvIsVulkanEnv(vstate.context()->target_env)) {
+          const auto* models = vstate.GetExecutionModels(entry_point);
+          const bool has_frag =
+              models->find(SpvExecutionModelFragment) != models->end();
+          const bool has_vert =
+              models->find(SpvExecutionModelVertex) != models->end();
+          for (const auto& decoration :
+               vstate.id_decorations(var_instr->id())) {
+            if (decoration == SpvDecorationFlat ||
+                decoration == SpvDecorationNoPerspective ||
+                decoration == SpvDecorationSample ||
+                decoration == SpvDecorationCentroid) {
+              // VUID 04670 already validates these decorations are input/output
+              if (storage_class == SpvStorageClassInput &&
+                  (models->size() > 1 || has_vert)) {
+                return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
+                       << vstate.VkErrorID(6202)
+                       << "OpEntryPoint interfaces variable must not be vertex "
+                          "execution model with an input storage class for "
+                          "Entry Point id "
+                       << entry_point << ".";
+              } else if (storage_class == SpvStorageClassOutput &&
+                         (models->size() > 1 || has_frag)) {
+                return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
+                       << vstate.VkErrorID(6201)
+                       << "OpEntryPoint interfaces variable must not be "
+                          "fragment "
+                          "execution model with an output storage class for "
+                          "Entry Point id "
+                       << entry_point << ".";
+              }
+            }
+          }
+
+          const bool has_flat =
+              hasDecoration(var_instr->id(), SpvDecorationFlat, vstate);
+          if (has_frag && storage_class == SpvStorageClassInput && !has_flat &&
+              ((vstate.IsFloatScalarType(type_id) &&
+                vstate.GetBitWidth(type_id) == 64) ||
+               vstate.IsIntScalarOrVectorType(type_id))) {
+            return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
+                     << vstate.VkErrorID(4744)
+                     << "Fragment OpEntryPoint operand "
+                     << interface << " with Input interfaces with integer or "
+                                     "float type must have a Flat decoration "
+                                     "for Entry Point id "
+                     << entry_point << ".";
+          }
+        }
       }
-      if (num_builtin_inputs > 1 || num_builtin_outputs > 1) {
+      if (num_builtin_block_inputs > 1 || num_builtin_block_outputs > 1) {
         return vstate.diag(SPV_ERROR_INVALID_BINARY,
                            vstate.FindDef(entry_point))
                << "There must be at most one object per Storage Class that can "
@@ -798,8 +898,8 @@
       // targeted by an OpEntryPoint instruction
       for (auto& decoration : vstate.id_decorations(entry_point)) {
         if (SpvDecorationLinkageAttributes == decoration.dec_type()) {
-          const char* linkage_name =
-              reinterpret_cast<const char*>(&decoration.params()[0]);
+          const std::string linkage_name =
+              spvtools::utils::MakeString(decoration.params());
           return vstate.diag(SPV_ERROR_INVALID_BINARY,
                              vstate.FindDef(entry_point))
                  << "The LinkageAttributes Decoration (Linkage name: "
@@ -857,21 +957,23 @@
     LayoutConstraints& constraint =
         (*constraints)[std::make_pair(struct_id, memberIdx)];
     constraint = inherited;
-    for (auto& decoration : vstate.id_decorations(struct_id)) {
-      if (decoration.struct_member_index() == (int)memberIdx) {
-        switch (decoration.dec_type()) {
-          case SpvDecorationRowMajor:
-            constraint.majorness = kRowMajor;
-            break;
-          case SpvDecorationColMajor:
-            constraint.majorness = kColumnMajor;
-            break;
-          case SpvDecorationMatrixStride:
-            constraint.matrix_stride = decoration.params()[0];
-            break;
-          default:
-            break;
-        }
+    auto member_decorations =
+        vstate.id_member_decorations(struct_id, memberIdx);
+    for (auto decoration = member_decorations.begin;
+         decoration != member_decorations.end; ++decoration) {
+      assert(decoration->struct_member_index() == (int)memberIdx);
+      switch (decoration->dec_type()) {
+        case SpvDecorationRowMajor:
+          constraint.majorness = kRowMajor;
+          break;
+        case SpvDecorationColMajor:
+          constraint.majorness = kColumnMajor;
+          break;
+        case SpvDecorationMatrixStride:
+          constraint.matrix_stride = decoration->params()[0];
+          break;
+        default:
+          break;
       }
     }
 
@@ -935,41 +1037,41 @@
       const bool storage_buffer = storageClass == SpvStorageClassStorageBuffer;
 
       if (spvIsVulkanEnv(vstate.context()->target_env)) {
-        // Vulkan 14.5.1: There must be no more than one PushConstant block
-        // per entry point.
+        // Vulkan: There must be no more than one PushConstant block per entry
+        // point.
         if (push_constant) {
           auto entry_points = vstate.EntryPointReferences(var_id);
           for (auto ep_id : entry_points) {
             const bool already_used = !uses_push_constant.insert(ep_id).second;
             if (already_used) {
               return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
-                     << "Entry point id '" << ep_id
+                     << vstate.VkErrorID(6674) << "Entry point id '" << ep_id
                      << "' uses more than one PushConstant interface.\n"
-                     << "From Vulkan spec, section 14.5.1:\n"
+                     << "From Vulkan spec:\n"
                      << "There must be no more than one push constant block "
                      << "statically used per shader entry point.";
             }
           }
         }
-        // Vulkan 14.5.2: Check DescriptorSet and Binding decoration for
+        // Vulkan: Check DescriptorSet and Binding decoration for
         // UniformConstant which cannot be a struct.
         if (uniform_constant) {
           auto entry_points = vstate.EntryPointReferences(var_id);
           if (!entry_points.empty() &&
               !hasDecoration(var_id, SpvDecorationDescriptorSet, vstate)) {
             return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
-                   << "UniformConstant id '" << var_id
+                   << vstate.VkErrorID(6677) << "UniformConstant id '" << var_id
                    << "' is missing DescriptorSet decoration.\n"
-                   << "From Vulkan spec, section 14.5.2:\n"
+                   << "From Vulkan spec:\n"
                    << "These variables must have DescriptorSet and Binding "
                       "decorations specified";
           }
           if (!entry_points.empty() &&
               !hasDecoration(var_id, SpvDecorationBinding, vstate)) {
             return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
-                   << "UniformConstant id '" << var_id
+                   << vstate.VkErrorID(6677) << "UniformConstant id '" << var_id
                    << "' is missing Binding decoration.\n"
-                   << "From Vulkan spec, section 14.5.2:\n"
+                   << "From Vulkan spec:\n"
                    << "These variables must have DescriptorSet and Binding "
                       "decorations specified";
           }
@@ -996,7 +1098,7 @@
       }
 
       const bool phys_storage_buffer =
-          storageClass == SpvStorageClassPhysicalStorageBufferEXT;
+          storageClass == SpvStorageClassPhysicalStorageBuffer;
       const bool workgroup =
           storageClass == SpvStorageClassWorkgroup &&
           vstate.HasCapability(SpvCapabilityWorkgroupMemoryExplicitLayoutKHR);
@@ -1030,55 +1132,55 @@
               hasDecoration(id, SpvDecorationBufferBlock, vstate);
           if (storage_buffer && buffer_block) {
             return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
-                   << "Storage buffer id '" << var_id
+                   << vstate.VkErrorID(6675) << "Storage buffer id '" << var_id
                    << " In Vulkan, BufferBlock is disallowed on variables in "
                       "the StorageBuffer storage class";
           }
-          // Vulkan 14.5.1/2: Check Block decoration for PushConstant, Uniform
+          // Vulkan: Check Block decoration for PushConstant, Uniform
           // and StorageBuffer variables. Uniform can also use BufferBlock.
           if (push_constant && !block) {
             return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
-                   << "PushConstant id '" << id
+                   << vstate.VkErrorID(6675) << "PushConstant id '" << id
                    << "' is missing Block decoration.\n"
-                   << "From Vulkan spec, section 14.5.1:\n"
+                   << "From Vulkan spec:\n"
                    << "Such variables must be identified with a Block "
                       "decoration";
           }
           if (storage_buffer && !block) {
             return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
-                   << "StorageBuffer id '" << id
+                   << vstate.VkErrorID(6675) << "StorageBuffer id '" << id
                    << "' is missing Block decoration.\n"
-                   << "From Vulkan spec, section 14.5.2:\n"
+                   << "From Vulkan spec:\n"
                    << "Such variables must be identified with a Block "
                       "decoration";
           }
           if (uniform && !block && !buffer_block) {
             return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
-                   << "Uniform id '" << id
+                   << vstate.VkErrorID(6676) << "Uniform id '" << id
                    << "' is missing Block or BufferBlock decoration.\n"
-                   << "From Vulkan spec, section 14.5.2:\n"
+                   << "From Vulkan spec:\n"
                    << "Such variables must be identified with a Block or "
                       "BufferBlock decoration";
           }
-          // Vulkan 14.5.2: Check DescriptorSet and Binding decoration for
+          // Vulkan: Check DescriptorSet and Binding decoration for
           // Uniform and StorageBuffer variables.
           if (uniform || storage_buffer) {
             auto entry_points = vstate.EntryPointReferences(var_id);
             if (!entry_points.empty() &&
                 !hasDecoration(var_id, SpvDecorationDescriptorSet, vstate)) {
               return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
-                     << sc_str << " id '" << var_id
+                     << vstate.VkErrorID(6677) << sc_str << " id '" << var_id
                      << "' is missing DescriptorSet decoration.\n"
-                     << "From Vulkan spec, section 14.5.2:\n"
+                     << "From Vulkan spec:\n"
                      << "These variables must have DescriptorSet and Binding "
                         "decorations specified";
             }
             if (!entry_points.empty() &&
                 !hasDecoration(var_id, SpvDecorationBinding, vstate)) {
               return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(var_id))
-                     << sc_str << " id '" << var_id
+                     << vstate.VkErrorID(6677) << sc_str << " id '" << var_id
                      << "' is missing Binding decoration.\n"
-                     << "From Vulkan spec, section 14.5.2:\n"
+                     << "From Vulkan spec:\n"
                      << "These variables must have DescriptorSet and Binding "
                         "decorations specified";
             }
@@ -1123,30 +1225,48 @@
               return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
                      << "Structure id " << id << " decorated as " << deco_str
                      << " must not use GLSLPacked decoration.";
-            } else if (!checkForRequiredDecoration(id, SpvDecorationArrayStride,
-                                                   SpvOpTypeArray, vstate)) {
+            } else if (!checkForRequiredDecoration(
+                           id,
+                           [](SpvDecoration d) {
+                             return d == SpvDecorationArrayStride;
+                           },
+                           SpvOpTypeArray, vstate)) {
               return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
                      << "Structure id " << id << " decorated as " << deco_str
                      << " must be explicitly laid out with ArrayStride "
                         "decorations.";
-            } else if (!checkForRequiredDecoration(id,
-                                                   SpvDecorationMatrixStride,
-                                                   SpvOpTypeMatrix, vstate)) {
+            } else if (!checkForRequiredDecoration(
+                           id,
+                           [](SpvDecoration d) {
+                             return d == SpvDecorationMatrixStride;
+                           },
+                           SpvOpTypeMatrix, vstate)) {
               return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
                      << "Structure id " << id << " decorated as " << deco_str
                      << " must be explicitly laid out with MatrixStride "
                         "decorations.";
+            } else if (!checkForRequiredDecoration(
+                           id,
+                           [](SpvDecoration d) {
+                             return d == SpvDecorationRowMajor ||
+                                    d == SpvDecorationColMajor;
+                           },
+                           SpvOpTypeMatrix, vstate)) {
+              return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
+                     << "Structure id " << id << " decorated as " << deco_str
+                     << " must be explicitly laid out with RowMajor or "
+                        "ColMajor decorations.";
             } else if (blockRules &&
-                       (SPV_SUCCESS != (recursive_status = checkLayout(
-                                            id, sc_str, deco_str, true,
-                                            scalar_block_layout, 0,
-                                            constraints, vstate)))) {
+                       (SPV_SUCCESS !=
+                        (recursive_status = checkLayout(
+                             id, sc_str, deco_str, true, scalar_block_layout, 0,
+                             constraints, vstate)))) {
               return recursive_status;
             } else if (bufferRules &&
-                       (SPV_SUCCESS != (recursive_status = checkLayout(
-                                            id, sc_str, deco_str, false,
-                                            scalar_block_layout, 0,
-                                            constraints, vstate)))) {
+                       (SPV_SUCCESS !=
+                        (recursive_status = checkLayout(
+                             id, sc_str, deco_str, false, scalar_block_layout,
+                             0, constraints, vstate)))) {
               return recursive_status;
             }
           }
@@ -1176,32 +1296,6 @@
   }
 }
 
-// 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 PerIDKey = std::tuple<SpvDecoration, uint32_t>;
   using PerMemberKey = std::tuple<SpvDecoration, uint32_t, uint32_t>;
@@ -1234,7 +1328,7 @@
       if (already_used && AtMostOncePerId(dec_type)) {
         return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
                << "ID '" << id << "' decorated with "
-               << GetDecorationName(dec_type)
+               << vstate.SpvDecorationString(dec_type)
                << " multiple times is not allowed.";
       }
       // Verify certain mutually exclusive decorations are not both applied on
@@ -1254,8 +1348,9 @@
         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.";
+                 << vstate.SpvDecorationString(dec_type) << " and "
+                 << vstate.SpvDecorationString(excl_dec_type)
+                 << " is not allowed.";
         }
       }
     } else if (SpvOpMemberDecorate == inst.opcode()) {
@@ -1267,7 +1362,7 @@
       if (already_used && AtMostOncePerMember(dec_type)) {
         return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
                << "ID '" << id << "', member '" << member_id
-               << "' decorated with " << GetDecorationName(dec_type)
+               << "' decorated with " << vstate.SpvDecorationString(dec_type)
                << " multiple times is not allowed.";
       }
       // Verify certain mutually exclusive decorations are not both applied on
@@ -1287,8 +1382,9 @@
         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)
+                 << "' decorated with both "
+                 << vstate.SpvDecorationString(dec_type) << " and "
+                 << vstate.SpvDecorationString(excl_dec_type)
                  << " is not allowed.";
         }
       }
@@ -1386,11 +1482,11 @@
         storage != SpvStorageClassUniform &&
         storage != SpvStorageClassPushConstant &&
         storage != SpvStorageClassInput && storage != SpvStorageClassOutput &&
-        storage != SpvStorageClassPhysicalStorageBufferEXT) {
+        storage != SpvStorageClassPhysicalStorageBuffer) {
       return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
              << "FPRoundingMode decoration can be applied only to the "
                 "Object operand of an OpStore in the StorageBuffer, "
-                "PhysicalStorageBufferEXT, Uniform, PushConstant, Input, or "
+                "PhysicalStorageBuffer, Uniform, PushConstant, Input, or "
                 "Output Storage Classes.";
     }
   }
@@ -1633,6 +1729,24 @@
             "of a structure type";
 }
 
+spv_result_t CheckRelaxPrecisionDecoration(ValidationState_t& vstate,
+                                           const Instruction& inst,
+                                           const Decoration& decoration) {
+  // This is not the most precise check, but the rules for RelaxPrecision are
+  // very general, and it will be difficult to implement precisely.  For now,
+  // I will only check for the cases that cause problems for the optimizer.
+  if (!spvOpcodeGeneratesType(inst.opcode())) {
+    return SPV_SUCCESS;
+  }
+
+  if (decoration.struct_member_index() != Decoration::kInvalidMember &&
+      inst.opcode() == SpvOpTypeStruct) {
+    return SPV_SUCCESS;
+  }
+  return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
+         << "RelaxPrecision decoration cannot be applied to a type";
+}
+
 #define PASS_OR_BAIL_AT_LINE(X, LINE)           \
   {                                             \
     spv_result_t e##LINE = (X);                 \
@@ -1687,6 +1801,10 @@
         case SpvDecorationLocation:
           PASS_OR_BAIL(CheckLocationDecoration(vstate, *inst, decoration));
           break;
+        case SpvDecorationRelaxedPrecision:
+          PASS_OR_BAIL(
+              CheckRelaxPrecisionDecoration(vstate, *inst, decoration));
+          break;
         default:
           break;
       }
diff --git a/source/val/validate_execution_limitations.cpp b/source/val/validate_execution_limitations.cpp
index aac1c49..e1f4d7b 100644
--- a/source/val/validate_execution_limitations.cpp
+++ b/source/val/validate_execution_limitations.cpp
@@ -44,8 +44,8 @@
         std::string reason;
         if (!func->IsCompatibleWithExecutionModel(model, &reason)) {
           return _.diag(SPV_ERROR_INVALID_ID, inst)
-                 << "OpEntryPoint Entry Point <id> '" << _.getIdName(entry_id)
-                 << "'s callgraph contains function <id> "
+                 << "OpEntryPoint Entry Point <id> " << _.getIdName(entry_id)
+                 << "s callgraph contains function <id> "
                  << _.getIdName(inst->id())
                  << ", which cannot be used with the current execution "
                     "model:\n"
@@ -57,9 +57,8 @@
     std::string reason;
     if (!func->CheckLimitations(_, _.function(entry_id), &reason)) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
-             << "OpEntryPoint Entry Point <id> '" << _.getIdName(entry_id)
-             << "'s callgraph contains function <id> "
-             << _.getIdName(inst->id())
+             << "OpEntryPoint Entry Point <id> " << _.getIdName(entry_id)
+             << "s callgraph contains function <id> " << _.getIdName(inst->id())
              << ", which cannot be used with the current execution "
                 "modes:\n"
              << reason;
diff --git a/source/val/validate_extensions.cpp b/source/val/validate_extensions.cpp
index dccbe14..1e69cb3 100644
--- a/source/val/validate_extensions.cpp
+++ b/source/val/validate_extensions.cpp
@@ -129,7 +129,7 @@
   }
 
 // True if the operand of a debug info instruction |inst| at |word_index|
-// satisifies |expectation| that is given as a function. Otherwise,
+// satisfies |expectation| that is given as a function. Otherwise,
 // returns false.
 bool DoesDebugInfoOperandMatchExpectation(
     const ValidationState_t& _,
@@ -147,6 +147,24 @@
   return true;
 }
 
+// Overload for NonSemanticShaderDebugInfo100Instructions.
+bool DoesDebugInfoOperandMatchExpectation(
+    const ValidationState_t& _,
+    const std::function<bool(NonSemanticShaderDebugInfo100Instructions)>&
+        expectation,
+    const Instruction* inst, uint32_t word_index) {
+  if (inst->words().size() <= word_index) return false;
+  auto* debug_inst = _.FindDef(inst->word(word_index));
+  if (debug_inst->opcode() != SpvOpExtInst ||
+      (debug_inst->ext_inst_type() !=
+       SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100) ||
+      !expectation(
+          NonSemanticShaderDebugInfo100Instructions(debug_inst->word(4)))) {
+    return false;
+  }
+  return true;
+}
+
 // Check that the operand of a debug info instruction |inst| at |word_index|
 // is a result id of an debug info instruction whose debug instruction type
 // is |expected_debug_inst|.
@@ -223,6 +241,18 @@
     const Instruction* inst, uint32_t word_index,
     const std::function<std::string()>& ext_inst_name,
     bool allow_template_param) {
+  // Check for NonSemanticShaderDebugInfo100 specific types.
+  if (inst->ext_inst_type() ==
+      SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100) {
+    std::function<bool(NonSemanticShaderDebugInfo100Instructions)> expectation =
+        [](NonSemanticShaderDebugInfo100Instructions dbg_inst) {
+          return dbg_inst == NonSemanticShaderDebugInfo100DebugTypeMatrix;
+        };
+    if (DoesDebugInfoOperandMatchExpectation(_, expectation, inst, word_index))
+      return SPV_SUCCESS;
+  }
+
+  // Check for common types.
   std::function<bool(CommonDebugInfoInstructions)> expectation =
       [&allow_template_param](CommonDebugInfoInstructions dbg_inst) {
         if (allow_template_param &&
@@ -280,8 +310,7 @@
     return _.diag(SPV_ERROR_INVALID_ID, inst) << "Name must be an OpString";
   }
 
-  const std::string name_str = reinterpret_cast<const char*>(
-      name->words().data() + name->operands()[1].offset);
+  const std::string name_str = name->GetOperandAs<std::string>(1);
   bool found = false;
   for (auto& desc : _.entry_point_descriptions(kernel_id)) {
     if (name_str == desc.name) {
@@ -727,10 +756,10 @@
   if (_.version() < SPV_SPIRV_VERSION_WORD(1, 4)) {
     std::string extension = GetExtensionString(&(inst->c_inst()));
     if (extension ==
-        ExtensionToString(kSPV_KHR_workgroup_memory_explicit_layout)) {
+            ExtensionToString(kSPV_KHR_workgroup_memory_explicit_layout) ||
+        extension == ExtensionToString(kSPV_EXT_mesh_shader)) {
       return _.diag(SPV_ERROR_WRONG_VERSION, inst)
-             << "SPV_KHR_workgroup_memory_explicit_layout extension "
-                "requires SPIR-V version 1.4 or later.";
+             << extension << " extension requires SPIR-V version 1.4 or later.";
     }
   }
 
@@ -740,9 +769,9 @@
 spv_result_t ValidateExtInstImport(ValidationState_t& _,
                                    const Instruction* inst) {
   const auto name_id = 1;
-  if (!_.HasExtension(kSPV_KHR_non_semantic_info)) {
-    const std::string name(reinterpret_cast<const char*>(
-        inst->words().data() + inst->operands()[name_id].offset));
+  if (_.version() <= SPV_SPIRV_VERSION_WORD(1, 5) &&
+      !_.HasExtension(kSPV_KHR_non_semantic_info)) {
+    const std::string name = inst->GetOperandAs<std::string>(name_id);
     if (name.find("NonSemantic.") == 0) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << "NonSemantic extended instruction sets cannot be declared "
@@ -774,7 +803,7 @@
     assert(import_inst);
 
     std::ostringstream ss;
-    ss << reinterpret_cast<const char*>(import_inst->words().data() + 2);
+    ss << import_inst->GetOperandAs<std::string>(1);
     ss << " ";
     ss << desc->name;
 
@@ -2720,6 +2749,86 @@
 
     auto num_words = inst->words().size();
 
+    // Handle any non-common NonSemanticShaderDebugInfo instructions.
+    if (vulkanDebugInfo) {
+      const NonSemanticShaderDebugInfo100Instructions ext_inst_key =
+          NonSemanticShaderDebugInfo100Instructions(ext_inst_index);
+      switch (ext_inst_key) {
+        // The following block of instructions will be handled by the common
+        // validation.
+        case NonSemanticShaderDebugInfo100DebugInfoNone:
+        case NonSemanticShaderDebugInfo100DebugCompilationUnit:
+        case NonSemanticShaderDebugInfo100DebugTypeBasic:
+        case NonSemanticShaderDebugInfo100DebugTypePointer:
+        case NonSemanticShaderDebugInfo100DebugTypeQualifier:
+        case NonSemanticShaderDebugInfo100DebugTypeArray:
+        case NonSemanticShaderDebugInfo100DebugTypeVector:
+        case NonSemanticShaderDebugInfo100DebugTypedef:
+        case NonSemanticShaderDebugInfo100DebugTypeFunction:
+        case NonSemanticShaderDebugInfo100DebugTypeEnum:
+        case NonSemanticShaderDebugInfo100DebugTypeComposite:
+        case NonSemanticShaderDebugInfo100DebugTypeMember:
+        case NonSemanticShaderDebugInfo100DebugTypeInheritance:
+        case NonSemanticShaderDebugInfo100DebugTypePtrToMember:
+        case NonSemanticShaderDebugInfo100DebugTypeTemplate:
+        case NonSemanticShaderDebugInfo100DebugTypeTemplateParameter:
+        case NonSemanticShaderDebugInfo100DebugTypeTemplateTemplateParameter:
+        case NonSemanticShaderDebugInfo100DebugTypeTemplateParameterPack:
+        case NonSemanticShaderDebugInfo100DebugGlobalVariable:
+        case NonSemanticShaderDebugInfo100DebugFunctionDeclaration:
+        case NonSemanticShaderDebugInfo100DebugFunction:
+        case NonSemanticShaderDebugInfo100DebugLexicalBlock:
+        case NonSemanticShaderDebugInfo100DebugLexicalBlockDiscriminator:
+        case NonSemanticShaderDebugInfo100DebugScope:
+        case NonSemanticShaderDebugInfo100DebugNoScope:
+        case NonSemanticShaderDebugInfo100DebugInlinedAt:
+        case NonSemanticShaderDebugInfo100DebugLocalVariable:
+        case NonSemanticShaderDebugInfo100DebugInlinedVariable:
+        case NonSemanticShaderDebugInfo100DebugDeclare:
+        case NonSemanticShaderDebugInfo100DebugValue:
+        case NonSemanticShaderDebugInfo100DebugOperation:
+        case NonSemanticShaderDebugInfo100DebugExpression:
+        case NonSemanticShaderDebugInfo100DebugMacroDef:
+        case NonSemanticShaderDebugInfo100DebugMacroUndef:
+        case NonSemanticShaderDebugInfo100DebugImportedEntity:
+        case NonSemanticShaderDebugInfo100DebugSource:
+          break;
+        case NonSemanticShaderDebugInfo100DebugTypeMatrix: {
+          CHECK_DEBUG_OPERAND("Vector Type", CommonDebugInfoDebugTypeVector, 5);
+
+          CHECK_CONST_UINT_OPERAND("Vector Count", 6);
+
+          uint32_t vector_count = inst->word(6);
+          uint64_t const_val;
+          if (!_.GetConstantValUint64(vector_count, &const_val)) {
+            return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                   << ext_inst_name()
+                   << ": Vector Count must be 32-bit integer OpConstant";
+          }
+
+          vector_count = const_val & 0xffffffff;
+          if (!vector_count || vector_count > 4) {
+            return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                   << ext_inst_name() << ": Vector Count must be positive "
+                   << "integer less than or equal to 4";
+          }
+          break;
+        }
+        // TODO: Add validation rules for remaining cases as well.
+        case NonSemanticShaderDebugInfo100DebugFunctionDefinition:
+        case NonSemanticShaderDebugInfo100DebugSourceContinued:
+        case NonSemanticShaderDebugInfo100DebugLine:
+        case NonSemanticShaderDebugInfo100DebugNoLine:
+        case NonSemanticShaderDebugInfo100DebugBuildIdentifier:
+        case NonSemanticShaderDebugInfo100DebugStoragePath:
+        case NonSemanticShaderDebugInfo100DebugEntryPoint:
+          break;
+        case NonSemanticShaderDebugInfo100InstructionsMax:
+          assert(0);
+          break;
+      }
+    }
+
     // Handle any non-common OpenCL insts, then common
     if (ext_inst_type != SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100 ||
         OpenCLDebugInfo100Instructions(ext_inst_index) !=
@@ -2805,8 +2914,9 @@
             bool invalid = false;
             auto* component_count = _.FindDef(inst->word(i));
             if (IsConstIntScalarTypeWith32Or64Bits(_, component_count)) {
-              // TODO: We need a spec discussion for the bindless array.
-              if (!component_count->word(3)) {
+              // TODO: We need a spec discussion for the runtime array for
+              // OpenCL.
+              if (!vulkanDebugInfo && !component_count->word(3)) {
                 invalid = true;
               }
             } else if (component_count->words().size() > 6 &&
@@ -3264,8 +3374,7 @@
     }
   } else if (ext_inst_type == SPV_EXT_INST_TYPE_NONSEMANTIC_CLSPVREFLECTION) {
     auto import_inst = _.FindDef(inst->GetOperandAs<uint32_t>(2));
-    const std::string name(reinterpret_cast<const char*>(
-        import_inst->words().data() + import_inst->operands()[1].offset));
+    const std::string name = import_inst->GetOperandAs<std::string>(1);
     const std::string reflection = "NonSemantic.ClspvReflection.";
     char* end_ptr;
     auto version_string = name.substr(reflection.size());
diff --git a/source/val/validate_function.cpp b/source/val/validate_function.cpp
index 596186b..0ccf5a9 100644
--- a/source/val/validate_function.cpp
+++ b/source/val/validate_function.cpp
@@ -58,16 +58,16 @@
   const auto function_type = _.FindDef(function_type_id);
   if (!function_type || SpvOpTypeFunction != function_type->opcode()) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpFunction Function Type <id> '" << _.getIdName(function_type_id)
-           << "' is not a function type.";
+           << "OpFunction Function Type <id> " << _.getIdName(function_type_id)
+           << " is not a function type.";
   }
 
   const auto return_id = function_type->GetOperandAs<uint32_t>(1);
   if (return_id != inst->type_id()) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpFunction Result Type <id> '" << _.getIdName(inst->type_id())
-           << "' does not match the Function Type's return type <id> '"
-           << _.getIdName(return_id) << "'.";
+           << "OpFunction Result Type <id> " << _.getIdName(inst->type_id())
+           << " does not match the Function Type's return type <id> "
+           << _.getIdName(return_id) << ".";
   }
 
   const std::vector<SpvOp> acceptable = {
@@ -141,14 +141,14 @@
       _.FindDef(function_type->GetOperandAs<uint32_t>(param_index + 2));
   if (!param_type || inst->type_id() != param_type->id()) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpFunctionParameter Result Type <id> '"
+           << "OpFunctionParameter Result Type <id> "
            << _.getIdName(inst->type_id())
-           << "' does not match the OpTypeFunction parameter "
+           << " does not match the OpTypeFunction parameter "
               "type of the same index.";
   }
 
-  // Validate that PhysicalStorageBufferEXT have one of Restrict, Aliased,
-  // RestrictPointerEXT, or AliasedPointerEXT.
+  // Validate that PhysicalStorageBuffer have one of Restrict, Aliased,
+  // RestrictPointer, or AliasedPointer.
   auto param_nonarray_type_id = param_type->id();
   while (_.GetIdOpcode(param_nonarray_type_id) == SpvOpTypeArray) {
     param_nonarray_type_id =
@@ -157,7 +157,7 @@
   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) {
+        SpvStorageClassPhysicalStorageBuffer) {
       // check for Aliased or Restrict
       const auto& decorations = _.id_decorations(inst->id());
 
@@ -174,14 +174,14 @@
       if (!foundAliased && !foundRestrict) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
                << "OpFunctionParameter " << inst->id()
-               << ": expected Aliased or Restrict for PhysicalStorageBufferEXT "
+               << ": expected Aliased or Restrict for PhysicalStorageBuffer "
                   "pointer.";
       }
       if (foundAliased && foundRestrict) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
                << "OpFunctionParameter " << inst->id()
                << ": can't specify both Aliased and Restrict for "
-                  "PhysicalStorageBufferEXT pointer.";
+                  "PhysicalStorageBuffer pointer.";
       }
     } else {
       const auto pointee_type_id =
@@ -189,31 +189,31 @@
       const auto pointee_type = _.FindDef(pointee_type_id);
       if (SpvOpTypePointer == pointee_type->opcode() &&
           pointee_type->GetOperandAs<uint32_t>(1u) ==
-              SpvStorageClassPhysicalStorageBufferEXT) {
-        // check for AliasedPointerEXT/RestrictPointerEXT
+              SpvStorageClassPhysicalStorageBuffer) {
+        // check for AliasedPointer/RestrictPointer
         const auto& decorations = _.id_decorations(inst->id());
 
         bool foundAliased = std::any_of(
             decorations.begin(), decorations.end(), [](const Decoration& d) {
-              return SpvDecorationAliasedPointerEXT == d.dec_type();
+              return SpvDecorationAliasedPointer == d.dec_type();
             });
 
         bool foundRestrict = std::any_of(
             decorations.begin(), decorations.end(), [](const Decoration& d) {
-              return SpvDecorationRestrictPointerEXT == d.dec_type();
+              return SpvDecorationRestrictPointer == d.dec_type();
             });
 
         if (!foundAliased && !foundRestrict) {
           return _.diag(SPV_ERROR_INVALID_ID, inst)
                  << "OpFunctionParameter " << inst->id()
-                 << ": expected AliasedPointerEXT or RestrictPointerEXT for "
-                    "PhysicalStorageBufferEXT pointer.";
+                 << ": expected AliasedPointer or RestrictPointer for "
+                    "PhysicalStorageBuffer pointer.";
         }
         if (foundAliased && foundRestrict) {
           return _.diag(SPV_ERROR_INVALID_ID, inst)
                  << "OpFunctionParameter " << inst->id()
-                 << ": can't specify both AliasedPointerEXT and "
-                    "RestrictPointerEXT for PhysicalStorageBufferEXT pointer.";
+                 << ": can't specify both AliasedPointer and "
+                    "RestrictPointer for PhysicalStorageBuffer pointer.";
         }
       }
     }
@@ -228,17 +228,16 @@
   const auto function = _.FindDef(function_id);
   if (!function || SpvOpFunction != function->opcode()) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpFunctionCall Function <id> '" << _.getIdName(function_id)
-           << "' is not a function.";
+           << "OpFunctionCall Function <id> " << _.getIdName(function_id)
+           << " is not a function.";
   }
 
   auto return_type = _.FindDef(function->type_id());
   if (!return_type || return_type->id() != inst->type_id()) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpFunctionCall Result Type <id> '"
-           << _.getIdName(inst->type_id())
-           << "'s type does not match Function <id> '"
-           << _.getIdName(return_type->id()) << "'s return type.";
+           << "OpFunctionCall Result Type <id> " << _.getIdName(inst->type_id())
+           << "s type does not match Function <id> "
+           << _.getIdName(return_type->id()) << "s return type.";
   }
 
   const auto function_type_id = function->GetOperandAs<uint32_t>(3);
@@ -280,9 +279,9 @@
       if (!_.options()->before_hlsl_legalization ||
           !DoPointeesLogicallyMatch(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.";
+               << "OpFunctionCall Argument <id> " << _.getIdName(argument_id)
+               << "s type does not match Function <id> "
+               << _.getIdName(parameter_type_id) << "s parameter type.";
       }
     }
 
@@ -300,7 +299,7 @@
             // These are always allowed.
             break;
           case SpvStorageClassStorageBuffer:
-            if (!_.features().variable_pointers_storage_buffer) {
+            if (!_.features().variable_pointers) {
               return _.diag(SPV_ERROR_INVALID_ID, inst)
                      << "StorageBuffer pointer operand "
                      << _.getIdName(argument_id)
@@ -316,11 +315,10 @@
         // 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 ssbo_vptr = _.features().variable_pointers &&
+                                 sc == SpvStorageClassStorageBuffer;
+          const bool wg_vptr = _.HasCapability(SpvCapabilityVariablePointers) &&
+                               sc == SpvStorageClassWorkgroup;
           const bool uc_ptr = sc == SpvStorageClassUniformConstant;
           if (!ssbo_vptr && !wg_vptr && !uc_ptr) {
             return _.diag(SPV_ERROR_INVALID_ID, inst)
diff --git a/source/val/validate_image.cpp b/source/val/validate_image.cpp
index 64f6ba7..9c7c8c1 100644
--- a/source/val/validate_image.cpp
+++ b/source/val/validate_image.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Google Inc.
+// Copyright (c) 2017 Google Inc.
 // Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights
 // reserved.
 //
@@ -20,6 +20,7 @@
 
 #include "source/diagnostic.h"
 #include "source/opcode.h"
+#include "source/spirv_constant.h"
 #include "source/spirv_target_env.h"
 #include "source/util/bitutils.h"
 #include "source/val/instruction.h"
@@ -71,6 +72,7 @@
     //                blocks other PRs.
     // https://github.com/KhronosGroup/SPIRV-Tools/issues/4565
     case SpvImageOperandsOffsetsMask:
+    case SpvImageOperandsNontemporalMask:
       return true;
   }
   return false;
@@ -258,7 +260,8 @@
         mask & ~uint32_t(SpvImageOperandsNonPrivateTexelKHRMask |
                          SpvImageOperandsVolatileTexelKHRMask |
                          SpvImageOperandsSignExtendMask |
-                         SpvImageOperandsZeroExtendMask);
+                         SpvImageOperandsZeroExtendMask |
+                         SpvImageOperandsNontemporalMask);
     size_t expected_num_image_operand_words =
         spvtools::utils::CountSetBits(mask_bits_having_operands);
     if (mask & SpvImageOperandsGradMask) {
@@ -500,7 +503,7 @@
     if (!_.IsIntVectorType(component_type) ||
         _.GetDimension(component_type) != 2) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "Expected Image Operand ConstOffsets array componenets to be "
+             << "Expected Image Operand ConstOffsets array components to be "
                 "int vectors of size 2";
     }
 
@@ -630,68 +633,71 @@
     // TODO: add validation
   }
 
+  if (mask & SpvImageOperandsNontemporalMask) {
+    // Checked elsewhere: SPIR-V 1.6 version or later.
+  }
+
   return SPV_SUCCESS;
 }
 
-// Checks some of the validation rules which are common to multiple opcodes.
-spv_result_t ValidateImageCommon(ValidationState_t& _, const Instruction* inst,
-                                 const ImageTypeInfo& info) {
-  const SpvOp opcode = inst->opcode();
-  if (IsProj(opcode)) {
-    if (info.dim != SpvDim1D && info.dim != SpvDim2D && info.dim != SpvDim3D &&
-        info.dim != SpvDimRect) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "Expected Image 'Dim' parameter to be 1D, 2D, 3D or Rect";
-    }
-
-    if (info.multisampled != 0) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "Expected Image 'MS' parameter to be 0";
-    }
-
-    if (info.arrayed != 0) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "Expected Image 'arrayed' parameter to be 0";
-    }
+// Validate OpImage*Proj* instructions
+spv_result_t ValidateImageProj(ValidationState_t& _, const Instruction* inst,
+                               const ImageTypeInfo& info) {
+  if (info.dim != SpvDim1D && info.dim != SpvDim2D && info.dim != SpvDim3D &&
+      info.dim != SpvDimRect) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Expected Image 'Dim' parameter to be 1D, 2D, 3D or Rect";
   }
 
-  if (opcode == SpvOpImageRead || opcode == SpvOpImageSparseRead ||
-      opcode == SpvOpImageWrite) {
-    if (info.sampled == 0) {
-    } else if (info.sampled == 2) {
-      if (info.dim == SpvDim1D && !_.HasCapability(SpvCapabilityImage1D)) {
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Capability Image1D is required to access storage image";
-      } else if (info.dim == SpvDimRect &&
-                 !_.HasCapability(SpvCapabilityImageRect)) {
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Capability ImageRect is required to access storage image";
-      } else if (info.dim == SpvDimBuffer &&
-                 !_.HasCapability(SpvCapabilityImageBuffer)) {
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Capability ImageBuffer is required to access storage image";
-      } else if (info.dim == SpvDimCube && info.arrayed == 1 &&
-                 !_.HasCapability(SpvCapabilityImageCubeArray)) {
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "Capability ImageCubeArray is required to access "
-               << "storage image";
-      }
+  if (info.multisampled != 0) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Expected Image 'MS' parameter to be 0";
+  }
 
-      if (info.multisampled == 1 &&
-          !_.HasCapability(SpvCapabilityImageMSArray)) {
-#if 0
-        // TODO(atgoo@github.com) The description of this rule in the spec
-        // is unclear and Glslang doesn't declare ImageMSArray. Need to clarify
-        // and reenable.
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-            << "Capability ImageMSArray is required to access storage "
-            << "image";
-#endif
-      }
-    } else {
+  if (info.arrayed != 0) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Expected Image 'arrayed' parameter to be 0";
+  }
+
+  return SPV_SUCCESS;
+}
+
+// Validate OpImage*Read and OpImage*Write instructions
+spv_result_t ValidateImageReadWrite(ValidationState_t& _,
+                                    const Instruction* inst,
+                                    const ImageTypeInfo& info) {
+  if (info.sampled == 2) {
+    if (info.dim == SpvDim1D && !_.HasCapability(SpvCapabilityImage1D)) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "Expected Image 'Sampled' parameter to be 0 or 2";
+             << "Capability Image1D is required to access storage image";
+    } else if (info.dim == SpvDimRect &&
+               !_.HasCapability(SpvCapabilityImageRect)) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Capability ImageRect is required to access storage image";
+    } else if (info.dim == SpvDimBuffer &&
+               !_.HasCapability(SpvCapabilityImageBuffer)) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Capability ImageBuffer is required to access storage image";
+    } else if (info.dim == SpvDimCube && info.arrayed == 1 &&
+               !_.HasCapability(SpvCapabilityImageCubeArray)) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Capability ImageCubeArray is required to access "
+             << "storage image";
     }
+
+    if (info.multisampled == 1 && !_.HasCapability(SpvCapabilityImageMSArray)) {
+#if 0
+      // TODO(atgoo@github.com) The description of this rule in the spec
+      // is unclear and Glslang doesn't declare ImageMSArray. Need to clarify
+      // and reenable.
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+          << "Capability ImageMSArray is required to access storage "
+          << "image";
+#endif
+    }
+  } else if (info.sampled != 0) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Expected Image 'Sampled' parameter to be 0 or 2";
   }
 
   return SPV_SUCCESS;
@@ -806,7 +812,8 @@
     }
   }
 
-  // Dim is checked elsewhere.
+  // Universal checks on image type operands
+  // Dim and Format and Access Qualifier are checked elsewhere.
 
   if (info.depth > 2) {
     return _.diag(SPV_ERROR_INVALID_DATA, inst)
@@ -818,6 +825,35 @@
            << "Invalid Arrayed " << info.arrayed << " (must be 0 or 1)";
   }
 
+  if (info.multisampled > 1) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Invalid MS " << info.multisampled << " (must be 0 or 1)";
+  }
+
+  if (info.sampled > 2) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Invalid Sampled " << info.sampled << " (must be 0, 1 or 2)";
+  }
+
+  if (info.dim == SpvDimSubpassData) {
+    if (info.sampled != 2) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << _.VkErrorID(6214) << "Dim SubpassData requires Sampled to be 2";
+    }
+
+    if (info.format != SpvImageFormatUnknown) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Dim SubpassData requires format Unknown";
+    }
+  } else {
+    if (info.multisampled && (info.sampled == 2) &&
+        !_.HasCapability(SpvCapabilityStorageImageMultisample)) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Capability StorageImageMultisample is required when using "
+                "multisampled storage image";
+    }
+  }
+
   if (spvIsOpenCLEnv(target_env)) {
     if ((info.arrayed == 1) && (info.dim != SpvDim1D) &&
         (info.dim != SpvDim2D)) {
@@ -825,23 +861,22 @@
              << "In the OpenCL environment, Arrayed may only be set to 1 "
              << "when Dim is either 1D or 2D.";
     }
-  }
 
-  if (info.multisampled > 1) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "Invalid MS " << info.multisampled << " (must be 0 or 1)";
-  }
-
-  if (spvIsOpenCLEnv(target_env)) {
     if (info.multisampled != 0) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << "MS must be 0 in the OpenCL environment.";
     }
-  }
 
-  if (info.sampled > 2) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "Invalid Sampled " << info.sampled << " (must be 0, 1 or 2)";
+    if (info.sampled != 0) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Sampled must be 0 in the OpenCL environment.";
+    }
+
+    if (info.access_qualifier == SpvAccessQualifierMax) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "In the OpenCL environment, the optional Access Qualifier"
+             << " must be present.";
+    }
   }
 
   if (spvIsVulkanEnv(target_env)) {
@@ -850,43 +885,10 @@
              << _.VkErrorID(4657)
              << "Sampled must be 1 or 2 in the Vulkan environment.";
     }
-  }
 
-  if (spvIsOpenCLEnv(_.context()->target_env)) {
-    if (info.sampled != 0) {
+    if (info.dim == SpvDimSubpassData && info.arrayed != 0) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "Sampled must be 0 in the OpenCL environment.";
-    }
-  }
-
-  if (info.dim == SpvDimSubpassData) {
-    if (info.sampled != 2) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "Dim SubpassData requires Sampled to be 2";
-    }
-
-    if (info.format != SpvImageFormatUnknown) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "Dim SubpassData requires format Unknown";
-    }
-  }
-
-  // Format and Access Qualifier are also checked elsewhere.
-
-  if (spvIsOpenCLEnv(_.context()->target_env)) {
-    if (info.access_qualifier == SpvAccessQualifierMax) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "In the OpenCL environment, the optional Access Qualifier"
-             << " must be present.";
-    }
-  }
-
-  if (info.multisampled && (info.sampled == 2) &&
-      (info.dim != SpvDimSubpassData)) {
-    if (!_.HasCapability(SpvCapabilityStorageImageMultisample)) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "Capability StorageImageMultisample is required when using "
-                "multisampled storage image";
+             << _.VkErrorID(6214) << "Dim SubpassData requires Arrayed to be 0";
     }
   }
 
@@ -915,10 +917,17 @@
               "operand set to 0 or 1";
   }
 
+  // This covers both OpTypeSampledImage and OpSampledImage.
+  if (_.version() >= SPV_SPIRV_VERSION_WORD(1, 6) && info.dim == SpvDimBuffer) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "In SPIR-V 1.6 or later, sampled image dimension must not be "
+              "Buffer";
+  }
+
   return SPV_SUCCESS;
 }
 
-bool IsAllowedSampledImageOperand(SpvOp opcode) {
+bool IsAllowedSampledImageOperand(SpvOp opcode, ValidationState_t& _) {
   switch (opcode) {
     case SpvOpSampledImage:
     case SpvOpImageSampleImplicitLod:
@@ -941,6 +950,9 @@
     case SpvOpImageSparseDrefGather:
     case SpvOpCopyObject:
       return true;
+    case SpvOpStore:
+      if (_.HasCapability(SpvCapabilityBindlessTextureNV)) return true;
+      return false;
     default:
       return false;
   }
@@ -971,8 +983,9 @@
   if (spvIsVulkanEnv(_.context()->target_env)) {
     if (info.sampled != 1) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "Expected Image 'Sampled' parameter to be 1 "
-             << "for Vulkan environment.";
+             << _.VkErrorID(6671)
+             << "Expected Image 'Sampled' parameter to be 1 for Vulkan "
+                "environment.";
     }
   } else {
     if (info.sampled != 0 && info.sampled != 1) {
@@ -1007,11 +1020,11 @@
                << "All OpSampledImage instructions must be in the same block "
                   "in "
                   "which their Result <id> are consumed. OpSampledImage Result "
-                  "Type <id> '"
+                  "Type <id> "
                << _.getIdName(inst->id())
-               << "' has a consumer in a different basic "
-                  "block. The consumer instruction <id> is '"
-               << _.getIdName(consumer_instr->id()) << "'.";
+               << " has a consumer in a different basic "
+                  "block. The consumer instruction <id> is "
+               << _.getIdName(consumer_instr->id()) << ".";
       }
 
       if (consumer_opcode == SpvOpPhi || consumer_opcode == SpvOpSelect) {
@@ -1020,21 +1033,21 @@
                   "as "
                   "operands of Op"
                << spvOpcodeString(static_cast<SpvOp>(consumer_opcode)) << "."
-               << " Found result <id> '" << _.getIdName(inst->id())
-               << "' as an operand of <id> '"
-               << _.getIdName(consumer_instr->id()) << "'.";
+               << " Found result <id> " << _.getIdName(inst->id())
+               << " as an operand of <id> " << _.getIdName(consumer_instr->id())
+               << ".";
       }
 
-      if (!IsAllowedSampledImageOperand(consumer_opcode)) {
+      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 "
+               << ", since it is not specified as taking an "
                << "OpTypeSampledImage."
-               << " Found result <id> '" << _.getIdName(inst->id())
-               << "' as an operand of <id> '"
-               << _.getIdName(consumer_instr->id()) << "'.";
+               << " Found result <id> " << _.getIdName(inst->id())
+               << " as an operand of <id> " << _.getIdName(consumer_instr->id())
+               << ".";
       }
     }
   }
@@ -1191,7 +1204,9 @@
            << "Corrupt image type definition";
   }
 
-  if (spv_result_t result = ValidateImageCommon(_, inst, info)) return result;
+  if (IsProj(opcode)) {
+    if (spv_result_t result = ValidateImageProj(_, inst, info)) return result;
+  }
 
   if (info.multisampled) {
     // When using image operands, the Sample image operand is required if and
@@ -1254,6 +1269,27 @@
   return SPV_SUCCESS;
 }
 
+// Validates anything OpImage*Dref* instruction
+spv_result_t ValidateImageDref(ValidationState_t& _, const Instruction* inst,
+                               const ImageTypeInfo& info) {
+  const uint32_t dref_type = _.GetOperandTypeId(inst, 4);
+  if (!_.IsFloatScalarType(dref_type) || _.GetBitWidth(dref_type) != 32) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Expected Dref to be of 32-bit float type";
+  }
+
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    if (info.dim == SpvDim3D) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << _.VkErrorID(4777)
+             << "In Vulkan, OpImage*Dref* instructions must not use images "
+                "with a 3D Dim";
+    }
+  }
+
+  return SPV_SUCCESS;
+}
+
 spv_result_t ValidateImageDrefLod(ValidationState_t& _,
                                   const Instruction* inst) {
   const SpvOp opcode = inst->opcode();
@@ -1281,7 +1317,9 @@
            << "Corrupt image type definition";
   }
 
-  if (spv_result_t result = ValidateImageCommon(_, inst, info)) return result;
+  if (IsProj(opcode)) {
+    if (spv_result_t result = ValidateImageProj(_, inst, info)) return result;
+  }
 
   if (info.multisampled) {
     // When using image operands, the Sample image operand is required if and
@@ -1311,11 +1349,7 @@
            << " components, but given only " << actual_coord_size;
   }
 
-  const uint32_t dref_type = _.GetOperandTypeId(inst, 4);
-  if (!_.IsFloatScalarType(dref_type) || _.GetBitWidth(dref_type) != 32) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "Expected Dref to be of 32-bit float type";
-  }
+  if (spv_result_t result = ValidateImageDref(_, inst, info)) return result;
 
   if (spv_result_t result =
           ValidateImageOperands(_, inst, info, /* word_index = */ 7))
@@ -1450,7 +1484,8 @@
   if (info.dim != SpvDim2D && info.dim != SpvDimCube &&
       info.dim != SpvDimRect) {
     return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "Expected Image 'Dim' cannot be Cube";
+           << _.VkErrorID(4777)
+           << "Expected Image 'Dim' to be 2D, Cube, or Rect";
   }
 
   const uint32_t coord_type = _.GetOperandTypeId(inst, 3);
@@ -1486,11 +1521,7 @@
   } else {
     assert(opcode == SpvOpImageDrefGather ||
            opcode == SpvOpImageSparseDrefGather);
-    const uint32_t dref_type = _.GetOperandTypeId(inst, 4);
-    if (!_.IsFloatScalarType(dref_type) || _.GetBitWidth(dref_type) != 32) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "Expected Dref to be of 32-bit float type";
-    }
+    if (spv_result_t result = ValidateImageDref(_, inst, info)) return result;
   }
 
   if (spv_result_t result =
@@ -1558,6 +1589,13 @@
                << " to have 4 components";
       }
     }
+
+    const uint32_t mask = inst->words().size() <= 5 ? 0 : inst->word(5);
+    if (mask & SpvImageOperandsConstOffsetMask) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "ConstOffset image operand not allowed "
+             << "in the OpenCL environment.";
+    }
   }
 
   if (info.dim == SpvDimSubpassData) {
@@ -1583,7 +1621,8 @@
     }
   }
 
-  if (spv_result_t result = ValidateImageCommon(_, inst, info)) return result;
+  if (spv_result_t result = ValidateImageReadWrite(_, inst, info))
+    return result;
 
   const uint32_t coord_type = _.GetOperandTypeId(inst, 3);
   if (!_.IsIntScalarOrVectorType(coord_type)) {
@@ -1608,16 +1647,6 @@
     }
   }
 
-  const uint32_t mask = inst->words().size() <= 5 ? 0 : inst->word(5);
-
-  if (mask & SpvImageOperandsConstOffsetMask) {
-    if (spvIsOpenCLEnv(_.context()->target_env)) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "ConstOffset image operand not allowed "
-             << "in the OpenCL environment.";
-    }
-  }
-
   if (spv_result_t result =
           ValidateImageOperands(_, inst, info, /* word_index = */ 6))
     return result;
@@ -1643,7 +1672,8 @@
            << "Image 'Dim' cannot be SubpassData";
   }
 
-  if (spv_result_t result = ValidateImageCommon(_, inst, info)) return result;
+  if (spv_result_t result = ValidateImageReadWrite(_, inst, info))
+    return result;
 
   const uint32_t coord_type = _.GetOperandTypeId(inst, 1);
   if (!_.IsIntScalarOrVectorType(coord_type)) {
@@ -1659,8 +1689,7 @@
            << " components, but given only " << actual_coord_size;
   }
 
-  // TODO(atgoo@github.com) The spec doesn't explicitely say what the type
-  // of texel should be.
+  // because it needs to match with 'Sampled Type' the Texel can't be a boolean
   const uint32_t texel_type = _.GetOperandTypeId(inst, 2);
   if (!_.IsIntScalarOrVectorType(texel_type) &&
       !_.IsFloatScalarOrVectorType(texel_type)) {
@@ -1668,14 +1697,6 @@
            << "Expected Texel to be int or float vector or scalar";
   }
 
-#if 0
-  // TODO: See above.
-  if (_.GetDimension(texel_type) != 4) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-        << "Expected Texel to have 4 components";
-  }
-#endif
-
   if (_.GetIdOpcode(info.sampled_type) != SpvOpTypeVoid) {
     const uint32_t texel_component_type = _.GetComponentType(texel_type);
     if (texel_component_type != info.sampled_type) {
diff --git a/source/val/validate_instruction.cpp b/source/val/validate_instruction.cpp
index dad9867..767c0ce 100644
--- a/source/val/validate_instruction.cpp
+++ b/source/val/validate_instruction.cpp
@@ -297,7 +297,7 @@
   }
 
   // OpTerminateInvocation is special because it is enabled by Shader
-  // capability, but also requries a extension and/or version check.
+  // capability, but also requires an extension and/or version check.
   const bool capability_check_is_sufficient =
       inst->opcode() != SpvOpTerminateInvocation;
 
@@ -406,7 +406,7 @@
     // The instruction syntax is as follows:
     // OpSwitch <selector ID> <Default ID> literal label literal label ...
     // literal,label pairs come after the first 2 operands.
-    // It is guaranteed at this point that num_operands is an even numner.
+    // It is guaranteed at this point that num_operands is an even number.
     size_t num_pairs = (inst->operands().size() - 2) / 2;
     const unsigned int num_pairs_limit =
         _.options()->universal_limits_.max_switch_branches;
@@ -483,6 +483,22 @@
     if (auto error = LimitCheckNumVars(_, inst->id(), storage_class)) {
       return error;
     }
+  } else if (opcode == SpvOpSamplerImageAddressingModeNV) {
+    if (!_.HasCapability(SpvCapabilityBindlessTextureNV)) {
+      return _.diag(SPV_ERROR_MISSING_EXTENSION, inst)
+             << "OpSamplerImageAddressingModeNV supported only with extension "
+                "SPV_NV_bindless_texture";
+    }
+    uint32_t bitwidth = inst->GetOperandAs<uint32_t>(0);
+    if (_.samplerimage_variable_address_mode() != 0) {
+      return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
+             << "OpSamplerImageAddressingModeNV should only be provided once";
+    }
+    if (bitwidth != 32 && bitwidth != 64) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "OpSamplerImageAddressingModeNV bitwidth should be 64 or 32";
+    }
+    _.set_samplerimage_variable_address_mode(bitwidth);
   }
 
   if (auto error = ReservedCheck(_, inst)) return error;
diff --git a/source/val/validate_interfaces.cpp b/source/val/validate_interfaces.cpp
index 7ccb637..7f2d648 100644
--- a/source/val/validate_interfaces.cpp
+++ b/source/val/validate_interfaces.cpp
@@ -154,7 +154,7 @@
       // Members cannot have location decorations at this point.
       if (_.HasDecoration(type->id(), SpvDecorationLocation)) {
         return _.diag(SPV_ERROR_INVALID_DATA, type)
-               << "Members cannot be assigned a location";
+               << _.VkErrorID(4918) << "Members cannot be assigned a location";
       }
 
       // Structs consume locations equal to the sum of the locations consumed
@@ -238,7 +238,7 @@
   uint32_t index = 0;
   bool has_patch = false;
   bool has_per_task_nv = false;
-  bool has_per_vertex_nv = false;
+  bool has_per_vertex_khr = false;
   for (auto& dec : _.id_decorations(variable->id())) {
     if (dec.dec_type() == SpvDecorationLocation) {
       if (has_location && dec.params()[0] != location) {
@@ -272,8 +272,20 @@
       has_patch = true;
     } else if (dec.dec_type() == SpvDecorationPerTaskNV) {
       has_per_task_nv = true;
-    } else if (dec.dec_type() == SpvDecorationPerVertexNV) {
-      has_per_vertex_nv = true;
+    } else if (dec.dec_type() == SpvDecorationPerVertexKHR) {
+      if (!is_fragment) {
+        return _.diag(SPV_ERROR_INVALID_DATA, variable)
+               << _.VkErrorID(6777)
+               << "PerVertexKHR can only be applied to Fragment Execution "
+                  "Models";
+      }
+      if (type->opcode() != SpvOpTypeArray &&
+          type->opcode() != SpvOpTypeRuntimeArray) {
+        return _.diag(SPV_ERROR_INVALID_DATA, variable)
+               << _.VkErrorID(6778)
+               << "PerVertexKHR must be declared as arrays";
+      }
+      has_per_vertex_khr = true;
     }
   }
 
@@ -298,7 +310,7 @@
       }
       break;
     case SpvExecutionModelFragment:
-      if (!is_output && has_per_vertex_nv) {
+      if (!is_output && has_per_vertex_khr) {
         is_arrayed = true;
       }
       break;
@@ -326,8 +338,9 @@
   // Only block-decorated structs don't need a location on the variable.
   const bool is_block = _.HasDecoration(type_id, SpvDecorationBlock);
   if (!has_location && !is_block) {
+    const auto vuid = (type->opcode() == SpvOpTypeStruct) ? 4917 : 4916;
     return _.diag(SPV_ERROR_INVALID_DATA, variable)
-           << "Variable must be decorated with a location";
+           << _.VkErrorID(vuid) << "Variable must be decorated with a location";
   }
 
   const std::string storage_class = is_output ? "output" : "input";
@@ -411,7 +424,7 @@
       auto where = member_locations.find(i - 1);
       if (where == member_locations.end()) {
         return _.diag(SPV_ERROR_INVALID_DATA, type)
-               << "Member index " << i - 1
+               << _.VkErrorID(4919) << "Member index " << i - 1
                << " is missing a location assignment";
       }
 
@@ -476,6 +489,9 @@
                                const Instruction* entry_point) {
   // According to Vulkan 14.1 only the following execution models have
   // locations assigned.
+  // TODO(dneto): SPV_NV_ray_tracing also uses locations on interface variables,
+  // in other shader stages. Similarly, the *provisional* version of
+  // SPV_KHR_ray_tracing did as well, but not the final version.
   switch (entry_point->GetOperandAs<SpvExecutionModel>(0)) {
     case SpvExecutionModelVertex:
     case SpvExecutionModelTessellationControl:
diff --git a/source/val/validate_layout.cpp b/source/val/validate_layout.cpp
index d582321..6f95135 100644
--- a/source/val/validate_layout.cpp
+++ b/source/val/validate_layout.cpp
@@ -363,6 +363,7 @@
     case kLayoutExtensions:
     case kLayoutExtInstImport:
     case kLayoutMemoryModel:
+    case kLayoutSamplerImageAddressMode:
     case kLayoutEntryPoint:
     case kLayoutExecutionMode:
     case kLayoutDebug1:
diff --git a/source/val/validate_logicals.cpp b/source/val/validate_logicals.cpp
index bb35f55..ec1e207 100644
--- a/source/val/validate_logicals.cpp
+++ b/source/val/validate_logicals.cpp
@@ -163,14 +163,23 @@
         switch (type_opcode) {
           case SpvOpTypePointer: {
             if (_.addressing_model() == SpvAddressingModelLogical &&
-                !_.features().variable_pointers &&
-                !_.features().variable_pointers_storage_buffer)
+                !_.features().variable_pointers)
               return _.diag(SPV_ERROR_INVALID_DATA, inst)
                      << "Using pointers with OpSelect requires capability "
                      << "VariablePointers or VariablePointersStorageBuffer";
             break;
           }
 
+          case SpvOpTypeSampledImage:
+          case SpvOpTypeImage:
+          case SpvOpTypeSampler: {
+            if (!_.HasCapability(SpvCapabilityBindlessTextureNV))
+              return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                     << "Using image/sampler with OpSelect requires capability "
+                     << "BindlessTextureNV";
+            break;
+          }
+
           case SpvOpTypeVector: {
             dimension = type_inst->word(3);
             break;
diff --git a/source/val/validate_memory.cpp b/source/val/validate_memory.cpp
index a7b0f82..074bdb8 100644
--- a/source/val/validate_memory.cpp
+++ b/source/val/validate_memory.cpp
@@ -35,8 +35,8 @@
                                  const Instruction*);
 bool HaveSameLayoutDecorations(ValidationState_t&, const Instruction*,
                                const Instruction*);
-bool HasConflictingMemberOffsets(const std::vector<Decoration>&,
-                                 const std::vector<Decoration>&);
+bool HasConflictingMemberOffsets(const std::set<Decoration>&,
+                                 const std::set<Decoration>&);
 
 bool IsAllowedTypeOrArrayOfSame(ValidationState_t& _, const Instruction* type,
                                 std::initializer_list<uint32_t> allowed) {
@@ -105,10 +105,8 @@
          "type1 must be an OpTypeStruct instruction.");
   assert(type2->opcode() == SpvOpTypeStruct &&
          "type2 must be an OpTypeStruct instruction.");
-  const std::vector<Decoration>& type1_decorations =
-      _.id_decorations(type1->id());
-  const std::vector<Decoration>& type2_decorations =
-      _.id_decorations(type2->id());
+  const std::set<Decoration>& type1_decorations = _.id_decorations(type1->id());
+  const std::set<Decoration>& type2_decorations = _.id_decorations(type2->id());
 
   // TODO: Will have to add other check for arrays an matricies if we want to
   // handle them.
@@ -120,8 +118,8 @@
 }
 
 bool HasConflictingMemberOffsets(
-    const std::vector<Decoration>& type1_decorations,
-    const std::vector<Decoration>& type2_decorations) {
+    const std::set<Decoration>& type1_decorations,
+    const std::set<Decoration>& type2_decorations) {
   {
     // We are interested in conflicting decoration.  If a decoration is in one
     // list but not the other, then we will assume the code is correct.  We are
@@ -315,11 +313,12 @@
   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) {
+    // Cases where lack of some operand is invalid
+    if (src_sc == SpvStorageClassPhysicalStorageBuffer ||
+        dst_sc == SpvStorageClassPhysicalStorageBuffer) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
-             << "Memory accesses with PhysicalStorageBufferEXT must use "
-                "Aligned.";
+             << _.VkErrorID(4708)
+             << "Memory accesses with PhysicalStorageBuffer must use Aligned.";
     }
     return SPV_SUCCESS;
   }
@@ -368,7 +367,7 @@
         dst_sc != SpvStorageClassCrossWorkgroup &&
         dst_sc != SpvStorageClassGeneric && dst_sc != SpvStorageClassImage &&
         dst_sc != SpvStorageClassStorageBuffer &&
-        dst_sc != SpvStorageClassPhysicalStorageBufferEXT) {
+        dst_sc != SpvStorageClassPhysicalStorageBuffer) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
              << "NonPrivatePointerKHR requires a pointer in Uniform, "
              << "Workgroup, CrossWorkgroup, Generic, Image or StorageBuffer "
@@ -379,7 +378,7 @@
         src_sc != SpvStorageClassCrossWorkgroup &&
         src_sc != SpvStorageClassGeneric && src_sc != SpvStorageClassImage &&
         src_sc != SpvStorageClassStorageBuffer &&
-        src_sc != SpvStorageClassPhysicalStorageBufferEXT) {
+        src_sc != SpvStorageClassPhysicalStorageBuffer) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
              << "NonPrivatePointerKHR requires a pointer in Uniform, "
              << "Workgroup, CrossWorkgroup, Generic, Image or StorageBuffer "
@@ -388,11 +387,11 @@
   }
 
   if (!(mask & SpvMemoryAccessAlignedMask)) {
-    if (src_sc == SpvStorageClassPhysicalStorageBufferEXT ||
-        dst_sc == SpvStorageClassPhysicalStorageBufferEXT) {
+    if (src_sc == SpvStorageClassPhysicalStorageBuffer ||
+        dst_sc == SpvStorageClassPhysicalStorageBuffer) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
-             << "Memory accesses with PhysicalStorageBufferEXT must use "
-                "Aligned.";
+             << _.VkErrorID(4708)
+             << "Memory accesses with PhysicalStorageBuffer must use Aligned.";
     }
   }
 
@@ -403,8 +402,8 @@
   auto result_type = _.FindDef(inst->type_id());
   if (!result_type || result_type->opcode() != SpvOpTypePointer) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpVariable Result Type <id> '" << _.getIdName(inst->type_id())
-           << "' is not a pointer type.";
+           << "OpVariable Result Type <id> " << _.getIdName(inst->type_id())
+           << " is not a pointer type.";
   }
 
   const auto type_index = 2;
@@ -424,8 +423,8 @@
         initializer && spvOpcodeIsConstant(initializer->opcode());
     if (!initializer || !(is_constant || is_module_scope_var)) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
-             << "OpVariable Initializer <id> '" << _.getIdName(initializer_id)
-             << "' is not a constant or module-scope variable.";
+             << "OpVariable Initializer <id> " << _.getIdName(initializer_id)
+             << " is not a constant or module-scope variable.";
     }
     if (initializer->type_id() != value_id) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
@@ -439,11 +438,12 @@
       storage_class != SpvStorageClassCrossWorkgroup &&
       storage_class != SpvStorageClassPrivate &&
       storage_class != SpvStorageClassFunction &&
-      storage_class != SpvStorageClassRayPayloadNV &&
-      storage_class != SpvStorageClassIncomingRayPayloadNV &&
-      storage_class != SpvStorageClassHitAttributeNV &&
-      storage_class != SpvStorageClassCallableDataNV &&
-      storage_class != SpvStorageClassIncomingCallableDataNV) {
+      storage_class != SpvStorageClassRayPayloadKHR &&
+      storage_class != SpvStorageClassIncomingRayPayloadKHR &&
+      storage_class != SpvStorageClassHitAttributeKHR &&
+      storage_class != SpvStorageClassCallableDataKHR &&
+      storage_class != SpvStorageClassIncomingCallableDataKHR &&
+      storage_class != SpvStorageClassTaskPayloadWorkgroupEXT) {
     bool storage_input_or_output = storage_class == SpvStorageClassInput ||
                                    storage_class == SpvStorageClassOutput;
     bool builtin = false;
@@ -455,12 +455,24 @@
         }
       }
     }
-    if (!(storage_input_or_output && builtin) &&
+    if (!builtin &&
         ContainsInvalidBool(_, value_type, storage_input_or_output)) {
-      return _.diag(SPV_ERROR_INVALID_ID, inst)
-             << "If OpTypeBool is stored in conjunction with OpVariable, it "
-             << "can only be used with non-externally visible shader Storage "
-             << "Classes: Workgroup, CrossWorkgroup, Private, and Function";
+      if (storage_input_or_output) {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << _.VkErrorID(7290)
+               << "If OpTypeBool is stored in conjunction with OpVariable "
+                  "using Input or Output Storage Classes it requires a BuiltIn "
+                  "decoration";
+
+      } else {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << "If OpTypeBool is stored in conjunction with OpVariable, it "
+                  "can only be used with non-externally visible shader Storage "
+                  "Classes: Workgroup, CrossWorkgroup, Private, Function, "
+                  "Input, Output, RayPayloadKHR, IncomingRayPayloadKHR, "
+                  "HitAttributeKHR, CallableDataKHR, or "
+                  "IncomingCallableDataKHR";
+      }
     }
   }
 
@@ -519,28 +531,29 @@
     }
   }
 
-  // Vulkan 14.5.1: Check type of PushConstant variables.
-  // Vulkan 14.5.2: Check type of UniformConstant and Uniform variables.
   if (spvIsVulkanEnv(_.context()->target_env)) {
+    // Vulkan Push Constant Interface section: Check type of PushConstant
+    // variables.
     if (storage_class == SpvStorageClassPushConstant) {
-      if (!IsAllowedTypeOrArrayOfSame(_, pointee, {SpvOpTypeStruct})) {
+      if (pointee->opcode() != SpvOpTypeStruct) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
-               << "PushConstant OpVariable <id> '" << _.getIdName(inst->id())
-               << "' has illegal type.\n"
-               << "From Vulkan spec, section 14.5.1:\n"
-               << "Such variables must be typed as OpTypeStruct, "
-               << "or an array of this type";
+               << _.VkErrorID(6808) << "PushConstant OpVariable <id> "
+               << _.getIdName(inst->id()) << " has illegal type.\n"
+               << "From Vulkan spec, Push Constant Interface section:\n"
+               << "Such variables must be typed as OpTypeStruct";
       }
     }
 
+    // Vulkan Descriptor Set Interface: Check type of UniformConstant and
+    // Uniform variables.
     if (storage_class == SpvStorageClassUniformConstant) {
       if (!IsAllowedTypeOrArrayOfSame(
               _, pointee,
               {SpvOpTypeImage, SpvOpTypeSampler, SpvOpTypeSampledImage,
                SpvOpTypeAccelerationStructureKHR})) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
-               << _.VkErrorID(4655) << "UniformConstant OpVariable <id> '"
-               << _.getIdName(inst->id()) << "' has illegal type.\n"
+               << _.VkErrorID(4655) << "UniformConstant OpVariable <id> "
+               << _.getIdName(inst->id()) << " has illegal type.\n"
                << "Variables identified with the UniformConstant storage class "
                << "are used only as handles to refer to opaque resources. Such "
                << "variables must be typed as OpTypeImage, OpTypeSampler, "
@@ -552,9 +565,9 @@
     if (storage_class == SpvStorageClassUniform) {
       if (!IsAllowedTypeOrArrayOfSame(_, pointee, {SpvOpTypeStruct})) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
-               << "Uniform OpVariable <id> '" << _.getIdName(inst->id())
-               << "' has illegal type.\n"
-               << "From Vulkan spec, section 14.5.2:\n"
+               << _.VkErrorID(6807) << "Uniform OpVariable <id> "
+               << _.getIdName(inst->id()) << " has illegal type.\n"
+               << "From Vulkan spec:\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 "
@@ -565,9 +578,9 @@
     if (storage_class == SpvStorageClassStorageBuffer) {
       if (!IsAllowedTypeOrArrayOfSame(_, pointee, {SpvOpTypeStruct})) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
-               << "StorageBuffer OpVariable <id> '" << _.getIdName(inst->id())
-               << "' has illegal type.\n"
-               << "From Vulkan spec, section 14.5.2:\n"
+               << _.VkErrorID(6807) << "StorageBuffer OpVariable <id> "
+               << _.getIdName(inst->id()) << " has illegal type.\n"
+               << "From Vulkan spec:\n"
                << "Variables identified with the StorageBuffer storage class "
                   "are used to access transparent buffer backed resources. "
                   "Such variables must be typed as OpTypeStruct, or an array "
@@ -596,27 +609,27 @@
         }
       }
     }
-  }
 
-  // Vulkan Appendix A: Check that if contains initializer, then
-  // storage class is Output, Private, or Function.
-  if (inst->operands().size() > 3 && storage_class != SpvStorageClassOutput &&
-      storage_class != SpvStorageClassPrivate &&
-      storage_class != SpvStorageClassFunction) {
-    if (spvIsVulkanEnv(_.context()->target_env)) {
+    // Initializers in Vulkan are only allowed in some storage clases
+    if (inst->operands().size() > 3) {
       if (storage_class == SpvStorageClassWorkgroup) {
         auto init_id = inst->GetOperandAs<uint32_t>(3);
         auto init = _.FindDef(init_id);
         if (init->opcode() != SpvOpConstantNull) {
           return _.diag(SPV_ERROR_INVALID_ID, inst)
-                 << "Variable initializers in Workgroup storage class are "
-                    "limited to OpConstantNull";
+                 << _.VkErrorID(4734) << "OpVariable, <id> "
+                 << _.getIdName(inst->id())
+                 << ", initializers are limited to OpConstantNull in "
+                    "Workgroup "
+                    "storage class";
         }
-      } else {
+      } else if (storage_class != SpvStorageClassOutput &&
+                 storage_class != SpvStorageClassPrivate &&
+                 storage_class != SpvStorageClassFunction) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
-               << _.VkErrorID(4651) << "OpVariable, <id> '"
+               << _.VkErrorID(4651) << "OpVariable, <id> "
                << _.getIdName(inst->id())
-               << "', has a disallowed initializer & storage class "
+               << ", has a disallowed initializer & storage class "
                << "combination.\n"
                << "From " << spvLogStringForEnv(_.context()->target_env)
                << " spec:\n"
@@ -627,9 +640,22 @@
     }
   }
 
-  if (storage_class == SpvStorageClassPhysicalStorageBufferEXT) {
+  if (inst->operands().size() > 3) {
+    if (storage_class == SpvStorageClassTaskPayloadWorkgroupEXT) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "OpVariable, <id> " << _.getIdName(inst->id())
+             << ", initializer are not allowed for TaskPayloadWorkgroupEXT";
+    }
+    if (storage_class == SpvStorageClassInput) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "OpVariable, <id> " << _.getIdName(inst->id())
+             << ", initializer are not allowed for Input";
+    }
+  }
+
+  if (storage_class == SpvStorageClassPhysicalStorageBuffer) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "PhysicalStorageBufferEXT must not be used with OpVariable.";
+           << "PhysicalStorageBuffer must not be used with OpVariable.";
   }
 
   auto pointee_base = pointee;
@@ -638,23 +664,23 @@
   }
   if (pointee_base->opcode() == SpvOpTypePointer) {
     if (pointee_base->GetOperandAs<uint32_t>(1u) ==
-        SpvStorageClassPhysicalStorageBufferEXT) {
-      // check for AliasedPointerEXT/RestrictPointerEXT
+        SpvStorageClassPhysicalStorageBuffer) {
+      // check for AliasedPointer/RestrictPointer
       bool foundAliased =
-          _.HasDecoration(inst->id(), SpvDecorationAliasedPointerEXT);
+          _.HasDecoration(inst->id(), SpvDecorationAliasedPointer);
       bool foundRestrict =
-          _.HasDecoration(inst->id(), SpvDecorationRestrictPointerEXT);
+          _.HasDecoration(inst->id(), SpvDecorationRestrictPointer);
       if (!foundAliased && !foundRestrict) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
                << "OpVariable " << inst->id()
-               << ": expected AliasedPointerEXT or RestrictPointerEXT for "
-               << "PhysicalStorageBufferEXT pointer.";
+               << ": expected AliasedPointer or RestrictPointer for "
+               << "PhysicalStorageBuffer pointer.";
       }
       if (foundAliased && foundRestrict) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
                << "OpVariable " << inst->id()
-               << ": can't specify both AliasedPointerEXT and "
-               << "RestrictPointerEXT for PhysicalStorageBufferEXT pointer.";
+               << ": can't specify both AliasedPointer and "
+               << "RestrictPointer for PhysicalStorageBuffer pointer.";
       }
     }
   }
@@ -667,8 +693,9 @@
     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, "
+               << _.VkErrorID(4680) << "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";
@@ -679,6 +706,7 @@
             storage_class != SpvStorageClassUniform &&
             storage_class != SpvStorageClassUniformConstant) {
           return _.diag(SPV_ERROR_INVALID_ID, inst)
+                 << _.VkErrorID(4680)
                  << "For Vulkan with RuntimeDescriptorArrayEXT, a variable "
                  << "containing OpTypeRuntimeArray must have storage class of "
                  << "StorageBuffer, Uniform, or UniformConstant.";
@@ -692,25 +720,30 @@
     // as BufferBlock.
     if (value_type && value_type->opcode() == SpvOpTypeStruct) {
       if (DoesStructContainRTA(_, value_type)) {
-        if (storage_class == SpvStorageClassStorageBuffer) {
+        if (storage_class == SpvStorageClassStorageBuffer ||
+            storage_class == SpvStorageClassPhysicalStorageBuffer) {
           if (!_.HasDecoration(value_id, SpvDecorationBlock)) {
             return _.diag(SPV_ERROR_INVALID_ID, inst)
+                   << _.VkErrorID(4680)
                    << "For Vulkan, an OpTypeStruct variable containing an "
                    << "OpTypeRuntimeArray must be decorated with Block if it "
-                   << "has storage class StorageBuffer.";
+                   << "has storage class StorageBuffer or "
+                      "PhysicalStorageBuffer.";
           }
         } else if (storage_class == SpvStorageClassUniform) {
           if (!_.HasDecoration(value_id, SpvDecorationBufferBlock)) {
             return _.diag(SPV_ERROR_INVALID_ID, inst)
+                   << _.VkErrorID(4680)
                    << "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)
+                 << _.VkErrorID(4680)
                  << "For Vulkan, OpTypeStruct variables containing "
                  << "OpTypeRuntimeArray must have storage class of "
-                 << "StorageBuffer or Uniform.";
+                 << "StorageBuffer, PhysicalStorageBuffer, or Uniform.";
         }
       }
     }
@@ -745,7 +778,7 @@
           SPV_OPERAND_TYPE_STORAGE_CLASS, storage_class);
       switch (storage_class) {
         case SpvStorageClassStorageBuffer:
-        case SpvStorageClassPhysicalStorageBufferEXT:
+        case SpvStorageClassPhysicalStorageBuffer:
           if (!_.HasCapability(SpvCapabilityStorageBuffer16BitAccess)) {
             storage_class_ok = false;
           }
@@ -807,7 +840,7 @@
           SPV_OPERAND_TYPE_STORAGE_CLASS, storage_class);
       switch (storage_class) {
         case SpvStorageClassStorageBuffer:
-        case SpvStorageClassPhysicalStorageBufferEXT:
+        case SpvStorageClassPhysicalStorageBuffer:
           if (!_.HasCapability(SpvCapabilityStorageBuffer8BitAccess)) {
             storage_class_ok = false;
           }
@@ -857,40 +890,40 @@
   const auto result_type = _.FindDef(inst->type_id());
   if (!result_type) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpLoad Result Type <id> '" << _.getIdName(inst->type_id())
-           << "' is not defined.";
+           << "OpLoad Result Type <id> " << _.getIdName(inst->type_id())
+           << " is not defined.";
   }
 
-  const bool uses_variable_pointers =
-      _.features().variable_pointers ||
-      _.features().variable_pointers_storage_buffer;
   const auto pointer_index = 2;
   const auto pointer_id = inst->GetOperandAs<uint32_t>(pointer_index);
   const auto pointer = _.FindDef(pointer_id);
   if (!pointer ||
       ((_.addressing_model() == SpvAddressingModelLogical) &&
-       ((!uses_variable_pointers &&
+       ((!_.features().variable_pointers &&
          !spvOpcodeReturnsLogicalPointer(pointer->opcode())) ||
-        (uses_variable_pointers &&
+        (_.features().variable_pointers &&
          !spvOpcodeReturnsLogicalVariablePointer(pointer->opcode()))))) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpLoad Pointer <id> '" << _.getIdName(pointer_id)
-           << "' is not a logical pointer.";
+           << "OpLoad Pointer <id> " << _.getIdName(pointer_id)
+           << " is not a logical pointer.";
   }
 
   const auto pointer_type = _.FindDef(pointer->type_id());
   if (!pointer_type || pointer_type->opcode() != SpvOpTypePointer) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpLoad type for pointer <id> '" << _.getIdName(pointer_id)
-           << "' is not a pointer type.";
+           << "OpLoad type for pointer <id> " << _.getIdName(pointer_id)
+           << " is not a pointer type.";
   }
 
-  const auto pointee_type = _.FindDef(pointer_type->GetOperandAs<uint32_t>(2));
-  if (!pointee_type || result_type->id() != pointee_type->id()) {
+  uint32_t pointee_data_type;
+  uint32_t storage_class;
+  if (!_.GetPointerTypeInfo(pointer_type->id(), &pointee_data_type,
+                            &storage_class) ||
+      result_type->id() != pointee_data_type) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpLoad Result Type <id> '" << _.getIdName(inst->type_id())
-           << "' does not match Pointer <id> '" << _.getIdName(pointer->id())
-           << "'s type.";
+           << "OpLoad Result Type <id> " << _.getIdName(inst->type_id())
+           << " does not match Pointer <id> " << _.getIdName(pointer->id())
+           << "s type.";
   }
 
   if (!_.options()->before_hlsl_legalization &&
@@ -917,34 +950,31 @@
 }
 
 spv_result_t ValidateStore(ValidationState_t& _, const Instruction* inst) {
-  const bool uses_variable_pointer =
-      _.features().variable_pointers ||
-      _.features().variable_pointers_storage_buffer;
   const auto pointer_index = 0;
   const auto pointer_id = inst->GetOperandAs<uint32_t>(pointer_index);
   const auto pointer = _.FindDef(pointer_id);
   if (!pointer ||
       (_.addressing_model() == SpvAddressingModelLogical &&
-       ((!uses_variable_pointer &&
+       ((!_.features().variable_pointers &&
          !spvOpcodeReturnsLogicalPointer(pointer->opcode())) ||
-        (uses_variable_pointer &&
+        (_.features().variable_pointers &&
          !spvOpcodeReturnsLogicalVariablePointer(pointer->opcode()))))) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpStore Pointer <id> '" << _.getIdName(pointer_id)
-           << "' is not a logical pointer.";
+           << "OpStore Pointer <id> " << _.getIdName(pointer_id)
+           << " is not a logical pointer.";
   }
   const auto pointer_type = _.FindDef(pointer->type_id());
   if (!pointer_type || pointer_type->opcode() != SpvOpTypePointer) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpStore type for pointer <id> '" << _.getIdName(pointer_id)
-           << "' is not a pointer type.";
+           << "OpStore type for pointer <id> " << _.getIdName(pointer_id)
+           << " is not a pointer type.";
   }
   const auto type_id = pointer_type->GetOperandAs<uint32_t>(2);
   const auto type = _.FindDef(type_id);
   if (!type || SpvOpTypeVoid == type->opcode()) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpStore Pointer <id> '" << _.getIdName(pointer_id)
-           << "'s type is void.";
+           << "OpStore Pointer <id> " << _.getIdName(pointer_id)
+           << "s type is void.";
   }
 
   // validate storage class
@@ -953,16 +983,36 @@
     uint32_t storage_class;
     if (!_.GetPointerTypeInfo(pointer_type->id(), &data_type, &storage_class)) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
-             << "OpStore Pointer <id> '" << _.getIdName(pointer_id)
-             << "' is not pointer type";
+             << "OpStore Pointer <id> " << _.getIdName(pointer_id)
+             << " is not pointer type";
     }
 
     if (storage_class == SpvStorageClassUniformConstant ||
         storage_class == SpvStorageClassInput ||
         storage_class == SpvStorageClassPushConstant) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
-             << "OpStore Pointer <id> '" << _.getIdName(pointer_id)
-             << "' storage class is read-only";
+             << "OpStore Pointer <id> " << _.getIdName(pointer_id)
+             << " storage class is read-only";
+    } else if (storage_class == SpvStorageClassShaderRecordBufferKHR) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "ShaderRecordBufferKHR Storage Class variables are read only";
+    } else if (storage_class == SpvStorageClassHitAttributeKHR) {
+      std::string errorVUID = _.VkErrorID(4703);
+      _.function(inst->function()->id())
+          ->RegisterExecutionModelLimitation(
+              [errorVUID](SpvExecutionModel model, std::string* message) {
+                if (model == SpvExecutionModelAnyHitKHR ||
+                    model == SpvExecutionModelClosestHitKHR) {
+                  if (message) {
+                    *message =
+                        errorVUID +
+                        "HitAttributeKHR Storage Class variables are read only "
+                        "with AnyHitKHR and ClosestHitKHR";
+                  }
+                  return false;
+                }
+                return true;
+              });
     }
 
     if (spvIsVulkanEnv(_.context()->target_env) &&
@@ -979,6 +1029,7 @@
         }
         if (_.HasDecoration(base_type->id(), SpvDecorationBlock)) {
           return _.diag(SPV_ERROR_INVALID_ID, inst)
+                 << _.VkErrorID(6925)
                  << "In the Vulkan environment, cannot store to Uniform Blocks";
         }
       }
@@ -990,31 +1041,31 @@
   const auto object = _.FindDef(object_id);
   if (!object || !object->type_id()) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpStore Object <id> '" << _.getIdName(object_id)
-           << "' is not an object.";
+           << "OpStore Object <id> " << _.getIdName(object_id)
+           << " is not an object.";
   }
   const auto object_type = _.FindDef(object->type_id());
   if (!object_type || SpvOpTypeVoid == object_type->opcode()) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpStore Object <id> '" << _.getIdName(object_id)
-           << "'s type is void.";
+           << "OpStore Object <id> " << _.getIdName(object_id)
+           << "s type is void.";
   }
 
   if (type->id() != object_type->id()) {
     if (!_.options()->relax_struct_store || type->opcode() != SpvOpTypeStruct ||
         object_type->opcode() != SpvOpTypeStruct) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
-             << "OpStore Pointer <id> '" << _.getIdName(pointer_id)
-             << "'s type does not match Object <id> '"
-             << _.getIdName(object->id()) << "'s type.";
+             << "OpStore Pointer <id> " << _.getIdName(pointer_id)
+             << "s type does not match Object <id> "
+             << _.getIdName(object->id()) << "s type.";
     }
 
     // TODO: Check for layout compatible matricies and arrays as well.
     if (!AreLayoutCompatibleStructs(_, type, object_type)) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
-             << "OpStore Pointer <id> '" << _.getIdName(pointer_id)
-             << "'s layout does not match Object <id> '"
-             << _.getIdName(object->id()) << "'s layout.";
+             << "OpStore Pointer <id> " << _.getIdName(pointer_id)
+             << "s layout does not match Object <id> "
+             << _.getIdName(object->id()) << "s layout.";
     }
   }
 
@@ -1086,8 +1137,8 @@
   const auto target = _.FindDef(target_id);
   if (!target) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "Target operand <id> '" << _.getIdName(target_id)
-           << "' is not defined.";
+           << "Target operand <id> " << _.getIdName(target_id)
+           << " is not defined.";
   }
 
   const auto source_index = 1;
@@ -1095,24 +1146,24 @@
   const auto source = _.FindDef(source_id);
   if (!source) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "Source operand <id> '" << _.getIdName(source_id)
-           << "' is not defined.";
+           << "Source operand <id> " << _.getIdName(source_id)
+           << " is not defined.";
   }
 
   const auto target_pointer_type = _.FindDef(target->type_id());
   if (!target_pointer_type ||
       target_pointer_type->opcode() != SpvOpTypePointer) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "Target operand <id> '" << _.getIdName(target_id)
-           << "' is not a pointer.";
+           << "Target operand <id> " << _.getIdName(target_id)
+           << " is not a pointer.";
   }
 
   const auto source_pointer_type = _.FindDef(source->type_id());
   if (!source_pointer_type ||
       source_pointer_type->opcode() != SpvOpTypePointer) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "Source operand <id> '" << _.getIdName(source_id)
-           << "' is not a pointer.";
+           << "Source operand <id> " << _.getIdName(source_id)
+           << " is not a pointer.";
   }
 
   if (inst->opcode() == SpvOpCopyMemory) {
@@ -1120,70 +1171,66 @@
         _.FindDef(target_pointer_type->GetOperandAs<uint32_t>(2));
     if (!target_type || target_type->opcode() == SpvOpTypeVoid) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
-             << "Target operand <id> '" << _.getIdName(target_id)
-             << "' cannot be a void pointer.";
+             << "Target operand <id> " << _.getIdName(target_id)
+             << " cannot be a void pointer.";
     }
 
     const auto source_type =
         _.FindDef(source_pointer_type->GetOperandAs<uint32_t>(2));
     if (!source_type || source_type->opcode() == SpvOpTypeVoid) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
-             << "Source operand <id> '" << _.getIdName(source_id)
-             << "' cannot be a void pointer.";
+             << "Source operand <id> " << _.getIdName(source_id)
+             << " cannot be a void pointer.";
     }
 
     if (target_type->id() != source_type->id()) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
-             << "Target <id> '" << _.getIdName(source_id)
-             << "'s type does not match Source <id> '"
-             << _.getIdName(source_type->id()) << "'s type.";
+             << "Target <id> " << _.getIdName(source_id)
+             << "s type does not match Source <id> "
+             << _.getIdName(source_type->id()) << "s type.";
     }
-
-    if (auto error = CheckMemoryAccess(_, inst, 2)) return error;
   } else {
     const auto size_id = inst->GetOperandAs<uint32_t>(2);
     const auto size = _.FindDef(size_id);
     if (!size) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
-             << "Size operand <id> '" << _.getIdName(size_id)
-             << "' is not defined.";
+             << "Size operand <id> " << _.getIdName(size_id)
+             << " is not defined.";
     }
 
     const auto size_type = _.FindDef(size->type_id());
     if (!_.IsIntScalarType(size_type->id())) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
-             << "Size operand <id> '" << _.getIdName(size_id)
-             << "' must be a scalar integer type.";
+             << "Size operand <id> " << _.getIdName(size_id)
+             << " must be a scalar integer type.";
     }
 
     bool is_zero = true;
     switch (size->opcode()) {
       case SpvOpConstantNull:
         return _.diag(SPV_ERROR_INVALID_ID, inst)
-               << "Size operand <id> '" << _.getIdName(size_id)
-               << "' cannot be a constant zero.";
+               << "Size operand <id> " << _.getIdName(size_id)
+               << " cannot be a constant zero.";
       case SpvOpConstant:
         if (size_type->word(3) == 1 &&
             size->word(size->words().size() - 1) & 0x80000000) {
           return _.diag(SPV_ERROR_INVALID_ID, inst)
-                 << "Size operand <id> '" << _.getIdName(size_id)
-                 << "' cannot have the sign bit set to 1.";
+                 << "Size operand <id> " << _.getIdName(size_id)
+                 << " cannot have the sign bit set to 1.";
         }
         for (size_t i = 3; is_zero && i < size->words().size(); ++i) {
           is_zero &= (size->word(i) == 0);
         }
         if (is_zero) {
           return _.diag(SPV_ERROR_INVALID_ID, inst)
-                 << "Size operand <id> '" << _.getIdName(size_id)
-                 << "' cannot be a constant zero.";
+                 << "Size operand <id> " << _.getIdName(size_id)
+                 << " cannot be a constant zero.";
         }
         break;
       default:
         // Cannot infer any other opcodes.
         break;
     }
-
-    if (auto error = CheckMemoryAccess(_, inst, 3)) return error;
   }
   if (auto error = ValidateCopyMemoryMemoryAccess(_, inst)) return error;
 
@@ -1210,8 +1257,8 @@
   auto result_type = _.FindDef(inst->type_id());
   if (SpvOpTypePointer != result_type->opcode()) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "The Result Type of " << instr_name << " <id> '"
-           << _.getIdName(inst->id()) << "' must be OpTypePointer. Found Op"
+           << "The Result Type of " << instr_name << " <id> "
+           << _.getIdName(inst->id()) << " must be OpTypePointer. Found Op"
            << spvOpcodeString(static_cast<SpvOp>(result_type->opcode())) << ".";
   }
 
@@ -1227,7 +1274,7 @@
   const auto base_type = _.FindDef(base->type_id());
   if (!base_type || SpvOpTypePointer != base_type->opcode()) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "The Base <id> '" << _.getIdName(base_id) << "' in " << instr_name
+           << "The Base <id> " << _.getIdName(base_id) << " in " << instr_name
            << " instruction must be a pointer.";
   }
 
@@ -1319,8 +1366,8 @@
           return _.diag(SPV_ERROR_INVALID_ID, cur_word_instr)
                  << "Index is out of bounds: " << instr_name
                  << " can not find index " << cur_index
-                 << " into the structure <id> '"
-                 << _.getIdName(type_pointee->id()) << "'. This structure has "
+                 << " into the structure <id> "
+                 << _.getIdName(type_pointee->id()) << ". This structure has "
                  << num_struct_members << " members. Largest valid index is "
                  << num_struct_members - 1 << ".";
         }
@@ -1357,8 +1404,7 @@
 spv_result_t ValidatePtrAccessChain(ValidationState_t& _,
                                     const Instruction* inst) {
   if (_.addressing_model() == SpvAddressingModelLogical) {
-    if (!_.features().variable_pointers &&
-        !_.features().variable_pointers_storage_buffer) {
+    if (!_.features().variable_pointers) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << "Generating variable pointers requires capability "
              << "VariablePointers or VariablePointersStorageBuffer";
@@ -1378,9 +1424,9 @@
       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> '"
+           << "The Result Type of " << instr_name << " <id> "
            << state.getIdName(inst->id())
-           << "' must be OpTypeInt with width 32 and signedness 0.";
+           << " must be OpTypeInt with width 32 and signedness 0.";
   }
 
   // The structure that is passed in must be an pointer to a structure, whose
@@ -1389,17 +1435,17 @@
   auto pointer_type = state.FindDef(pointer->type_id());
   if (pointer_type->opcode() != SpvOpTypePointer) {
     return state.diag(SPV_ERROR_INVALID_ID, inst)
-           << "The Struture's type in " << instr_name << " <id> '"
+           << "The Structure's type in " << instr_name << " <id> "
            << state.getIdName(inst->id())
-           << "' must be a pointer to an OpTypeStruct.";
+           << " must be a pointer to an OpTypeStruct.";
   }
 
   auto structure_type = state.FindDef(pointer_type->GetOperandAs<uint32_t>(2));
   if (structure_type->opcode() != SpvOpTypeStruct) {
     return state.diag(SPV_ERROR_INVALID_ID, inst)
-           << "The Struture's type in " << instr_name << " <id> '"
+           << "The Structure's type in " << instr_name << " <id> "
            << state.getIdName(inst->id())
-           << "' must be a pointer to an OpTypeStruct.";
+           << " must be a pointer to an OpTypeStruct.";
   }
 
   auto num_of_members = structure_type->operands().size() - 1;
@@ -1407,17 +1453,17 @@
       state.FindDef(structure_type->GetOperandAs<uint32_t>(num_of_members));
   if (last_member->opcode() != SpvOpTypeRuntimeArray) {
     return state.diag(SPV_ERROR_INVALID_ID, inst)
-           << "The Struture's last member in " << instr_name << " <id> '"
-           << state.getIdName(inst->id()) << "' must be an OpTypeRuntimeArray.";
+           << "The Structure's last member in " << instr_name << " <id> "
+           << state.getIdName(inst->id()) << " must be an OpTypeRuntimeArray.";
   }
 
-  // The array member must the the index of the last element (the run time
+  // The array member must the index of the last element (the run time
   // array).
   if (inst->GetOperandAs<uint32_t>(3) != num_of_members - 1) {
     return state.diag(SPV_ERROR_INVALID_ID, inst)
-           << "The array member in " << instr_name << " <id> '"
+           << "The array member in " << instr_name << " <id> "
            << state.getIdName(inst->id())
-           << "' must be an the last member of the struct.";
+           << " must be an the last member of the struct.";
   }
   return SPV_SUCCESS;
 }
@@ -1433,18 +1479,17 @@
       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> '"
+           << "The Result Type of " << instr_name << " <id> "
            << state.getIdName(inst->id())
-           << "' must be OpTypeInt with width 32 and signedness 0.";
+           << " 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.";
+           << "The type in " << instr_name << " <id> "
+           << state.getIdName(type_id) << " must be OpTypeCooperativeMatrixNV.";
   }
   return SPV_SUCCESS;
 }
@@ -1467,39 +1512,36 @@
   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.";
+             << "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.";
+             << "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 &&
+       ((!_.features().variable_pointers &&
          !spvOpcodeReturnsLogicalPointer(pointer->opcode())) ||
-        (uses_variable_pointers &&
+        (_.features().variable_pointers &&
          !spvOpcodeReturnsLogicalVariablePointer(pointer->opcode()))))) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << opname << " Pointer <id> '" << _.getIdName(pointer_id)
-           << "' is not a logical pointer.";
+           << 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.";
+           << opname << " type for pointer <id> " << _.getIdName(pointer_id)
+           << " is not a pointer type.";
   }
 
   const auto storage_class_index = 1u;
@@ -1508,11 +1550,11 @@
 
   if (storage_class != SpvStorageClassWorkgroup &&
       storage_class != SpvStorageClassStorageBuffer &&
-      storage_class != SpvStorageClassPhysicalStorageBufferEXT) {
+      storage_class != SpvStorageClassPhysicalStorageBuffer) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << opname << " storage class for pointer type <id> '"
+           << opname << " storage class for pointer type <id> "
            << _.getIdName(pointer_type_id)
-           << "' is not Workgroup or StorageBuffer.";
+           << " is not Workgroup or StorageBuffer.";
   }
 
   const auto pointee_id = pointer_type->GetOperandAs<uint32_t>(2);
@@ -1520,8 +1562,8 @@
   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.";
+           << opname << " Pointer <id> " << _.getIdName(pointer->id())
+           << "s Type must be a scalar or vector type.";
   }
 
   const auto stride_index =
@@ -1530,8 +1572,8 @@
   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.";
+           << "Stride operand <id> " << _.getIdName(stride_id)
+           << " must be a scalar integer type.";
   }
 
   const auto colmajor_index =
@@ -1542,8 +1584,8 @@
       !(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.";
+           << "Column Major operand <id> " << _.getIdName(colmajor_id)
+           << " must be a boolean constant instruction.";
   }
 
   const auto memory_access_index =
@@ -1559,10 +1601,10 @@
 spv_result_t ValidatePtrComparison(ValidationState_t& _,
                                    const Instruction* inst) {
   if (_.addressing_model() == SpvAddressingModelLogical &&
-      !_.features().variable_pointers_storage_buffer) {
+      !_.features().variable_pointers) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "Instruction cannot be used without a variable pointers "
-              "capability";
+           << "Instruction cannot for logical addressing model be used without "
+              "a variable pointers capability";
   }
 
   const auto result_type = _.FindDef(inst->type_id());
@@ -1597,7 +1639,8 @@
              << "Invalid pointer storage class";
     }
 
-    if (sc == SpvStorageClassWorkgroup && !_.features().variable_pointers) {
+    if (sc == SpvStorageClassWorkgroup &&
+        !_.HasCapability(SpvCapabilityVariablePointers)) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
              << "Workgroup storage class pointer requires VariablePointers "
                 "capability to be specified";
diff --git a/source/val/validate_mesh_shading.cpp b/source/val/validate_mesh_shading.cpp
new file mode 100644
index 0000000..a7f0726
--- /dev/null
+++ b/source/val/validate_mesh_shading.cpp
@@ -0,0 +1,123 @@
+// Copyright (c) 2022 The Khronos Group 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.
+
+// Validates ray query instructions from SPV_KHR_ray_query
+
+#include "source/opcode.h"
+#include "source/val/instruction.h"
+#include "source/val/validate.h"
+#include "source/val/validation_state.h"
+
+namespace spvtools {
+namespace val {
+
+spv_result_t MeshShadingPass(ValidationState_t& _, const Instruction* inst) {
+  const SpvOp opcode = inst->opcode();
+  switch (opcode) {
+    case SpvOpEmitMeshTasksEXT: {
+      _.function(inst->function()->id())
+          ->RegisterExecutionModelLimitation(
+              [](SpvExecutionModel model, std::string* message) {
+                if (model != SpvExecutionModelTaskEXT) {
+                  if (message) {
+                    *message =
+                        "OpEmitMeshTasksEXT requires TaskEXT execution model";
+                  }
+                  return false;
+                }
+                return true;
+              });
+
+      const uint32_t group_count_x = _.GetOperandTypeId(inst, 0);
+      if (!_.IsUnsignedIntScalarType(group_count_x) ||
+          _.GetBitWidth(group_count_x) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Group Count X must be a 32-bit unsigned int scalar";
+      }
+
+      const uint32_t group_count_y = _.GetOperandTypeId(inst, 1);
+      if (!_.IsUnsignedIntScalarType(group_count_y) ||
+          _.GetBitWidth(group_count_y) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Group Count Y must be a 32-bit unsigned int scalar";
+      }
+
+      const uint32_t group_count_z = _.GetOperandTypeId(inst, 2);
+      if (!_.IsUnsignedIntScalarType(group_count_z) ||
+          _.GetBitWidth(group_count_z) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Group Count Z must be a 32-bit unsigned int scalar";
+      }
+
+      if (inst->operands().size() == 4) {
+        const auto payload = _.FindDef(inst->GetOperandAs<uint32_t>(3));
+        if (payload->opcode() != SpvOpVariable) {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Payload must be the result of a OpVariable";
+        }
+        if (SpvStorageClass(payload->GetOperandAs<uint32_t>(2)) !=
+            SpvStorageClassTaskPayloadWorkgroupEXT) {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Payload OpVariable must have a storage class of "
+                    "TaskPayloadWorkgroupEXT";
+        }
+      }
+      break;
+    }
+
+    case SpvOpSetMeshOutputsEXT: {
+      _.function(inst->function()->id())
+          ->RegisterExecutionModelLimitation(
+              [](SpvExecutionModel model, std::string* message) {
+                if (model != SpvExecutionModelMeshEXT) {
+                  if (message) {
+                    *message =
+                        "OpSetMeshOutputsEXT requires MeshEXT execution model";
+                  }
+                  return false;
+                }
+                return true;
+              });
+
+      const uint32_t vertex_count = _.GetOperandTypeId(inst, 0);
+      if (!_.IsUnsignedIntScalarType(vertex_count) ||
+          _.GetBitWidth(vertex_count) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Vertex Count must be a 32-bit unsigned int scalar";
+      }
+
+      const uint32_t primitive_count = _.GetOperandTypeId(inst, 1);
+      if (!_.IsUnsignedIntScalarType(primitive_count) ||
+          _.GetBitWidth(primitive_count) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Primitive Count must be a 32-bit unsigned int scalar";
+      }
+
+      break;
+    }
+
+    case SpvOpWritePackedPrimitiveIndices4x8NV: {
+      // No validation rules (for the moment).
+      break;
+    }
+
+    default:
+      break;
+  }
+
+  return SPV_SUCCESS;
+}
+
+}  // namespace val
+}  // namespace spvtools
diff --git a/source/val/validate_misc.cpp b/source/val/validate_misc.cpp
index 3bc15ca..5acc21e 100644
--- a/source/val/validate_misc.cpp
+++ b/source/val/validate_misc.cpp
@@ -59,10 +59,7 @@
   // a vector of two - components of 32 -
   // bit unsigned integer type
   const uint32_t result_type = inst->type_id();
-  if (!(_.IsUnsignedIntScalarType(result_type) &&
-        _.GetBitWidth(result_type) == 64) &&
-      !(_.IsUnsignedIntVectorType(result_type) &&
-        _.GetDimension(result_type) == 2 && _.GetBitWidth(result_type) == 32)) {
+  if (!_.IsUnsigned64BitHandle(result_type)) {
     return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Expected Value to be a "
                                                    "vector of two components"
                                                    " of unsigned integer"
diff --git a/source/val/validate_mode_setting.cpp b/source/val/validate_mode_setting.cpp
index 9635268..672192b 100644
--- a/source/val/validate_mode_setting.cpp
+++ b/source/val/validate_mode_setting.cpp
@@ -12,13 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 //
-#include "source/val/validate.h"
-
 #include <algorithm>
 
 #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 {
@@ -30,8 +29,8 @@
   auto entry_point = _.FindDef(entry_point_id);
   if (!entry_point || SpvOpFunction != entry_point->opcode()) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpEntryPoint Entry Point <id> '" << _.getIdName(entry_point_id)
-           << "' is not a function.";
+           << "OpEntryPoint Entry Point <id> " << _.getIdName(entry_point_id)
+           << " is not a function.";
   }
 
   // Only check the shader execution models
@@ -42,18 +41,18 @@
     const auto entry_point_type = _.FindDef(entry_point_type_id);
     if (!entry_point_type || 3 != entry_point_type->words().size()) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
-             << _.VkErrorID(4633) << "OpEntryPoint Entry Point <id> '"
+             << _.VkErrorID(4633) << "OpEntryPoint Entry Point <id> "
              << _.getIdName(entry_point_id)
-             << "'s function parameter count is not zero.";
+             << "s function parameter count is not zero.";
     }
   }
 
   auto return_type = _.FindDef(entry_point->type_id());
   if (!return_type || SpvOpTypeVoid != return_type->opcode()) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << _.VkErrorID(4633) << "OpEntryPoint Entry Point <id> '"
+           << _.VkErrorID(4633) << "OpEntryPoint Entry Point <id> "
            << _.getIdName(entry_point_id)
-           << "'s function return type is not void.";
+           << "s function return type is not void.";
   }
 
   const auto* execution_modes = _.GetExecutionModes(entry_point_id);
@@ -112,6 +111,44 @@
                  << "Fragment execution model entry points can specify at most "
                     "one fragment shader interlock execution mode.";
         }
+        if (execution_modes &&
+            1 < std::count_if(
+                    execution_modes->begin(), execution_modes->end(),
+                    [](const SpvExecutionMode& mode) {
+                      switch (mode) {
+                        case SpvExecutionModeStencilRefUnchangedFrontAMD:
+                        case SpvExecutionModeStencilRefLessFrontAMD:
+                        case SpvExecutionModeStencilRefGreaterFrontAMD:
+                          return true;
+                        default:
+                          return false;
+                      }
+                    })) {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Fragment execution model entry points can specify at most "
+                    "one of StencilRefUnchangedFrontAMD, "
+                    "StencilRefLessFrontAMD or StencilRefGreaterFrontAMD "
+                    "execution modes.";
+        }
+        if (execution_modes &&
+            1 < std::count_if(
+                    execution_modes->begin(), execution_modes->end(),
+                    [](const SpvExecutionMode& mode) {
+                      switch (mode) {
+                        case SpvExecutionModeStencilRefUnchangedBackAMD:
+                        case SpvExecutionModeStencilRefLessBackAMD:
+                        case SpvExecutionModeStencilRefGreaterBackAMD:
+                          return true;
+                        default:
+                          return false;
+                      }
+                    })) {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "Fragment execution model entry points can specify at most "
+                    "one of StencilRefUnchangedBackAMD, "
+                    "StencilRefLessBackAMD or StencilRefGreaterBackAMD "
+                    "execution modes.";
+        }
         break;
       case SpvExecutionModelTessellationControl:
       case SpvExecutionModelTessellationEvaluation:
@@ -204,6 +241,39 @@
                     "OutputTriangleStrip execution modes.";
         }
         break;
+      case SpvExecutionModelMeshEXT:
+        if (!execution_modes ||
+            1 != std::count_if(execution_modes->begin(), execution_modes->end(),
+                               [](const SpvExecutionMode& mode) {
+                                 switch (mode) {
+                                   case SpvExecutionModeOutputPoints:
+                                   case SpvExecutionModeOutputLinesEXT:
+                                   case SpvExecutionModeOutputTrianglesEXT:
+                                     return true;
+                                   default:
+                                     return false;
+                                 }
+                               })) {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "MeshEXT execution model entry points must specify exactly "
+                    "one of OutputPoints, OutputLinesEXT, or "
+                    "OutputTrianglesEXT Execution Modes.";
+        } else if (2 != std::count_if(
+                            execution_modes->begin(), execution_modes->end(),
+                            [](const SpvExecutionMode& mode) {
+                              switch (mode) {
+                                case SpvExecutionModeOutputPrimitivesEXT:
+                                case SpvExecutionModeOutputVertices:
+                                  return true;
+                                default:
+                                  return false;
+                              }
+                            })) {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "MeshEXT execution model entry points must specify both "
+                    "OutputPrimitivesEXT and OutputVertices Execution Modes.";
+        }
+        break;
       default:
         break;
     }
@@ -258,9 +328,8 @@
                                _.entry_points().cend(), entry_point_id);
   if (found == _.entry_points().cend()) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpExecutionMode Entry Point <id> '"
-           << _.getIdName(entry_point_id)
-           << "' is not the Entry Point "
+           << "OpExecutionMode Entry Point <id> " << _.getIdName(entry_point_id)
+           << " is not the Entry Point "
               "operand of an OpEntryPoint.";
   }
 
@@ -321,14 +390,18 @@
                              return true;
                            case SpvExecutionModelMeshNV:
                              return _.HasCapability(SpvCapabilityMeshShadingNV);
+                           case SpvExecutionModelMeshEXT:
+                             return _.HasCapability(
+                                 SpvCapabilityMeshShadingEXT);
                            default:
                              return false;
                          }
                        })) {
-        if (_.HasCapability(SpvCapabilityMeshShadingNV)) {
+        if (_.HasCapability(SpvCapabilityMeshShadingNV) ||
+            _.HasCapability(SpvCapabilityMeshShadingEXT)) {
           return _.diag(SPV_ERROR_INVALID_DATA, inst)
-                 << "Execution mode can only be used with the Geometry or "
-                    "MeshNV execution model.";
+                 << "Execution mode can only be used with the Geometry "
+                    "MeshNV or MeshEXT execution model.";
         } else {
           return _.diag(SPV_ERROR_INVALID_DATA, inst)
                  << "Execution mode can only be used with the Geometry "
@@ -383,14 +456,18 @@
                              return true;
                            case SpvExecutionModelMeshNV:
                              return _.HasCapability(SpvCapabilityMeshShadingNV);
+                           case SpvExecutionModelMeshEXT:
+                             return _.HasCapability(
+                                 SpvCapabilityMeshShadingEXT);
                            default:
                              return false;
                          }
                        })) {
-        if (_.HasCapability(SpvCapabilityMeshShadingNV)) {
+        if (_.HasCapability(SpvCapabilityMeshShadingNV) ||
+            _.HasCapability(SpvCapabilityMeshShadingEXT)) {
           return _.diag(SPV_ERROR_INVALID_DATA, inst)
                  << "Execution mode can only be used with a Geometry, "
-                    "tessellation or MeshNV execution model.";
+                    "tessellation, MeshNV or MeshEXT execution model.";
         } else {
           return _.diag(SPV_ERROR_INVALID_DATA, inst)
                  << "Execution mode can only be used with a Geometry or "
@@ -398,6 +475,20 @@
         }
       }
       break;
+    case SpvExecutionModeOutputLinesEXT:
+    case SpvExecutionModeOutputTrianglesEXT:
+    case SpvExecutionModeOutputPrimitivesEXT:
+      if (!std::all_of(models->begin(), models->end(),
+                       [](const SpvExecutionModel& model) {
+                         return (model == SpvExecutionModelMeshEXT ||
+                                 model == SpvExecutionModelMeshNV);
+                       })) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Execution mode can only be used with the MeshEXT or MeshNV "
+                  "execution "
+                  "model.";
+      }
+      break;
     case SpvExecutionModePixelCenterInteger:
     case SpvExecutionModeOriginUpperLeft:
     case SpvExecutionModeOriginLowerLeft:
@@ -412,6 +503,13 @@
     case SpvExecutionModeSampleInterlockUnorderedEXT:
     case SpvExecutionModeShadingRateInterlockOrderedEXT:
     case SpvExecutionModeShadingRateInterlockUnorderedEXT:
+    case SpvExecutionModeEarlyAndLateFragmentTestsAMD:
+    case SpvExecutionModeStencilRefUnchangedFrontAMD:
+    case SpvExecutionModeStencilRefGreaterFrontAMD:
+    case SpvExecutionModeStencilRefLessFrontAMD:
+    case SpvExecutionModeStencilRefUnchangedBackAMD:
+    case SpvExecutionModeStencilRefGreaterBackAMD:
+    case SpvExecutionModeStencilRefLessBackAMD:
       if (!std::all_of(models->begin(), models->end(),
                        [](const SpvExecutionModel& model) {
                          return model == SpvExecutionModelFragment;
@@ -449,14 +547,19 @@
                            case SpvExecutionModelTaskNV:
                            case SpvExecutionModelMeshNV:
                              return _.HasCapability(SpvCapabilityMeshShadingNV);
+                           case SpvExecutionModelTaskEXT:
+                           case SpvExecutionModelMeshEXT:
+                             return _.HasCapability(
+                                 SpvCapabilityMeshShadingEXT);
                            default:
                              return false;
                          }
                        })) {
-        if (_.HasCapability(SpvCapabilityMeshShadingNV)) {
+        if (_.HasCapability(SpvCapabilityMeshShadingNV) ||
+            _.HasCapability(SpvCapabilityMeshShadingEXT)) {
           return _.diag(SPV_ERROR_INVALID_DATA, inst)
                  << "Execution mode can only be used with a Kernel, GLCompute, "
-                    "MeshNV, or TaskNV execution model.";
+                    "MeshNV, MeshEXT, TaskNV or TaskEXT execution model.";
         } else {
           return _.diag(SPV_ERROR_INVALID_DATA, inst)
                  << "Execution mode can only be used with a Kernel or "
diff --git a/source/val/validate_non_uniform.cpp b/source/val/validate_non_uniform.cpp
index 2b6eb8b..6d4f8a2 100644
--- a/source/val/validate_non_uniform.cpp
+++ b/source/val/validate_non_uniform.cpp
@@ -63,6 +63,59 @@
   return SPV_SUCCESS;
 }
 
+spv_result_t ValidateGroupNonUniformRotateKHR(ValidationState_t& _,
+                                              const Instruction* inst) {
+  // Scope is already checked by ValidateExecutionScope() above.
+  const uint32_t result_type = inst->type_id();
+  if (!_.IsIntScalarOrVectorType(result_type) &&
+      !_.IsFloatScalarOrVectorType(result_type) &&
+      !_.IsBoolScalarOrVectorType(result_type)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Expected Result Type to be a scalar or vector of "
+              "floating-point, integer or boolean type.";
+  }
+
+  const uint32_t value_type = _.GetTypeId(inst->GetOperandAs<uint32_t>(3));
+  if (value_type != result_type) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Result Type must be the same as the type of Value.";
+  }
+
+  const uint32_t delta_type = _.GetTypeId(inst->GetOperandAs<uint32_t>(4));
+  if (!_.IsUnsignedIntScalarType(delta_type)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Delta must be a scalar of integer type, whose Signedness "
+              "operand is 0.";
+  }
+
+  if (inst->words().size() > 6) {
+    const uint32_t cluster_size_op_id = inst->GetOperandAs<uint32_t>(5);
+    const uint32_t cluster_size_type = _.GetTypeId(cluster_size_op_id);
+    if (!_.IsUnsignedIntScalarType(cluster_size_type)) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "ClusterSize must be a scalar of integer type, whose "
+                "Signedness operand is 0.";
+    }
+
+    uint64_t cluster_size;
+    if (!_.GetConstantValUint64(cluster_size_op_id, &cluster_size)) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "ClusterSize must come from a constant instruction.";
+    }
+
+    if ((cluster_size == 0) || ((cluster_size & (cluster_size - 1)) != 0)) {
+      return _.diag(SPV_WARNING, inst)
+             << "Behavior is undefined unless ClusterSize is at least 1 and a "
+                "power of 2.";
+    }
+
+    // TODO(kpet) Warn about undefined behavior when ClusterSize is greater than
+    // the declared SubGroupSize
+  }
+
+  return SPV_SUCCESS;
+}
+
 }  // namespace
 
 // Validates correctness of non-uniform group instructions.
@@ -79,6 +132,8 @@
   switch (opcode) {
     case SpvOpGroupNonUniformBallotBitCount:
       return ValidateGroupNonUniformBallotBitCount(_, inst);
+    case SpvOpGroupNonUniformRotateKHR:
+      return ValidateGroupNonUniformRotateKHR(_, inst);
     default:
       break;
   }
diff --git a/source/val/validate_ray_query.cpp b/source/val/validate_ray_query.cpp
new file mode 100644
index 0000000..b553449
--- /dev/null
+++ b/source/val/validate_ray_query.cpp
@@ -0,0 +1,273 @@
+// Copyright (c) 2022 The Khronos Group 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.
+
+// Validates ray query instructions from SPV_KHR_ray_query
+
+#include "source/opcode.h"
+#include "source/val/instruction.h"
+#include "source/val/validate.h"
+#include "source/val/validation_state.h"
+
+namespace spvtools {
+namespace val {
+namespace {
+
+spv_result_t ValidateRayQueryPointer(ValidationState_t& _,
+                                     const Instruction* inst,
+                                     uint32_t ray_query_index) {
+  const uint32_t ray_query_id = inst->GetOperandAs<uint32_t>(ray_query_index);
+  auto variable = _.FindDef(ray_query_id);
+  const auto var_opcode = variable->opcode();
+  if (!variable ||
+      (var_opcode != SpvOpVariable && var_opcode != SpvOpFunctionParameter &&
+       var_opcode != SpvOpAccessChain)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Ray Query must be a memory object declaration";
+  }
+  auto pointer = _.FindDef(variable->GetOperandAs<uint32_t>(0));
+  if (!pointer || pointer->opcode() != SpvOpTypePointer) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Ray Query must be a pointer";
+  }
+  auto type = _.FindDef(pointer->GetOperandAs<uint32_t>(2));
+  if (!type || type->opcode() != SpvOpTypeRayQueryKHR) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Ray Query must be a pointer to OpTypeRayQueryKHR";
+  }
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateIntersectionId(ValidationState_t& _,
+                                    const Instruction* inst,
+                                    uint32_t intersection_index) {
+  const uint32_t intersection_id =
+      inst->GetOperandAs<uint32_t>(intersection_index);
+  const uint32_t intersection_type = _.GetTypeId(intersection_id);
+  const SpvOp intersection_opcode = _.GetIdOpcode(intersection_id);
+  if (!_.IsIntScalarType(intersection_type) ||
+      _.GetBitWidth(intersection_type) != 32 ||
+      !spvOpcodeIsConstant(intersection_opcode)) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "expected Intersection ID to be a constant 32-bit int scalar";
+  }
+
+  return SPV_SUCCESS;
+}
+
+}  // namespace
+
+spv_result_t RayQueryPass(ValidationState_t& _, const Instruction* inst) {
+  const SpvOp opcode = inst->opcode();
+  const uint32_t result_type = inst->type_id();
+
+  switch (opcode) {
+    case SpvOpRayQueryInitializeKHR: {
+      if (auto error = ValidateRayQueryPointer(_, inst, 0)) return error;
+
+      if (_.GetIdOpcode(_.GetOperandTypeId(inst, 1)) !=
+          SpvOpTypeAccelerationStructureKHR) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Expected Acceleration Structure to be of type "
+                  "OpTypeAccelerationStructureKHR";
+      }
+
+      const uint32_t ray_flags = _.GetOperandTypeId(inst, 2);
+      if (!_.IsIntScalarType(ray_flags) || _.GetBitWidth(ray_flags) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Ray Flags must be a 32-bit int scalar";
+      }
+
+      const uint32_t cull_mask = _.GetOperandTypeId(inst, 3);
+      if (!_.IsIntScalarType(cull_mask) || _.GetBitWidth(cull_mask) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Cull Mask must be a 32-bit int scalar";
+      }
+
+      const uint32_t ray_origin = _.GetOperandTypeId(inst, 4);
+      if (!_.IsFloatVectorType(ray_origin) || _.GetDimension(ray_origin) != 3 ||
+          _.GetBitWidth(ray_origin) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Ray Origin must be a 32-bit float 3-component vector";
+      }
+
+      const uint32_t ray_tmin = _.GetOperandTypeId(inst, 5);
+      if (!_.IsFloatScalarType(ray_tmin) || _.GetBitWidth(ray_tmin) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Ray TMin must be a 32-bit float scalar";
+      }
+
+      const uint32_t ray_direction = _.GetOperandTypeId(inst, 6);
+      if (!_.IsFloatVectorType(ray_direction) ||
+          _.GetDimension(ray_direction) != 3 ||
+          _.GetBitWidth(ray_direction) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Ray Direction must be a 32-bit float 3-component vector";
+      }
+
+      const uint32_t ray_tmax = _.GetOperandTypeId(inst, 7);
+      if (!_.IsFloatScalarType(ray_tmax) || _.GetBitWidth(ray_tmax) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Ray TMax must be a 32-bit float scalar";
+      }
+      break;
+    }
+
+    case SpvOpRayQueryTerminateKHR:
+    case SpvOpRayQueryConfirmIntersectionKHR: {
+      if (auto error = ValidateRayQueryPointer(_, inst, 0)) return error;
+      break;
+    }
+
+    case SpvOpRayQueryGenerateIntersectionKHR: {
+      if (auto error = ValidateRayQueryPointer(_, inst, 0)) return error;
+
+      const uint32_t hit_t_id = _.GetOperandTypeId(inst, 1);
+      if (!_.IsFloatScalarType(hit_t_id) || _.GetBitWidth(hit_t_id) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Hit T must be a 32-bit float scalar";
+      }
+
+      break;
+    }
+
+    case SpvOpRayQueryGetIntersectionFrontFaceKHR:
+    case SpvOpRayQueryProceedKHR:
+    case SpvOpRayQueryGetIntersectionCandidateAABBOpaqueKHR: {
+      if (auto error = ValidateRayQueryPointer(_, inst, 2)) return error;
+
+      if (!_.IsBoolScalarType(result_type)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "expected Result Type to be bool scalar type";
+      }
+
+      if (opcode == SpvOpRayQueryGetIntersectionFrontFaceKHR) {
+        if (auto error = ValidateIntersectionId(_, inst, 3)) return error;
+      }
+
+      break;
+    }
+
+    case SpvOpRayQueryGetIntersectionTKHR:
+    case SpvOpRayQueryGetRayTMinKHR: {
+      if (auto error = ValidateRayQueryPointer(_, inst, 2)) return error;
+
+      if (!_.IsFloatScalarType(result_type) ||
+          _.GetBitWidth(result_type) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "expected Result Type to be 32-bit float scalar type";
+      }
+
+      if (opcode == SpvOpRayQueryGetIntersectionTKHR) {
+        if (auto error = ValidateIntersectionId(_, inst, 3)) return error;
+      }
+
+      break;
+    }
+
+    case SpvOpRayQueryGetIntersectionTypeKHR:
+    case SpvOpRayQueryGetIntersectionInstanceCustomIndexKHR:
+    case SpvOpRayQueryGetIntersectionInstanceIdKHR:
+    case SpvOpRayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR:
+    case SpvOpRayQueryGetIntersectionGeometryIndexKHR:
+    case SpvOpRayQueryGetIntersectionPrimitiveIndexKHR:
+    case SpvOpRayQueryGetRayFlagsKHR: {
+      if (auto error = ValidateRayQueryPointer(_, inst, 2)) return error;
+
+      if (!_.IsIntScalarType(result_type) || _.GetBitWidth(result_type) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "expected Result Type to be 32-bit int scalar type";
+      }
+
+      if (opcode != SpvOpRayQueryGetRayFlagsKHR) {
+        if (auto error = ValidateIntersectionId(_, inst, 3)) return error;
+      }
+
+      break;
+    }
+
+    case SpvOpRayQueryGetIntersectionObjectRayDirectionKHR:
+    case SpvOpRayQueryGetIntersectionObjectRayOriginKHR:
+    case SpvOpRayQueryGetWorldRayDirectionKHR:
+    case SpvOpRayQueryGetWorldRayOriginKHR: {
+      if (auto error = ValidateRayQueryPointer(_, inst, 2)) return error;
+
+      if (!_.IsFloatVectorType(result_type) ||
+          _.GetDimension(result_type) != 3 ||
+          _.GetBitWidth(result_type) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "expected Result Type to be 32-bit float 3-component "
+                  "vector type";
+      }
+
+      if (opcode == SpvOpRayQueryGetIntersectionObjectRayDirectionKHR ||
+          opcode == SpvOpRayQueryGetIntersectionObjectRayOriginKHR) {
+        if (auto error = ValidateIntersectionId(_, inst, 3)) return error;
+      }
+
+      break;
+    }
+
+    case SpvOpRayQueryGetIntersectionBarycentricsKHR: {
+      if (auto error = ValidateRayQueryPointer(_, inst, 2)) return error;
+      if (auto error = ValidateIntersectionId(_, inst, 3)) return error;
+
+      if (!_.IsFloatVectorType(result_type) ||
+          _.GetDimension(result_type) != 2 ||
+          _.GetBitWidth(result_type) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "expected Result Type to be 32-bit float 2-component "
+                  "vector type";
+      }
+
+      break;
+    }
+
+    case SpvOpRayQueryGetIntersectionObjectToWorldKHR:
+    case SpvOpRayQueryGetIntersectionWorldToObjectKHR: {
+      if (auto error = ValidateRayQueryPointer(_, inst, 2)) return error;
+      if (auto error = ValidateIntersectionId(_, inst, 3)) return error;
+
+      uint32_t num_rows = 0;
+      uint32_t num_cols = 0;
+      uint32_t col_type = 0;
+      uint32_t component_type = 0;
+      if (!_.GetMatrixTypeInfo(result_type, &num_rows, &num_cols, &col_type,
+                               &component_type)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "expected matrix type as Result Type";
+      }
+
+      if (num_cols != 4) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "expected Result Type matrix to have a Column Count of 4";
+      }
+
+      if (!_.IsFloatScalarType(component_type) ||
+          _.GetBitWidth(result_type) != 32 || num_rows != 3) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "expected Result Type matrix to have a Column Type of "
+                  "3-component 32-bit float vectors";
+      }
+      break;
+    }
+
+    default:
+      break;
+  }
+
+  return SPV_SUCCESS;
+}
+
+}  // namespace val
+}  // namespace spvtools
diff --git a/source/val/validate_ray_tracing.cpp b/source/val/validate_ray_tracing.cpp
new file mode 100644
index 0000000..5b5c8da
--- /dev/null
+++ b/source/val/validate_ray_tracing.cpp
@@ -0,0 +1,209 @@
+// Copyright (c) 2022 The Khronos Group 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.
+
+// Validates ray tracing instructions from SPV_KHR_ray_tracing
+
+#include "source/opcode.h"
+#include "source/val/instruction.h"
+#include "source/val/validate.h"
+#include "source/val/validation_state.h"
+
+namespace spvtools {
+namespace val {
+
+spv_result_t RayTracingPass(ValidationState_t& _, const Instruction* inst) {
+  const SpvOp opcode = inst->opcode();
+  const uint32_t result_type = inst->type_id();
+
+  switch (opcode) {
+    case SpvOpTraceRayKHR: {
+      _.function(inst->function()->id())
+          ->RegisterExecutionModelLimitation(
+              [](SpvExecutionModel model, std::string* message) {
+                if (model != SpvExecutionModelRayGenerationKHR &&
+                    model != SpvExecutionModelClosestHitKHR &&
+                    model != SpvExecutionModelMissKHR) {
+                  if (message) {
+                    *message =
+                        "OpTraceRayKHR requires RayGenerationKHR, "
+                        "ClosestHitKHR and MissKHR execution models";
+                  }
+                  return false;
+                }
+                return true;
+              });
+
+      if (_.GetIdOpcode(_.GetOperandTypeId(inst, 0)) !=
+          SpvOpTypeAccelerationStructureKHR) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Expected Acceleration Structure to be of type "
+                  "OpTypeAccelerationStructureKHR";
+      }
+
+      const uint32_t ray_flags = _.GetOperandTypeId(inst, 1);
+      if (!_.IsIntScalarType(ray_flags) || _.GetBitWidth(ray_flags) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Ray Flags must be a 32-bit int scalar";
+      }
+
+      const uint32_t cull_mask = _.GetOperandTypeId(inst, 2);
+      if (!_.IsIntScalarType(cull_mask) || _.GetBitWidth(cull_mask) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Cull Mask must be a 32-bit int scalar";
+      }
+
+      const uint32_t sbt_offset = _.GetOperandTypeId(inst, 3);
+      if (!_.IsIntScalarType(sbt_offset) || _.GetBitWidth(sbt_offset) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "SBT Offset must be a 32-bit int scalar";
+      }
+
+      const uint32_t sbt_stride = _.GetOperandTypeId(inst, 4);
+      if (!_.IsIntScalarType(sbt_stride) || _.GetBitWidth(sbt_stride) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "SBT Stride must be a 32-bit int scalar";
+      }
+
+      const uint32_t miss_index = _.GetOperandTypeId(inst, 5);
+      if (!_.IsIntScalarType(miss_index) || _.GetBitWidth(miss_index) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Miss Index must be a 32-bit int scalar";
+      }
+
+      const uint32_t ray_origin = _.GetOperandTypeId(inst, 6);
+      if (!_.IsFloatVectorType(ray_origin) || _.GetDimension(ray_origin) != 3 ||
+          _.GetBitWidth(ray_origin) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Ray Origin must be a 32-bit float 3-component vector";
+      }
+
+      const uint32_t ray_tmin = _.GetOperandTypeId(inst, 7);
+      if (!_.IsFloatScalarType(ray_tmin) || _.GetBitWidth(ray_tmin) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Ray TMin must be a 32-bit float scalar";
+      }
+
+      const uint32_t ray_direction = _.GetOperandTypeId(inst, 8);
+      if (!_.IsFloatVectorType(ray_direction) ||
+          _.GetDimension(ray_direction) != 3 ||
+          _.GetBitWidth(ray_direction) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Ray Direction must be a 32-bit float 3-component vector";
+      }
+
+      const uint32_t ray_tmax = _.GetOperandTypeId(inst, 9);
+      if (!_.IsFloatScalarType(ray_tmax) || _.GetBitWidth(ray_tmax) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Ray TMax must be a 32-bit float scalar";
+      }
+
+      const Instruction* payload = _.FindDef(inst->GetOperandAs<uint32_t>(10));
+      if (payload->opcode() != SpvOpVariable) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Payload must be the result of a OpVariable";
+      } else if (payload->GetOperandAs<uint32_t>(2) !=
+                     SpvStorageClassRayPayloadKHR &&
+                 payload->GetOperandAs<uint32_t>(2) !=
+                     SpvStorageClassIncomingRayPayloadKHR) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Payload must have storage class RayPayloadKHR or "
+                  "IncomingRayPayloadKHR";
+      }
+      break;
+    }
+
+    case SpvOpReportIntersectionKHR: {
+      _.function(inst->function()->id())
+          ->RegisterExecutionModelLimitation(
+              [](SpvExecutionModel model, std::string* message) {
+                if (model != SpvExecutionModelIntersectionKHR) {
+                  if (message) {
+                    *message =
+                        "OpReportIntersectionKHR requires IntersectionKHR "
+                        "execution model";
+                  }
+                  return false;
+                }
+                return true;
+              });
+
+      if (!_.IsBoolScalarType(result_type)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "expected Result Type to be bool scalar type";
+      }
+
+      const uint32_t hit = _.GetOperandTypeId(inst, 2);
+      if (!_.IsFloatScalarType(hit) || _.GetBitWidth(hit) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Hit must be a 32-bit int scalar";
+      }
+
+      const uint32_t hit_kind = _.GetOperandTypeId(inst, 3);
+      if (!_.IsUnsignedIntScalarType(hit_kind) ||
+          _.GetBitWidth(hit_kind) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Hit Kind must be a 32-bit unsigned int scalar";
+      }
+      break;
+    }
+
+    case SpvOpExecuteCallableKHR: {
+      _.function(inst->function()->id())
+          ->RegisterExecutionModelLimitation([](SpvExecutionModel model,
+                                                std::string* message) {
+            if (model != SpvExecutionModelRayGenerationKHR &&
+                model != SpvExecutionModelClosestHitKHR &&
+                model != SpvExecutionModelMissKHR &&
+                model != SpvExecutionModelCallableKHR) {
+              if (message) {
+                *message =
+                    "OpExecuteCallableKHR requires RayGenerationKHR, "
+                    "ClosestHitKHR, MissKHR and CallableKHR execution models";
+              }
+              return false;
+            }
+            return true;
+          });
+
+      const uint32_t sbt_index = _.GetOperandTypeId(inst, 0);
+      if (!_.IsUnsignedIntScalarType(sbt_index) ||
+          _.GetBitWidth(sbt_index) != 32) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "SBT Index must be a 32-bit unsigned int scalar";
+      }
+
+      const auto callable_data = _.FindDef(inst->GetOperandAs<uint32_t>(1));
+      if (callable_data->opcode() != SpvOpVariable) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Callable Data must be the result of a OpVariable";
+      } else if (callable_data->GetOperandAs<uint32_t>(2) !=
+                     SpvStorageClassCallableDataKHR &&
+                 callable_data->GetOperandAs<uint32_t>(2) !=
+                     SpvStorageClassIncomingCallableDataKHR) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "Callable Data must have storage class CallableDataKHR or "
+                  "IncomingCallableDataKHR";
+      }
+
+      break;
+    }
+
+    default:
+      break;
+  }
+
+  return SPV_SUCCESS;
+}
+}  // namespace val
+}  // namespace spvtools
diff --git a/source/val/validate_scopes.cpp b/source/val/validate_scopes.cpp
index 29ba583..e978180 100644
--- a/source/val/validate_scopes.cpp
+++ b/source/val/validate_scopes.cpp
@@ -144,14 +144,16 @@
               [errorVUID](SpvExecutionModel model, std::string* message) {
                 if (model != SpvExecutionModelTaskNV &&
                     model != SpvExecutionModelMeshNV &&
+                    model != SpvExecutionModelTaskEXT &&
+                    model != SpvExecutionModelMeshEXT &&
                     model != SpvExecutionModelTessellationControl &&
                     model != SpvExecutionModelGLCompute) {
                   if (message) {
                     *message =
                         errorVUID +
                         "in Vulkan environment, Workgroup execution scope is "
-                        "only for TaskNV, MeshNV, TessellationControl, and "
-                        "GLCompute execution models";
+                        "only for TaskNV, MeshNV, TaskEXT, MeshEXT, "
+                        "TessellationControl, and GLCompute execution models";
                   }
                   return false;
                 }
@@ -220,30 +222,23 @@
 
   // Vulkan Specific rules
   if (spvIsVulkanEnv(_.context()->target_env)) {
-    if (value == SpvScopeCrossDevice) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << _.VkErrorID(4638) << spvOpcodeString(opcode)
-             << ": in Vulkan environment, Memory Scope cannot be CrossDevice";
-    }
-    // Vulkan 1.0 specifc rules
-    if (_.context()->target_env == SPV_ENV_VULKAN_1_0 &&
-        value != SpvScopeDevice && value != SpvScopeWorkgroup &&
-        value != SpvScopeInvocation) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << _.VkErrorID(4638) << spvOpcodeString(opcode)
-             << ": in Vulkan 1.0 environment Memory Scope is limited to "
-             << "Device, Workgroup and Invocation";
-    }
-    // Vulkan 1.1 specifc rules
-    if ((_.context()->target_env == SPV_ENV_VULKAN_1_1 ||
-         _.context()->target_env == SPV_ENV_VULKAN_1_2) &&
-        value != SpvScopeDevice && value != SpvScopeWorkgroup &&
+    if (value != SpvScopeDevice && value != SpvScopeWorkgroup &&
         value != SpvScopeSubgroup && value != SpvScopeInvocation &&
-        value != SpvScopeShaderCallKHR) {
+        value != SpvScopeShaderCallKHR && value != SpvScopeQueueFamily) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << _.VkErrorID(4638) << spvOpcodeString(opcode)
-             << ": in Vulkan 1.1 and 1.2 environment Memory Scope is limited "
-             << "to Device, Workgroup, Invocation, and ShaderCall";
+             << ": in Vulkan environment Memory Scope is limited to Device, "
+                "QueueFamily, Workgroup, ShaderCallKHR, Subgroup, or "
+                "Invocation";
+    } else if (_.context()->target_env == SPV_ENV_VULKAN_1_0 &&
+               value == SpvScopeSubgroup &&
+               !_.HasCapability(SpvCapabilitySubgroupBallotKHR) &&
+               !_.HasCapability(SpvCapabilitySubgroupVoteKHR)) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << _.VkErrorID(6997) << spvOpcodeString(opcode)
+             << ": in Vulkan 1.0 environment Memory Scope is can not be "
+                "Subgroup without SubgroupBallotKHR or SubgroupVoteKHR "
+                "declared";
     }
 
     if (value == SpvScopeShaderCallKHR) {
@@ -270,22 +265,44 @@
     }
 
     if (value == SpvScopeWorkgroup) {
-      std::string errorVUID = _.VkErrorID(4639);
+      std::string errorVUID = _.VkErrorID(7321);
       _.function(inst->function()->id())
           ->RegisterExecutionModelLimitation(
               [errorVUID](SpvExecutionModel model, std::string* message) {
                 if (model != SpvExecutionModelGLCompute &&
+                    model != SpvExecutionModelTessellationControl &&
                     model != SpvExecutionModelTaskNV &&
-                    model != SpvExecutionModelMeshNV) {
+                    model != SpvExecutionModelMeshNV &&
+                    model != SpvExecutionModelTaskEXT &&
+                    model != SpvExecutionModelMeshEXT) {
                   if (message) {
                     *message = errorVUID +
                                "Workgroup Memory Scope is limited to MeshNV, "
-                               "TaskNV, and GLCompute execution model";
+                               "TaskNV, MeshEXT, TaskEXT, TessellationControl, "
+                               "and GLCompute execution model";
                   }
                   return false;
                 }
                 return true;
               });
+
+      if (_.memory_model() == SpvMemoryModelGLSL450) {
+        errorVUID = _.VkErrorID(7320);
+        _.function(inst->function()->id())
+            ->RegisterExecutionModelLimitation(
+                [errorVUID](SpvExecutionModel model, std::string* message) {
+                  if (model == SpvExecutionModelTessellationControl) {
+                    if (message) {
+                      *message =
+                          errorVUID +
+                          "Workgroup Memory Scope can't be used with "
+                          "TessellationControl using GLSL450 Memory Model";
+                    }
+                    return false;
+                  }
+                  return true;
+                });
+      }
     }
   }
 
diff --git a/source/val/validate_type.cpp b/source/val/validate_type.cpp
index 4376b52..6b0881c 100644
--- a/source/val/validate_type.cpp
+++ b/source/val/validate_type.cpp
@@ -152,8 +152,8 @@
   const auto component_type = _.FindDef(component_id);
   if (!component_type || !spvOpcodeIsScalarType(component_type->opcode())) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpTypeVector Component Type <id> '" << _.getIdName(component_id)
-           << "' is not a scalar type.";
+           << "OpTypeVector Component Type <id> " << _.getIdName(component_id)
+           << " is not a scalar type.";
   }
 
   // Validates that the number of components in the vector is valid.
@@ -215,21 +215,21 @@
   const auto element_type = _.FindDef(element_type_id);
   if (!element_type || !spvOpcodeGeneratesType(element_type->opcode())) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpTypeArray Element Type <id> '" << _.getIdName(element_type_id)
-           << "' is not a type.";
+           << "OpTypeArray Element Type <id> " << _.getIdName(element_type_id)
+           << " is not a type.";
   }
 
   if (element_type->opcode() == SpvOpTypeVoid) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpTypeArray Element Type <id> '" << _.getIdName(element_type_id)
-           << "' is a void type.";
+           << "OpTypeArray Element Type <id> " << _.getIdName(element_type_id)
+           << " is a void type.";
   }
 
   if (spvIsVulkanEnv(_.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 "
+           << _.VkErrorID(4680) << "OpTypeArray Element Type <id> "
+           << _.getIdName(element_type_id) << " is not valid in "
            << spvLogStringForEnv(_.context()->target_env) << " environments.";
   }
 
@@ -238,8 +238,8 @@
   const auto length = _.FindDef(length_id);
   if (!length || !spvOpcodeIsConstant(length->opcode())) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpTypeArray Length <id> '" << _.getIdName(length_id)
-           << "' is not a scalar constant type.";
+           << "OpTypeArray Length <id> " << _.getIdName(length_id)
+           << " is not a scalar constant type.";
   }
 
   // NOTE: Check the initialiser value of the constant
@@ -248,8 +248,8 @@
   const auto const_result_type = _.FindDef(const_inst[const_result_type_index]);
   if (!const_result_type || SpvOpTypeInt != const_result_type->opcode()) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpTypeArray Length <id> '" << _.getIdName(length_id)
-           << "' is not a constant integer type.";
+           << "OpTypeArray Length <id> " << _.getIdName(length_id)
+           << " is not a constant integer type.";
   }
 
   switch (length->opcode()) {
@@ -261,14 +261,14 @@
       const int64_t ivalue = ConstantLiteralAsInt64(width, length->words());
       if (ivalue == 0 || (ivalue < 0 && is_signed)) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
-               << "OpTypeArray Length <id> '" << _.getIdName(length_id)
-               << "' default value must be at least 1: found " << ivalue;
+               << "OpTypeArray Length <id> " << _.getIdName(length_id)
+               << " default value must be at least 1: found " << ivalue;
       }
     } break;
     case SpvOpConstantNull:
       return _.diag(SPV_ERROR_INVALID_ID, inst)
-             << "OpTypeArray Length <id> '" << _.getIdName(length_id)
-             << "' default value must be at least 1.";
+             << "OpTypeArray Length <id> " << _.getIdName(length_id)
+             << " default value must be at least 1.";
     case SpvOpSpecConstantOp:
       // Assume it's OK, rather than try to evaluate the operation.
       break;
@@ -285,56 +285,27 @@
   const auto element_type = _.FindDef(element_id);
   if (!element_type || !spvOpcodeGeneratesType(element_type->opcode())) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpTypeRuntimeArray Element Type <id> '"
-           << _.getIdName(element_id) << "' is not a type.";
+           << "OpTypeRuntimeArray Element Type <id> " << _.getIdName(element_id)
+           << " is not a type.";
   }
 
   if (element_type->opcode() == SpvOpTypeVoid) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpTypeRuntimeArray Element Type <id> '"
-           << _.getIdName(element_id) << "' is a void type.";
+           << "OpTypeRuntimeArray Element Type <id> " << _.getIdName(element_id)
+           << " is a void type.";
   }
 
   if (spvIsVulkanEnv(_.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 "
+           << _.VkErrorID(4680) << "OpTypeRuntimeArray Element Type <id> "
+           << _.getIdName(element_id) << " is not valid in "
            << spvLogStringForEnv(_.context()->target_env) << " environments.";
   }
 
   return SPV_SUCCESS;
 }
 
-bool ContainsOpaqueType(ValidationState_t& _, const Instruction* str) {
-  const size_t elem_type_index = 1;
-  uint32_t elem_type_id;
-  Instruction* elem_type;
-
-  if (spvOpcodeIsBaseOpaqueType(str->opcode())) {
-    return true;
-  }
-
-  switch (str->opcode()) {
-    case SpvOpTypeArray:
-    case SpvOpTypeRuntimeArray:
-      elem_type_id = str->GetOperandAs<uint32_t>(elem_type_index);
-      elem_type = _.FindDef(elem_type_id);
-      return ContainsOpaqueType(_, elem_type);
-    case SpvOpTypeStruct:
-      for (size_t member_type_index = 1;
-           member_type_index < str->operands().size(); ++member_type_index) {
-        auto member_type_id = str->GetOperandAs<uint32_t>(member_type_index);
-        auto member_type = _.FindDef(member_type_id);
-        if (ContainsOpaqueType(_, member_type)) return true;
-      }
-      break;
-    default:
-      break;
-  }
-  return false;
-}
-
 spv_result_t ValidateTypeStruct(ValidationState_t& _, const Instruction* inst) {
   const uint32_t struct_id = inst->GetOperandAs<uint32_t>(0);
   for (size_t member_type_index = 1;
@@ -348,8 +319,8 @@
     auto member_type = _.FindDef(member_type_id);
     if (!member_type || !spvOpcodeGeneratesType(member_type->opcode())) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
-             << "OpTypeStruct Member Type <id> '" << _.getIdName(member_type_id)
-             << "' is not a type.";
+             << "OpTypeStruct Member Type <id> " << _.getIdName(member_type_id)
+             << " is not a type.";
     }
     if (member_type->opcode() == SpvOpTypeVoid) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
@@ -373,7 +344,8 @@
           member_type_index == inst->operands().size() - 1;
       if (!is_last_member) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
-               << "In " << spvLogStringForEnv(_.context()->target_env)
+               << _.VkErrorID(4680) << "In "
+               << spvLogStringForEnv(_.context()->target_env)
                << ", OpTypeRuntimeArray must only be used for the last member "
                   "of an OpTypeStruct";
       }
@@ -424,8 +396,21 @@
     _.RegisterStructTypeWithBuiltInMember(struct_id);
   }
 
+  const auto isOpaqueType = [&_](const Instruction* opaque_inst) {
+    auto opcode = opaque_inst->opcode();
+    if (_.HasCapability(SpvCapabilityBindlessTextureNV) &&
+        (opcode == SpvOpTypeImage || opcode == SpvOpTypeSampler ||
+         opcode == SpvOpTypeSampledImage)) {
+      return false;
+    } else if (spvOpcodeIsBaseOpaqueType(opcode)) {
+      return true;
+    }
+    return false;
+  };
+
   if (spvIsVulkanEnv(_.context()->target_env) &&
-      !_.options()->before_hlsl_legalization && ContainsOpaqueType(_, inst)) {
+      !_.options()->before_hlsl_legalization &&
+      _.ContainsType(inst->id(), isOpaqueType)) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
            << _.VkErrorID(4667) << "In "
            << spvLogStringForEnv(_.context()->target_env)
@@ -441,8 +426,8 @@
   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.";
+           << "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);
@@ -476,8 +461,8 @@
   const auto return_type = _.FindDef(return_type_id);
   if (!return_type || !spvOpcodeGeneratesType(return_type->opcode())) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpTypeFunction Return Type <id> '" << _.getIdName(return_type_id)
-           << "' is not a type.";
+           << "OpTypeFunction Return Type <id> " << _.getIdName(return_type_id)
+           << " is not a type.";
   }
   size_t num_args = 0;
   for (size_t param_type_index = 2; param_type_index < inst->operands().size();
@@ -486,14 +471,14 @@
     const auto param_type = _.FindDef(param_id);
     if (!param_type || !spvOpcodeGeneratesType(param_type->opcode())) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
-             << "OpTypeFunction Parameter Type <id> '" << _.getIdName(param_id)
-             << "' is not a type.";
+             << "OpTypeFunction Parameter Type <id> " << _.getIdName(param_id)
+             << " is not a type.";
     }
 
     if (param_type->opcode() == SpvOpTypeVoid) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
-             << "OpTypeFunction Parameter Type <id> '" << _.getIdName(param_id)
-             << "' cannot be OpTypeVoid.";
+             << "OpTypeFunction Parameter Type <id> " << _.getIdName(param_id)
+             << " cannot be OpTypeVoid.";
     }
   }
   const uint32_t num_function_args_limit =
@@ -501,8 +486,8 @@
   if (num_args > num_function_args_limit) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
            << "OpTypeFunction may not take more than "
-           << num_function_args_limit << " arguments. OpTypeFunction <id> '"
-           << _.getIdName(inst->GetOperandAs<uint32_t>(0)) << "' has "
+           << num_function_args_limit << " arguments. OpTypeFunction <id> "
+           << _.getIdName(inst->GetOperandAs<uint32_t>(0)) << " has "
            << num_args << " arguments.";
   }
 
@@ -565,9 +550,9 @@
   if (!component_type || (SpvOpTypeFloat != component_type->opcode() &&
                           SpvOpTypeInt != component_type->opcode())) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpTypeCooperativeMatrixNV Component Type <id> '"
+           << "OpTypeCooperativeMatrixNV Component Type <id> "
            << _.getIdName(component_type_id)
-           << "' is not a scalar numerical type.";
+           << " is not a scalar numerical type.";
   }
 
   const auto scope_index = 2;
@@ -576,8 +561,8 @@
   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.";
+           << "OpTypeCooperativeMatrixNV Scope <id> " << _.getIdName(scope_id)
+           << " is not a constant instruction with scalar integer type.";
   }
 
   const auto rows_index = 3;
@@ -586,8 +571,8 @@
   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.";
+           << "OpTypeCooperativeMatrixNV Rows <id> " << _.getIdName(rows_id)
+           << " is not a constant instruction with scalar integer type.";
   }
 
   const auto cols_index = 4;
@@ -596,8 +581,8 @@
   if (!cols || !_.IsIntScalarType(cols->type_id()) ||
       !spvOpcodeIsConstant(cols->opcode())) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpTypeCooperativeMatrixNV Cols <id> '" << _.getIdName(cols_id)
-           << "' is not a constant instruction with scalar integer type.";
+           << "OpTypeCooperativeMatrixNV Cols <id> " << _.getIdName(cols_id)
+           << " is not a constant instruction with scalar integer type.";
   }
 
   return SPV_SUCCESS;
diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp
index 8d1a0d3..d5ddc9c 100644
--- a/source/val/validation_state.cpp
+++ b/source/val/validation_state.cpp
@@ -90,6 +90,8 @@
       if (current_section == kLayoutFunctionDeclarations)
         return kLayoutFunctionDeclarations;
       return kLayoutFunctionDefinitions;
+    case SpvOpSamplerImageAddressingModeNV:
+      return kLayoutSamplerImageAddressMode;
     default:
       break;
   }
@@ -161,6 +163,7 @@
       addressing_model_(SpvAddressingModelMax),
       memory_model_(SpvMemoryModelMax),
       pointer_size_and_alignment_(0),
+      sampler_image_addressing_mode_(0),
       in_function_(false),
       num_of_warnings_(0),
       max_num_of_warnings_(max_warnings) {
@@ -175,8 +178,18 @@
     }
   }
 
-  // LocalSizeId is always allowed in non-Vulkan environments.
-  features_.env_allow_localsizeid = !spvIsVulkanEnv(env);
+  // LocalSizeId is only disallowed prior to Vulkan 1.3 without maintenance4.
+  switch (env) {
+    case SPV_ENV_VULKAN_1_0:
+    case SPV_ENV_VULKAN_1_1:
+    case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
+    case SPV_ENV_VULKAN_1_2:
+      features_.env_allow_localsizeid = false;
+      break;
+    default:
+      features_.env_allow_localsizeid = true;
+      break;
+  }
 
   // Only attempt to count if we have words, otherwise let the other validation
   // fail and generate an error.
@@ -195,9 +208,12 @@
   }
   UpdateFeaturesBasedOnSpirvVersion(&features_, version_);
 
-  friendly_mapper_ = spvtools::MakeUnique<spvtools::FriendlyNameMapper>(
-      context_, words_, num_words_);
-  name_mapper_ = friendly_mapper_->GetNameMapper();
+  name_mapper_ = spvtools::GetTrivialNameMapper();
+  if (options_->use_friendly_names) {
+    friendly_mapper_ = spvtools::MakeUnique<spvtools::FriendlyNameMapper>(
+        context_, words_, num_words_);
+    name_mapper_ = friendly_mapper_->GetNameMapper();
+  }
 }
 
 void ValidationState_t::preallocateStorage() {
@@ -232,7 +248,7 @@
   const std::string id_name = name_mapper_(id);
 
   std::stringstream out;
-  out << id << "[%" << id_name << "]";
+  out << "'" << id << "[%" << id_name << "]'";
   return out.str();
 }
 
@@ -382,13 +398,19 @@
       features_.free_fp_rounding_mode = true;
       break;
     case SpvCapabilityVariablePointers:
-      features_.variable_pointers = true;
-      features_.variable_pointers_storage_buffer = true;
-      break;
     case SpvCapabilityVariablePointersStorageBuffer:
-      features_.variable_pointers_storage_buffer = true;
+      features_.variable_pointers = true;
       break;
     default:
+      // TODO(dneto): For now don't validate SPV_NV_ray_tracing, which uses
+      // capability SpvCapabilityRayTracingNV.
+      // SpvCapabilityRayTracingProvisionalKHR would need the same treatment.
+      // One of the differences going from SPV_KHR_ray_tracing from
+      // provisional to final spec was the provisional spec uses Locations
+      // for variables in certain storage classes, just like the
+      // SPV_NV_ray_tracing extension.  So it mimics the NVIDIA extension.
+      // The final SPV_KHR_ray_tracing uses a different capability token
+      // number, so it doesn't fall into this case.
       break;
   }
 }
@@ -441,7 +463,7 @@
     default:
     // fall through
     case SpvAddressingModelPhysical64:
-    case SpvAddressingModelPhysicalStorageBuffer64EXT:
+    case SpvAddressingModelPhysicalStorageBuffer64:
       pointer_size_and_alignment_ = 8;
       break;
   }
@@ -457,6 +479,15 @@
 
 SpvMemoryModel ValidationState_t::memory_model() const { return memory_model_; }
 
+void ValidationState_t::set_samplerimage_variable_address_mode(
+    uint32_t bit_width) {
+  sampler_image_addressing_mode_ = bit_width;
+}
+
+uint32_t ValidationState_t::samplerimage_variable_address_mode() const {
+  return sampler_image_addressing_mode_;
+}
+
 spv_result_t ValidationState_t::RegisterFunction(
     uint32_t id, uint32_t ret_type_id, SpvFunctionControlMask function_control,
     uint32_t function_type_id) {
@@ -479,7 +510,7 @@
          "inside of another function");
   assert(in_block() == false &&
          "RegisterFunctionParameter can only be called when parsing the binary "
-         "ouside of a block");
+         "outside of a block");
   current_function().RegisterFunctionEnd();
   in_function_ = false;
   return SPV_SUCCESS;
@@ -497,15 +528,13 @@
   switch (inst->opcode()) {
     case SpvOpName: {
       const auto target = inst->GetOperandAs<uint32_t>(0);
-      const auto* str = reinterpret_cast<const char*>(inst->words().data() +
-                                                      inst->operand(1).offset);
+      const std::string str = inst->GetOperandAs<std::string>(1);
       AssignNameToId(target, str);
       break;
     }
     case SpvOpMemberName: {
       const auto target = inst->GetOperandAs<uint32_t>(0);
-      const auto* str = reinterpret_cast<const char*>(inst->words().data() +
-                                                      inst->operand(2).offset);
+      const std::string str = inst->GetOperandAs<std::string>(2);
       AssignNameToId(target, str);
       break;
     }
@@ -524,7 +553,7 @@
   if (inst->id()) all_definitions_.insert(std::make_pair(inst->id(), inst));
 
   // Some validation checks are easier by getting all the consumers
-  for (uint16_t i = 0; i < inst->operands().size(); ++i) {
+  for (size_t i = 0; i < inst->operands().size(); ++i) {
     const spv_parsed_operand_t& operand = inst->operand(i);
     if ((SPV_OPERAND_TYPE_ID == operand.type) ||
         (SPV_OPERAND_TYPE_TYPE_ID == operand.type)) {
@@ -582,7 +611,8 @@
       std::string errorVUID = VkErrorID(4644);
       function(consumer->function()->id())
           ->RegisterExecutionModelLimitation([errorVUID](
-              SpvExecutionModel model, std::string* message) {
+                                                 SpvExecutionModel model,
+                                                 std::string* message) {
             if (model == SpvExecutionModelGLCompute ||
                 model == SpvExecutionModelRayGenerationKHR ||
                 model == SpvExecutionModelIntersectionKHR ||
@@ -593,7 +623,7 @@
               if (message) {
                 *message =
                     errorVUID +
-                    "in Vulkan evironment, Output Storage Class must not be "
+                    "in Vulkan environment, Output Storage Class must not be "
                     "used in GLCompute, RayGenerationKHR, IntersectionKHR, "
                     "AnyHitKHR, ClosestHitKHR, MissKHR, or CallableKHR "
                     "execution models";
@@ -608,14 +638,17 @@
       std::string errorVUID = VkErrorID(4645);
       function(consumer->function()->id())
           ->RegisterExecutionModelLimitation([errorVUID](
-              SpvExecutionModel model, std::string* message) {
+                                                 SpvExecutionModel model,
+                                                 std::string* message) {
             if (model != SpvExecutionModelGLCompute &&
                 model != SpvExecutionModelTaskNV &&
-                model != SpvExecutionModelMeshNV) {
+                model != SpvExecutionModelMeshNV &&
+                model != SpvExecutionModelTaskEXT &&
+                model != SpvExecutionModelMeshEXT) {
               if (message) {
                 *message =
                     errorVUID +
-                    "in Vulkan evironment, Workgroup Storage Class is limited "
+                    "in Vulkan environment, Workgroup Storage Class is limited "
                     "to MeshNV, TaskNV, and GLCompute execution model";
               }
               return false;
@@ -624,6 +657,133 @@
           });
     }
   }
+
+  if (storage_class == SpvStorageClassCallableDataKHR) {
+    std::string errorVUID = VkErrorID(4704);
+    function(consumer->function()->id())
+        ->RegisterExecutionModelLimitation([errorVUID](SpvExecutionModel model,
+                                                       std::string* message) {
+          if (model != SpvExecutionModelRayGenerationKHR &&
+              model != SpvExecutionModelClosestHitKHR &&
+              model != SpvExecutionModelCallableKHR &&
+              model != SpvExecutionModelMissKHR) {
+            if (message) {
+              *message = errorVUID +
+                         "CallableDataKHR Storage Class is limited to "
+                         "RayGenerationKHR, ClosestHitKHR, CallableKHR, and "
+                         "MissKHR execution model";
+            }
+            return false;
+          }
+          return true;
+        });
+  } else if (storage_class == SpvStorageClassIncomingCallableDataKHR) {
+    std::string errorVUID = VkErrorID(4705);
+    function(consumer->function()->id())
+        ->RegisterExecutionModelLimitation([errorVUID](SpvExecutionModel model,
+                                                       std::string* message) {
+          if (model != SpvExecutionModelCallableKHR) {
+            if (message) {
+              *message = errorVUID +
+                         "IncomingCallableDataKHR Storage Class is limited to "
+                         "CallableKHR execution model";
+            }
+            return false;
+          }
+          return true;
+        });
+  } else if (storage_class == SpvStorageClassRayPayloadKHR) {
+    std::string errorVUID = VkErrorID(4698);
+    function(consumer->function()->id())
+        ->RegisterExecutionModelLimitation([errorVUID](SpvExecutionModel model,
+                                                       std::string* message) {
+          if (model != SpvExecutionModelRayGenerationKHR &&
+              model != SpvExecutionModelClosestHitKHR &&
+              model != SpvExecutionModelMissKHR) {
+            if (message) {
+              *message =
+                  errorVUID +
+                  "RayPayloadKHR Storage Class is limited to RayGenerationKHR, "
+                  "ClosestHitKHR, and MissKHR execution model";
+            }
+            return false;
+          }
+          return true;
+        });
+  } else if (storage_class == SpvStorageClassHitAttributeKHR) {
+    std::string errorVUID = VkErrorID(4701);
+    function(consumer->function()->id())
+        ->RegisterExecutionModelLimitation(
+            [errorVUID](SpvExecutionModel model, std::string* message) {
+              if (model != SpvExecutionModelIntersectionKHR &&
+                  model != SpvExecutionModelAnyHitKHR &&
+                  model != SpvExecutionModelClosestHitKHR) {
+                if (message) {
+                  *message = errorVUID +
+                             "HitAttributeKHR Storage Class is limited to "
+                             "IntersectionKHR, AnyHitKHR, sand ClosestHitKHR "
+                             "execution model";
+                }
+                return false;
+              }
+              return true;
+            });
+  } else if (storage_class == SpvStorageClassIncomingRayPayloadKHR) {
+    std::string errorVUID = VkErrorID(4699);
+    function(consumer->function()->id())
+        ->RegisterExecutionModelLimitation(
+            [errorVUID](SpvExecutionModel model, std::string* message) {
+              if (model != SpvExecutionModelAnyHitKHR &&
+                  model != SpvExecutionModelClosestHitKHR &&
+                  model != SpvExecutionModelMissKHR) {
+                if (message) {
+                  *message =
+                      errorVUID +
+                      "IncomingRayPayloadKHR Storage Class is limited to "
+                      "AnyHitKHR, ClosestHitKHR, and MissKHR execution model";
+                }
+                return false;
+              }
+              return true;
+            });
+  } else if (storage_class == SpvStorageClassShaderRecordBufferKHR) {
+    std::string errorVUID = VkErrorID(7119);
+    function(consumer->function()->id())
+        ->RegisterExecutionModelLimitation(
+            [errorVUID](SpvExecutionModel model, std::string* message) {
+              if (model != SpvExecutionModelRayGenerationKHR &&
+                  model != SpvExecutionModelIntersectionKHR &&
+                  model != SpvExecutionModelAnyHitKHR &&
+                  model != SpvExecutionModelClosestHitKHR &&
+                  model != SpvExecutionModelCallableKHR &&
+                  model != SpvExecutionModelMissKHR) {
+                if (message) {
+                  *message =
+                      errorVUID +
+                      "ShaderRecordBufferKHR Storage Class is limited to "
+                      "RayGenerationKHR, IntersectionKHR, AnyHitKHR, "
+                      "ClosestHitKHR, CallableKHR, and MissKHR execution model";
+                }
+                return false;
+              }
+              return true;
+            });
+  } else if (storage_class == SpvStorageClassTaskPayloadWorkgroupEXT) {
+    function(consumer->function()->id())
+        ->RegisterExecutionModelLimitation(
+            [](SpvExecutionModel model, std::string* message) {
+              if (model != SpvExecutionModelTaskEXT &&
+                  model != SpvExecutionModelMeshEXT) {
+                if (message) {
+                  *message =
+                      "TaskPayloadWorkgroupEXT Storage Class is limited to "
+                      "TaskEXT and MeshKHR execution model";
+                }
+                return false;
+              }
+              return true;
+            });
+  }
 }
 
 uint32_t ValidationState_t::getIdBound() const { return id_bound_; }
@@ -951,6 +1111,11 @@
   return true;
 }
 
+bool ValidationState_t::IsAccelerationStructureType(uint32_t id) const {
+  const Instruction* inst = FindDef(id);
+  return inst && inst->opcode() == SpvOpTypeAccelerationStructureKHR;
+}
+
 bool ValidationState_t::IsCooperativeMatrixType(uint32_t id) const {
   const Instruction* inst = FindDef(id);
   return inst && inst->opcode() == SpvOpTypeCooperativeMatrixNV;
@@ -971,6 +1136,13 @@
   return IsUnsignedIntScalarType(FindDef(id)->word(2));
 }
 
+// Either a 32 bit 2-component uint vector or a 64 bit uint scalar
+bool ValidationState_t::IsUnsigned64BitHandle(uint32_t id) const {
+  return ((IsUnsignedIntScalarType(id) && GetBitWidth(id) == 64) ||
+          (IsUnsignedIntVectorType(id) && GetDimension(id) == 2 &&
+           GetBitWidth(id) == 32));
+}
+
 spv_result_t ValidationState_t::CooperativeMatrixShapesMatch(
     const Instruction* inst, uint32_t m1, uint32_t m2) {
   const auto m1_type = FindDef(m1);
@@ -1372,6 +1544,7 @@
       case SpvStorageClassCallableDataKHR:
       case SpvStorageClassIncomingCallableDataKHR:
       case SpvStorageClassShaderRecordBufferKHR:
+      case SpvStorageClassTaskPayloadWorkgroupEXT:
         return true;
       default:
         return false;
@@ -1390,13 +1563,25 @@
     return "";
   }
 
-  // This large switch case is only searched when an error has occured.
+  // This large switch case is only searched when an error has occurred.
   // If an id is changed, the old case must be modified or removed. Each string
   // here is interpreted as being "implemented"
 
   // Clang format adds spaces between hyphens
   // clang-format off
   switch (id) {
+    case 4154:
+      return VUID_WRAP(VUID-BaryCoordKHR-BaryCoordKHR-04154);
+    case 4155:
+      return VUID_WRAP(VUID-BaryCoordKHR-BaryCoordKHR-04155);
+    case 4156:
+      return VUID_WRAP(VUID-BaryCoordKHR-BaryCoordKHR-04156);
+    case 4160:
+      return VUID_WRAP(VUID-BaryCoordNoPerspKHR-BaryCoordNoPerspKHR-04160);
+    case 4161:
+      return VUID_WRAP(VUID-BaryCoordNoPerspKHR-BaryCoordNoPerspKHR-04161);
+    case 4162:
+      return VUID_WRAP(VUID-BaryCoordNoPerspKHR-BaryCoordNoPerspKHR-04162);
     case 4181:
       return VUID_WRAP(VUID-BaseInstance-BaseInstance-04181);
     case 4182:
@@ -1429,6 +1614,12 @@
       return VUID_WRAP(VUID-CullDistance-CullDistance-04199);
     case 4200:
       return VUID_WRAP(VUID-CullDistance-CullDistance-04200);
+    case 6735:
+      return VUID_WRAP(VUID-CullMaskKHR-CullMaskKHR-06735); // Execution Model
+    case 6736:
+      return VUID_WRAP(VUID-CullMaskKHR-CullMaskKHR-06736); // input storage
+    case 6737:
+      return VUID_WRAP(VUID-CullMaskKHR-CullMaskKHR-06737); // 32 int scalar
     case 4205:
       return VUID_WRAP(VUID-DeviceIndex-DeviceIndex-04205);
     case 4206:
@@ -1797,8 +1988,8 @@
       return VUID_WRAP(VUID-StandaloneSpirv-None-04637);
     case 4638:
       return VUID_WRAP(VUID-StandaloneSpirv-None-04638);
-    case 4639:
-      return VUID_WRAP(VUID-StandaloneSpirv-None-04639);
+    case 7321:
+      return VUID_WRAP(VUID-StandaloneSpirv-None-07321);
     case 4640:
       return VUID_WRAP(VUID-StandaloneSpirv-None-04640);
     case 4641:
@@ -1845,6 +2036,8 @@
       return VUID_WRAP(VUID-StandaloneSpirv-FPRoundingMode-04675);
     case 4677:
       return VUID_WRAP(VUID-StandaloneSpirv-Invariant-04677);
+    case 4680:
+      return VUID_WRAP(VUID-StandaloneSpirv-OpTypeRuntimeArray-04680);
     case 4682:
       return VUID_WRAP(VUID-StandaloneSpirv-OpControlBarrier-04682);
     case 6426:
@@ -1853,6 +2046,22 @@
       return VUID_WRAP(VUID-StandaloneSpirv-OpGroupNonUniformBallotBitCount-04685);
     case 4686:
       return VUID_WRAP(VUID-StandaloneSpirv-None-04686);
+    case 4698:
+      return VUID_WRAP(VUID-StandaloneSpirv-RayPayloadKHR-04698);
+    case 4699:
+      return VUID_WRAP(VUID-StandaloneSpirv-IncomingRayPayloadKHR-04699);
+    case 4701:
+      return VUID_WRAP(VUID-StandaloneSpirv-HitAttributeKHR-04701);
+    case 4703:
+      return VUID_WRAP(VUID-StandaloneSpirv-HitAttributeKHR-04703);
+    case 4704:
+      return VUID_WRAP(VUID-StandaloneSpirv-CallableDataKHR-04704);
+    case 4705:
+      return VUID_WRAP(VUID-StandaloneSpirv-IncomingCallableDataKHR-04705);
+    case 7119:
+      return VUID_WRAP(VUID-StandaloneSpirv-ShaderRecordBufferKHR-07119);
+    case 4708:
+      return VUID_WRAP(VUID-StandaloneSpirv-PhysicalStorageBuffer64-04708);
     case 4710:
       return VUID_WRAP(VUID-StandaloneSpirv-PhysicalStorageBuffer64-04710);
     case 4711:
@@ -1865,8 +2074,66 @@
       return VUID_WRAP(VUID-StandaloneSpirv-OpMemoryBarrier-04732);
     case 4733:
       return VUID_WRAP(VUID-StandaloneSpirv-OpMemoryBarrier-04733);
+    case 4734:
+      return VUID_WRAP(VUID-StandaloneSpirv-OpVariable-04734);
+    case 4744:
+      return VUID_WRAP(VUID-StandaloneSpirv-Flat-04744);
+    case 4777:
+      return VUID_WRAP(VUID-StandaloneSpirv-OpImage-04777);
     case 4780:
       return VUID_WRAP(VUID-StandaloneSpirv-Result-04780);
+    case 4781:
+      return VUID_WRAP(VUID-StandaloneSpirv-Base-04781);
+    case 4915:
+      return VUID_WRAP(VUID-StandaloneSpirv-Location-04915);
+    case 4916:
+      return VUID_WRAP(VUID-StandaloneSpirv-Location-04916);
+    case 4917:
+      return VUID_WRAP(VUID-StandaloneSpirv-Location-04917);
+    case 4918:
+      return VUID_WRAP(VUID-StandaloneSpirv-Location-04918);
+    case 4919:
+      return VUID_WRAP(VUID-StandaloneSpirv-Location-04919);
+    case 6201:
+      return VUID_WRAP(VUID-StandaloneSpirv-Flat-06201);
+    case 6202:
+      return VUID_WRAP(VUID-StandaloneSpirv-Flat-06202);
+    case 6214:
+      return VUID_WRAP(VUID-StandaloneSpirv-OpTypeImage-06214);
+    case 6491:
+      return VUID_WRAP(VUID-StandaloneSpirv-DescriptorSet-06491);
+    case 6671:
+      return VUID_WRAP(VUID-StandaloneSpirv-OpTypeSampledImage-06671);
+    case 6672:
+      return VUID_WRAP(VUID-StandaloneSpirv-Location-06672);
+    case 6674:
+      return VUID_WRAP(VUID-StandaloneSpirv-OpEntryPoint-06674);
+    case 6675:
+      return VUID_WRAP(VUID-StandaloneSpirv-PushConstant-06675);
+    case 6676:
+      return VUID_WRAP(VUID-StandaloneSpirv-Uniform-06676);
+    case 6677:
+      return VUID_WRAP(VUID-StandaloneSpirv-UniformConstant-06677);
+    case 6678:
+      return VUID_WRAP(VUID-StandaloneSpirv-InputAttachmentIndex-06678);
+    case 6777:
+      return VUID_WRAP(VUID-StandaloneSpirv-PerVertexKHR-06777);
+    case 6778:
+      return VUID_WRAP(VUID-StandaloneSpirv-Input-06778);
+    case 6807:
+      return VUID_WRAP(VUID-StandaloneSpirv-Uniform-06807);
+    case 6808:
+      return VUID_WRAP(VUID-StandaloneSpirv-PushConstant-06808);
+    case 6925:
+      return VUID_WRAP(VUID-StandaloneSpirv-Uniform-06925);
+    case 6997:
+      return VUID_WRAP(VUID-StandaloneSpirv-SubgroupVoteKHR-06997);
+    case 7102:
+      return VUID_WRAP(VUID-StandaloneSpirv-MeshEXT-07102);
+    case 7320:
+      return VUID_WRAP(VUID-StandaloneSpirv-ExecutionModel-07320);
+    case 7290:
+      return VUID_WRAP(VUID-StandaloneSpirv-Input-07290);
     default:
       return "";  // unknown id
   }
diff --git a/source/val/validation_state.h b/source/val/validation_state.h
index 2ddfa4a..1b599ff 100644
--- a/source/val/validation_state.h
+++ b/source/val/validation_state.h
@@ -44,19 +44,20 @@
 /// of the SPIRV spec for additional details of the order. The enumerant values
 /// are in the same order as the vector returned by GetModuleOrder
 enum ModuleLayoutSection {
-  kLayoutCapabilities,          /// < Section 2.4 #1
-  kLayoutExtensions,            /// < Section 2.4 #2
-  kLayoutExtInstImport,         /// < Section 2.4 #3
-  kLayoutMemoryModel,           /// < Section 2.4 #4
-  kLayoutEntryPoint,            /// < Section 2.4 #5
-  kLayoutExecutionMode,         /// < Section 2.4 #6
-  kLayoutDebug1,                /// < Section 2.4 #7 > 1
-  kLayoutDebug2,                /// < Section 2.4 #7 > 2
-  kLayoutDebug3,                /// < Section 2.4 #7 > 3
-  kLayoutAnnotations,           /// < Section 2.4 #8
-  kLayoutTypes,                 /// < Section 2.4 #9
-  kLayoutFunctionDeclarations,  /// < Section 2.4 #10
-  kLayoutFunctionDefinitions    /// < Section 2.4 #11
+  kLayoutCapabilities,             /// < Section 2.4 #1
+  kLayoutExtensions,               /// < Section 2.4 #2
+  kLayoutExtInstImport,            /// < Section 2.4 #3
+  kLayoutMemoryModel,              /// < Section 2.4 #4
+  kLayoutSamplerImageAddressMode,  /// < Section 2.4 #5
+  kLayoutEntryPoint,               /// < Section 2.4 #6
+  kLayoutExecutionMode,            /// < Section 2.4 #7
+  kLayoutDebug1,                   /// < Section 2.4 #8 > 1
+  kLayoutDebug2,                   /// < Section 2.4 #8 > 2
+  kLayoutDebug3,                   /// < Section 2.4 #8 > 3
+  kLayoutAnnotations,              /// < Section 2.4 #9
+  kLayoutTypes,                    /// < Section 2.4 #10
+  kLayoutFunctionDeclarations,     /// < Section 2.4 #11
+  kLayoutFunctionDefinitions       /// < Section 2.4 #12
 };
 
 /// This class manages the state of the SPIR-V validation as it is being parsed.
@@ -67,14 +68,12 @@
     bool declare_int16_type = false;     // Allow OpTypeInt with 16 bit width?
     bool declare_float16_type = false;   // Allow OpTypeFloat with 16 bit width?
     bool free_fp_rounding_mode = false;  // Allow the FPRoundingMode decoration
-                                         // and its vaules to be used without
+                                         // and its values to be used without
                                          // requiring any capability
 
-    // Allow functionalities enabled by VariablePointers capability.
+    // Allow functionalities enabled by VariablePointers or
+    // VariablePointersStorageBuffer capability.
     bool variable_pointers = false;
-    // Allow functionalities enabled by VariablePointersStorageBuffer
-    // capability.
-    bool variable_pointers_storage_buffer = false;
 
     // Permit group oerations Reduce, InclusiveScan, ExclusiveScan
     bool group_ops_reduce_and_scans = false;
@@ -362,6 +361,20 @@
   /// Returns the memory model of this module, or Simple if uninitialized.
   SpvMemoryModel memory_model() const;
 
+  /// Sets the bit width for sampler/image type variables. If not set, they are
+  /// considered opaque
+  void set_samplerimage_variable_address_mode(uint32_t bit_width);
+
+  /// Get the addressing mode currently set. If 0, it means addressing mode is
+  /// invalid Sampler/Image type variables must be considered opaque This mode
+  /// is only valid after the instruction has been read
+  uint32_t samplerimage_variable_address_mode() const;
+
+  /// Returns true if the OpSamplerImageAddressingModeNV was found.
+  bool has_samplerimage_variable_address_mode_specified() const {
+    return sampler_image_addressing_mode_ != 0;
+  }
+
   const AssemblyGrammar& grammar() const { return grammar_; }
 
   /// Inserts the instruction into the list of ordered instructions in the file.
@@ -377,17 +390,14 @@
   /// Registers the decoration for the given <id>
   void RegisterDecorationForId(uint32_t id, const Decoration& dec) {
     auto& dec_list = id_decorations_[id];
-    auto lb = std::find(dec_list.begin(), dec_list.end(), dec);
-    if (lb == dec_list.end()) {
-      dec_list.push_back(dec);
-    }
+    dec_list.insert(dec);
   }
 
   /// Registers the list of decorations for the given <id>
   template <class InputIt>
   void RegisterDecorationsForId(uint32_t id, InputIt begin, InputIt end) {
-    std::vector<Decoration>& cur_decs = id_decorations_[id];
-    cur_decs.insert(cur_decs.end(), begin, end);
+    std::set<Decoration>& cur_decs = id_decorations_[id];
+    cur_decs.insert(begin, end);
   }
 
   /// Registers the list of decorations for the given member of the given
@@ -396,21 +406,44 @@
   void RegisterDecorationsForStructMember(uint32_t struct_id,
                                           uint32_t member_index, InputIt begin,
                                           InputIt end) {
-    RegisterDecorationsForId(struct_id, begin, end);
-    for (auto& decoration : id_decorations_[struct_id]) {
-      decoration.set_struct_member_index(member_index);
+    std::set<Decoration>& cur_decs = id_decorations_[struct_id];
+    for (InputIt iter = begin; iter != end; ++iter) {
+      Decoration dec = *iter;
+      dec.set_struct_member_index(member_index);
+      cur_decs.insert(dec);
     }
   }
 
   /// Returns all the decorations for the given <id>. If no decorations exist
-  /// for the <id>, it registers an empty vector for it in the map and
-  /// returns the empty vector.
-  std::vector<Decoration>& id_decorations(uint32_t id) {
+  /// for the <id>, it registers an empty set for it in the map and
+  /// returns the empty set.
+  std::set<Decoration>& id_decorations(uint32_t id) {
     return id_decorations_[id];
   }
 
+  /// Returns the range of decorations for the given field of the given <id>.
+  struct FieldDecorationsIter {
+    std::set<Decoration>::const_iterator begin;
+    std::set<Decoration>::const_iterator end;
+  };
+  FieldDecorationsIter id_member_decorations(uint32_t id,
+                                             uint32_t member_index) {
+    const auto& decorations = id_decorations_[id];
+
+    // The decorations are sorted by member_index, so this look up will give the
+    // exact range of decorations for this member index.
+    Decoration min_decoration((SpvDecoration)0, {}, member_index);
+    Decoration max_decoration(SpvDecorationMax, {}, member_index);
+
+    FieldDecorationsIter result;
+    result.begin = decorations.lower_bound(min_decoration);
+    result.end = decorations.upper_bound(max_decoration);
+
+    return result;
+  }
+
   // Returns const pointer to the internal decoration container.
-  const std::map<uint32_t, std::vector<Decoration>>& id_decorations() const {
+  const std::map<uint32_t, std::set<Decoration>>& id_decorations() const {
     return id_decorations_;
   }
 
@@ -574,10 +607,12 @@
   bool IsBoolVectorType(uint32_t id) const;
   bool IsBoolScalarOrVectorType(uint32_t id) const;
   bool IsPointerType(uint32_t id) const;
+  bool IsAccelerationStructureType(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;
+  bool IsUnsigned64BitHandle(uint32_t id) const;
 
   // Returns true if |id| is a type id that contains |type| (or integer or
   // floating point type) of |width| bits.
@@ -688,6 +723,16 @@
   // Returns the disassembly string for the given instruction.
   std::string Disassemble(const uint32_t* words, uint16_t num_words) const;
 
+  // Returns the string name for |decoration|.
+  std::string SpvDecorationString(uint32_t decoration) {
+    spv_operand_desc desc = nullptr;
+    if (grammar_.lookupOperand(SPV_OPERAND_TYPE_DECORATION, decoration,
+                               &desc) != SPV_SUCCESS) {
+      return std::string("Unknown");
+    }
+    return std::string(desc->name);
+  }
+
   // 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.
@@ -797,7 +842,7 @@
   /// IDs that are entry points, ie, arguments to OpEntryPoint.
   std::vector<uint32_t> entry_points_;
 
-  /// Maps an entry point id to its desciptions.
+  /// Maps an entry point id to its descriptions.
   std::unordered_map<uint32_t, std::vector<EntryPointDescription>>
       entry_point_descriptions_;
 
@@ -828,7 +873,7 @@
       struct_has_nested_blockorbufferblock_struct_;
 
   /// Stores the list of decorations for a given <id>
-  std::map<uint32_t, std::vector<Decoration>> id_decorations_;
+  std::map<uint32_t, std::set<Decoration>> id_decorations_;
 
   /// Stores type declarations which need to be unique (i.e. non-aggregates),
   /// in the form [opcode, operand words], result_id is not stored.
@@ -844,6 +889,9 @@
   // have the same pointer size (for physical pointer types).
   uint32_t pointer_size_and_alignment_;
 
+  /// bit width of sampler/image type variables. Valid values are 32 and 64
+  uint32_t sampler_image_addressing_mode_;
+
   /// NOTE: See correspoding getter functions
   bool in_function_;
 
diff --git a/source/wasm/spirv-tools.cpp b/source/wasm/spirv-tools.cpp
index 90407f3..33f2f05 100644
--- a/source/wasm/spirv-tools.cpp
+++ b/source/wasm/spirv-tools.cpp
@@ -78,7 +78,8 @@
   constant("SPV_ENV_VULKAN_1_1_SPIRV_1_4", static_cast<uint32_t>(SPV_ENV_VULKAN_1_1_SPIRV_1_4));
   constant("SPV_ENV_UNIVERSAL_1_5", static_cast<uint32_t>(SPV_ENV_UNIVERSAL_1_5));
   constant("SPV_ENV_VULKAN_1_2", static_cast<uint32_t>(SPV_ENV_VULKAN_1_2));
-
+  constant("SPV_ENV_UNIVERSAL_1_6",
+           static_cast<uint32_t>(SPV_ENV_UNIVERSAL_1_6));
 
   constant("SPV_BINARY_TO_TEXT_OPTION_NONE", static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_NONE));
   constant("SPV_BINARY_TO_TEXT_OPTION_PRINT", static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_PRINT));
@@ -90,4 +91,4 @@
 
   constant("SPV_TEXT_TO_BINARY_OPTION_NONE", static_cast<uint32_t>(SPV_TEXT_TO_BINARY_OPTION_NONE));
   constant("SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS", static_cast<uint32_t>(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS));
-}
\ No newline at end of file
+}
diff --git a/source/wasm/spirv-tools.d.ts b/source/wasm/spirv-tools.d.ts
index 9c19797..c06bdf1 100644
--- a/source/wasm/spirv-tools.d.ts
+++ b/source/wasm/spirv-tools.d.ts
@@ -40,6 +40,7 @@
   SPV_ENV_VULKAN_1_1_SPIRV_1_4: number;
   SPV_ENV_UNIVERSAL_1_5: number;
   SPV_ENV_VULKAN_1_2: number;
+  SPV_ENV_UNIVERSAL_1_6: number;
 
   SPV_TEXT_TO_BINARY_OPTION_NONE: number;
   SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS: number;
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index e88df04..4ca8ef8 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -185,6 +185,7 @@
 endif()
 
 
+add_subdirectory(diff)
 add_subdirectory(link)
 add_subdirectory(lint)
 add_subdirectory(opt)
diff --git a/test/binary_header_get_test.cpp b/test/binary_header_get_test.cpp
index 3ce0b63..f94f0c1 100644
--- a/test/binary_header_get_test.cpp
+++ b/test/binary_header_get_test.cpp
@@ -51,8 +51,8 @@
   ASSERT_EQ(SPV_SUCCESS, spvBinaryHeaderGet(&const_bin, endian, &header));
 
   ASSERT_EQ(static_cast<uint32_t>(SpvMagicNumber), header.magic);
-  // Expect SPIRV-Headers updated to SPIR-V 1.5.
-  ASSERT_EQ(0x00010500u, header.version);
+  // Expect SPIRV-Headers updated to SPIR-V 1.6.
+  ASSERT_EQ(0x00010600u, 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 9a13f22..f0810a3 100644
--- a/test/binary_parse_test.cpp
+++ b/test/binary_parse_test.cpp
@@ -203,16 +203,7 @@
   void Parse(const SpirvVector& words, spv_result_t expected_result,
              bool flip_words = false) {
     SpirvVector flipped_words(words);
-    SCOPED_TRACE(flip_words ? "Flipped Endianness" : "Normal Endianness");
-    if (flip_words) {
-      std::transform(flipped_words.begin(), flipped_words.end(),
-                     flipped_words.begin(), [](const uint32_t raw_word) {
-                       return spvFixWord(raw_word,
-                                         I32_ENDIAN_HOST == I32_ENDIAN_BIG
-                                             ? SPV_ENDIANNESS_LITTLE
-                                             : SPV_ENDIANNESS_BIG);
-                     });
-    }
+    MaybeFlipWords(flip_words, flipped_words.begin(), flipped_words.end());
     EXPECT_EQ(expected_result,
               spvBinaryParse(ScopedContext().context, &client_,
                              flipped_words.data(), flipped_words.size(),
@@ -486,27 +477,27 @@
 }
 
 TEST_F(BinaryParseTest, InstructionWithStringOperand) {
-  const std::string str =
-      "the future is already here, it's just not evenly distributed";
-  const auto str_words = MakeVector(str);
-  const auto instruction = MakeInstruction(SpvOpName, {99}, str_words);
-  const auto words = Concatenate({ExpectedHeaderForBound(100), instruction});
-  InSequence calls_expected_in_specific_order;
-  EXPECT_HEADER(100).WillOnce(Return(SPV_SUCCESS));
-  const auto operands = std::vector<spv_parsed_operand_t>{
-      MakeSimpleOperand(1, SPV_OPERAND_TYPE_ID),
-      MakeLiteralStringOperand(2, static_cast<uint16_t>(str_words.size()))};
-  EXPECT_CALL(client_,
-              Instruction(ParsedInstruction(spv_parsed_instruction_t{
-                  instruction.data(), static_cast<uint16_t>(instruction.size()),
-                  SpvOpName, SPV_EXT_INST_TYPE_NONE, 0 /*type id*/,
-                  0 /* No result id for OpName*/, operands.data(),
-                  static_cast<uint16_t>(operands.size())})))
-      .WillOnce(Return(SPV_SUCCESS));
-  // Since we are actually checking the output, don't test the
-  // endian-swapped version.
-  Parse(words, SPV_SUCCESS, false);
-  EXPECT_EQ(nullptr, diagnostic_);
+  for (bool endian_swap : kSwapEndians) {
+    const std::string str =
+        "the future is already here, it's just not evenly distributed";
+    const auto str_words = MakeVector(str);
+    const auto instruction = MakeInstruction(SpvOpName, {99}, str_words);
+    const auto words = Concatenate({ExpectedHeaderForBound(100), instruction});
+    InSequence calls_expected_in_specific_order;
+    EXPECT_HEADER(100).WillOnce(Return(SPV_SUCCESS));
+    const auto operands = std::vector<spv_parsed_operand_t>{
+        MakeSimpleOperand(1, SPV_OPERAND_TYPE_ID),
+        MakeLiteralStringOperand(2, static_cast<uint16_t>(str_words.size()))};
+    EXPECT_CALL(client_, Instruction(ParsedInstruction(spv_parsed_instruction_t{
+                             instruction.data(),
+                             static_cast<uint16_t>(instruction.size()),
+                             SpvOpName, SPV_EXT_INST_TYPE_NONE, 0 /*type id*/,
+                             0 /* No result id for OpName*/, operands.data(),
+                             static_cast<uint16_t>(operands.size())})))
+        .WillOnce(Return(SPV_SUCCESS));
+    Parse(words, SPV_SUCCESS, endian_swap);
+    EXPECT_EQ(nullptr, diagnostic_);
+  }
 }
 
 // Checks for non-zero values for the result_id and ext_inst_type members
@@ -613,7 +604,7 @@
                       MakeInstruction(SpvOpNop, {42})}),
          "Invalid instruction OpNop starting at word 5: expected "
          "no more operands after 1 words, but stated word count is 2."},
-        // Supply several more unexpectd words.
+        // Supply several more unexpected words.
         {Concatenate({ExpectedHeaderForBound(1),
                       MakeInstruction(SpvOpNop, {42, 43, 44, 45, 46, 47})}),
          "Invalid instruction OpNop starting at word 5: expected "
diff --git a/test/binary_to_text.literal_test.cpp b/test/binary_to_text.literal_test.cpp
index 02daac7..5956984 100644
--- a/test/binary_to_text.literal_test.cpp
+++ b/test/binary_to_text.literal_test.cpp
@@ -27,8 +27,15 @@
 using RoundTripLiteralsTest =
     spvtest::TextToBinaryTestBase<::testing::TestWithParam<std::string>>;
 
+static const bool kSwapEndians[] = {false, true};
+
 TEST_P(RoundTripLiteralsTest, Sample) {
-  EXPECT_THAT(EncodeAndDecodeSuccessfully(GetParam()), Eq(GetParam()));
+  for (bool endian_swap : kSwapEndians) {
+    EXPECT_THAT(
+        EncodeAndDecodeSuccessfully(GetParam(), SPV_BINARY_TO_TEXT_OPTION_NONE,
+                                    SPV_ENV_UNIVERSAL_1_0, endian_swap),
+        Eq(GetParam()));
+  }
 }
 
 // clang-format off
@@ -58,8 +65,12 @@
 // Test case where the generated disassembly is not the same as the
 // assembly passed in.
 TEST_P(RoundTripSpecialCaseLiteralsTest, Sample) {
-  EXPECT_THAT(EncodeAndDecodeSuccessfully(std::get<0>(GetParam())),
-              Eq(std::get<1>(GetParam())));
+  for (bool endian_swap : kSwapEndians) {
+    EXPECT_THAT(EncodeAndDecodeSuccessfully(std::get<0>(GetParam()),
+                                            SPV_BINARY_TO_TEXT_OPTION_NONE,
+                                            SPV_ENV_UNIVERSAL_1_0, endian_swap),
+                Eq(std::get<1>(GetParam())));
+  }
 }
 
 // clang-format off
diff --git a/test/binary_to_text_test.cpp b/test/binary_to_text_test.cpp
index df703e5..44705f2 100644
--- a/test/binary_to_text_test.cpp
+++ b/test/binary_to_text_test.cpp
@@ -101,6 +101,17 @@
   spvTextDestroy(text);
 }
 
+TEST_F(BinaryToText, Print) {
+  spv_text text = nullptr;
+  spv_diagnostic diagnostic = nullptr;
+  ASSERT_EQ(
+      SPV_SUCCESS,
+      spvBinaryToText(context, binary->code, binary->wordCount,
+                      SPV_BINARY_TO_TEXT_OPTION_PRINT, &text, &diagnostic));
+  ASSERT_EQ(text, nullptr);
+  spvTextDestroy(text);
+}
+
 TEST_F(BinaryToText, MissingModule) {
   spv_text text;
   spv_diagnostic diagnostic = nullptr;
diff --git a/test/c_interface_test.cpp b/test/c_interface_test.cpp
index 841bb2c..4424d7f 100644
--- a/test/c_interface_test.cpp
+++ b/test/c_interface_test.cpp
@@ -117,12 +117,15 @@
                     const spv_position_t& position, const char* message) {
         ++invocation;
         EXPECT_EQ(SPV_MSG_ERROR, level);
-        // The error happens at scanning the begining of second line.
+        // The error happens at scanning the beginning of second line.
         EXPECT_STREQ("input", source);
         EXPECT_EQ(1u, position.line);
         EXPECT_EQ(0u, position.column);
         EXPECT_EQ(12u, position.index);
-        EXPECT_STREQ("Expected operand, found end of stream.", message);
+        EXPECT_STREQ(
+            "Expected operand for OpName instruction, but found the end of the "
+            "stream.",
+            message);
       });
 
   spv_binary binary = nullptr;
@@ -228,7 +231,10 @@
             spvTextToBinary(context, input_text, sizeof(input_text), &binary,
                             &diagnostic));
   EXPECT_EQ(0, invocation);  // Consumer should not be invoked at all.
-  EXPECT_STREQ("Expected operand, found end of stream.", diagnostic->error);
+  EXPECT_STREQ(
+      "Expected operand for OpName instruction, but found the end of the "
+      "stream.",
+      diagnostic->error);
 
   spvDiagnosticDestroy(diagnostic);
   spvBinaryDestroy(binary);
diff --git a/test/cpp_interface_test.cpp b/test/cpp_interface_test.cpp
index 538d40f..4cab4df 100644
--- a/test/cpp_interface_test.cpp
+++ b/test/cpp_interface_test.cpp
@@ -58,7 +58,7 @@
     EXPECT_EQ(0u, position.line);
     EXPECT_EQ(0u, position.column);
     EXPECT_EQ(1u, position.index);
-    EXPECT_STREQ("ID 1[%1] has not been defined\n  %2 = OpSizeOf %1 %3\n",
+    EXPECT_STREQ("ID '1[%1]' has not been defined\n  %2 = OpSizeOf %1 %3\n",
                  message);
   });
   EXPECT_FALSE(t.Validate(binary));
diff --git a/test/diff/CMakeLists.txt b/test/diff/CMakeLists.txt
new file mode 100644
index 0000000..811805b
--- /dev/null
+++ b/test/diff/CMakeLists.txt
@@ -0,0 +1,26 @@
+# Copyright (c) 2022 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(diff_files/diff_test_files_autogen.cmake)
+
+add_spvtools_unittest(TARGET lcs
+  SRCS lcs_test.cpp
+  LIBS SPIRV-Tools-diff
+)
+
+add_spvtools_unittest(TARGET diff
+  SRCS diff_test.cpp diff_test_utils.h diff_test_utils.cpp
+       ${DIFF_TEST_FILES} ${spirv-tools_SOURCE_DIR}/tools/util/cli_consumer.cpp
+  LIBS SPIRV-Tools-diff
+)
diff --git a/test/diff/diff_files/.gitignore b/test/diff/diff_files/.gitignore
new file mode 100644
index 0000000..727526e
--- /dev/null
+++ b/test/diff/diff_files/.gitignore
@@ -0,0 +1,3 @@
+# To aid debugging no-dbg variants, the temporary files used to strip debug information are placed
+# in a hidden directory and aren't removed after generation.
+.no_dbg/
diff --git a/test/diff/diff_files/OpExtInst_in_dst_only_autogen.cpp b/test/diff/diff_files/OpExtInst_in_dst_only_autogen.cpp
new file mode 100644
index 0000000..ce899ed
--- /dev/null
+++ b/test/diff/diff_files/OpExtInst_in_dst_only_autogen.cpp
@@ -0,0 +1,242 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Tests a diff where the src shader doesn't have OpExtImport while the
+// dst shader does (and uses OpExtInst).  This test ensures that when matching,
+// the OpExtImport instruction from the correct module is referenced.
+constexpr char kSrc[] = R"(               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %9 %11
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %9 "color"
+               OpName %11 "v"
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %9 Location 0
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %11 Location 0
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %13 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+          %8 = OpTypePointer Output %7
+          %9 = OpVariable %8 Output
+         %10 = OpTypePointer Input %6
+         %11 = OpVariable %10 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpLoad %6 %11
+         %13 = OpCompositeConstruct %7 %12 %12 %12 %12
+               OpStore %9 %13
+               OpReturn
+               OpFunctionEnd
+)";
+constexpr char kDst[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %9 %11
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %9 "color"
+               OpName %11 "v"
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %9 Location 0
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %11 Location 0
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %13 RelaxedPrecision
+               OpDecorate %14 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+          %8 = OpTypePointer Output %7
+          %9 = OpVariable %8 Output
+         %10 = OpTypePointer Input %6
+         %11 = OpVariable %10 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpLoad %6 %11
+         %13 = OpExtInst %6 %1 Log2 %12
+         %14 = OpCompositeConstruct %7 %13 %13 %13 %13
+               OpStore %9 %14
+               OpReturn
+               OpFunctionEnd
+
+)";
+
+TEST(DiffTest, OpextinstInDstOnly) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 14
++; Bound: 16
+ ; Schema: 0
+ OpCapability Shader
++%14 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %9 %11
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %9 "color"
+ OpName %11 "v"
+ OpDecorate %9 RelaxedPrecision
+ OpDecorate %9 Location 0
+ OpDecorate %11 RelaxedPrecision
+ OpDecorate %11 Location 0
+ OpDecorate %12 RelaxedPrecision
++OpDecorate %15 RelaxedPrecision
+ OpDecorate %13 RelaxedPrecision
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypeVector %6 4
+ %8 = OpTypePointer Output %7
+ %9 = OpVariable %8 Output
+ %10 = OpTypePointer Input %6
+ %11 = OpVariable %10 Input
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %12 = OpLoad %6 %11
++%15 = OpExtInst %6 %14 Log2 %12
+-%13 = OpCompositeConstruct %7 %12 %12 %12 %12
++%13 = OpCompositeConstruct %7 %15 %15 %15 %15
+ OpStore %9 %13
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, OpextinstInDstOnlyNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %9 %11
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %9 Location 0
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %11 Location 0
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %13 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+          %8 = OpTypePointer Output %7
+          %9 = OpVariable %8 Output
+         %10 = OpTypePointer Input %6
+         %11 = OpVariable %10 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpLoad %6 %11
+         %13 = OpCompositeConstruct %7 %12 %12 %12 %12
+               OpStore %9 %13
+               OpReturn
+               OpFunctionEnd
+
+)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %9 %11
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %9 Location 0
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %11 Location 0
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %13 RelaxedPrecision
+               OpDecorate %14 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+          %8 = OpTypePointer Output %7
+          %9 = OpVariable %8 Output
+         %10 = OpTypePointer Input %6
+         %11 = OpVariable %10 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpLoad %6 %11
+         %13 = OpExtInst %6 %1 Log2 %12
+         %14 = OpCompositeConstruct %7 %13 %13 %13 %13
+               OpStore %9 %14
+               OpReturn
+               OpFunctionEnd
+
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 14
++; Bound: 16
+ ; Schema: 0
+ OpCapability Shader
++%14 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %9 %11
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpDecorate %9 RelaxedPrecision
+ OpDecorate %9 Location 0
+ OpDecorate %11 RelaxedPrecision
+ OpDecorate %11 Location 0
+ OpDecorate %12 RelaxedPrecision
++OpDecorate %15 RelaxedPrecision
+ OpDecorate %13 RelaxedPrecision
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypeVector %6 4
+ %8 = OpTypePointer Output %7
+ %9 = OpVariable %8 Output
+ %10 = OpTypePointer Input %6
+ %11 = OpVariable %10 Input
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %12 = OpLoad %6 %11
++%15 = OpExtInst %6 %14 Log2 %12
+-%13 = OpCompositeConstruct %7 %12 %12 %12 %12
++%13 = OpCompositeConstruct %7 %15 %15 %15 %15
+ OpStore %9 %13
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/OpExtInst_in_dst_only_dst.spvasm b/test/diff/diff_files/OpExtInst_in_dst_only_dst.spvasm
new file mode 100644
index 0000000..e599d1e
--- /dev/null
+++ b/test/diff/diff_files/OpExtInst_in_dst_only_dst.spvasm
@@ -0,0 +1,33 @@
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %9 %11
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %9 "color"
+               OpName %11 "v"
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %9 Location 0
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %11 Location 0
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %13 RelaxedPrecision
+               OpDecorate %14 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+          %8 = OpTypePointer Output %7
+          %9 = OpVariable %8 Output
+         %10 = OpTypePointer Input %6
+         %11 = OpVariable %10 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpLoad %6 %11
+         %13 = OpExtInst %6 %1 Log2 %12
+         %14 = OpCompositeConstruct %7 %13 %13 %13 %13
+               OpStore %9 %14
+               OpReturn
+               OpFunctionEnd
+
diff --git a/test/diff/diff_files/OpExtInst_in_dst_only_src.spvasm b/test/diff/diff_files/OpExtInst_in_dst_only_src.spvasm
new file mode 100644
index 0000000..9f6fc18
--- /dev/null
+++ b/test/diff/diff_files/OpExtInst_in_dst_only_src.spvasm
@@ -0,0 +1,33 @@
+;; Tests a diff where the src shader doesn't have OpExtImport while the
+;; dst shader does (and uses OpExtInst).  This test ensures that when matching,
+;; the OpExtImport instruction from the correct module is referenced.
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %9 %11
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %9 "color"
+               OpName %11 "v"
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %9 Location 0
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %11 Location 0
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %13 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+          %8 = OpTypePointer Output %7
+          %9 = OpVariable %8 Output
+         %10 = OpTypePointer Input %6
+         %11 = OpVariable %10 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpLoad %6 %11
+         %13 = OpCompositeConstruct %7 %12 %12 %12 %12
+               OpStore %9 %13
+               OpReturn
+               OpFunctionEnd
+
diff --git a/test/diff/diff_files/OpExtInst_in_src_only_autogen.cpp b/test/diff/diff_files/OpExtInst_in_src_only_autogen.cpp
new file mode 100644
index 0000000..9944c2c
--- /dev/null
+++ b/test/diff/diff_files/OpExtInst_in_src_only_autogen.cpp
@@ -0,0 +1,242 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Tests a diff where the dst shader doesn't have OpExtImport while the
+// src shader does (and uses OpExtInst).  This test ensures that when matching,
+// the OpExtImport instruction from the correct module is referenced.
+constexpr char kSrc[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %9 %11
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %9 "color"
+               OpName %11 "v"
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %9 Location 0
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %11 Location 0
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %13 RelaxedPrecision
+               OpDecorate %14 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+          %8 = OpTypePointer Output %7
+          %9 = OpVariable %8 Output
+         %10 = OpTypePointer Input %6
+         %11 = OpVariable %10 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpLoad %6 %11
+         %13 = OpExtInst %6 %1 Log2 %12
+         %14 = OpCompositeConstruct %7 %13 %13 %13 %13
+               OpStore %9 %14
+               OpReturn
+               OpFunctionEnd
+)";
+constexpr char kDst[] = R"(               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %9 %11
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %9 "color"
+               OpName %11 "v"
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %9 Location 0
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %11 Location 0
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %13 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+          %8 = OpTypePointer Output %7
+          %9 = OpVariable %8 Output
+         %10 = OpTypePointer Input %6
+         %11 = OpVariable %10 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpLoad %6 %11
+         %13 = OpCompositeConstruct %7 %12 %12 %12 %12
+               OpStore %9 %13
+               OpReturn
+               OpFunctionEnd
+
+)";
+
+TEST(DiffTest, OpextinstInSrcOnly) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 15
++; Bound: 16
+ ; Schema: 0
+ OpCapability Shader
+-%1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %9 %11
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %9 "color"
+ OpName %11 "v"
+ OpDecorate %9 RelaxedPrecision
+ OpDecorate %9 Location 0
+ OpDecorate %11 RelaxedPrecision
+ OpDecorate %11 Location 0
+ OpDecorate %12 RelaxedPrecision
+-OpDecorate %13 RelaxedPrecision
+ OpDecorate %14 RelaxedPrecision
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypeVector %6 4
+ %8 = OpTypePointer Output %7
+ %9 = OpVariable %8 Output
+ %10 = OpTypePointer Input %6
+ %11 = OpVariable %10 Input
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %12 = OpLoad %6 %11
+-%13 = OpExtInst %6 %1 Log2 %12
+-%14 = OpCompositeConstruct %7 %13 %13 %13 %13
++%14 = OpCompositeConstruct %7 %12 %12 %12 %12
+ OpStore %9 %14
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, OpextinstInSrcOnlyNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %9 %11
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %9 Location 0
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %11 Location 0
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %13 RelaxedPrecision
+               OpDecorate %14 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+          %8 = OpTypePointer Output %7
+          %9 = OpVariable %8 Output
+         %10 = OpTypePointer Input %6
+         %11 = OpVariable %10 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpLoad %6 %11
+         %13 = OpExtInst %6 %1 Log2 %12
+         %14 = OpCompositeConstruct %7 %13 %13 %13 %13
+               OpStore %9 %14
+               OpReturn
+               OpFunctionEnd
+
+)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %9 %11
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %9 Location 0
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %11 Location 0
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %13 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+          %8 = OpTypePointer Output %7
+          %9 = OpVariable %8 Output
+         %10 = OpTypePointer Input %6
+         %11 = OpVariable %10 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpLoad %6 %11
+         %13 = OpCompositeConstruct %7 %12 %12 %12 %12
+               OpStore %9 %13
+               OpReturn
+               OpFunctionEnd
+
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 15
++; Bound: 16
+ ; Schema: 0
+ OpCapability Shader
+-%1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %9 %11
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpDecorate %9 RelaxedPrecision
+ OpDecorate %9 Location 0
+ OpDecorate %11 RelaxedPrecision
+ OpDecorate %11 Location 0
+ OpDecorate %12 RelaxedPrecision
+-OpDecorate %13 RelaxedPrecision
+ OpDecorate %14 RelaxedPrecision
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypeVector %6 4
+ %8 = OpTypePointer Output %7
+ %9 = OpVariable %8 Output
+ %10 = OpTypePointer Input %6
+ %11 = OpVariable %10 Input
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %12 = OpLoad %6 %11
+-%13 = OpExtInst %6 %1 Log2 %12
+-%14 = OpCompositeConstruct %7 %13 %13 %13 %13
++%14 = OpCompositeConstruct %7 %12 %12 %12 %12
+ OpStore %9 %14
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/OpExtInst_in_src_only_dst.spvasm b/test/diff/diff_files/OpExtInst_in_src_only_dst.spvasm
new file mode 100644
index 0000000..4723305
--- /dev/null
+++ b/test/diff/diff_files/OpExtInst_in_src_only_dst.spvasm
@@ -0,0 +1,30 @@
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %9 %11
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %9 "color"
+               OpName %11 "v"
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %9 Location 0
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %11 Location 0
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %13 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+          %8 = OpTypePointer Output %7
+          %9 = OpVariable %8 Output
+         %10 = OpTypePointer Input %6
+         %11 = OpVariable %10 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpLoad %6 %11
+         %13 = OpCompositeConstruct %7 %12 %12 %12 %12
+               OpStore %9 %13
+               OpReturn
+               OpFunctionEnd
+
diff --git a/test/diff/diff_files/OpExtInst_in_src_only_src.spvasm b/test/diff/diff_files/OpExtInst_in_src_only_src.spvasm
new file mode 100644
index 0000000..efb663a
--- /dev/null
+++ b/test/diff/diff_files/OpExtInst_in_src_only_src.spvasm
@@ -0,0 +1,36 @@
+;; Tests a diff where the dst shader doesn't have OpExtImport while the
+;; src shader does (and uses OpExtInst).  This test ensures that when matching,
+;; the OpExtImport instruction from the correct module is referenced.
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %9 %11
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %9 "color"
+               OpName %11 "v"
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %9 Location 0
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %11 Location 0
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %13 RelaxedPrecision
+               OpDecorate %14 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+          %8 = OpTypePointer Output %7
+          %9 = OpVariable %8 Output
+         %10 = OpTypePointer Input %6
+         %11 = OpVariable %10 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpLoad %6 %11
+         %13 = OpExtInst %6 %1 Log2 %12
+         %14 = OpCompositeConstruct %7 %13 %13 %13 %13
+               OpStore %9 %14
+               OpReturn
+               OpFunctionEnd
+
diff --git a/test/diff/diff_files/OpTypeForwardPointer_basic_autogen.cpp b/test/diff/diff_files/OpTypeForwardPointer_basic_autogen.cpp
new file mode 100644
index 0000000..af252b1
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_basic_autogen.cpp
@@ -0,0 +1,136 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Basic test that OpTypeForwardPointer is matched
+constexpr char kSrc[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %structptr "structptr"
+               OpTypeForwardPointer %structptr UniformConstant
+       %uint = OpTypeInt 32 0
+   %structt1 = OpTypeStruct %structptr %uint
+   %structt2 = OpTypeStruct %uint %structptr
+   %structt3 = OpTypeStruct %uint %uint %structptr
+   %structt4 = OpTypeStruct %uint %uint %uint %structptr
+  %structptr = OpTypePointer UniformConstant %structt1)";
+constexpr char kDst[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %structptr "structptr"
+               OpName %structptr2 "structptr2"
+               OpTypeForwardPointer %structptr UniformConstant
+               OpTypeForwardPointer %structptr2 Function
+       %uint = OpTypeInt 32 0
+   %structt1 = OpTypeStruct %structptr %uint
+   %structt2 = OpTypeStruct %uint %structptr
+   %structt3 = OpTypeStruct %uint %uint %structptr
+   %structt4 = OpTypeStruct %uint %uint %uint %structptr
+  %structptr = OpTypePointer UniformConstant %structt1
+  %structptr2 = OpTypePointer Function %structt1
+)";
+
+TEST(DiffTest, OptypeforwardpointerBasic) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 7
++; Bound: 8
+ ; Schema: 0
+ OpCapability Kernel
+ OpCapability Addresses
+ OpCapability Linkage
+ OpMemoryModel Logical OpenCL
+ OpName %1 "structptr"
++OpName %7 "structptr2"
+ OpTypeForwardPointer %1 UniformConstant
++OpTypeForwardPointer %7 Function
+ %2 = OpTypeInt 32 0
+ %3 = OpTypeStruct %1 %2
+ %4 = OpTypeStruct %2 %1
+ %5 = OpTypeStruct %2 %2 %1
+ %6 = OpTypeStruct %2 %2 %2 %1
+ %1 = OpTypePointer UniformConstant %3
++%7 = OpTypePointer Function %3
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, OptypeforwardpointerBasicNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %structptr UniformConstant
+       %uint = OpTypeInt 32 0
+   %structt1 = OpTypeStruct %structptr %uint
+   %structt2 = OpTypeStruct %uint %structptr
+   %structt3 = OpTypeStruct %uint %uint %structptr
+   %structt4 = OpTypeStruct %uint %uint %uint %structptr
+  %structptr = OpTypePointer UniformConstant %structt1
+)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %structptr UniformConstant
+               OpTypeForwardPointer %structptr2 Function
+       %uint = OpTypeInt 32 0
+   %structt1 = OpTypeStruct %structptr %uint
+   %structt2 = OpTypeStruct %uint %structptr
+   %structt3 = OpTypeStruct %uint %uint %structptr
+   %structt4 = OpTypeStruct %uint %uint %uint %structptr
+  %structptr = OpTypePointer UniformConstant %structt1
+  %structptr2 = OpTypePointer Function %structt1
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 7
++; Bound: 8
+ ; Schema: 0
+ OpCapability Kernel
+ OpCapability Addresses
+ OpCapability Linkage
+ OpMemoryModel Logical OpenCL
+ OpTypeForwardPointer %1 UniformConstant
++OpTypeForwardPointer %7 Function
+ %2 = OpTypeInt 32 0
+ %3 = OpTypeStruct %1 %2
+ %4 = OpTypeStruct %2 %1
+ %5 = OpTypeStruct %2 %2 %1
+ %6 = OpTypeStruct %2 %2 %2 %1
+ %1 = OpTypePointer UniformConstant %3
++%7 = OpTypePointer Function %3
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/OpTypeForwardPointer_basic_dst.spvasm b/test/diff/diff_files/OpTypeForwardPointer_basic_dst.spvasm
new file mode 100644
index 0000000..0c6e0cb
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_basic_dst.spvasm
@@ -0,0 +1,15 @@
+               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %structptr "structptr"
+               OpName %structptr2 "structptr2"
+               OpTypeForwardPointer %structptr UniformConstant
+               OpTypeForwardPointer %structptr2 Function
+       %uint = OpTypeInt 32 0
+   %structt1 = OpTypeStruct %structptr %uint
+   %structt2 = OpTypeStruct %uint %structptr
+   %structt3 = OpTypeStruct %uint %uint %structptr
+   %structt4 = OpTypeStruct %uint %uint %uint %structptr
+  %structptr = OpTypePointer UniformConstant %structt1
+  %structptr2 = OpTypePointer Function %structt1
diff --git a/test/diff/diff_files/OpTypeForwardPointer_basic_src.spvasm b/test/diff/diff_files/OpTypeForwardPointer_basic_src.spvasm
new file mode 100644
index 0000000..408ec98
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_basic_src.spvasm
@@ -0,0 +1,13 @@
+;; Basic test that OpTypeForwardPointer is matched
+               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %structptr "structptr"
+               OpTypeForwardPointer %structptr UniformConstant
+       %uint = OpTypeInt 32 0
+   %structt1 = OpTypeStruct %structptr %uint
+   %structt2 = OpTypeStruct %uint %structptr
+   %structt3 = OpTypeStruct %uint %uint %structptr
+   %structt4 = OpTypeStruct %uint %uint %uint %structptr
+  %structptr = OpTypePointer UniformConstant %structt1
diff --git a/test/diff/diff_files/OpTypeForwardPointer_intertwined_autogen.cpp b/test/diff/diff_files/OpTypeForwardPointer_intertwined_autogen.cpp
new file mode 100644
index 0000000..f2c9008
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_intertwined_autogen.cpp
@@ -0,0 +1,138 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Tests that two forwarded types whose declarations are intertwined match
+// correctly
+constexpr char kSrc[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpName %Bptr "Bptr"
+               OpTypeForwardPointer %Aptr UniformConstant
+               OpTypeForwardPointer %Bptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %A = OpTypeStruct %Aptr %uint %Bptr
+          %B = OpTypeStruct %uint %Aptr %Bptr
+  %Aptr = OpTypePointer UniformConstant %A
+  %Bptr = OpTypePointer UniformConstant %B)";
+constexpr char kDst[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpName %Bptr "Bptr"
+               OpTypeForwardPointer %Bptr UniformConstant
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %B = OpTypeStruct %uint %Aptr %Bptr %uint
+          %A = OpTypeStruct %Aptr %uint %Bptr
+  %Aptr = OpTypePointer UniformConstant %A
+  %Bptr = OpTypePointer UniformConstant %B
+)";
+
+TEST(DiffTest, OptypeforwardpointerIntertwined) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 6
++; Bound: 7
+ ; Schema: 0
+ OpCapability Kernel
+ OpCapability Addresses
+ OpCapability Linkage
+ OpMemoryModel Logical OpenCL
+ OpName %1 "Aptr"
+ OpName %2 "Bptr"
+ OpTypeForwardPointer %1 UniformConstant
+ OpTypeForwardPointer %2 UniformConstant
+ %3 = OpTypeInt 32 0
++%6 = OpTypeStruct %3 %1 %2 %3
+ %4 = OpTypeStruct %1 %3 %2
+-%5 = OpTypeStruct %3 %1 %2
+ %1 = OpTypePointer UniformConstant %4
+-%2 = OpTypePointer UniformConstant %5
++%2 = OpTypePointer UniformConstant %6
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, OptypeforwardpointerIntertwinedNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %Aptr UniformConstant
+               OpTypeForwardPointer %Bptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %A = OpTypeStruct %Aptr %uint %Bptr
+          %B = OpTypeStruct %uint %Aptr %Bptr
+  %Aptr = OpTypePointer UniformConstant %A
+  %Bptr = OpTypePointer UniformConstant %B
+)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %Bptr UniformConstant
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %B = OpTypeStruct %uint %Aptr %Bptr %uint
+          %A = OpTypeStruct %Aptr %uint %Bptr
+  %Aptr = OpTypePointer UniformConstant %A
+  %Bptr = OpTypePointer UniformConstant %B
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 6
++; Bound: 10
+ ; Schema: 0
+ OpCapability Kernel
+ OpCapability Addresses
+ OpCapability Linkage
+ OpMemoryModel Logical OpenCL
+-OpTypeForwardPointer %1 UniformConstant
+-OpTypeForwardPointer %2 UniformConstant
++OpTypeForwardPointer %6 UniformConstant
++OpTypeForwardPointer %7 UniformConstant
+ %3 = OpTypeInt 32 0
+-%4 = OpTypeStruct %1 %3 %2
+-%5 = OpTypeStruct %3 %1 %2
+-%1 = OpTypePointer UniformConstant %4
+-%2 = OpTypePointer UniformConstant %5
++%8 = OpTypeStruct %3 %7 %6 %3
++%9 = OpTypeStruct %7 %3 %6
++%7 = OpTypePointer UniformConstant %9
++%6 = OpTypePointer UniformConstant %8
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/OpTypeForwardPointer_intertwined_dst.spvasm b/test/diff/diff_files/OpTypeForwardPointer_intertwined_dst.spvasm
new file mode 100644
index 0000000..bd73501
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_intertwined_dst.spvasm
@@ -0,0 +1,13 @@
+               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpName %Bptr "Bptr"
+               OpTypeForwardPointer %Bptr UniformConstant
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %B = OpTypeStruct %uint %Aptr %Bptr %uint
+          %A = OpTypeStruct %Aptr %uint %Bptr
+  %Aptr = OpTypePointer UniformConstant %A
+  %Bptr = OpTypePointer UniformConstant %B
diff --git a/test/diff/diff_files/OpTypeForwardPointer_intertwined_src.spvasm b/test/diff/diff_files/OpTypeForwardPointer_intertwined_src.spvasm
new file mode 100644
index 0000000..8fdaf28
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_intertwined_src.spvasm
@@ -0,0 +1,15 @@
+;; Tests that two forwarded types whose declarations are intertwined match
+;; correctly
+               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpName %Bptr "Bptr"
+               OpTypeForwardPointer %Aptr UniformConstant
+               OpTypeForwardPointer %Bptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %A = OpTypeStruct %Aptr %uint %Bptr
+          %B = OpTypeStruct %uint %Aptr %Bptr
+  %Aptr = OpTypePointer UniformConstant %A
+  %Bptr = OpTypePointer UniformConstant %B
diff --git a/test/diff/diff_files/OpTypeForwardPointer_mismatching_class_autogen.cpp b/test/diff/diff_files/OpTypeForwardPointer_mismatching_class_autogen.cpp
new file mode 100644
index 0000000..0a59be3
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_mismatching_class_autogen.cpp
@@ -0,0 +1,116 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Tests that two forwarded type pointers with mismatching storage classes
+// aren't matched
+constexpr char kSrc[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %A = OpTypeStruct %Aptr %uint
+  %Aptr = OpTypePointer UniformConstant %A)";
+constexpr char kDst[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpTypeForwardPointer %Aptr Function
+       %uint = OpTypeInt 32 0
+          %A = OpTypeStruct %Aptr %uint
+  %Aptr = OpTypePointer Function %A
+)";
+
+TEST(DiffTest, OptypeforwardpointerMismatchingClass) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 4
++; Bound: 6
+ ; Schema: 0
+ OpCapability Kernel
+ OpCapability Addresses
+ OpCapability Linkage
+ OpMemoryModel Logical OpenCL
+-OpName %1 "Aptr"
++OpName %4 "Aptr"
+-OpTypeForwardPointer %1 UniformConstant
++OpTypeForwardPointer %4 Function
+ %2 = OpTypeInt 32 0
+-%3 = OpTypeStruct %1 %2
+-%1 = OpTypePointer UniformConstant %3
++%5 = OpTypeStruct %4 %2
++%4 = OpTypePointer Function %5
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, OptypeforwardpointerMismatchingClassNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %A = OpTypeStruct %Aptr %uint
+  %Aptr = OpTypePointer UniformConstant %A
+)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %Aptr Function
+       %uint = OpTypeInt 32 0
+          %A = OpTypeStruct %Aptr %uint
+  %Aptr = OpTypePointer Function %A
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 4
++; Bound: 6
+ ; Schema: 0
+ OpCapability Kernel
+ OpCapability Addresses
+ OpCapability Linkage
+ OpMemoryModel Logical OpenCL
+-OpTypeForwardPointer %1 UniformConstant
++OpTypeForwardPointer %4 Function
+ %2 = OpTypeInt 32 0
+-%3 = OpTypeStruct %1 %2
+-%1 = OpTypePointer UniformConstant %3
++%5 = OpTypeStruct %4 %2
++%4 = OpTypePointer Function %5
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/OpTypeForwardPointer_mismatching_class_dst.spvasm b/test/diff/diff_files/OpTypeForwardPointer_mismatching_class_dst.spvasm
new file mode 100644
index 0000000..e874a0c
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_mismatching_class_dst.spvasm
@@ -0,0 +1,9 @@
+               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpTypeForwardPointer %Aptr Function
+       %uint = OpTypeInt 32 0
+          %A = OpTypeStruct %Aptr %uint
+  %Aptr = OpTypePointer Function %A
diff --git a/test/diff/diff_files/OpTypeForwardPointer_mismatching_class_src.spvasm b/test/diff/diff_files/OpTypeForwardPointer_mismatching_class_src.spvasm
new file mode 100644
index 0000000..8a33933
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_mismatching_class_src.spvasm
@@ -0,0 +1,11 @@
+;; Tests that two forwarded type pointers with mismatching storage classes
+;; aren't matched
+               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %A = OpTypeStruct %Aptr %uint
+  %Aptr = OpTypePointer UniformConstant %A
diff --git a/test/diff/diff_files/OpTypeForwardPointer_mismatching_type_autogen.cpp b/test/diff/diff_files/OpTypeForwardPointer_mismatching_type_autogen.cpp
new file mode 100644
index 0000000..0067cdf
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_mismatching_type_autogen.cpp
@@ -0,0 +1,111 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Tests that two forwarded type pointers with mismatching types aren't matched
+constexpr char kSrc[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %A = OpTypeStruct %Aptr %uint
+  %Aptr = OpTypePointer UniformConstant %A)";
+constexpr char kDst[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+  %Aptr = OpTypePointer UniformConstant %uint
+)";
+
+TEST(DiffTest, OptypeforwardpointerMismatchingType) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 4
++; Bound: 5
+ ; Schema: 0
+ OpCapability Kernel
+ OpCapability Addresses
+ OpCapability Linkage
+ OpMemoryModel Logical OpenCL
+-OpName %1 "Aptr"
++OpName %4 "Aptr"
+-OpTypeForwardPointer %1 UniformConstant
++OpTypeForwardPointer %4 UniformConstant
+ %2 = OpTypeInt 32 0
+-%3 = OpTypeStruct %1 %2
+-%1 = OpTypePointer UniformConstant %3
++%4 = OpTypePointer UniformConstant %2
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, OptypeforwardpointerMismatchingTypeNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %A = OpTypeStruct %Aptr %uint
+  %Aptr = OpTypePointer UniformConstant %A
+)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+  %Aptr = OpTypePointer UniformConstant %uint
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 4
++; Bound: 5
+ ; Schema: 0
+ OpCapability Kernel
+ OpCapability Addresses
+ OpCapability Linkage
+ OpMemoryModel Logical OpenCL
+-OpTypeForwardPointer %1 UniformConstant
++OpTypeForwardPointer %4 UniformConstant
+ %2 = OpTypeInt 32 0
+-%3 = OpTypeStruct %1 %2
+-%1 = OpTypePointer UniformConstant %3
++%4 = OpTypePointer UniformConstant %2
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/OpTypeForwardPointer_mismatching_type_dst.spvasm b/test/diff/diff_files/OpTypeForwardPointer_mismatching_type_dst.spvasm
new file mode 100644
index 0000000..ee3d35c
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_mismatching_type_dst.spvasm
@@ -0,0 +1,8 @@
+               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+  %Aptr = OpTypePointer UniformConstant %uint
diff --git a/test/diff/diff_files/OpTypeForwardPointer_mismatching_type_src.spvasm b/test/diff/diff_files/OpTypeForwardPointer_mismatching_type_src.spvasm
new file mode 100644
index 0000000..a4596a0
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_mismatching_type_src.spvasm
@@ -0,0 +1,10 @@
+;; Tests that two forwarded type pointers with mismatching types aren't matched
+               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %A = OpTypeStruct %Aptr %uint
+  %Aptr = OpTypePointer UniformConstant %A
diff --git a/test/diff/diff_files/OpTypeForwardPointer_nested_autogen.cpp b/test/diff/diff_files/OpTypeForwardPointer_nested_autogen.cpp
new file mode 100644
index 0000000..d66c28a
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_nested_autogen.cpp
@@ -0,0 +1,127 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Tests that two forwarded declarations match even if the type pointer is used
+// in a nested struct declaration, and in multiple places
+constexpr char kSrc[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %C = OpTypeStruct %Aptr %uint %Aptr
+          %B = OpTypeStruct %C %Aptr %uint
+          %A = OpTypeStruct %B %C %B
+  %Aptr = OpTypePointer UniformConstant %A)";
+constexpr char kDst[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %C = OpTypeStruct %Aptr %uint %Aptr
+          %B = OpTypeStruct %C %Aptr
+          %A = OpTypeStruct %B %C %B
+  %Aptr = OpTypePointer UniformConstant %A
+)";
+
+TEST(DiffTest, OptypeforwardpointerNested) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 6
++; Bound: 8
+ ; Schema: 0
+ OpCapability Kernel
+ OpCapability Addresses
+ OpCapability Linkage
+ OpMemoryModel Logical OpenCL
+ OpName %1 "Aptr"
+ OpTypeForwardPointer %1 UniformConstant
+ %2 = OpTypeInt 32 0
+ %3 = OpTypeStruct %1 %2 %1
+-%4 = OpTypeStruct %3 %1 %2
+-%5 = OpTypeStruct %4 %3 %4
++%6 = OpTypeStruct %3 %1
++%7 = OpTypeStruct %6 %3 %6
+-%1 = OpTypePointer UniformConstant %5
++%1 = OpTypePointer UniformConstant %7
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, OptypeforwardpointerNestedNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %C = OpTypeStruct %Aptr %uint %Aptr
+          %B = OpTypeStruct %C %Aptr %uint
+          %A = OpTypeStruct %B %C %B
+  %Aptr = OpTypePointer UniformConstant %A
+)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %C = OpTypeStruct %Aptr %uint %Aptr
+          %B = OpTypeStruct %C %Aptr
+          %A = OpTypeStruct %B %C %B
+  %Aptr = OpTypePointer UniformConstant %A
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 6
++; Bound: 8
+ ; Schema: 0
+ OpCapability Kernel
+ OpCapability Addresses
+ OpCapability Linkage
+ OpMemoryModel Logical OpenCL
+ OpTypeForwardPointer %1 UniformConstant
+ %2 = OpTypeInt 32 0
+ %3 = OpTypeStruct %1 %2 %1
+-%4 = OpTypeStruct %3 %1 %2
+-%5 = OpTypeStruct %4 %3 %4
++%6 = OpTypeStruct %3 %1
++%7 = OpTypeStruct %6 %3 %6
+-%1 = OpTypePointer UniformConstant %5
++%1 = OpTypePointer UniformConstant %7
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/OpTypeForwardPointer_nested_dst.spvasm b/test/diff/diff_files/OpTypeForwardPointer_nested_dst.spvasm
new file mode 100644
index 0000000..e248355
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_nested_dst.spvasm
@@ -0,0 +1,11 @@
+               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %C = OpTypeStruct %Aptr %uint %Aptr
+          %B = OpTypeStruct %C %Aptr
+          %A = OpTypeStruct %B %C %B
+  %Aptr = OpTypePointer UniformConstant %A
diff --git a/test/diff/diff_files/OpTypeForwardPointer_nested_src.spvasm b/test/diff/diff_files/OpTypeForwardPointer_nested_src.spvasm
new file mode 100644
index 0000000..035410e
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_nested_src.spvasm
@@ -0,0 +1,13 @@
+;; Tests that two forwarded declarations match even if the type pointer is used
+;; in a nested struct declaration, and in multiple places
+               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %Aptr "Aptr"
+               OpTypeForwardPointer %Aptr UniformConstant
+       %uint = OpTypeInt 32 0
+          %C = OpTypeStruct %Aptr %uint %Aptr
+          %B = OpTypeStruct %C %Aptr %uint
+          %A = OpTypeStruct %B %C %B
+  %Aptr = OpTypePointer UniformConstant %A
diff --git a/test/diff/diff_files/OpTypeForwardPointer_onesided_debug_autogen.cpp b/test/diff/diff_files/OpTypeForwardPointer_onesided_debug_autogen.cpp
new file mode 100644
index 0000000..df86fef
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_onesided_debug_autogen.cpp
@@ -0,0 +1,124 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Test that OpTypeForwardPointer is matched when one SPIR-V doesn't have debug
+// info
+constexpr char kSrc[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %structptr "structptr"
+               OpTypeForwardPointer %structptr UniformConstant
+       %uint = OpTypeInt 32 0
+   %structt1 = OpTypeStruct %structptr %uint
+   %structt2 = OpTypeStruct %uint %structptr
+   %structt3 = OpTypeStruct %uint %uint %structptr
+   %structt4 = OpTypeStruct %uint %uint %uint %structptr
+  %structptr = OpTypePointer UniformConstant %structt1)";
+constexpr char kDst[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %structptr UniformConstant
+       %uint = OpTypeInt 32 0
+   %structt1 = OpTypeStruct %structptr %uint
+   %structt2 = OpTypeStruct %uint %structptr
+   %structt3 = OpTypeStruct %uint %uint %structptr
+   %structt4 = OpTypeStruct %uint %uint %uint %structptr
+  %structptr = OpTypePointer UniformConstant %structt1
+)";
+
+TEST(DiffTest, OptypeforwardpointerOnesidedDebug) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+ ; Bound: 7
+ ; Schema: 0
+ OpCapability Kernel
+ OpCapability Addresses
+ OpCapability Linkage
+ OpMemoryModel Logical OpenCL
+-OpName %1 "structptr"
+ OpTypeForwardPointer %1 UniformConstant
+ %2 = OpTypeInt 32 0
+ %3 = OpTypeStruct %1 %2
+ %4 = OpTypeStruct %2 %1
+ %5 = OpTypeStruct %2 %2 %1
+ %6 = OpTypeStruct %2 %2 %2 %1
+ %1 = OpTypePointer UniformConstant %3
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, OptypeforwardpointerOnesidedDebugNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %structptr UniformConstant
+       %uint = OpTypeInt 32 0
+   %structt1 = OpTypeStruct %structptr %uint
+   %structt2 = OpTypeStruct %uint %structptr
+   %structt3 = OpTypeStruct %uint %uint %structptr
+   %structt4 = OpTypeStruct %uint %uint %uint %structptr
+  %structptr = OpTypePointer UniformConstant %structt1
+)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %structptr UniformConstant
+       %uint = OpTypeInt 32 0
+   %structt1 = OpTypeStruct %structptr %uint
+   %structt2 = OpTypeStruct %uint %structptr
+   %structt3 = OpTypeStruct %uint %uint %structptr
+   %structt4 = OpTypeStruct %uint %uint %uint %structptr
+  %structptr = OpTypePointer UniformConstant %structt1
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+ ; Bound: 7
+ ; Schema: 0
+ OpCapability Kernel
+ OpCapability Addresses
+ OpCapability Linkage
+ OpMemoryModel Logical OpenCL
+ OpTypeForwardPointer %1 UniformConstant
+ %2 = OpTypeInt 32 0
+ %3 = OpTypeStruct %1 %2
+ %4 = OpTypeStruct %2 %1
+ %5 = OpTypeStruct %2 %2 %1
+ %6 = OpTypeStruct %2 %2 %2 %1
+ %1 = OpTypePointer UniformConstant %3
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/OpTypeForwardPointer_onesided_debug_dst.spvasm b/test/diff/diff_files/OpTypeForwardPointer_onesided_debug_dst.spvasm
new file mode 100644
index 0000000..7e25710
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_onesided_debug_dst.spvasm
@@ -0,0 +1,11 @@
+               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpTypeForwardPointer %structptr UniformConstant
+       %uint = OpTypeInt 32 0
+   %structt1 = OpTypeStruct %structptr %uint
+   %structt2 = OpTypeStruct %uint %structptr
+   %structt3 = OpTypeStruct %uint %uint %structptr
+   %structt4 = OpTypeStruct %uint %uint %uint %structptr
+  %structptr = OpTypePointer UniformConstant %structt1
diff --git a/test/diff/diff_files/OpTypeForwardPointer_onesided_debug_src.spvasm b/test/diff/diff_files/OpTypeForwardPointer_onesided_debug_src.spvasm
new file mode 100644
index 0000000..e949b27
--- /dev/null
+++ b/test/diff/diff_files/OpTypeForwardPointer_onesided_debug_src.spvasm
@@ -0,0 +1,14 @@
+;; Test that OpTypeForwardPointer is matched when one SPIR-V doesn't have debug
+;; info
+               OpCapability Kernel
+               OpCapability Addresses
+               OpCapability Linkage
+               OpMemoryModel Logical OpenCL
+               OpName %structptr "structptr"
+               OpTypeForwardPointer %structptr UniformConstant
+       %uint = OpTypeInt 32 0
+   %structt1 = OpTypeStruct %structptr %uint
+   %structt2 = OpTypeStruct %uint %structptr
+   %structt3 = OpTypeStruct %uint %uint %structptr
+   %structt4 = OpTypeStruct %uint %uint %uint %structptr
+  %structptr = OpTypePointer UniformConstant %structt1
diff --git a/test/diff/diff_files/README.md b/test/diff/diff_files/README.md
new file mode 100644
index 0000000..5dcee25
--- /dev/null
+++ b/test/diff/diff_files/README.md
@@ -0,0 +1,17 @@
+# Diff tests
+
+This directory contains files used to ensure correctness of the `spirv-diff` implementation.  The
+`generate_tests.py` script takes `name_src.spvasm` and `name_dst.spvasm` (for each `name`) and
+produces unit test files in the form of `name_autogen.cpp`.
+
+The unit test files test the diff between the src and dst inputs, as well as between debug-stripped
+versions of those.  Additionally, based on the `{variant}_TESTS` lists defined in
+`generate_tests.py`, extra unit tests are added to exercise different options of spirv-diff.
+
+New tests are added simply by placing a new `name_src.spvasm` and `name_dst.spvasm` pair in this
+directory and running `generate_tests.py`.  Note that this script needs the path to the spirv-diff
+executable that is built.
+
+The `generate_tests.py` script additionally expects `name_src.spvasm` to include a heading where the
+purpose of the test is explained.  This heading is parsed as a block of lines starting with `;;` at
+the top of the file.
diff --git a/test/diff/diff_files/basic_autogen.cpp b/test/diff/diff_files/basic_autogen.cpp
new file mode 100644
index 0000000..f3afc70
--- /dev/null
+++ b/test/diff/diff_files/basic_autogen.cpp
@@ -0,0 +1,407 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Basic test for spirv-diff
+constexpr char kSrc[] = R"(; SPIR-V
+; Version: 1.0
+; Generator: Google ANGLE Shader Compiler; 0
+; Bound: 27
+; Schema: 0
+OpCapability Shader
+  OpMemoryModel Logical GLSL450
+  OpEntryPoint Vertex %22 "main" %4 %14 %19
+  OpSource GLSL 450
+  OpName %4 "_ua_position"
+  OpName %14 "ANGLEXfbPosition"
+  OpName %17 "gl_PerVertex"
+  OpMemberName %17 0 "gl_Position"
+OpMemberName %17 1 "gl_PointSize"
+OpMemberName %17 2 "gl_ClipDistance"
+  OpMemberName %17 3 "gl_CullDistance"
+  OpName %19 ""
+  OpName %22 "main"
+  OpDecorate %4 Location 0
+  OpDecorate %14 Location 0
+  OpMemberDecorate %17 1 RelaxedPrecision
+  OpMemberDecorate %17 0 BuiltIn Position
+  OpMemberDecorate %17 1 BuiltIn PointSize
+OpMemberDecorate %17 2 BuiltIn ClipDistance
+OpMemberDecorate %17 3 BuiltIn CullDistance
+OpDecorate %17 Block
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%5 = OpTypeInt 32 0
+%6 = OpTypeInt 32 1
+%15 = OpConstant %5 8
+%16 = OpTypeArray %1 %15
+%17 = OpTypeStruct %2 %1 %16 %16
+%20 = OpTypeVoid
+%25 = OpConstant %6 0
+%3 = OpTypePointer Input %2
+%13 = OpTypePointer Output %2
+%18 = OpTypePointer Output %17
+%21 = OpTypeFunction %20
+%4 = OpVariable %3 Input
+%14 = OpVariable %13 Output
+%19 = OpVariable %18 Output
+%22 = OpFunction %20 None %21
+%23 = OpLabel
+%24 = OpLoad %2 %4
+%26 = OpAccessChain %13 %19 %25
+OpStore %26 %24
+OpReturn
+OpFunctionEnd)";
+constexpr char kDst[] = R"(; SPIR-V
+; Version: 1.0
+; Generator: Khronos Glslang Reference Front End; 10
+; Bound: 28
+; Schema: 0
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %4 "main" %13 %17 %27
+OpSource GLSL 450
+OpName %4 "main"
+OpName %11 "gl_PerVertex"
+OpMemberName %11 0 "gl_Position"
+OpMemberName %11 1 "gl_PointSize"
+OpMemberName %11 2 "gl_ClipDistance"
+OpMemberName %11 3 "gl_CullDistance"
+OpName %13 ""
+OpName %17 "_ua_position"
+OpName %27 "ANGLEXfbPosition"
+OpMemberDecorate %11 0 BuiltIn Position
+OpMemberDecorate %11 1 BuiltIn PointSize
+OpMemberDecorate %11 2 BuiltIn ClipDistance
+OpMemberDecorate %11 3 BuiltIn CullDistance
+OpDecorate %11 Block
+OpDecorate %17 Location 0
+OpDecorate %27 Location 0
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%6 = OpTypeFloat 32
+%7 = OpTypeVector %6 4
+%8 = OpTypeInt 32 0
+%9 = OpConstant %8 1
+%10 = OpTypeArray %6 %9
+%11 = OpTypeStruct %7 %6 %10 %10
+%12 = OpTypePointer Output %11
+%13 = OpVariable %12 Output
+%14 = OpTypeInt 32 1
+%15 = OpConstant %14 0
+%16 = OpTypePointer Input %7
+%17 = OpVariable %16 Input
+%19 = OpTypePointer Output %7
+%27 = OpVariable %19 Output
+%4 = OpFunction %2 None %3
+%5 = OpLabel
+%18 = OpLoad %7 %17
+%20 = OpAccessChain %19 %13 %15
+OpStore %20 %18
+OpReturn
+OpFunctionEnd
+)";
+
+TEST(DiffTest, Basic) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 27
++; Bound: 36
+ ; Schema: 0
+ OpCapability Shader
++%27 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+-OpEntryPoint Vertex %22 "main" %4 %14 %19
++OpEntryPoint Vertex %22 "main" %19 %4 %14
+ OpSource GLSL 450
+ OpName %4 "_ua_position"
+ OpName %14 "ANGLEXfbPosition"
+ OpName %17 "gl_PerVertex"
+ OpMemberName %17 0 "gl_Position"
+ OpMemberName %17 1 "gl_PointSize"
+ OpMemberName %17 2 "gl_ClipDistance"
+ OpMemberName %17 3 "gl_CullDistance"
+ OpName %19 ""
+ OpName %22 "main"
+ OpDecorate %4 Location 0
+ OpDecorate %14 Location 0
+-OpMemberDecorate %17 1 RelaxedPrecision
+ OpMemberDecorate %17 0 BuiltIn Position
+ OpMemberDecorate %17 1 BuiltIn PointSize
+ OpMemberDecorate %17 2 BuiltIn ClipDistance
+ OpMemberDecorate %17 3 BuiltIn CullDistance
+ OpDecorate %17 Block
+ %1 = OpTypeFloat 32
+ %2 = OpTypeVector %1 4
+ %5 = OpTypeInt 32 0
+ %6 = OpTypeInt 32 1
+-%15 = OpConstant %5 8
+-%16 = OpTypeArray %1 %15
+-%17 = OpTypeStruct %2 %1 %16 %16
++%17 = OpTypeStruct %2 %1 %29 %29
++%28 = OpConstant %5 1
++%29 = OpTypeArray %1 %28
+ %20 = OpTypeVoid
+ %25 = OpConstant %6 0
+ %3 = OpTypePointer Input %2
+ %13 = OpTypePointer Output %2
+ %18 = OpTypePointer Output %17
+ %21 = OpTypeFunction %20
+ %4 = OpVariable %3 Input
+ %14 = OpVariable %13 Output
+ %19 = OpVariable %18 Output
+ %22 = OpFunction %20 None %21
+ %23 = OpLabel
+ %24 = OpLoad %2 %4
+ %26 = OpAccessChain %13 %19 %25
+ OpStore %26 %24
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, BasicNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(; SPIR-V
+; Version: 1.0
+; Generator: Google ANGLE Shader Compiler; 0
+; Bound: 27
+; Schema: 0
+OpCapability Shader
+  OpMemoryModel Logical GLSL450
+  OpEntryPoint Vertex %22 "main" %4 %14 %19
+  OpSource GLSL 450
+  OpDecorate %4 Location 0
+  OpDecorate %14 Location 0
+  OpMemberDecorate %17 1 RelaxedPrecision
+  OpMemberDecorate %17 0 BuiltIn Position
+  OpMemberDecorate %17 1 BuiltIn PointSize
+OpMemberDecorate %17 2 BuiltIn ClipDistance
+OpMemberDecorate %17 3 BuiltIn CullDistance
+OpDecorate %17 Block
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%5 = OpTypeInt 32 0
+%6 = OpTypeInt 32 1
+%15 = OpConstant %5 8
+%16 = OpTypeArray %1 %15
+%17 = OpTypeStruct %2 %1 %16 %16
+%20 = OpTypeVoid
+%25 = OpConstant %6 0
+%3 = OpTypePointer Input %2
+%13 = OpTypePointer Output %2
+%18 = OpTypePointer Output %17
+%21 = OpTypeFunction %20
+%4 = OpVariable %3 Input
+%14 = OpVariable %13 Output
+%19 = OpVariable %18 Output
+%22 = OpFunction %20 None %21
+%23 = OpLabel
+%24 = OpLoad %2 %4
+%26 = OpAccessChain %13 %19 %25
+OpStore %26 %24
+OpReturn
+OpFunctionEnd
+)";
+  constexpr char kDstNoDebug[] = R"(; SPIR-V
+; Version: 1.0
+; Generator: Khronos Glslang Reference Front End; 10
+; Bound: 28
+; Schema: 0
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %4 "main" %13 %17 %27
+OpSource GLSL 450
+OpMemberDecorate %11 0 BuiltIn Position
+OpMemberDecorate %11 1 BuiltIn PointSize
+OpMemberDecorate %11 2 BuiltIn ClipDistance
+OpMemberDecorate %11 3 BuiltIn CullDistance
+OpDecorate %11 Block
+OpDecorate %17 Location 0
+OpDecorate %27 Location 0
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%6 = OpTypeFloat 32
+%7 = OpTypeVector %6 4
+%8 = OpTypeInt 32 0
+%9 = OpConstant %8 1
+%10 = OpTypeArray %6 %9
+%11 = OpTypeStruct %7 %6 %10 %10
+%12 = OpTypePointer Output %11
+%13 = OpVariable %12 Output
+%14 = OpTypeInt 32 1
+%15 = OpConstant %14 0
+%16 = OpTypePointer Input %7
+%17 = OpVariable %16 Input
+%19 = OpTypePointer Output %7
+%27 = OpVariable %19 Output
+%4 = OpFunction %2 None %3
+%5 = OpLabel
+%18 = OpLoad %7 %17
+%20 = OpAccessChain %19 %13 %15
+OpStore %20 %18
+OpReturn
+OpFunctionEnd
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 27
++; Bound: 36
+ ; Schema: 0
+ OpCapability Shader
++%27 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+-OpEntryPoint Vertex %22 "main" %4 %14 %19
++OpEntryPoint Vertex %22 "main" %19 %4 %14
+ OpSource GLSL 450
+ OpDecorate %4 Location 0
+ OpDecorate %14 Location 0
+-OpMemberDecorate %17 1 RelaxedPrecision
+ OpMemberDecorate %17 0 BuiltIn Position
+ OpMemberDecorate %17 1 BuiltIn PointSize
+ OpMemberDecorate %17 2 BuiltIn ClipDistance
+ OpMemberDecorate %17 3 BuiltIn CullDistance
+ OpDecorate %17 Block
+ %1 = OpTypeFloat 32
+ %2 = OpTypeVector %1 4
+ %5 = OpTypeInt 32 0
+ %6 = OpTypeInt 32 1
+-%15 = OpConstant %5 8
+-%16 = OpTypeArray %1 %15
+-%17 = OpTypeStruct %2 %1 %16 %16
++%17 = OpTypeStruct %2 %1 %29 %29
++%28 = OpConstant %5 1
++%29 = OpTypeArray %1 %28
+ %20 = OpTypeVoid
+ %25 = OpConstant %6 0
+ %3 = OpTypePointer Input %2
+ %13 = OpTypePointer Output %2
+ %18 = OpTypePointer Output %17
+ %21 = OpTypeFunction %20
+ %4 = OpVariable %3 Input
+ %14 = OpVariable %13 Output
+ %19 = OpVariable %18 Output
+ %22 = OpFunction %20 None %21
+ %23 = OpLabel
+ %24 = OpLoad %2 %4
+ %26 = OpAccessChain %13 %19 %25
+ OpStore %26 %24
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+TEST(DiffTest, BasicDumpIds) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 27
++; Bound: 36
+ ; Schema: 0
+ OpCapability Shader
++%27 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+-OpEntryPoint Vertex %22 "main" %4 %14 %19
++OpEntryPoint Vertex %22 "main" %19 %4 %14
+ OpSource GLSL 450
+ OpName %4 "_ua_position"
+ OpName %14 "ANGLEXfbPosition"
+ OpName %17 "gl_PerVertex"
+ OpMemberName %17 0 "gl_Position"
+ OpMemberName %17 1 "gl_PointSize"
+ OpMemberName %17 2 "gl_ClipDistance"
+ OpMemberName %17 3 "gl_CullDistance"
+ OpName %19 ""
+ OpName %22 "main"
+ OpDecorate %4 Location 0
+ OpDecorate %14 Location 0
+-OpMemberDecorate %17 1 RelaxedPrecision
+ OpMemberDecorate %17 0 BuiltIn Position
+ OpMemberDecorate %17 1 BuiltIn PointSize
+ OpMemberDecorate %17 2 BuiltIn ClipDistance
+ OpMemberDecorate %17 3 BuiltIn CullDistance
+ OpDecorate %17 Block
+ %1 = OpTypeFloat 32
+ %2 = OpTypeVector %1 4
+ %5 = OpTypeInt 32 0
+ %6 = OpTypeInt 32 1
+-%15 = OpConstant %5 8
+-%16 = OpTypeArray %1 %15
+-%17 = OpTypeStruct %2 %1 %16 %16
++%17 = OpTypeStruct %2 %1 %29 %29
++%28 = OpConstant %5 1
++%29 = OpTypeArray %1 %28
+ %20 = OpTypeVoid
+ %25 = OpConstant %6 0
+ %3 = OpTypePointer Input %2
+ %13 = OpTypePointer Output %2
+ %18 = OpTypePointer Output %17
+ %21 = OpTypeFunction %20
+ %4 = OpVariable %3 Input
+ %14 = OpVariable %13 Output
+ %19 = OpVariable %18 Output
+ %22 = OpFunction %20 None %21
+ %23 = OpLabel
+ %24 = OpLoad %2 %4
+ %26 = OpAccessChain %13 %19 %25
+ OpStore %26 %24
+ OpReturn
+ OpFunctionEnd
+ Src ->  Dst
+   1 ->    6 [TypeFloat]
+   2 ->    7 [TypeVector]
+   3 ->   16 [TypePointer]
+   4 ->   17 [Variable]
+   5 ->    8 [TypeInt]
+   6 ->   14 [TypeInt]
+  13 ->   19 [TypePointer]
+  14 ->   27 [Variable]
+  15 ->   34 [Constant]
+  16 ->   35 [TypeArray]
+  17 ->   11 [TypeStruct]
+  18 ->   12 [TypePointer]
+  19 ->   13 [Variable]
+  20 ->    2 [TypeVoid]
+  21 ->    3 [TypeFunction]
+  22 ->    4 [Function]
+  23 ->    5 [Label]
+  24 ->   18 [Load]
+  25 ->   15 [Constant]
+  26 ->   20 [AccessChain]
+)";
+  Options options;
+  options.dump_id_map = true;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/basic_dst.spvasm b/test/diff/diff_files/basic_dst.spvasm
new file mode 100644
index 0000000..79cb888
--- /dev/null
+++ b/test/diff/diff_files/basic_dst.spvasm
@@ -0,0 +1,49 @@
+; SPIR-V
+; Version: 1.0
+; Generator: Khronos Glslang Reference Front End; 10
+; Bound: 28
+; Schema: 0
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %4 "main" %13 %17 %27
+OpSource GLSL 450
+OpName %4 "main"
+OpName %11 "gl_PerVertex"
+OpMemberName %11 0 "gl_Position"
+OpMemberName %11 1 "gl_PointSize"
+OpMemberName %11 2 "gl_ClipDistance"
+OpMemberName %11 3 "gl_CullDistance"
+OpName %13 ""
+OpName %17 "_ua_position"
+OpName %27 "ANGLEXfbPosition"
+OpMemberDecorate %11 0 BuiltIn Position
+OpMemberDecorate %11 1 BuiltIn PointSize
+OpMemberDecorate %11 2 BuiltIn ClipDistance
+OpMemberDecorate %11 3 BuiltIn CullDistance
+OpDecorate %11 Block
+OpDecorate %17 Location 0
+OpDecorate %27 Location 0
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%6 = OpTypeFloat 32
+%7 = OpTypeVector %6 4
+%8 = OpTypeInt 32 0
+%9 = OpConstant %8 1
+%10 = OpTypeArray %6 %9
+%11 = OpTypeStruct %7 %6 %10 %10
+%12 = OpTypePointer Output %11
+%13 = OpVariable %12 Output
+%14 = OpTypeInt 32 1
+%15 = OpConstant %14 0
+%16 = OpTypePointer Input %7
+%17 = OpVariable %16 Input
+%19 = OpTypePointer Output %7
+%27 = OpVariable %19 Output
+%4 = OpFunction %2 None %3
+%5 = OpLabel
+%18 = OpLoad %7 %17
+%20 = OpAccessChain %19 %13 %15
+OpStore %20 %18
+OpReturn
+OpFunctionEnd
diff --git a/test/diff/diff_files/basic_src.spvasm b/test/diff/diff_files/basic_src.spvasm
new file mode 100644
index 0000000..c55ec7a
--- /dev/null
+++ b/test/diff/diff_files/basic_src.spvasm
@@ -0,0 +1,50 @@
+;; Basic test for spirv-diff
+; SPIR-V
+; Version: 1.0
+; Generator: Google ANGLE Shader Compiler; 0
+; Bound: 27
+; Schema: 0
+OpCapability Shader
+  OpMemoryModel Logical GLSL450
+  OpEntryPoint Vertex %22 "main" %4 %14 %19
+  OpSource GLSL 450
+  OpName %4 "_ua_position"
+  OpName %14 "ANGLEXfbPosition"
+  OpName %17 "gl_PerVertex"
+  OpMemberName %17 0 "gl_Position"
+OpMemberName %17 1 "gl_PointSize"
+OpMemberName %17 2 "gl_ClipDistance"
+  OpMemberName %17 3 "gl_CullDistance"
+  OpName %19 ""
+  OpName %22 "main"
+  OpDecorate %4 Location 0
+  OpDecorate %14 Location 0
+  OpMemberDecorate %17 1 RelaxedPrecision
+  OpMemberDecorate %17 0 BuiltIn Position
+  OpMemberDecorate %17 1 BuiltIn PointSize
+OpMemberDecorate %17 2 BuiltIn ClipDistance
+OpMemberDecorate %17 3 BuiltIn CullDistance
+OpDecorate %17 Block
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%5 = OpTypeInt 32 0
+%6 = OpTypeInt 32 1
+%15 = OpConstant %5 8
+%16 = OpTypeArray %1 %15
+%17 = OpTypeStruct %2 %1 %16 %16
+%20 = OpTypeVoid
+%25 = OpConstant %6 0
+%3 = OpTypePointer Input %2
+%13 = OpTypePointer Output %2
+%18 = OpTypePointer Output %17
+%21 = OpTypeFunction %20
+%4 = OpVariable %3 Input
+%14 = OpVariable %13 Output
+%19 = OpVariable %18 Output
+%22 = OpFunction %20 None %21
+%23 = OpLabel
+%24 = OpLoad %2 %4
+%26 = OpAccessChain %13 %19 %25
+OpStore %26 %24
+OpReturn
+OpFunctionEnd
diff --git a/test/diff/diff_files/constant_array_size_autogen.cpp b/test/diff/diff_files/constant_array_size_autogen.cpp
new file mode 100644
index 0000000..16975ff
--- /dev/null
+++ b/test/diff/diff_files/constant_array_size_autogen.cpp
@@ -0,0 +1,306 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Tests that identical integer constants are matched when used as array size,
+// regardless of int or uint.
+constexpr char kSrc[] = R"(; SPIR-V
+; Version: 1.0
+; Generator: Google ANGLE Shader Compiler; 0
+; Bound: 27
+; Schema: 0
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %22 "main" %4 %19
+OpSource GLSL 450
+OpName %4 "_ua_position"
+OpName %17 "gl_PerVertex"
+OpMemberName %17 0 "gl_Position"
+OpMemberName %17 1 "gl_PointSize"
+OpMemberName %17 2 "gl_ClipDistance"
+OpMemberName %17 3 "gl_CullDistance"
+OpName %19 ""
+OpName %22 "main"
+OpDecorate %4 Location 0
+OpMemberDecorate %17 1 RelaxedPrecision
+OpMemberDecorate %17 0 BuiltIn Position
+OpMemberDecorate %17 1 BuiltIn PointSize
+OpMemberDecorate %17 2 BuiltIn ClipDistance
+OpMemberDecorate %17 3 BuiltIn CullDistance
+OpDecorate %17 Block
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%5 = OpTypeInt 32 0
+%8 = OpTypeVector %5 4
+%15 = OpConstant %5 8
+%16 = OpTypeArray %1 %15
+%17 = OpTypeStruct %2 %1 %16 %16
+%20 = OpTypeVoid
+%25 = OpConstant %5 0
+%3 = OpTypePointer Input %2
+%13 = OpTypePointer Output %2
+%18 = OpTypePointer Output %17
+%21 = OpTypeFunction %20
+%4 = OpVariable %3 Input
+%19 = OpVariable %18 Output
+%22 = OpFunction %20 None %21
+%23 = OpLabel
+%24 = OpLoad %2 %4
+%26 = OpAccessChain %13 %19 %25
+OpStore %26 %24
+OpReturn
+OpFunctionEnd)";
+constexpr char kDst[] = R"(; SPIR-V
+; Version: 1.0
+; Generator: Google ANGLE Shader Compiler; 0
+; Bound: 27
+; Schema: 0
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %22 "main" %4 %19
+OpSource GLSL 450
+OpName %4 "_ua_position"
+OpName %17 "gl_PerVertex"
+OpMemberName %17 0 "gl_Position"
+OpMemberName %17 1 "gl_PointSize"
+OpMemberName %17 2 "gl_ClipDistance"
+OpMemberName %17 3 "gl_CullDistance"
+OpName %19 ""
+OpName %22 "main"
+OpDecorate %4 Location 0
+OpMemberDecorate %17 1 RelaxedPrecision
+OpMemberDecorate %17 0 BuiltIn Position
+OpMemberDecorate %17 1 BuiltIn PointSize
+OpMemberDecorate %17 2 BuiltIn ClipDistance
+OpMemberDecorate %17 3 BuiltIn CullDistance
+OpDecorate %17 Block
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%5 = OpTypeInt 32 0
+%6 = OpTypeInt 32 1
+%8 = OpTypeVector %5 4
+%15 = OpConstant %6 8
+%16 = OpTypeArray %1 %15
+%17 = OpTypeStruct %2 %1 %16 %16
+%20 = OpTypeVoid
+%25 = OpConstant %5 0
+%3 = OpTypePointer Input %2
+%13 = OpTypePointer Output %2
+%18 = OpTypePointer Output %17
+%21 = OpTypeFunction %20
+%4 = OpVariable %3 Input
+%19 = OpVariable %18 Output
+%22 = OpFunction %20 None %21
+%23 = OpLabel
+%24 = OpLoad %2 %4
+%26 = OpAccessChain %13 %19 %25
+OpStore %26 %24
+OpReturn
+OpFunctionEnd
+)";
+
+TEST(DiffTest, ConstantArraySize) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 27
++; Bound: 34
+ ; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %22 "main" %4 %19
+ OpSource GLSL 450
+ OpName %4 "_ua_position"
+ OpName %17 "gl_PerVertex"
+ OpMemberName %17 0 "gl_Position"
+ OpMemberName %17 1 "gl_PointSize"
+ OpMemberName %17 2 "gl_ClipDistance"
+ OpMemberName %17 3 "gl_CullDistance"
+ OpName %19 ""
+ OpName %22 "main"
+ OpDecorate %4 Location 0
+ OpMemberDecorate %17 1 RelaxedPrecision
+ OpMemberDecorate %17 0 BuiltIn Position
+ OpMemberDecorate %17 1 BuiltIn PointSize
+ OpMemberDecorate %17 2 BuiltIn ClipDistance
+ OpMemberDecorate %17 3 BuiltIn CullDistance
+ OpDecorate %17 Block
+ %1 = OpTypeFloat 32
+ %2 = OpTypeVector %1 4
+ %5 = OpTypeInt 32 0
++%27 = OpTypeInt 32 1
+ %8 = OpTypeVector %5 4
+-%15 = OpConstant %5 8
++%15 = OpConstant %27 8
+ %16 = OpTypeArray %1 %15
+ %17 = OpTypeStruct %2 %1 %16 %16
+ %20 = OpTypeVoid
+ %25 = OpConstant %5 0
+ %3 = OpTypePointer Input %2
+ %13 = OpTypePointer Output %2
+ %18 = OpTypePointer Output %17
+ %21 = OpTypeFunction %20
+ %4 = OpVariable %3 Input
+ %19 = OpVariable %18 Output
+ %22 = OpFunction %20 None %21
+ %23 = OpLabel
+ %24 = OpLoad %2 %4
+ %26 = OpAccessChain %13 %19 %25
+ OpStore %26 %24
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, ConstantArraySizeNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(; SPIR-V
+; Version: 1.0
+; Generator: Google ANGLE Shader Compiler; 0
+; Bound: 27
+; Schema: 0
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %22 "main" %4 %19
+OpSource GLSL 450
+OpDecorate %4 Location 0
+OpMemberDecorate %17 1 RelaxedPrecision
+OpMemberDecorate %17 0 BuiltIn Position
+OpMemberDecorate %17 1 BuiltIn PointSize
+OpMemberDecorate %17 2 BuiltIn ClipDistance
+OpMemberDecorate %17 3 BuiltIn CullDistance
+OpDecorate %17 Block
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%5 = OpTypeInt 32 0
+%8 = OpTypeVector %5 4
+%15 = OpConstant %5 8
+%16 = OpTypeArray %1 %15
+%17 = OpTypeStruct %2 %1 %16 %16
+%20 = OpTypeVoid
+%25 = OpConstant %5 0
+%3 = OpTypePointer Input %2
+%13 = OpTypePointer Output %2
+%18 = OpTypePointer Output %17
+%21 = OpTypeFunction %20
+%4 = OpVariable %3 Input
+%19 = OpVariable %18 Output
+%22 = OpFunction %20 None %21
+%23 = OpLabel
+%24 = OpLoad %2 %4
+%26 = OpAccessChain %13 %19 %25
+OpStore %26 %24
+OpReturn
+OpFunctionEnd
+)";
+  constexpr char kDstNoDebug[] = R"(; SPIR-V
+; Version: 1.0
+; Generator: Google ANGLE Shader Compiler; 0
+; Bound: 27
+; Schema: 0
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %22 "main" %4 %19
+OpSource GLSL 450
+OpDecorate %4 Location 0
+OpMemberDecorate %17 1 RelaxedPrecision
+OpMemberDecorate %17 0 BuiltIn Position
+OpMemberDecorate %17 1 BuiltIn PointSize
+OpMemberDecorate %17 2 BuiltIn ClipDistance
+OpMemberDecorate %17 3 BuiltIn CullDistance
+OpDecorate %17 Block
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%5 = OpTypeInt 32 0
+%6 = OpTypeInt 32 1
+%8 = OpTypeVector %5 4
+%15 = OpConstant %6 8
+%16 = OpTypeArray %1 %15
+%17 = OpTypeStruct %2 %1 %16 %16
+%20 = OpTypeVoid
+%25 = OpConstant %5 0
+%3 = OpTypePointer Input %2
+%13 = OpTypePointer Output %2
+%18 = OpTypePointer Output %17
+%21 = OpTypeFunction %20
+%4 = OpVariable %3 Input
+%19 = OpVariable %18 Output
+%22 = OpFunction %20 None %21
+%23 = OpLabel
+%24 = OpLoad %2 %4
+%26 = OpAccessChain %13 %19 %25
+OpStore %26 %24
+OpReturn
+OpFunctionEnd
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 27
++; Bound: 34
+ ; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %22 "main" %4 %19
+ OpSource GLSL 450
+ OpDecorate %4 Location 0
+ OpMemberDecorate %17 1 RelaxedPrecision
+ OpMemberDecorate %17 0 BuiltIn Position
+ OpMemberDecorate %17 1 BuiltIn PointSize
+ OpMemberDecorate %17 2 BuiltIn ClipDistance
+ OpMemberDecorate %17 3 BuiltIn CullDistance
+ OpDecorate %17 Block
+ %1 = OpTypeFloat 32
+ %2 = OpTypeVector %1 4
+ %5 = OpTypeInt 32 0
++%27 = OpTypeInt 32 1
+ %8 = OpTypeVector %5 4
+-%15 = OpConstant %5 8
++%15 = OpConstant %27 8
+ %16 = OpTypeArray %1 %15
+ %17 = OpTypeStruct %2 %1 %16 %16
+ %20 = OpTypeVoid
+ %25 = OpConstant %5 0
+ %3 = OpTypePointer Input %2
+ %13 = OpTypePointer Output %2
+ %18 = OpTypePointer Output %17
+ %21 = OpTypeFunction %20
+ %4 = OpVariable %3 Input
+ %19 = OpVariable %18 Output
+ %22 = OpFunction %20 None %21
+ %23 = OpLabel
+ %24 = OpLoad %2 %4
+ %26 = OpAccessChain %13 %19 %25
+ OpStore %26 %24
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/constant_array_size_dst.spvasm b/test/diff/diff_files/constant_array_size_dst.spvasm
new file mode 100644
index 0000000..a432b61
--- /dev/null
+++ b/test/diff/diff_files/constant_array_size_dst.spvasm
@@ -0,0 +1,47 @@
+; SPIR-V
+; Version: 1.0
+; Generator: Google ANGLE Shader Compiler; 0
+; Bound: 27
+; Schema: 0
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %22 "main" %4 %19
+OpSource GLSL 450
+OpName %4 "_ua_position"
+OpName %17 "gl_PerVertex"
+OpMemberName %17 0 "gl_Position"
+OpMemberName %17 1 "gl_PointSize"
+OpMemberName %17 2 "gl_ClipDistance"
+OpMemberName %17 3 "gl_CullDistance"
+OpName %19 ""
+OpName %22 "main"
+OpDecorate %4 Location 0
+OpMemberDecorate %17 1 RelaxedPrecision
+OpMemberDecorate %17 0 BuiltIn Position
+OpMemberDecorate %17 1 BuiltIn PointSize
+OpMemberDecorate %17 2 BuiltIn ClipDistance
+OpMemberDecorate %17 3 BuiltIn CullDistance
+OpDecorate %17 Block
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%5 = OpTypeInt 32 0
+%6 = OpTypeInt 32 1
+%8 = OpTypeVector %5 4
+%15 = OpConstant %6 8
+%16 = OpTypeArray %1 %15
+%17 = OpTypeStruct %2 %1 %16 %16
+%20 = OpTypeVoid
+%25 = OpConstant %5 0
+%3 = OpTypePointer Input %2
+%13 = OpTypePointer Output %2
+%18 = OpTypePointer Output %17
+%21 = OpTypeFunction %20
+%4 = OpVariable %3 Input
+%19 = OpVariable %18 Output
+%22 = OpFunction %20 None %21
+%23 = OpLabel
+%24 = OpLoad %2 %4
+%26 = OpAccessChain %13 %19 %25
+OpStore %26 %24
+OpReturn
+OpFunctionEnd
diff --git a/test/diff/diff_files/constant_array_size_src.spvasm b/test/diff/diff_files/constant_array_size_src.spvasm
new file mode 100644
index 0000000..4e1ba64
--- /dev/null
+++ b/test/diff/diff_files/constant_array_size_src.spvasm
@@ -0,0 +1,48 @@
+;; Tests that identical integer constants are matched when used as array size,
+;; regardless of int or uint.
+; SPIR-V
+; Version: 1.0
+; Generator: Google ANGLE Shader Compiler; 0
+; Bound: 27
+; Schema: 0
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %22 "main" %4 %19
+OpSource GLSL 450
+OpName %4 "_ua_position"
+OpName %17 "gl_PerVertex"
+OpMemberName %17 0 "gl_Position"
+OpMemberName %17 1 "gl_PointSize"
+OpMemberName %17 2 "gl_ClipDistance"
+OpMemberName %17 3 "gl_CullDistance"
+OpName %19 ""
+OpName %22 "main"
+OpDecorate %4 Location 0
+OpMemberDecorate %17 1 RelaxedPrecision
+OpMemberDecorate %17 0 BuiltIn Position
+OpMemberDecorate %17 1 BuiltIn PointSize
+OpMemberDecorate %17 2 BuiltIn ClipDistance
+OpMemberDecorate %17 3 BuiltIn CullDistance
+OpDecorate %17 Block
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%5 = OpTypeInt 32 0
+%8 = OpTypeVector %5 4
+%15 = OpConstant %5 8
+%16 = OpTypeArray %1 %15
+%17 = OpTypeStruct %2 %1 %16 %16
+%20 = OpTypeVoid
+%25 = OpConstant %5 0
+%3 = OpTypePointer Input %2
+%13 = OpTypePointer Output %2
+%18 = OpTypePointer Output %17
+%21 = OpTypeFunction %20
+%4 = OpVariable %3 Input
+%19 = OpVariable %18 Output
+%22 = OpFunction %20 None %21
+%23 = OpLabel
+%24 = OpLoad %2 %4
+%26 = OpAccessChain %13 %19 %25
+OpStore %26 %24
+OpReturn
+OpFunctionEnd
diff --git a/test/diff/diff_files/diff_test_files_autogen.cmake b/test/diff/diff_files/diff_test_files_autogen.cmake
new file mode 100644
index 0000000..6440d0b
--- /dev/null
+++ b/test/diff/diff_files/diff_test_files_autogen.cmake
@@ -0,0 +1,47 @@
+# GENERATED FILE - DO NOT EDIT.
+# Generated by generate_tests.py
+#
+# Copyright (c) 2022 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.
+
+list(APPEND DIFF_TEST_FILES
+"diff_files/OpExtInst_in_dst_only_autogen.cpp"
+"diff_files/OpExtInst_in_src_only_autogen.cpp"
+"diff_files/OpTypeForwardPointer_basic_autogen.cpp"
+"diff_files/OpTypeForwardPointer_intertwined_autogen.cpp"
+"diff_files/OpTypeForwardPointer_mismatching_class_autogen.cpp"
+"diff_files/OpTypeForwardPointer_mismatching_type_autogen.cpp"
+"diff_files/OpTypeForwardPointer_nested_autogen.cpp"
+"diff_files/OpTypeForwardPointer_onesided_debug_autogen.cpp"
+"diff_files/basic_autogen.cpp"
+"diff_files/constant_array_size_autogen.cpp"
+"diff_files/different_decorations_fragment_autogen.cpp"
+"diff_files/different_decorations_vertex_autogen.cpp"
+"diff_files/different_function_parameter_count_autogen.cpp"
+"diff_files/extra_if_block_autogen.cpp"
+"diff_files/index_signedness_autogen.cpp"
+"diff_files/int_vs_uint_constants_autogen.cpp"
+"diff_files/large_functions_large_diffs_autogen.cpp"
+"diff_files/large_functions_small_diffs_autogen.cpp"
+"diff_files/multiple_different_entry_points_autogen.cpp"
+"diff_files/multiple_same_entry_points_autogen.cpp"
+"diff_files/reordered_if_blocks_autogen.cpp"
+"diff_files/reordered_switch_blocks_autogen.cpp"
+"diff_files/small_functions_small_diffs_autogen.cpp"
+"diff_files/spec_constant_array_size_autogen.cpp"
+"diff_files/spec_constant_composite_autogen.cpp"
+"diff_files/spec_constant_op_autogen.cpp"
+"diff_files/spec_constant_specid_autogen.cpp"
+"diff_files/unrelated_shaders_autogen.cpp"
+)
diff --git a/test/diff/diff_files/different_decorations_fragment_autogen.cpp b/test/diff/diff_files/different_decorations_fragment_autogen.cpp
new file mode 100644
index 0000000..0d34654
--- /dev/null
+++ b/test/diff/diff_files/different_decorations_fragment_autogen.cpp
@@ -0,0 +1,1626 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Test where variable set/binding/location decorations are different between
+// src and dst fragment shaders.
+constexpr char kSrc[] = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %63 "main" %4 %22
+OpExecutionMode %63 OriginUpperLeft
+OpSource GLSL 450
+OpName %4 "_ue"
+OpName %8 "_uf"
+OpName %11 "_ug"
+OpName %12 "_uA"
+OpMemberName %12 0 "_ux"
+OpName %14 "_uc"
+OpName %15 "_uB"
+OpMemberName %15 0 "_ux"
+OpName %20 "_ud"
+OpName %22 "_ucol"
+OpName %26 "ANGLEDepthRangeParams"
+OpMemberName %26 0 "near"
+OpMemberName %26 1 "far"
+OpMemberName %26 2 "diff"
+OpMemberName %26 3 "reserved"
+OpName %27 "ANGLEUniformBlock"
+OpMemberName %27 0 "viewport"
+OpMemberName %27 1 "clipDistancesEnabled"
+OpMemberName %27 2 "xfbActiveUnpaused"
+OpMemberName %27 3 "xfbVerticesPerInstance"
+OpMemberName %27 4 "numSamples"
+OpMemberName %27 5 "xfbBufferOffsets"
+OpMemberName %27 6 "acbBufferOffsets"
+OpMemberName %27 7 "depthRange"
+OpName %29 "ANGLEUniforms"
+OpName %33 "_uc"
+OpName %32 "_uh"
+OpName %49 "_ux"
+OpName %50 "_uy"
+OpName %48 "_ui"
+OpName %63 "main"
+OpName %65 "param"
+OpName %68 "param"
+OpName %73 "param"
+OpDecorate %4 Location 0
+OpDecorate %8 RelaxedPrecision
+OpDecorate %8 DescriptorSet 0
+OpDecorate %8 Binding 0
+OpDecorate %11 DescriptorSet 0
+OpDecorate %11 Binding 1
+OpMemberDecorate %12 0 Offset 0
+OpMemberDecorate %12 0 RelaxedPrecision
+OpDecorate %12 Block
+OpDecorate %14 DescriptorSet 0
+OpDecorate %14 Binding 2
+OpMemberDecorate %15 0 Offset 0
+OpMemberDecorate %15 0 RelaxedPrecision
+OpDecorate %15 BufferBlock
+OpDecorate %20 DescriptorSet 0
+OpDecorate %20 Binding 3
+OpDecorate %22 RelaxedPrecision
+OpDecorate %22 Location 0
+OpMemberDecorate %26 0 Offset 0
+OpMemberDecorate %26 1 Offset 4
+OpMemberDecorate %26 2 Offset 8
+OpMemberDecorate %26 3 Offset 12
+OpMemberDecorate %27 0 Offset 0
+OpMemberDecorate %27 1 Offset 16
+OpMemberDecorate %27 2 Offset 20
+OpMemberDecorate %27 3 Offset 24
+OpMemberDecorate %27 4 Offset 28
+OpMemberDecorate %27 5 Offset 32
+OpMemberDecorate %27 6 Offset 48
+OpMemberDecorate %27 7 Offset 64
+OpMemberDecorate %27 2 RelaxedPrecision
+OpMemberDecorate %27 4 RelaxedPrecision
+OpDecorate %27 Block
+OpDecorate %29 DescriptorSet 0
+OpDecorate %29 Binding 4
+OpDecorate %32 RelaxedPrecision
+OpDecorate %33 RelaxedPrecision
+OpDecorate %36 RelaxedPrecision
+OpDecorate %37 RelaxedPrecision
+OpDecorate %38 RelaxedPrecision
+OpDecorate %39 RelaxedPrecision
+OpDecorate %41 RelaxedPrecision
+OpDecorate %42 RelaxedPrecision
+OpDecorate %43 RelaxedPrecision
+OpDecorate %48 RelaxedPrecision
+OpDecorate %49 RelaxedPrecision
+OpDecorate %50 RelaxedPrecision
+OpDecorate %52 RelaxedPrecision
+OpDecorate %53 RelaxedPrecision
+OpDecorate %54 RelaxedPrecision
+OpDecorate %55 RelaxedPrecision
+OpDecorate %56 RelaxedPrecision
+OpDecorate %57 RelaxedPrecision
+OpDecorate %58 RelaxedPrecision
+OpDecorate %59 RelaxedPrecision
+OpDecorate %60 RelaxedPrecision
+OpDecorate %67 RelaxedPrecision
+OpDecorate %68 RelaxedPrecision
+OpDecorate %72 RelaxedPrecision
+OpDecorate %73 RelaxedPrecision
+OpDecorate %75 RelaxedPrecision
+OpDecorate %76 RelaxedPrecision
+OpDecorate %77 RelaxedPrecision
+OpDecorate %80 RelaxedPrecision
+OpDecorate %81 RelaxedPrecision
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%5 = OpTypeImage %1 2D 0 0 0 1 Unknown
+%6 = OpTypeSampledImage %5
+%9 = OpTypeImage %1 2D 0 0 0 2 Rgba8
+%12 = OpTypeStruct %2
+%15 = OpTypeStruct %2
+%16 = OpTypeInt 32 0
+%17 = OpConstant %16 2
+%18 = OpTypeArray %15 %17
+%23 = OpTypeInt 32 1
+%24 = OpTypeVector %23 4
+%25 = OpTypeVector %16 4
+%26 = OpTypeStruct %1 %1 %1 %1
+%27 = OpTypeStruct %2 %16 %16 %23 %23 %24 %25 %26
+%35 = OpTypeVector %1 2
+%40 = OpTypeVector %23 2
+%61 = OpTypeVoid
+%69 = OpConstant %16 0
+%78 = OpConstant %16 1
+%3 = OpTypePointer Input %2
+%7 = OpTypePointer UniformConstant %6
+%10 = OpTypePointer UniformConstant %9
+%13 = OpTypePointer Uniform %12
+%19 = OpTypePointer Uniform %18
+%21 = OpTypePointer Output %2
+%28 = OpTypePointer Uniform %27
+%30 = OpTypePointer Function %2
+%70 = OpTypePointer Uniform %2
+%31 = OpTypeFunction %2 %30
+%47 = OpTypeFunction %2 %30 %30
+%62 = OpTypeFunction %61
+%4 = OpVariable %3 Input
+%8 = OpVariable %7 UniformConstant
+%11 = OpVariable %10 UniformConstant
+%14 = OpVariable %13 Uniform
+%20 = OpVariable %19 Uniform
+%22 = OpVariable %21 Output
+%29 = OpVariable %28 Uniform
+%32 = OpFunction %2 None %31
+%33 = OpFunctionParameter %30
+%34 = OpLabel
+%36 = OpLoad %6 %8
+%37 = OpLoad %2 %33
+%38 = OpVectorShuffle %35 %37 %37 0 1
+%39 = OpImageSampleImplicitLod %2 %36 %38
+%41 = OpLoad %2 %33
+%42 = OpVectorShuffle %35 %41 %41 2 3
+%43 = OpConvertFToS %40 %42
+%44 = OpLoad %9 %11
+%45 = OpImageRead %2 %44 %43
+%46 = OpFAdd %2 %39 %45
+OpReturnValue %46
+OpFunctionEnd
+%48 = OpFunction %2 None %47
+%49 = OpFunctionParameter %30
+%50 = OpFunctionParameter %30
+%51 = OpLabel
+%52 = OpLoad %2 %49
+%53 = OpVectorShuffle %35 %52 %52 0 1
+%54 = OpLoad %2 %50
+%55 = OpVectorShuffle %35 %54 %54 2 3
+%56 = OpCompositeExtract %1 %53 0
+%57 = OpCompositeExtract %1 %53 1
+%58 = OpCompositeExtract %1 %55 0
+%59 = OpCompositeExtract %1 %55 1
+%60 = OpCompositeConstruct %2 %56 %57 %58 %59
+OpReturnValue %60
+OpFunctionEnd
+%63 = OpFunction %61 None %62
+%64 = OpLabel
+%65 = OpVariable %30 Function
+%68 = OpVariable %30 Function
+%73 = OpVariable %30 Function
+%66 = OpLoad %2 %4
+OpStore %65 %66
+%67 = OpFunctionCall %2 %32 %65
+%71 = OpAccessChain %70 %14 %69
+%72 = OpLoad %2 %71
+OpStore %68 %72
+%74 = OpAccessChain %70 %20 %69 %69
+%75 = OpLoad %2 %74
+OpStore %73 %75
+%76 = OpFunctionCall %2 %48 %68 %73
+%77 = OpFAdd %2 %67 %76
+%79 = OpAccessChain %70 %20 %78 %69
+%80 = OpLoad %2 %79
+%81 = OpFAdd %2 %77 %80
+OpStore %22 %81
+OpReturn
+OpFunctionEnd
+)";
+constexpr char kDst[] = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %63 "main" %4 %22
+OpExecutionMode %63 OriginUpperLeft
+OpSource GLSL 450
+OpName %4 "_ue"
+OpName %8 "_uf"
+OpName %11 "_ug"
+OpName %12 "_uA"
+OpMemberName %12 0 "_ux"
+OpName %14 "_uc"
+OpName %15 "_uB"
+OpMemberName %15 0 "_ux"
+OpName %20 "_ud"
+OpName %22 "_ucol"
+OpName %26 "ANGLEDepthRangeParams"
+OpMemberName %26 0 "near"
+OpMemberName %26 1 "far"
+OpMemberName %26 2 "diff"
+OpMemberName %26 3 "reserved"
+OpName %27 "ANGLEUniformBlock"
+OpMemberName %27 0 "viewport"
+OpMemberName %27 1 "clipDistancesEnabled"
+OpMemberName %27 2 "xfbActiveUnpaused"
+OpMemberName %27 3 "xfbVerticesPerInstance"
+OpMemberName %27 4 "numSamples"
+OpMemberName %27 5 "xfbBufferOffsets"
+OpMemberName %27 6 "acbBufferOffsets"
+OpMemberName %27 7 "depthRange"
+OpName %29 "ANGLEUniforms"
+OpName %33 "_uc"
+OpName %32 "_uh"
+OpName %49 "_ux"
+OpName %50 "_uy"
+OpName %48 "_ui"
+OpName %63 "main"
+OpName %65 "param"
+OpName %68 "param"
+OpName %73 "param"
+OpDecorate %4 Location 1
+OpDecorate %8 RelaxedPrecision
+OpDecorate %8 DescriptorSet 2
+OpDecorate %8 Binding 0
+OpDecorate %11 DescriptorSet 3
+OpDecorate %11 Binding 0
+OpMemberDecorate %12 0 Offset 0
+OpMemberDecorate %12 0 RelaxedPrecision
+OpDecorate %12 Block
+OpDecorate %14 DescriptorSet 3
+OpDecorate %14 Binding 1
+OpMemberDecorate %15 0 Offset 0
+OpMemberDecorate %15 0 RelaxedPrecision
+OpDecorate %15 BufferBlock
+OpDecorate %20 DescriptorSet 3
+OpDecorate %20 Binding 2
+OpDecorate %22 RelaxedPrecision
+OpDecorate %22 Location 1
+OpMemberDecorate %26 0 Offset 0
+OpMemberDecorate %26 1 Offset 4
+OpMemberDecorate %26 2 Offset 8
+OpMemberDecorate %26 3 Offset 12
+OpMemberDecorate %27 0 Offset 0
+OpMemberDecorate %27 1 Offset 16
+OpMemberDecorate %27 2 Offset 20
+OpMemberDecorate %27 3 Offset 24
+OpMemberDecorate %27 4 Offset 28
+OpMemberDecorate %27 5 Offset 32
+OpMemberDecorate %27 6 Offset 48
+OpMemberDecorate %27 7 Offset 64
+OpMemberDecorate %27 2 RelaxedPrecision
+OpMemberDecorate %27 4 RelaxedPrecision
+OpDecorate %27 Block
+OpDecorate %29 DescriptorSet 0
+OpDecorate %29 Binding 0
+OpDecorate %32 RelaxedPrecision
+OpDecorate %33 RelaxedPrecision
+OpDecorate %36 RelaxedPrecision
+OpDecorate %37 RelaxedPrecision
+OpDecorate %38 RelaxedPrecision
+OpDecorate %39 RelaxedPrecision
+OpDecorate %41 RelaxedPrecision
+OpDecorate %42 RelaxedPrecision
+OpDecorate %43 RelaxedPrecision
+OpDecorate %48 RelaxedPrecision
+OpDecorate %49 RelaxedPrecision
+OpDecorate %50 RelaxedPrecision
+OpDecorate %52 RelaxedPrecision
+OpDecorate %53 RelaxedPrecision
+OpDecorate %54 RelaxedPrecision
+OpDecorate %55 RelaxedPrecision
+OpDecorate %56 RelaxedPrecision
+OpDecorate %57 RelaxedPrecision
+OpDecorate %58 RelaxedPrecision
+OpDecorate %59 RelaxedPrecision
+OpDecorate %60 RelaxedPrecision
+OpDecorate %67 RelaxedPrecision
+OpDecorate %68 RelaxedPrecision
+OpDecorate %72 RelaxedPrecision
+OpDecorate %73 RelaxedPrecision
+OpDecorate %75 RelaxedPrecision
+OpDecorate %76 RelaxedPrecision
+OpDecorate %77 RelaxedPrecision
+OpDecorate %80 RelaxedPrecision
+OpDecorate %81 RelaxedPrecision
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%5 = OpTypeImage %1 2D 0 0 0 1 Unknown
+%6 = OpTypeSampledImage %5
+%9 = OpTypeImage %1 2D 0 0 0 2 Rgba8
+%12 = OpTypeStruct %2
+%15 = OpTypeStruct %2
+%16 = OpTypeInt 32 0
+%17 = OpConstant %16 2
+%18 = OpTypeArray %15 %17
+%23 = OpTypeInt 32 1
+%24 = OpTypeVector %23 4
+%25 = OpTypeVector %16 4
+%26 = OpTypeStruct %1 %1 %1 %1
+%27 = OpTypeStruct %2 %16 %16 %23 %23 %24 %25 %26
+%35 = OpTypeVector %1 2
+%40 = OpTypeVector %23 2
+%61 = OpTypeVoid
+%69 = OpConstant %16 0
+%78 = OpConstant %16 1
+%82 = OpTypePointer Private %2
+%3 = OpTypePointer Input %2
+%7 = OpTypePointer UniformConstant %6
+%10 = OpTypePointer UniformConstant %9
+%13 = OpTypePointer Uniform %12
+%19 = OpTypePointer Uniform %18
+%83 = OpTypePointer Private %2
+%21 = OpTypePointer Output %2
+%28 = OpTypePointer Uniform %27
+%30 = OpTypePointer Function %2
+%70 = OpTypePointer Uniform %2
+%31 = OpTypeFunction %2 %30
+%47 = OpTypeFunction %2 %30 %30
+%62 = OpTypeFunction %61
+%4 = OpVariable %3 Input
+%8 = OpVariable %7 UniformConstant
+%11 = OpVariable %10 UniformConstant
+%14 = OpVariable %13 Uniform
+%20 = OpVariable %19 Uniform
+%22 = OpVariable %21 Output
+%29 = OpVariable %28 Uniform
+%84 = OpConstant %23 0
+%85 = OpConstant %1 0.5
+%32 = OpFunction %2 None %31
+%33 = OpFunctionParameter %30
+%34 = OpLabel
+%36 = OpLoad %6 %8
+%37 = OpLoad %2 %33
+%38 = OpVectorShuffle %35 %37 %37 0 1
+%39 = OpImageSampleImplicitLod %2 %36 %38
+%41 = OpLoad %2 %33
+%42 = OpVectorShuffle %35 %41 %41 2 3
+%43 = OpConvertFToS %40 %42
+%44 = OpLoad %9 %11
+%45 = OpImageRead %2 %44 %43
+%46 = OpFAdd %2 %39 %45
+OpReturnValue %46
+OpFunctionEnd
+%48 = OpFunction %2 None %47
+%49 = OpFunctionParameter %30
+%50 = OpFunctionParameter %30
+%51 = OpLabel
+%52 = OpLoad %2 %49
+%53 = OpVectorShuffle %35 %52 %52 0 1
+%54 = OpLoad %2 %50
+%55 = OpVectorShuffle %35 %54 %54 2 3
+%56 = OpCompositeExtract %1 %53 0
+%57 = OpCompositeExtract %1 %53 1
+%58 = OpCompositeExtract %1 %55 0
+%59 = OpCompositeExtract %1 %55 1
+%60 = OpCompositeConstruct %2 %56 %57 %58 %59
+OpReturnValue %60
+OpFunctionEnd
+%63 = OpFunction %61 None %62
+%64 = OpLabel
+%65 = OpVariable %30 Function
+%68 = OpVariable %30 Function
+%73 = OpVariable %30 Function
+%66 = OpLoad %2 %4
+OpStore %65 %66
+%67 = OpFunctionCall %2 %32 %65
+%71 = OpAccessChain %70 %14 %69
+%72 = OpLoad %2 %71
+OpStore %68 %72
+%74 = OpAccessChain %70 %20 %69 %69
+%75 = OpLoad %2 %74
+OpStore %73 %75
+%76 = OpFunctionCall %2 %48 %68 %73
+%77 = OpFAdd %2 %67 %76
+%79 = OpAccessChain %70 %20 %78 %69
+%80 = OpLoad %2 %79
+%81 = OpFAdd %2 %77 %80
+OpStore %22 %81
+OpReturn
+OpFunctionEnd
+)";
+
+TEST(DiffTest, DifferentDecorationsFragment) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 82
++; Bound: 86
+ ; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %63 "main" %4 %22
+ OpExecutionMode %63 OriginUpperLeft
+ OpSource GLSL 450
+ OpName %4 "_ue"
+ OpName %8 "_uf"
+ OpName %11 "_ug"
+ OpName %12 "_uA"
+ OpMemberName %12 0 "_ux"
+ OpName %14 "_uc"
+ OpName %15 "_uB"
+ OpMemberName %15 0 "_ux"
+ OpName %20 "_ud"
+ OpName %22 "_ucol"
+ OpName %26 "ANGLEDepthRangeParams"
+ OpMemberName %26 0 "near"
+ OpMemberName %26 1 "far"
+ OpMemberName %26 2 "diff"
+ OpMemberName %26 3 "reserved"
+ OpName %27 "ANGLEUniformBlock"
+ OpMemberName %27 0 "viewport"
+ OpMemberName %27 1 "clipDistancesEnabled"
+ OpMemberName %27 2 "xfbActiveUnpaused"
+ OpMemberName %27 3 "xfbVerticesPerInstance"
+ OpMemberName %27 4 "numSamples"
+ OpMemberName %27 5 "xfbBufferOffsets"
+ OpMemberName %27 6 "acbBufferOffsets"
+ OpMemberName %27 7 "depthRange"
+ OpName %29 "ANGLEUniforms"
+ OpName %33 "_uc"
+ OpName %32 "_uh"
+ OpName %49 "_ux"
+ OpName %50 "_uy"
+ OpName %48 "_ui"
+ OpName %63 "main"
+ OpName %65 "param"
+ OpName %68 "param"
+ OpName %73 "param"
+-OpDecorate %4 Location 0
++OpDecorate %4 Location 1
+ OpDecorate %8 RelaxedPrecision
+-OpDecorate %8 DescriptorSet 0
++OpDecorate %8 DescriptorSet 2
+ OpDecorate %8 Binding 0
+-OpDecorate %11 DescriptorSet 0
++OpDecorate %11 DescriptorSet 3
+-OpDecorate %11 Binding 1
++OpDecorate %11 Binding 0
+ OpMemberDecorate %12 0 Offset 0
+ OpMemberDecorate %12 0 RelaxedPrecision
+ OpDecorate %12 Block
+-OpDecorate %14 DescriptorSet 0
++OpDecorate %14 DescriptorSet 3
+-OpDecorate %14 Binding 2
++OpDecorate %14 Binding 1
+ OpMemberDecorate %15 0 Offset 0
+ OpMemberDecorate %15 0 RelaxedPrecision
+ OpDecorate %15 BufferBlock
+-OpDecorate %20 DescriptorSet 0
++OpDecorate %20 DescriptorSet 3
+-OpDecorate %20 Binding 3
++OpDecorate %20 Binding 2
+ OpDecorate %22 RelaxedPrecision
+-OpDecorate %22 Location 0
++OpDecorate %22 Location 1
+ OpMemberDecorate %26 0 Offset 0
+ OpMemberDecorate %26 1 Offset 4
+ OpMemberDecorate %26 2 Offset 8
+ OpMemberDecorate %26 3 Offset 12
+ OpMemberDecorate %27 0 Offset 0
+ OpMemberDecorate %27 1 Offset 16
+ OpMemberDecorate %27 2 Offset 20
+ OpMemberDecorate %27 3 Offset 24
+ OpMemberDecorate %27 4 Offset 28
+ OpMemberDecorate %27 5 Offset 32
+ OpMemberDecorate %27 6 Offset 48
+ OpMemberDecorate %27 7 Offset 64
+ OpMemberDecorate %27 2 RelaxedPrecision
+ OpMemberDecorate %27 4 RelaxedPrecision
+ OpDecorate %27 Block
+ OpDecorate %29 DescriptorSet 0
+-OpDecorate %29 Binding 4
++OpDecorate %29 Binding 0
+ OpDecorate %32 RelaxedPrecision
+ OpDecorate %33 RelaxedPrecision
+ OpDecorate %36 RelaxedPrecision
+ OpDecorate %37 RelaxedPrecision
+ OpDecorate %38 RelaxedPrecision
+ OpDecorate %39 RelaxedPrecision
+ OpDecorate %41 RelaxedPrecision
+ OpDecorate %42 RelaxedPrecision
+ OpDecorate %43 RelaxedPrecision
+ OpDecorate %48 RelaxedPrecision
+ OpDecorate %49 RelaxedPrecision
+ OpDecorate %50 RelaxedPrecision
+ OpDecorate %52 RelaxedPrecision
+ OpDecorate %53 RelaxedPrecision
+ OpDecorate %54 RelaxedPrecision
+ OpDecorate %55 RelaxedPrecision
+ OpDecorate %56 RelaxedPrecision
+ OpDecorate %57 RelaxedPrecision
+ OpDecorate %58 RelaxedPrecision
+ OpDecorate %59 RelaxedPrecision
+ OpDecorate %60 RelaxedPrecision
+ OpDecorate %67 RelaxedPrecision
+ OpDecorate %68 RelaxedPrecision
+ OpDecorate %72 RelaxedPrecision
+ OpDecorate %73 RelaxedPrecision
+ OpDecorate %75 RelaxedPrecision
+ OpDecorate %76 RelaxedPrecision
+ OpDecorate %77 RelaxedPrecision
+ OpDecorate %80 RelaxedPrecision
+ OpDecorate %81 RelaxedPrecision
+ %1 = OpTypeFloat 32
+ %2 = OpTypeVector %1 4
+ %5 = OpTypeImage %1 2D 0 0 0 1 Unknown
+ %6 = OpTypeSampledImage %5
+ %9 = OpTypeImage %1 2D 0 0 0 2 Rgba8
+ %12 = OpTypeStruct %2
+ %15 = OpTypeStruct %2
+ %16 = OpTypeInt 32 0
+ %17 = OpConstant %16 2
+ %18 = OpTypeArray %15 %17
+ %23 = OpTypeInt 32 1
+ %24 = OpTypeVector %23 4
+ %25 = OpTypeVector %16 4
+ %26 = OpTypeStruct %1 %1 %1 %1
+ %27 = OpTypeStruct %2 %16 %16 %23 %23 %24 %25 %26
+ %35 = OpTypeVector %1 2
+ %40 = OpTypeVector %23 2
+ %61 = OpTypeVoid
+ %69 = OpConstant %16 0
+ %78 = OpConstant %16 1
++%82 = OpTypePointer Private %2
+ %3 = OpTypePointer Input %2
+ %7 = OpTypePointer UniformConstant %6
+ %10 = OpTypePointer UniformConstant %9
+ %13 = OpTypePointer Uniform %12
+ %19 = OpTypePointer Uniform %18
++%83 = OpTypePointer Private %2
+ %21 = OpTypePointer Output %2
+ %28 = OpTypePointer Uniform %27
+ %30 = OpTypePointer Function %2
+ %70 = OpTypePointer Uniform %2
+ %31 = OpTypeFunction %2 %30
+ %47 = OpTypeFunction %2 %30 %30
+ %62 = OpTypeFunction %61
+ %4 = OpVariable %3 Input
+ %8 = OpVariable %7 UniformConstant
+ %11 = OpVariable %10 UniformConstant
+ %14 = OpVariable %13 Uniform
+ %20 = OpVariable %19 Uniform
+ %22 = OpVariable %21 Output
+ %29 = OpVariable %28 Uniform
++%84 = OpConstant %23 0
++%85 = OpConstant %1 0.5
+ %32 = OpFunction %2 None %31
+ %33 = OpFunctionParameter %30
+ %34 = OpLabel
+ %36 = OpLoad %6 %8
+ %37 = OpLoad %2 %33
+ %38 = OpVectorShuffle %35 %37 %37 0 1
+ %39 = OpImageSampleImplicitLod %2 %36 %38
+ %41 = OpLoad %2 %33
+ %42 = OpVectorShuffle %35 %41 %41 2 3
+ %43 = OpConvertFToS %40 %42
+ %44 = OpLoad %9 %11
+ %45 = OpImageRead %2 %44 %43
+ %46 = OpFAdd %2 %39 %45
+ OpReturnValue %46
+ OpFunctionEnd
+ %48 = OpFunction %2 None %47
+ %49 = OpFunctionParameter %30
+ %50 = OpFunctionParameter %30
+ %51 = OpLabel
+ %52 = OpLoad %2 %49
+ %53 = OpVectorShuffle %35 %52 %52 0 1
+ %54 = OpLoad %2 %50
+ %55 = OpVectorShuffle %35 %54 %54 2 3
+ %56 = OpCompositeExtract %1 %53 0
+ %57 = OpCompositeExtract %1 %53 1
+ %58 = OpCompositeExtract %1 %55 0
+ %59 = OpCompositeExtract %1 %55 1
+ %60 = OpCompositeConstruct %2 %56 %57 %58 %59
+ OpReturnValue %60
+ OpFunctionEnd
+ %63 = OpFunction %61 None %62
+ %64 = OpLabel
+ %65 = OpVariable %30 Function
+ %68 = OpVariable %30 Function
+ %73 = OpVariable %30 Function
+ %66 = OpLoad %2 %4
+ OpStore %65 %66
+ %67 = OpFunctionCall %2 %32 %65
+ %71 = OpAccessChain %70 %14 %69
+ %72 = OpLoad %2 %71
+ OpStore %68 %72
+ %74 = OpAccessChain %70 %20 %69 %69
+ %75 = OpLoad %2 %74
+ OpStore %73 %75
+ %76 = OpFunctionCall %2 %48 %68 %73
+ %77 = OpFAdd %2 %67 %76
+ %79 = OpAccessChain %70 %20 %78 %69
+ %80 = OpLoad %2 %79
+ %81 = OpFAdd %2 %77 %80
+ OpStore %22 %81
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, DifferentDecorationsFragmentNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %63 "main" %4 %22
+OpExecutionMode %63 OriginUpperLeft
+OpSource GLSL 450
+OpDecorate %4 Location 0
+OpDecorate %8 RelaxedPrecision
+OpDecorate %8 DescriptorSet 0
+OpDecorate %8 Binding 0
+OpDecorate %11 DescriptorSet 0
+OpDecorate %11 Binding 1
+OpMemberDecorate %12 0 Offset 0
+OpMemberDecorate %12 0 RelaxedPrecision
+OpDecorate %12 Block
+OpDecorate %14 DescriptorSet 0
+OpDecorate %14 Binding 2
+OpMemberDecorate %15 0 Offset 0
+OpMemberDecorate %15 0 RelaxedPrecision
+OpDecorate %15 BufferBlock
+OpDecorate %20 DescriptorSet 0
+OpDecorate %20 Binding 3
+OpDecorate %22 RelaxedPrecision
+OpDecorate %22 Location 0
+OpMemberDecorate %26 0 Offset 0
+OpMemberDecorate %26 1 Offset 4
+OpMemberDecorate %26 2 Offset 8
+OpMemberDecorate %26 3 Offset 12
+OpMemberDecorate %27 0 Offset 0
+OpMemberDecorate %27 1 Offset 16
+OpMemberDecorate %27 2 Offset 20
+OpMemberDecorate %27 3 Offset 24
+OpMemberDecorate %27 4 Offset 28
+OpMemberDecorate %27 5 Offset 32
+OpMemberDecorate %27 6 Offset 48
+OpMemberDecorate %27 7 Offset 64
+OpMemberDecorate %27 2 RelaxedPrecision
+OpMemberDecorate %27 4 RelaxedPrecision
+OpDecorate %27 Block
+OpDecorate %29 DescriptorSet 0
+OpDecorate %29 Binding 4
+OpDecorate %32 RelaxedPrecision
+OpDecorate %33 RelaxedPrecision
+OpDecorate %36 RelaxedPrecision
+OpDecorate %37 RelaxedPrecision
+OpDecorate %38 RelaxedPrecision
+OpDecorate %39 RelaxedPrecision
+OpDecorate %41 RelaxedPrecision
+OpDecorate %42 RelaxedPrecision
+OpDecorate %43 RelaxedPrecision
+OpDecorate %48 RelaxedPrecision
+OpDecorate %49 RelaxedPrecision
+OpDecorate %50 RelaxedPrecision
+OpDecorate %52 RelaxedPrecision
+OpDecorate %53 RelaxedPrecision
+OpDecorate %54 RelaxedPrecision
+OpDecorate %55 RelaxedPrecision
+OpDecorate %56 RelaxedPrecision
+OpDecorate %57 RelaxedPrecision
+OpDecorate %58 RelaxedPrecision
+OpDecorate %59 RelaxedPrecision
+OpDecorate %60 RelaxedPrecision
+OpDecorate %67 RelaxedPrecision
+OpDecorate %68 RelaxedPrecision
+OpDecorate %72 RelaxedPrecision
+OpDecorate %73 RelaxedPrecision
+OpDecorate %75 RelaxedPrecision
+OpDecorate %76 RelaxedPrecision
+OpDecorate %77 RelaxedPrecision
+OpDecorate %80 RelaxedPrecision
+OpDecorate %81 RelaxedPrecision
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%5 = OpTypeImage %1 2D 0 0 0 1 Unknown
+%6 = OpTypeSampledImage %5
+%9 = OpTypeImage %1 2D 0 0 0 2 Rgba8
+%12 = OpTypeStruct %2
+%15 = OpTypeStruct %2
+%16 = OpTypeInt 32 0
+%17 = OpConstant %16 2
+%18 = OpTypeArray %15 %17
+%23 = OpTypeInt 32 1
+%24 = OpTypeVector %23 4
+%25 = OpTypeVector %16 4
+%26 = OpTypeStruct %1 %1 %1 %1
+%27 = OpTypeStruct %2 %16 %16 %23 %23 %24 %25 %26
+%35 = OpTypeVector %1 2
+%40 = OpTypeVector %23 2
+%61 = OpTypeVoid
+%69 = OpConstant %16 0
+%78 = OpConstant %16 1
+%3 = OpTypePointer Input %2
+%7 = OpTypePointer UniformConstant %6
+%10 = OpTypePointer UniformConstant %9
+%13 = OpTypePointer Uniform %12
+%19 = OpTypePointer Uniform %18
+%21 = OpTypePointer Output %2
+%28 = OpTypePointer Uniform %27
+%30 = OpTypePointer Function %2
+%70 = OpTypePointer Uniform %2
+%31 = OpTypeFunction %2 %30
+%47 = OpTypeFunction %2 %30 %30
+%62 = OpTypeFunction %61
+%4 = OpVariable %3 Input
+%8 = OpVariable %7 UniformConstant
+%11 = OpVariable %10 UniformConstant
+%14 = OpVariable %13 Uniform
+%20 = OpVariable %19 Uniform
+%22 = OpVariable %21 Output
+%29 = OpVariable %28 Uniform
+%32 = OpFunction %2 None %31
+%33 = OpFunctionParameter %30
+%34 = OpLabel
+%36 = OpLoad %6 %8
+%37 = OpLoad %2 %33
+%38 = OpVectorShuffle %35 %37 %37 0 1
+%39 = OpImageSampleImplicitLod %2 %36 %38
+%41 = OpLoad %2 %33
+%42 = OpVectorShuffle %35 %41 %41 2 3
+%43 = OpConvertFToS %40 %42
+%44 = OpLoad %9 %11
+%45 = OpImageRead %2 %44 %43
+%46 = OpFAdd %2 %39 %45
+OpReturnValue %46
+OpFunctionEnd
+%48 = OpFunction %2 None %47
+%49 = OpFunctionParameter %30
+%50 = OpFunctionParameter %30
+%51 = OpLabel
+%52 = OpLoad %2 %49
+%53 = OpVectorShuffle %35 %52 %52 0 1
+%54 = OpLoad %2 %50
+%55 = OpVectorShuffle %35 %54 %54 2 3
+%56 = OpCompositeExtract %1 %53 0
+%57 = OpCompositeExtract %1 %53 1
+%58 = OpCompositeExtract %1 %55 0
+%59 = OpCompositeExtract %1 %55 1
+%60 = OpCompositeConstruct %2 %56 %57 %58 %59
+OpReturnValue %60
+OpFunctionEnd
+%63 = OpFunction %61 None %62
+%64 = OpLabel
+%65 = OpVariable %30 Function
+%68 = OpVariable %30 Function
+%73 = OpVariable %30 Function
+%66 = OpLoad %2 %4
+OpStore %65 %66
+%67 = OpFunctionCall %2 %32 %65
+%71 = OpAccessChain %70 %14 %69
+%72 = OpLoad %2 %71
+OpStore %68 %72
+%74 = OpAccessChain %70 %20 %69 %69
+%75 = OpLoad %2 %74
+OpStore %73 %75
+%76 = OpFunctionCall %2 %48 %68 %73
+%77 = OpFAdd %2 %67 %76
+%79 = OpAccessChain %70 %20 %78 %69
+%80 = OpLoad %2 %79
+%81 = OpFAdd %2 %77 %80
+OpStore %22 %81
+OpReturn
+OpFunctionEnd
+
+)";
+  constexpr char kDstNoDebug[] = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %63 "main" %4 %22
+OpExecutionMode %63 OriginUpperLeft
+OpSource GLSL 450
+OpDecorate %4 Location 1
+OpDecorate %8 RelaxedPrecision
+OpDecorate %8 DescriptorSet 2
+OpDecorate %8 Binding 0
+OpDecorate %11 DescriptorSet 3
+OpDecorate %11 Binding 0
+OpMemberDecorate %12 0 Offset 0
+OpMemberDecorate %12 0 RelaxedPrecision
+OpDecorate %12 Block
+OpDecorate %14 DescriptorSet 3
+OpDecorate %14 Binding 1
+OpMemberDecorate %15 0 Offset 0
+OpMemberDecorate %15 0 RelaxedPrecision
+OpDecorate %15 BufferBlock
+OpDecorate %20 DescriptorSet 3
+OpDecorate %20 Binding 2
+OpDecorate %22 RelaxedPrecision
+OpDecorate %22 Location 1
+OpMemberDecorate %26 0 Offset 0
+OpMemberDecorate %26 1 Offset 4
+OpMemberDecorate %26 2 Offset 8
+OpMemberDecorate %26 3 Offset 12
+OpMemberDecorate %27 0 Offset 0
+OpMemberDecorate %27 1 Offset 16
+OpMemberDecorate %27 2 Offset 20
+OpMemberDecorate %27 3 Offset 24
+OpMemberDecorate %27 4 Offset 28
+OpMemberDecorate %27 5 Offset 32
+OpMemberDecorate %27 6 Offset 48
+OpMemberDecorate %27 7 Offset 64
+OpMemberDecorate %27 2 RelaxedPrecision
+OpMemberDecorate %27 4 RelaxedPrecision
+OpDecorate %27 Block
+OpDecorate %29 DescriptorSet 0
+OpDecorate %29 Binding 0
+OpDecorate %32 RelaxedPrecision
+OpDecorate %33 RelaxedPrecision
+OpDecorate %36 RelaxedPrecision
+OpDecorate %37 RelaxedPrecision
+OpDecorate %38 RelaxedPrecision
+OpDecorate %39 RelaxedPrecision
+OpDecorate %41 RelaxedPrecision
+OpDecorate %42 RelaxedPrecision
+OpDecorate %43 RelaxedPrecision
+OpDecorate %48 RelaxedPrecision
+OpDecorate %49 RelaxedPrecision
+OpDecorate %50 RelaxedPrecision
+OpDecorate %52 RelaxedPrecision
+OpDecorate %53 RelaxedPrecision
+OpDecorate %54 RelaxedPrecision
+OpDecorate %55 RelaxedPrecision
+OpDecorate %56 RelaxedPrecision
+OpDecorate %57 RelaxedPrecision
+OpDecorate %58 RelaxedPrecision
+OpDecorate %59 RelaxedPrecision
+OpDecorate %60 RelaxedPrecision
+OpDecorate %67 RelaxedPrecision
+OpDecorate %68 RelaxedPrecision
+OpDecorate %72 RelaxedPrecision
+OpDecorate %73 RelaxedPrecision
+OpDecorate %75 RelaxedPrecision
+OpDecorate %76 RelaxedPrecision
+OpDecorate %77 RelaxedPrecision
+OpDecorate %80 RelaxedPrecision
+OpDecorate %81 RelaxedPrecision
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%5 = OpTypeImage %1 2D 0 0 0 1 Unknown
+%6 = OpTypeSampledImage %5
+%9 = OpTypeImage %1 2D 0 0 0 2 Rgba8
+%12 = OpTypeStruct %2
+%15 = OpTypeStruct %2
+%16 = OpTypeInt 32 0
+%17 = OpConstant %16 2
+%18 = OpTypeArray %15 %17
+%23 = OpTypeInt 32 1
+%24 = OpTypeVector %23 4
+%25 = OpTypeVector %16 4
+%26 = OpTypeStruct %1 %1 %1 %1
+%27 = OpTypeStruct %2 %16 %16 %23 %23 %24 %25 %26
+%35 = OpTypeVector %1 2
+%40 = OpTypeVector %23 2
+%61 = OpTypeVoid
+%69 = OpConstant %16 0
+%78 = OpConstant %16 1
+%82 = OpTypePointer Private %2
+%3 = OpTypePointer Input %2
+%7 = OpTypePointer UniformConstant %6
+%10 = OpTypePointer UniformConstant %9
+%13 = OpTypePointer Uniform %12
+%19 = OpTypePointer Uniform %18
+%83 = OpTypePointer Private %2
+%21 = OpTypePointer Output %2
+%28 = OpTypePointer Uniform %27
+%30 = OpTypePointer Function %2
+%70 = OpTypePointer Uniform %2
+%31 = OpTypeFunction %2 %30
+%47 = OpTypeFunction %2 %30 %30
+%62 = OpTypeFunction %61
+%4 = OpVariable %3 Input
+%8 = OpVariable %7 UniformConstant
+%11 = OpVariable %10 UniformConstant
+%14 = OpVariable %13 Uniform
+%20 = OpVariable %19 Uniform
+%22 = OpVariable %21 Output
+%29 = OpVariable %28 Uniform
+%84 = OpConstant %23 0
+%85 = OpConstant %1 0.5
+%32 = OpFunction %2 None %31
+%33 = OpFunctionParameter %30
+%34 = OpLabel
+%36 = OpLoad %6 %8
+%37 = OpLoad %2 %33
+%38 = OpVectorShuffle %35 %37 %37 0 1
+%39 = OpImageSampleImplicitLod %2 %36 %38
+%41 = OpLoad %2 %33
+%42 = OpVectorShuffle %35 %41 %41 2 3
+%43 = OpConvertFToS %40 %42
+%44 = OpLoad %9 %11
+%45 = OpImageRead %2 %44 %43
+%46 = OpFAdd %2 %39 %45
+OpReturnValue %46
+OpFunctionEnd
+%48 = OpFunction %2 None %47
+%49 = OpFunctionParameter %30
+%50 = OpFunctionParameter %30
+%51 = OpLabel
+%52 = OpLoad %2 %49
+%53 = OpVectorShuffle %35 %52 %52 0 1
+%54 = OpLoad %2 %50
+%55 = OpVectorShuffle %35 %54 %54 2 3
+%56 = OpCompositeExtract %1 %53 0
+%57 = OpCompositeExtract %1 %53 1
+%58 = OpCompositeExtract %1 %55 0
+%59 = OpCompositeExtract %1 %55 1
+%60 = OpCompositeConstruct %2 %56 %57 %58 %59
+OpReturnValue %60
+OpFunctionEnd
+%63 = OpFunction %61 None %62
+%64 = OpLabel
+%65 = OpVariable %30 Function
+%68 = OpVariable %30 Function
+%73 = OpVariable %30 Function
+%66 = OpLoad %2 %4
+OpStore %65 %66
+%67 = OpFunctionCall %2 %32 %65
+%71 = OpAccessChain %70 %14 %69
+%72 = OpLoad %2 %71
+OpStore %68 %72
+%74 = OpAccessChain %70 %20 %69 %69
+%75 = OpLoad %2 %74
+OpStore %73 %75
+%76 = OpFunctionCall %2 %48 %68 %73
+%77 = OpFAdd %2 %67 %76
+%79 = OpAccessChain %70 %20 %78 %69
+%80 = OpLoad %2 %79
+%81 = OpFAdd %2 %77 %80
+OpStore %22 %81
+OpReturn
+OpFunctionEnd
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 82
++; Bound: 92
+ ; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %63 "main" %4 %22
+ OpExecutionMode %63 OriginUpperLeft
+ OpSource GLSL 450
+-OpDecorate %4 Location 0
++OpDecorate %4 Location 1
+ OpDecorate %8 RelaxedPrecision
+-OpDecorate %8 DescriptorSet 0
++OpDecorate %8 DescriptorSet 2
+ OpDecorate %8 Binding 0
+-OpDecorate %11 DescriptorSet 0
++OpDecorate %11 DescriptorSet 3
+-OpDecorate %11 Binding 1
++OpDecorate %11 Binding 0
+ OpMemberDecorate %12 0 Offset 0
+ OpMemberDecorate %12 0 RelaxedPrecision
+ OpDecorate %12 Block
++OpDecorate %82 DescriptorSet 3
++OpDecorate %82 Binding 1
+-OpDecorate %14 DescriptorSet 0
++OpDecorate %14 DescriptorSet 3
+ OpDecorate %14 Binding 2
+ OpMemberDecorate %15 0 Offset 0
+ OpMemberDecorate %15 0 RelaxedPrecision
+ OpDecorate %15 BufferBlock
+-OpDecorate %20 DescriptorSet 0
+-OpDecorate %20 Binding 3
+ OpDecorate %22 RelaxedPrecision
+-OpDecorate %22 Location 0
++OpDecorate %22 Location 1
+ OpMemberDecorate %26 0 Offset 0
+ OpMemberDecorate %26 1 Offset 4
+ OpMemberDecorate %26 2 Offset 8
+ OpMemberDecorate %26 3 Offset 12
+ OpMemberDecorate %27 0 Offset 0
+ OpMemberDecorate %27 1 Offset 16
+ OpMemberDecorate %27 2 Offset 20
+ OpMemberDecorate %27 3 Offset 24
+ OpMemberDecorate %27 4 Offset 28
+ OpMemberDecorate %27 5 Offset 32
+ OpMemberDecorate %27 6 Offset 48
+ OpMemberDecorate %27 7 Offset 64
+ OpMemberDecorate %27 2 RelaxedPrecision
+ OpMemberDecorate %27 4 RelaxedPrecision
+ OpDecorate %27 Block
+-OpDecorate %29 DescriptorSet 0
+-OpDecorate %29 Binding 4
++OpDecorate %83 DescriptorSet 0
++OpDecorate %83 Binding 0
+ OpDecorate %32 RelaxedPrecision
+-OpDecorate %33 RelaxedPrecision
++OpDecorate %84 RelaxedPrecision
+ OpDecorate %36 RelaxedPrecision
+ OpDecorate %37 RelaxedPrecision
+ OpDecorate %38 RelaxedPrecision
+ OpDecorate %39 RelaxedPrecision
+ OpDecorate %41 RelaxedPrecision
+ OpDecorate %42 RelaxedPrecision
+ OpDecorate %43 RelaxedPrecision
+ OpDecorate %48 RelaxedPrecision
+-OpDecorate %49 RelaxedPrecision
+-OpDecorate %50 RelaxedPrecision
++OpDecorate %85 RelaxedPrecision
++OpDecorate %86 RelaxedPrecision
+ OpDecorate %52 RelaxedPrecision
+ OpDecorate %53 RelaxedPrecision
+ OpDecorate %54 RelaxedPrecision
+ OpDecorate %55 RelaxedPrecision
+ OpDecorate %56 RelaxedPrecision
+ OpDecorate %57 RelaxedPrecision
+ OpDecorate %58 RelaxedPrecision
+ OpDecorate %59 RelaxedPrecision
+ OpDecorate %60 RelaxedPrecision
+ OpDecorate %67 RelaxedPrecision
+ OpDecorate %68 RelaxedPrecision
+ OpDecorate %72 RelaxedPrecision
+ OpDecorate %73 RelaxedPrecision
+ OpDecorate %75 RelaxedPrecision
+ OpDecorate %76 RelaxedPrecision
+ OpDecorate %77 RelaxedPrecision
+ OpDecorate %80 RelaxedPrecision
+ OpDecorate %81 RelaxedPrecision
+ %1 = OpTypeFloat 32
+ %2 = OpTypeVector %1 4
+ %5 = OpTypeImage %1 2D 0 0 0 1 Unknown
+ %6 = OpTypeSampledImage %5
+ %9 = OpTypeImage %1 2D 0 0 0 2 Rgba8
+ %12 = OpTypeStruct %2
+ %15 = OpTypeStruct %2
+ %16 = OpTypeInt 32 0
+ %17 = OpConstant %16 2
+ %18 = OpTypeArray %15 %17
+ %23 = OpTypeInt 32 1
+ %24 = OpTypeVector %23 4
+ %25 = OpTypeVector %16 4
+ %26 = OpTypeStruct %1 %1 %1 %1
+ %27 = OpTypeStruct %2 %16 %16 %23 %23 %24 %25 %26
+ %35 = OpTypeVector %1 2
+ %40 = OpTypeVector %23 2
+ %61 = OpTypeVoid
+ %69 = OpConstant %16 0
+ %78 = OpConstant %16 1
++%88 = OpTypePointer Private %2
+ %3 = OpTypePointer Input %2
+ %7 = OpTypePointer UniformConstant %6
+ %10 = OpTypePointer UniformConstant %9
+ %13 = OpTypePointer Uniform %12
+ %19 = OpTypePointer Uniform %18
++%89 = OpTypePointer Private %2
+ %21 = OpTypePointer Output %2
+ %28 = OpTypePointer Uniform %27
+ %30 = OpTypePointer Function %2
+ %70 = OpTypePointer Uniform %2
+ %31 = OpTypeFunction %2 %30
+ %47 = OpTypeFunction %2 %30 %30
+ %62 = OpTypeFunction %61
+ %4 = OpVariable %3 Input
+ %8 = OpVariable %7 UniformConstant
+ %11 = OpVariable %10 UniformConstant
++%82 = OpVariable %13 Uniform
+-%14 = OpVariable %13 Uniform
++%14 = OpVariable %19 Uniform
+-%20 = OpVariable %19 Uniform
+ %22 = OpVariable %21 Output
+-%29 = OpVariable %28 Uniform
++%83 = OpVariable %28 Uniform
++%90 = OpConstant %23 0
++%91 = OpConstant %1 0.5
+ %32 = OpFunction %2 None %31
+-%33 = OpFunctionParameter %30
++%84 = OpFunctionParameter %30
+ %34 = OpLabel
+ %36 = OpLoad %6 %8
+-%37 = OpLoad %2 %33
++%37 = OpLoad %2 %84
+ %38 = OpVectorShuffle %35 %37 %37 0 1
+ %39 = OpImageSampleImplicitLod %2 %36 %38
+-%41 = OpLoad %2 %33
++%41 = OpLoad %2 %84
+ %42 = OpVectorShuffle %35 %41 %41 2 3
+ %43 = OpConvertFToS %40 %42
+ %44 = OpLoad %9 %11
+ %45 = OpImageRead %2 %44 %43
+ %46 = OpFAdd %2 %39 %45
+ OpReturnValue %46
+ OpFunctionEnd
+ %48 = OpFunction %2 None %47
+-%49 = OpFunctionParameter %30
+-%50 = OpFunctionParameter %30
++%85 = OpFunctionParameter %30
++%86 = OpFunctionParameter %30
+ %51 = OpLabel
+-%52 = OpLoad %2 %49
++%52 = OpLoad %2 %85
+ %53 = OpVectorShuffle %35 %52 %52 0 1
+-%54 = OpLoad %2 %50
++%54 = OpLoad %2 %86
+ %55 = OpVectorShuffle %35 %54 %54 2 3
+ %56 = OpCompositeExtract %1 %53 0
+ %57 = OpCompositeExtract %1 %53 1
+ %58 = OpCompositeExtract %1 %55 0
+ %59 = OpCompositeExtract %1 %55 1
+ %60 = OpCompositeConstruct %2 %56 %57 %58 %59
+ OpReturnValue %60
+ OpFunctionEnd
+ %63 = OpFunction %61 None %62
+ %64 = OpLabel
+ %65 = OpVariable %30 Function
+ %68 = OpVariable %30 Function
+ %73 = OpVariable %30 Function
+ %66 = OpLoad %2 %4
+ OpStore %65 %66
+ %67 = OpFunctionCall %2 %32 %65
+-%71 = OpAccessChain %70 %14 %69
++%87 = OpAccessChain %70 %82 %69
+-%72 = OpLoad %2 %71
++%72 = OpLoad %2 %87
+ OpStore %68 %72
+-%74 = OpAccessChain %70 %20 %69 %69
++%74 = OpAccessChain %70 %14 %69 %69
+ %75 = OpLoad %2 %74
+ OpStore %73 %75
+ %76 = OpFunctionCall %2 %48 %68 %73
+ %77 = OpFAdd %2 %67 %76
+-%79 = OpAccessChain %70 %20 %78 %69
++%79 = OpAccessChain %70 %14 %78 %69
+ %80 = OpLoad %2 %79
+ %81 = OpFAdd %2 %77 %80
+ OpStore %22 %81
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+TEST(DiffTest, DifferentDecorationsFragmentIgnoreLocation) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 82
++; Bound: 86
+ ; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %63 "main" %4 %22
+ OpExecutionMode %63 OriginUpperLeft
+ OpSource GLSL 450
+ OpName %4 "_ue"
+ OpName %8 "_uf"
+ OpName %11 "_ug"
+ OpName %12 "_uA"
+ OpMemberName %12 0 "_ux"
+ OpName %14 "_uc"
+ OpName %15 "_uB"
+ OpMemberName %15 0 "_ux"
+ OpName %20 "_ud"
+ OpName %22 "_ucol"
+ OpName %26 "ANGLEDepthRangeParams"
+ OpMemberName %26 0 "near"
+ OpMemberName %26 1 "far"
+ OpMemberName %26 2 "diff"
+ OpMemberName %26 3 "reserved"
+ OpName %27 "ANGLEUniformBlock"
+ OpMemberName %27 0 "viewport"
+ OpMemberName %27 1 "clipDistancesEnabled"
+ OpMemberName %27 2 "xfbActiveUnpaused"
+ OpMemberName %27 3 "xfbVerticesPerInstance"
+ OpMemberName %27 4 "numSamples"
+ OpMemberName %27 5 "xfbBufferOffsets"
+ OpMemberName %27 6 "acbBufferOffsets"
+ OpMemberName %27 7 "depthRange"
+ OpName %29 "ANGLEUniforms"
+ OpName %33 "_uc"
+ OpName %32 "_uh"
+ OpName %49 "_ux"
+ OpName %50 "_uy"
+ OpName %48 "_ui"
+ OpName %63 "main"
+ OpName %65 "param"
+ OpName %68 "param"
+ OpName %73 "param"
+-OpDecorate %4 Location 0
++OpDecorate %4 Location 1
+ OpDecorate %8 RelaxedPrecision
+-OpDecorate %8 DescriptorSet 0
++OpDecorate %8 DescriptorSet 2
+ OpDecorate %8 Binding 0
+-OpDecorate %11 DescriptorSet 0
++OpDecorate %11 DescriptorSet 3
+-OpDecorate %11 Binding 1
++OpDecorate %11 Binding 0
+ OpMemberDecorate %12 0 Offset 0
+ OpMemberDecorate %12 0 RelaxedPrecision
+ OpDecorate %12 Block
+-OpDecorate %14 DescriptorSet 0
++OpDecorate %14 DescriptorSet 3
+-OpDecorate %14 Binding 2
++OpDecorate %14 Binding 1
+ OpMemberDecorate %15 0 Offset 0
+ OpMemberDecorate %15 0 RelaxedPrecision
+ OpDecorate %15 BufferBlock
+-OpDecorate %20 DescriptorSet 0
++OpDecorate %20 DescriptorSet 3
+-OpDecorate %20 Binding 3
++OpDecorate %20 Binding 2
+ OpDecorate %22 RelaxedPrecision
+-OpDecorate %22 Location 0
++OpDecorate %22 Location 1
+ OpMemberDecorate %26 0 Offset 0
+ OpMemberDecorate %26 1 Offset 4
+ OpMemberDecorate %26 2 Offset 8
+ OpMemberDecorate %26 3 Offset 12
+ OpMemberDecorate %27 0 Offset 0
+ OpMemberDecorate %27 1 Offset 16
+ OpMemberDecorate %27 2 Offset 20
+ OpMemberDecorate %27 3 Offset 24
+ OpMemberDecorate %27 4 Offset 28
+ OpMemberDecorate %27 5 Offset 32
+ OpMemberDecorate %27 6 Offset 48
+ OpMemberDecorate %27 7 Offset 64
+ OpMemberDecorate %27 2 RelaxedPrecision
+ OpMemberDecorate %27 4 RelaxedPrecision
+ OpDecorate %27 Block
+ OpDecorate %29 DescriptorSet 0
+-OpDecorate %29 Binding 4
++OpDecorate %29 Binding 0
+ OpDecorate %32 RelaxedPrecision
+ OpDecorate %33 RelaxedPrecision
+ OpDecorate %36 RelaxedPrecision
+ OpDecorate %37 RelaxedPrecision
+ OpDecorate %38 RelaxedPrecision
+ OpDecorate %39 RelaxedPrecision
+ OpDecorate %41 RelaxedPrecision
+ OpDecorate %42 RelaxedPrecision
+ OpDecorate %43 RelaxedPrecision
+ OpDecorate %48 RelaxedPrecision
+ OpDecorate %49 RelaxedPrecision
+ OpDecorate %50 RelaxedPrecision
+ OpDecorate %52 RelaxedPrecision
+ OpDecorate %53 RelaxedPrecision
+ OpDecorate %54 RelaxedPrecision
+ OpDecorate %55 RelaxedPrecision
+ OpDecorate %56 RelaxedPrecision
+ OpDecorate %57 RelaxedPrecision
+ OpDecorate %58 RelaxedPrecision
+ OpDecorate %59 RelaxedPrecision
+ OpDecorate %60 RelaxedPrecision
+ OpDecorate %67 RelaxedPrecision
+ OpDecorate %68 RelaxedPrecision
+ OpDecorate %72 RelaxedPrecision
+ OpDecorate %73 RelaxedPrecision
+ OpDecorate %75 RelaxedPrecision
+ OpDecorate %76 RelaxedPrecision
+ OpDecorate %77 RelaxedPrecision
+ OpDecorate %80 RelaxedPrecision
+ OpDecorate %81 RelaxedPrecision
+ %1 = OpTypeFloat 32
+ %2 = OpTypeVector %1 4
+ %5 = OpTypeImage %1 2D 0 0 0 1 Unknown
+ %6 = OpTypeSampledImage %5
+ %9 = OpTypeImage %1 2D 0 0 0 2 Rgba8
+ %12 = OpTypeStruct %2
+ %15 = OpTypeStruct %2
+ %16 = OpTypeInt 32 0
+ %17 = OpConstant %16 2
+ %18 = OpTypeArray %15 %17
+ %23 = OpTypeInt 32 1
+ %24 = OpTypeVector %23 4
+ %25 = OpTypeVector %16 4
+ %26 = OpTypeStruct %1 %1 %1 %1
+ %27 = OpTypeStruct %2 %16 %16 %23 %23 %24 %25 %26
+ %35 = OpTypeVector %1 2
+ %40 = OpTypeVector %23 2
+ %61 = OpTypeVoid
+ %69 = OpConstant %16 0
+ %78 = OpConstant %16 1
++%82 = OpTypePointer Private %2
+ %3 = OpTypePointer Input %2
+ %7 = OpTypePointer UniformConstant %6
+ %10 = OpTypePointer UniformConstant %9
+ %13 = OpTypePointer Uniform %12
+ %19 = OpTypePointer Uniform %18
++%83 = OpTypePointer Private %2
+ %21 = OpTypePointer Output %2
+ %28 = OpTypePointer Uniform %27
+ %30 = OpTypePointer Function %2
+ %70 = OpTypePointer Uniform %2
+ %31 = OpTypeFunction %2 %30
+ %47 = OpTypeFunction %2 %30 %30
+ %62 = OpTypeFunction %61
+ %4 = OpVariable %3 Input
+ %8 = OpVariable %7 UniformConstant
+ %11 = OpVariable %10 UniformConstant
+ %14 = OpVariable %13 Uniform
+ %20 = OpVariable %19 Uniform
+ %22 = OpVariable %21 Output
+ %29 = OpVariable %28 Uniform
++%84 = OpConstant %23 0
++%85 = OpConstant %1 0.5
+ %32 = OpFunction %2 None %31
+ %33 = OpFunctionParameter %30
+ %34 = OpLabel
+ %36 = OpLoad %6 %8
+ %37 = OpLoad %2 %33
+ %38 = OpVectorShuffle %35 %37 %37 0 1
+ %39 = OpImageSampleImplicitLod %2 %36 %38
+ %41 = OpLoad %2 %33
+ %42 = OpVectorShuffle %35 %41 %41 2 3
+ %43 = OpConvertFToS %40 %42
+ %44 = OpLoad %9 %11
+ %45 = OpImageRead %2 %44 %43
+ %46 = OpFAdd %2 %39 %45
+ OpReturnValue %46
+ OpFunctionEnd
+ %48 = OpFunction %2 None %47
+ %49 = OpFunctionParameter %30
+ %50 = OpFunctionParameter %30
+ %51 = OpLabel
+ %52 = OpLoad %2 %49
+ %53 = OpVectorShuffle %35 %52 %52 0 1
+ %54 = OpLoad %2 %50
+ %55 = OpVectorShuffle %35 %54 %54 2 3
+ %56 = OpCompositeExtract %1 %53 0
+ %57 = OpCompositeExtract %1 %53 1
+ %58 = OpCompositeExtract %1 %55 0
+ %59 = OpCompositeExtract %1 %55 1
+ %60 = OpCompositeConstruct %2 %56 %57 %58 %59
+ OpReturnValue %60
+ OpFunctionEnd
+ %63 = OpFunction %61 None %62
+ %64 = OpLabel
+ %65 = OpVariable %30 Function
+ %68 = OpVariable %30 Function
+ %73 = OpVariable %30 Function
+ %66 = OpLoad %2 %4
+ OpStore %65 %66
+ %67 = OpFunctionCall %2 %32 %65
+ %71 = OpAccessChain %70 %14 %69
+ %72 = OpLoad %2 %71
+ OpStore %68 %72
+ %74 = OpAccessChain %70 %20 %69 %69
+ %75 = OpLoad %2 %74
+ OpStore %73 %75
+ %76 = OpFunctionCall %2 %48 %68 %73
+ %77 = OpFAdd %2 %67 %76
+ %79 = OpAccessChain %70 %20 %78 %69
+ %80 = OpLoad %2 %79
+ %81 = OpFAdd %2 %77 %80
+ OpStore %22 %81
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  options.ignore_location = true;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, DifferentDecorationsFragmentIgnoreSetBindingLocation) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 82
++; Bound: 86
+ ; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %63 "main" %4 %22
+ OpExecutionMode %63 OriginUpperLeft
+ OpSource GLSL 450
+ OpName %4 "_ue"
+ OpName %8 "_uf"
+ OpName %11 "_ug"
+ OpName %12 "_uA"
+ OpMemberName %12 0 "_ux"
+ OpName %14 "_uc"
+ OpName %15 "_uB"
+ OpMemberName %15 0 "_ux"
+ OpName %20 "_ud"
+ OpName %22 "_ucol"
+ OpName %26 "ANGLEDepthRangeParams"
+ OpMemberName %26 0 "near"
+ OpMemberName %26 1 "far"
+ OpMemberName %26 2 "diff"
+ OpMemberName %26 3 "reserved"
+ OpName %27 "ANGLEUniformBlock"
+ OpMemberName %27 0 "viewport"
+ OpMemberName %27 1 "clipDistancesEnabled"
+ OpMemberName %27 2 "xfbActiveUnpaused"
+ OpMemberName %27 3 "xfbVerticesPerInstance"
+ OpMemberName %27 4 "numSamples"
+ OpMemberName %27 5 "xfbBufferOffsets"
+ OpMemberName %27 6 "acbBufferOffsets"
+ OpMemberName %27 7 "depthRange"
+ OpName %29 "ANGLEUniforms"
+ OpName %33 "_uc"
+ OpName %32 "_uh"
+ OpName %49 "_ux"
+ OpName %50 "_uy"
+ OpName %48 "_ui"
+ OpName %63 "main"
+ OpName %65 "param"
+ OpName %68 "param"
+ OpName %73 "param"
+-OpDecorate %4 Location 0
++OpDecorate %4 Location 1
+ OpDecorate %8 RelaxedPrecision
+-OpDecorate %8 DescriptorSet 0
++OpDecorate %8 DescriptorSet 2
+ OpDecorate %8 Binding 0
+-OpDecorate %11 DescriptorSet 0
++OpDecorate %11 DescriptorSet 3
+-OpDecorate %11 Binding 1
++OpDecorate %11 Binding 0
+ OpMemberDecorate %12 0 Offset 0
+ OpMemberDecorate %12 0 RelaxedPrecision
+ OpDecorate %12 Block
+-OpDecorate %14 DescriptorSet 0
++OpDecorate %14 DescriptorSet 3
+-OpDecorate %14 Binding 2
++OpDecorate %14 Binding 1
+ OpMemberDecorate %15 0 Offset 0
+ OpMemberDecorate %15 0 RelaxedPrecision
+ OpDecorate %15 BufferBlock
+-OpDecorate %20 DescriptorSet 0
++OpDecorate %20 DescriptorSet 3
+-OpDecorate %20 Binding 3
++OpDecorate %20 Binding 2
+ OpDecorate %22 RelaxedPrecision
+-OpDecorate %22 Location 0
++OpDecorate %22 Location 1
+ OpMemberDecorate %26 0 Offset 0
+ OpMemberDecorate %26 1 Offset 4
+ OpMemberDecorate %26 2 Offset 8
+ OpMemberDecorate %26 3 Offset 12
+ OpMemberDecorate %27 0 Offset 0
+ OpMemberDecorate %27 1 Offset 16
+ OpMemberDecorate %27 2 Offset 20
+ OpMemberDecorate %27 3 Offset 24
+ OpMemberDecorate %27 4 Offset 28
+ OpMemberDecorate %27 5 Offset 32
+ OpMemberDecorate %27 6 Offset 48
+ OpMemberDecorate %27 7 Offset 64
+ OpMemberDecorate %27 2 RelaxedPrecision
+ OpMemberDecorate %27 4 RelaxedPrecision
+ OpDecorate %27 Block
+ OpDecorate %29 DescriptorSet 0
+-OpDecorate %29 Binding 4
++OpDecorate %29 Binding 0
+ OpDecorate %32 RelaxedPrecision
+ OpDecorate %33 RelaxedPrecision
+ OpDecorate %36 RelaxedPrecision
+ OpDecorate %37 RelaxedPrecision
+ OpDecorate %38 RelaxedPrecision
+ OpDecorate %39 RelaxedPrecision
+ OpDecorate %41 RelaxedPrecision
+ OpDecorate %42 RelaxedPrecision
+ OpDecorate %43 RelaxedPrecision
+ OpDecorate %48 RelaxedPrecision
+ OpDecorate %49 RelaxedPrecision
+ OpDecorate %50 RelaxedPrecision
+ OpDecorate %52 RelaxedPrecision
+ OpDecorate %53 RelaxedPrecision
+ OpDecorate %54 RelaxedPrecision
+ OpDecorate %55 RelaxedPrecision
+ OpDecorate %56 RelaxedPrecision
+ OpDecorate %57 RelaxedPrecision
+ OpDecorate %58 RelaxedPrecision
+ OpDecorate %59 RelaxedPrecision
+ OpDecorate %60 RelaxedPrecision
+ OpDecorate %67 RelaxedPrecision
+ OpDecorate %68 RelaxedPrecision
+ OpDecorate %72 RelaxedPrecision
+ OpDecorate %73 RelaxedPrecision
+ OpDecorate %75 RelaxedPrecision
+ OpDecorate %76 RelaxedPrecision
+ OpDecorate %77 RelaxedPrecision
+ OpDecorate %80 RelaxedPrecision
+ OpDecorate %81 RelaxedPrecision
+ %1 = OpTypeFloat 32
+ %2 = OpTypeVector %1 4
+ %5 = OpTypeImage %1 2D 0 0 0 1 Unknown
+ %6 = OpTypeSampledImage %5
+ %9 = OpTypeImage %1 2D 0 0 0 2 Rgba8
+ %12 = OpTypeStruct %2
+ %15 = OpTypeStruct %2
+ %16 = OpTypeInt 32 0
+ %17 = OpConstant %16 2
+ %18 = OpTypeArray %15 %17
+ %23 = OpTypeInt 32 1
+ %24 = OpTypeVector %23 4
+ %25 = OpTypeVector %16 4
+ %26 = OpTypeStruct %1 %1 %1 %1
+ %27 = OpTypeStruct %2 %16 %16 %23 %23 %24 %25 %26
+ %35 = OpTypeVector %1 2
+ %40 = OpTypeVector %23 2
+ %61 = OpTypeVoid
+ %69 = OpConstant %16 0
+ %78 = OpConstant %16 1
++%82 = OpTypePointer Private %2
+ %3 = OpTypePointer Input %2
+ %7 = OpTypePointer UniformConstant %6
+ %10 = OpTypePointer UniformConstant %9
+ %13 = OpTypePointer Uniform %12
+ %19 = OpTypePointer Uniform %18
++%83 = OpTypePointer Private %2
+ %21 = OpTypePointer Output %2
+ %28 = OpTypePointer Uniform %27
+ %30 = OpTypePointer Function %2
+ %70 = OpTypePointer Uniform %2
+ %31 = OpTypeFunction %2 %30
+ %47 = OpTypeFunction %2 %30 %30
+ %62 = OpTypeFunction %61
+ %4 = OpVariable %3 Input
+ %8 = OpVariable %7 UniformConstant
+ %11 = OpVariable %10 UniformConstant
+ %14 = OpVariable %13 Uniform
+ %20 = OpVariable %19 Uniform
+ %22 = OpVariable %21 Output
+ %29 = OpVariable %28 Uniform
++%84 = OpConstant %23 0
++%85 = OpConstant %1 0.5
+ %32 = OpFunction %2 None %31
+ %33 = OpFunctionParameter %30
+ %34 = OpLabel
+ %36 = OpLoad %6 %8
+ %37 = OpLoad %2 %33
+ %38 = OpVectorShuffle %35 %37 %37 0 1
+ %39 = OpImageSampleImplicitLod %2 %36 %38
+ %41 = OpLoad %2 %33
+ %42 = OpVectorShuffle %35 %41 %41 2 3
+ %43 = OpConvertFToS %40 %42
+ %44 = OpLoad %9 %11
+ %45 = OpImageRead %2 %44 %43
+ %46 = OpFAdd %2 %39 %45
+ OpReturnValue %46
+ OpFunctionEnd
+ %48 = OpFunction %2 None %47
+ %49 = OpFunctionParameter %30
+ %50 = OpFunctionParameter %30
+ %51 = OpLabel
+ %52 = OpLoad %2 %49
+ %53 = OpVectorShuffle %35 %52 %52 0 1
+ %54 = OpLoad %2 %50
+ %55 = OpVectorShuffle %35 %54 %54 2 3
+ %56 = OpCompositeExtract %1 %53 0
+ %57 = OpCompositeExtract %1 %53 1
+ %58 = OpCompositeExtract %1 %55 0
+ %59 = OpCompositeExtract %1 %55 1
+ %60 = OpCompositeConstruct %2 %56 %57 %58 %59
+ OpReturnValue %60
+ OpFunctionEnd
+ %63 = OpFunction %61 None %62
+ %64 = OpLabel
+ %65 = OpVariable %30 Function
+ %68 = OpVariable %30 Function
+ %73 = OpVariable %30 Function
+ %66 = OpLoad %2 %4
+ OpStore %65 %66
+ %67 = OpFunctionCall %2 %32 %65
+ %71 = OpAccessChain %70 %14 %69
+ %72 = OpLoad %2 %71
+ OpStore %68 %72
+ %74 = OpAccessChain %70 %20 %69 %69
+ %75 = OpLoad %2 %74
+ OpStore %73 %75
+ %76 = OpFunctionCall %2 %48 %68 %73
+ %77 = OpFAdd %2 %67 %76
+ %79 = OpAccessChain %70 %20 %78 %69
+ %80 = OpLoad %2 %79
+ %81 = OpFAdd %2 %77 %80
+ OpStore %22 %81
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  options.ignore_set_binding = true;
+  options.ignore_location = true;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/different_decorations_fragment_dst.spvasm b/test/diff/diff_files/different_decorations_fragment_dst.spvasm
new file mode 100644
index 0000000..b5d1c9f
--- /dev/null
+++ b/test/diff/diff_files/different_decorations_fragment_dst.spvasm
@@ -0,0 +1,199 @@
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %63 "main" %4 %22
+OpExecutionMode %63 OriginUpperLeft
+OpSource GLSL 450
+OpName %4 "_ue"
+OpName %8 "_uf"
+OpName %11 "_ug"
+OpName %12 "_uA"
+OpMemberName %12 0 "_ux"
+OpName %14 "_uc"
+OpName %15 "_uB"
+OpMemberName %15 0 "_ux"
+OpName %20 "_ud"
+OpName %22 "_ucol"
+OpName %26 "ANGLEDepthRangeParams"
+OpMemberName %26 0 "near"
+OpMemberName %26 1 "far"
+OpMemberName %26 2 "diff"
+OpMemberName %26 3 "reserved"
+OpName %27 "ANGLEUniformBlock"
+OpMemberName %27 0 "viewport"
+OpMemberName %27 1 "clipDistancesEnabled"
+OpMemberName %27 2 "xfbActiveUnpaused"
+OpMemberName %27 3 "xfbVerticesPerInstance"
+OpMemberName %27 4 "numSamples"
+OpMemberName %27 5 "xfbBufferOffsets"
+OpMemberName %27 6 "acbBufferOffsets"
+OpMemberName %27 7 "depthRange"
+OpName %29 "ANGLEUniforms"
+OpName %33 "_uc"
+OpName %32 "_uh"
+OpName %49 "_ux"
+OpName %50 "_uy"
+OpName %48 "_ui"
+OpName %63 "main"
+OpName %65 "param"
+OpName %68 "param"
+OpName %73 "param"
+OpDecorate %4 Location 1
+OpDecorate %8 RelaxedPrecision
+OpDecorate %8 DescriptorSet 2
+OpDecorate %8 Binding 0
+OpDecorate %11 DescriptorSet 3
+OpDecorate %11 Binding 0
+OpMemberDecorate %12 0 Offset 0
+OpMemberDecorate %12 0 RelaxedPrecision
+OpDecorate %12 Block
+OpDecorate %14 DescriptorSet 3
+OpDecorate %14 Binding 1
+OpMemberDecorate %15 0 Offset 0
+OpMemberDecorate %15 0 RelaxedPrecision
+OpDecorate %15 BufferBlock
+OpDecorate %20 DescriptorSet 3
+OpDecorate %20 Binding 2
+OpDecorate %22 RelaxedPrecision
+OpDecorate %22 Location 1
+OpMemberDecorate %26 0 Offset 0
+OpMemberDecorate %26 1 Offset 4
+OpMemberDecorate %26 2 Offset 8
+OpMemberDecorate %26 3 Offset 12
+OpMemberDecorate %27 0 Offset 0
+OpMemberDecorate %27 1 Offset 16
+OpMemberDecorate %27 2 Offset 20
+OpMemberDecorate %27 3 Offset 24
+OpMemberDecorate %27 4 Offset 28
+OpMemberDecorate %27 5 Offset 32
+OpMemberDecorate %27 6 Offset 48
+OpMemberDecorate %27 7 Offset 64
+OpMemberDecorate %27 2 RelaxedPrecision
+OpMemberDecorate %27 4 RelaxedPrecision
+OpDecorate %27 Block
+OpDecorate %29 DescriptorSet 0
+OpDecorate %29 Binding 0
+OpDecorate %32 RelaxedPrecision
+OpDecorate %33 RelaxedPrecision
+OpDecorate %36 RelaxedPrecision
+OpDecorate %37 RelaxedPrecision
+OpDecorate %38 RelaxedPrecision
+OpDecorate %39 RelaxedPrecision
+OpDecorate %41 RelaxedPrecision
+OpDecorate %42 RelaxedPrecision
+OpDecorate %43 RelaxedPrecision
+OpDecorate %48 RelaxedPrecision
+OpDecorate %49 RelaxedPrecision
+OpDecorate %50 RelaxedPrecision
+OpDecorate %52 RelaxedPrecision
+OpDecorate %53 RelaxedPrecision
+OpDecorate %54 RelaxedPrecision
+OpDecorate %55 RelaxedPrecision
+OpDecorate %56 RelaxedPrecision
+OpDecorate %57 RelaxedPrecision
+OpDecorate %58 RelaxedPrecision
+OpDecorate %59 RelaxedPrecision
+OpDecorate %60 RelaxedPrecision
+OpDecorate %67 RelaxedPrecision
+OpDecorate %68 RelaxedPrecision
+OpDecorate %72 RelaxedPrecision
+OpDecorate %73 RelaxedPrecision
+OpDecorate %75 RelaxedPrecision
+OpDecorate %76 RelaxedPrecision
+OpDecorate %77 RelaxedPrecision
+OpDecorate %80 RelaxedPrecision
+OpDecorate %81 RelaxedPrecision
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%5 = OpTypeImage %1 2D 0 0 0 1 Unknown
+%6 = OpTypeSampledImage %5
+%9 = OpTypeImage %1 2D 0 0 0 2 Rgba8
+%12 = OpTypeStruct %2
+%15 = OpTypeStruct %2
+%16 = OpTypeInt 32 0
+%17 = OpConstant %16 2
+%18 = OpTypeArray %15 %17
+%23 = OpTypeInt 32 1
+%24 = OpTypeVector %23 4
+%25 = OpTypeVector %16 4
+%26 = OpTypeStruct %1 %1 %1 %1
+%27 = OpTypeStruct %2 %16 %16 %23 %23 %24 %25 %26
+%35 = OpTypeVector %1 2
+%40 = OpTypeVector %23 2
+%61 = OpTypeVoid
+%69 = OpConstant %16 0
+%78 = OpConstant %16 1
+%82 = OpTypePointer Private %2
+%3 = OpTypePointer Input %2
+%7 = OpTypePointer UniformConstant %6
+%10 = OpTypePointer UniformConstant %9
+%13 = OpTypePointer Uniform %12
+%19 = OpTypePointer Uniform %18
+%83 = OpTypePointer Private %2
+%21 = OpTypePointer Output %2
+%28 = OpTypePointer Uniform %27
+%30 = OpTypePointer Function %2
+%70 = OpTypePointer Uniform %2
+%31 = OpTypeFunction %2 %30
+%47 = OpTypeFunction %2 %30 %30
+%62 = OpTypeFunction %61
+%4 = OpVariable %3 Input
+%8 = OpVariable %7 UniformConstant
+%11 = OpVariable %10 UniformConstant
+%14 = OpVariable %13 Uniform
+%20 = OpVariable %19 Uniform
+%22 = OpVariable %21 Output
+%29 = OpVariable %28 Uniform
+%84 = OpConstant %23 0
+%85 = OpConstant %1 0.5
+%32 = OpFunction %2 None %31
+%33 = OpFunctionParameter %30
+%34 = OpLabel
+%36 = OpLoad %6 %8
+%37 = OpLoad %2 %33
+%38 = OpVectorShuffle %35 %37 %37 0 1
+%39 = OpImageSampleImplicitLod %2 %36 %38
+%41 = OpLoad %2 %33
+%42 = OpVectorShuffle %35 %41 %41 2 3
+%43 = OpConvertFToS %40 %42
+%44 = OpLoad %9 %11
+%45 = OpImageRead %2 %44 %43
+%46 = OpFAdd %2 %39 %45
+OpReturnValue %46
+OpFunctionEnd
+%48 = OpFunction %2 None %47
+%49 = OpFunctionParameter %30
+%50 = OpFunctionParameter %30
+%51 = OpLabel
+%52 = OpLoad %2 %49
+%53 = OpVectorShuffle %35 %52 %52 0 1
+%54 = OpLoad %2 %50
+%55 = OpVectorShuffle %35 %54 %54 2 3
+%56 = OpCompositeExtract %1 %53 0
+%57 = OpCompositeExtract %1 %53 1
+%58 = OpCompositeExtract %1 %55 0
+%59 = OpCompositeExtract %1 %55 1
+%60 = OpCompositeConstruct %2 %56 %57 %58 %59
+OpReturnValue %60
+OpFunctionEnd
+%63 = OpFunction %61 None %62
+%64 = OpLabel
+%65 = OpVariable %30 Function
+%68 = OpVariable %30 Function
+%73 = OpVariable %30 Function
+%66 = OpLoad %2 %4
+OpStore %65 %66
+%67 = OpFunctionCall %2 %32 %65
+%71 = OpAccessChain %70 %14 %69
+%72 = OpLoad %2 %71
+OpStore %68 %72
+%74 = OpAccessChain %70 %20 %69 %69
+%75 = OpLoad %2 %74
+OpStore %73 %75
+%76 = OpFunctionCall %2 %48 %68 %73
+%77 = OpFAdd %2 %67 %76
+%79 = OpAccessChain %70 %20 %78 %69
+%80 = OpLoad %2 %79
+%81 = OpFAdd %2 %77 %80
+OpStore %22 %81
+OpReturn
+OpFunctionEnd
diff --git a/test/diff/diff_files/different_decorations_fragment_src.spvasm b/test/diff/diff_files/different_decorations_fragment_src.spvasm
new file mode 100644
index 0000000..2c8cd64
--- /dev/null
+++ b/test/diff/diff_files/different_decorations_fragment_src.spvasm
@@ -0,0 +1,198 @@
+;; Test where variable set/binding/location decorations are different between
+;; src and dst fragment shaders.
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %63 "main" %4 %22
+OpExecutionMode %63 OriginUpperLeft
+OpSource GLSL 450
+OpName %4 "_ue"
+OpName %8 "_uf"
+OpName %11 "_ug"
+OpName %12 "_uA"
+OpMemberName %12 0 "_ux"
+OpName %14 "_uc"
+OpName %15 "_uB"
+OpMemberName %15 0 "_ux"
+OpName %20 "_ud"
+OpName %22 "_ucol"
+OpName %26 "ANGLEDepthRangeParams"
+OpMemberName %26 0 "near"
+OpMemberName %26 1 "far"
+OpMemberName %26 2 "diff"
+OpMemberName %26 3 "reserved"
+OpName %27 "ANGLEUniformBlock"
+OpMemberName %27 0 "viewport"
+OpMemberName %27 1 "clipDistancesEnabled"
+OpMemberName %27 2 "xfbActiveUnpaused"
+OpMemberName %27 3 "xfbVerticesPerInstance"
+OpMemberName %27 4 "numSamples"
+OpMemberName %27 5 "xfbBufferOffsets"
+OpMemberName %27 6 "acbBufferOffsets"
+OpMemberName %27 7 "depthRange"
+OpName %29 "ANGLEUniforms"
+OpName %33 "_uc"
+OpName %32 "_uh"
+OpName %49 "_ux"
+OpName %50 "_uy"
+OpName %48 "_ui"
+OpName %63 "main"
+OpName %65 "param"
+OpName %68 "param"
+OpName %73 "param"
+OpDecorate %4 Location 0
+OpDecorate %8 RelaxedPrecision
+OpDecorate %8 DescriptorSet 0
+OpDecorate %8 Binding 0
+OpDecorate %11 DescriptorSet 0
+OpDecorate %11 Binding 1
+OpMemberDecorate %12 0 Offset 0
+OpMemberDecorate %12 0 RelaxedPrecision
+OpDecorate %12 Block
+OpDecorate %14 DescriptorSet 0
+OpDecorate %14 Binding 2
+OpMemberDecorate %15 0 Offset 0
+OpMemberDecorate %15 0 RelaxedPrecision
+OpDecorate %15 BufferBlock
+OpDecorate %20 DescriptorSet 0
+OpDecorate %20 Binding 3
+OpDecorate %22 RelaxedPrecision
+OpDecorate %22 Location 0
+OpMemberDecorate %26 0 Offset 0
+OpMemberDecorate %26 1 Offset 4
+OpMemberDecorate %26 2 Offset 8
+OpMemberDecorate %26 3 Offset 12
+OpMemberDecorate %27 0 Offset 0
+OpMemberDecorate %27 1 Offset 16
+OpMemberDecorate %27 2 Offset 20
+OpMemberDecorate %27 3 Offset 24
+OpMemberDecorate %27 4 Offset 28
+OpMemberDecorate %27 5 Offset 32
+OpMemberDecorate %27 6 Offset 48
+OpMemberDecorate %27 7 Offset 64
+OpMemberDecorate %27 2 RelaxedPrecision
+OpMemberDecorate %27 4 RelaxedPrecision
+OpDecorate %27 Block
+OpDecorate %29 DescriptorSet 0
+OpDecorate %29 Binding 4
+OpDecorate %32 RelaxedPrecision
+OpDecorate %33 RelaxedPrecision
+OpDecorate %36 RelaxedPrecision
+OpDecorate %37 RelaxedPrecision
+OpDecorate %38 RelaxedPrecision
+OpDecorate %39 RelaxedPrecision
+OpDecorate %41 RelaxedPrecision
+OpDecorate %42 RelaxedPrecision
+OpDecorate %43 RelaxedPrecision
+OpDecorate %48 RelaxedPrecision
+OpDecorate %49 RelaxedPrecision
+OpDecorate %50 RelaxedPrecision
+OpDecorate %52 RelaxedPrecision
+OpDecorate %53 RelaxedPrecision
+OpDecorate %54 RelaxedPrecision
+OpDecorate %55 RelaxedPrecision
+OpDecorate %56 RelaxedPrecision
+OpDecorate %57 RelaxedPrecision
+OpDecorate %58 RelaxedPrecision
+OpDecorate %59 RelaxedPrecision
+OpDecorate %60 RelaxedPrecision
+OpDecorate %67 RelaxedPrecision
+OpDecorate %68 RelaxedPrecision
+OpDecorate %72 RelaxedPrecision
+OpDecorate %73 RelaxedPrecision
+OpDecorate %75 RelaxedPrecision
+OpDecorate %76 RelaxedPrecision
+OpDecorate %77 RelaxedPrecision
+OpDecorate %80 RelaxedPrecision
+OpDecorate %81 RelaxedPrecision
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%5 = OpTypeImage %1 2D 0 0 0 1 Unknown
+%6 = OpTypeSampledImage %5
+%9 = OpTypeImage %1 2D 0 0 0 2 Rgba8
+%12 = OpTypeStruct %2
+%15 = OpTypeStruct %2
+%16 = OpTypeInt 32 0
+%17 = OpConstant %16 2
+%18 = OpTypeArray %15 %17
+%23 = OpTypeInt 32 1
+%24 = OpTypeVector %23 4
+%25 = OpTypeVector %16 4
+%26 = OpTypeStruct %1 %1 %1 %1
+%27 = OpTypeStruct %2 %16 %16 %23 %23 %24 %25 %26
+%35 = OpTypeVector %1 2
+%40 = OpTypeVector %23 2
+%61 = OpTypeVoid
+%69 = OpConstant %16 0
+%78 = OpConstant %16 1
+%3 = OpTypePointer Input %2
+%7 = OpTypePointer UniformConstant %6
+%10 = OpTypePointer UniformConstant %9
+%13 = OpTypePointer Uniform %12
+%19 = OpTypePointer Uniform %18
+%21 = OpTypePointer Output %2
+%28 = OpTypePointer Uniform %27
+%30 = OpTypePointer Function %2
+%70 = OpTypePointer Uniform %2
+%31 = OpTypeFunction %2 %30
+%47 = OpTypeFunction %2 %30 %30
+%62 = OpTypeFunction %61
+%4 = OpVariable %3 Input
+%8 = OpVariable %7 UniformConstant
+%11 = OpVariable %10 UniformConstant
+%14 = OpVariable %13 Uniform
+%20 = OpVariable %19 Uniform
+%22 = OpVariable %21 Output
+%29 = OpVariable %28 Uniform
+%32 = OpFunction %2 None %31
+%33 = OpFunctionParameter %30
+%34 = OpLabel
+%36 = OpLoad %6 %8
+%37 = OpLoad %2 %33
+%38 = OpVectorShuffle %35 %37 %37 0 1
+%39 = OpImageSampleImplicitLod %2 %36 %38
+%41 = OpLoad %2 %33
+%42 = OpVectorShuffle %35 %41 %41 2 3
+%43 = OpConvertFToS %40 %42
+%44 = OpLoad %9 %11
+%45 = OpImageRead %2 %44 %43
+%46 = OpFAdd %2 %39 %45
+OpReturnValue %46
+OpFunctionEnd
+%48 = OpFunction %2 None %47
+%49 = OpFunctionParameter %30
+%50 = OpFunctionParameter %30
+%51 = OpLabel
+%52 = OpLoad %2 %49
+%53 = OpVectorShuffle %35 %52 %52 0 1
+%54 = OpLoad %2 %50
+%55 = OpVectorShuffle %35 %54 %54 2 3
+%56 = OpCompositeExtract %1 %53 0
+%57 = OpCompositeExtract %1 %53 1
+%58 = OpCompositeExtract %1 %55 0
+%59 = OpCompositeExtract %1 %55 1
+%60 = OpCompositeConstruct %2 %56 %57 %58 %59
+OpReturnValue %60
+OpFunctionEnd
+%63 = OpFunction %61 None %62
+%64 = OpLabel
+%65 = OpVariable %30 Function
+%68 = OpVariable %30 Function
+%73 = OpVariable %30 Function
+%66 = OpLoad %2 %4
+OpStore %65 %66
+%67 = OpFunctionCall %2 %32 %65
+%71 = OpAccessChain %70 %14 %69
+%72 = OpLoad %2 %71
+OpStore %68 %72
+%74 = OpAccessChain %70 %20 %69 %69
+%75 = OpLoad %2 %74
+OpStore %73 %75
+%76 = OpFunctionCall %2 %48 %68 %73
+%77 = OpFAdd %2 %67 %76
+%79 = OpAccessChain %70 %20 %78 %69
+%80 = OpLoad %2 %79
+%81 = OpFAdd %2 %77 %80
+OpStore %22 %81
+OpReturn
+OpFunctionEnd
+
diff --git a/test/diff/diff_files/different_decorations_vertex_autogen.cpp b/test/diff/diff_files/different_decorations_vertex_autogen.cpp
new file mode 100644
index 0000000..f65ee5a
--- /dev/null
+++ b/test/diff/diff_files/different_decorations_vertex_autogen.cpp
@@ -0,0 +1,1322 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Test where variable set/binding/location decorations are different between
+// src and dst vertex shaders.
+constexpr char kSrc[] = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %40 "main" %4 %5 %6 %8 %20 %25
+OpSource GLSL 450
+OpName %4 "_ub"
+OpName %5 "_uc"
+OpName %6 "_ud"
+OpName %8 "_ue"
+OpName %9 "defaultUniformsVS"
+OpMemberName %9 0 "_ua"
+OpName %11 ""
+OpName %16 "ANGLEDepthRangeParams"
+OpMemberName %16 0 "near"
+OpMemberName %16 1 "far"
+OpMemberName %16 2 "diff"
+OpMemberName %16 3 "reserved"
+OpName %17 "ANGLEUniformBlock"
+OpMemberName %17 0 "viewport"
+OpMemberName %17 1 "clipDistancesEnabled"
+OpMemberName %17 2 "xfbActiveUnpaused"
+OpMemberName %17 3 "xfbVerticesPerInstance"
+OpMemberName %17 4 "numSamples"
+OpMemberName %17 5 "xfbBufferOffsets"
+OpMemberName %17 6 "acbBufferOffsets"
+OpMemberName %17 7 "depthRange"
+OpName %19 "ANGLEUniforms"
+OpName %20 "ANGLEXfbPosition"
+OpName %23 "gl_PerVertex"
+OpMemberName %23 0 "gl_Position"
+OpMemberName %23 1 "gl_PointSize"
+OpMemberName %23 2 "gl_ClipDistance"
+OpMemberName %23 3 "gl_CullDistance"
+OpName %25 ""
+OpName %29 "_ua"
+OpName %28 "_uf"
+OpName %33 "_uf"
+OpName %32 "_ug"
+OpName %40 "main"
+OpName %42 "param"
+OpName %50 "param"
+OpName %53 "param"
+OpDecorate %4 Location 0
+OpDecorate %5 Location 1
+OpDecorate %6 Location 2
+OpDecorate %8 Location 0
+OpMemberDecorate %9 0 Offset 0
+OpDecorate %9 Block
+OpDecorate %11 DescriptorSet 0
+OpDecorate %11 Binding 0
+OpMemberDecorate %16 0 Offset 0
+OpMemberDecorate %16 1 Offset 4
+OpMemberDecorate %16 2 Offset 8
+OpMemberDecorate %16 3 Offset 12
+OpMemberDecorate %17 0 Offset 0
+OpMemberDecorate %17 1 Offset 16
+OpMemberDecorate %17 2 Offset 20
+OpMemberDecorate %17 3 Offset 24
+OpMemberDecorate %17 4 Offset 28
+OpMemberDecorate %17 5 Offset 32
+OpMemberDecorate %17 6 Offset 48
+OpMemberDecorate %17 7 Offset 64
+OpMemberDecorate %17 2 RelaxedPrecision
+OpMemberDecorate %17 4 RelaxedPrecision
+OpDecorate %17 Block
+OpDecorate %19 DescriptorSet 0
+OpDecorate %19 Binding 1
+OpDecorate %20 Location 1
+OpMemberDecorate %23 0 BuiltIn Position
+OpMemberDecorate %23 1 BuiltIn PointSize
+OpMemberDecorate %23 2 BuiltIn ClipDistance
+OpMemberDecorate %23 3 BuiltIn CullDistance
+OpDecorate %23 Block
+OpDecorate %28 RelaxedPrecision
+OpDecorate %29 RelaxedPrecision
+OpDecorate %31 RelaxedPrecision
+OpDecorate %32 RelaxedPrecision
+OpDecorate %33 RelaxedPrecision
+OpDecorate %35 RelaxedPrecision
+OpDecorate %36 RelaxedPrecision
+OpDecorate %37 RelaxedPrecision
+OpDecorate %44 RelaxedPrecision
+OpDecorate %52 RelaxedPrecision
+OpDecorate %55 RelaxedPrecision
+OpDecorate %56 RelaxedPrecision
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%9 = OpTypeStruct %2
+%12 = OpTypeInt 32 0
+%13 = OpTypeInt 32 1
+%14 = OpTypeVector %13 4
+%15 = OpTypeVector %12 4
+%16 = OpTypeStruct %1 %1 %1 %1
+%17 = OpTypeStruct %2 %12 %12 %13 %13 %14 %15 %16
+%21 = OpConstant %12 8
+%22 = OpTypeArray %1 %21
+%23 = OpTypeStruct %2 %1 %22 %22
+%38 = OpTypeVoid
+%45 = OpConstant %12 0
+%3 = OpTypePointer Input %2
+%7 = OpTypePointer Output %2
+%10 = OpTypePointer Uniform %9
+%18 = OpTypePointer Uniform %17
+%24 = OpTypePointer Output %23
+%26 = OpTypePointer Function %2
+%46 = OpTypePointer Uniform %2
+%27 = OpTypeFunction %2 %26
+%39 = OpTypeFunction %38
+%4 = OpVariable %3 Input
+%5 = OpVariable %3 Input
+%6 = OpVariable %3 Input
+%8 = OpVariable %7 Output
+%11 = OpVariable %10 Uniform
+%19 = OpVariable %18 Uniform
+%20 = OpVariable %7 Output
+%25 = OpVariable %24 Output
+%28 = OpFunction %2 None %27
+%29 = OpFunctionParameter %26
+%30 = OpLabel
+%31 = OpLoad %2 %29
+OpReturnValue %31
+OpFunctionEnd
+%32 = OpFunction %2 None %27
+%33 = OpFunctionParameter %26
+%34 = OpLabel
+%35 = OpLoad %2 %33
+%36 = OpLoad %2 %33
+%37 = OpFAdd %2 %35 %36
+OpReturnValue %37
+OpFunctionEnd
+%40 = OpFunction %38 None %39
+%41 = OpLabel
+%42 = OpVariable %26 Function
+%50 = OpVariable %26 Function
+%53 = OpVariable %26 Function
+%43 = OpLoad %2 %4
+OpStore %42 %43
+%44 = OpFunctionCall %2 %28 %42
+%47 = OpAccessChain %46 %11 %45
+%48 = OpLoad %2 %47
+%49 = OpFAdd %2 %44 %48
+OpStore %8 %49
+%51 = OpLoad %2 %5
+OpStore %50 %51
+%52 = OpFunctionCall %2 %32 %50
+%54 = OpLoad %2 %6
+OpStore %53 %54
+%55 = OpFunctionCall %2 %28 %53
+%56 = OpFAdd %2 %52 %55
+%57 = OpAccessChain %7 %25 %45
+OpStore %57 %56
+OpReturn
+OpFunctionEnd
+)";
+constexpr char kDst[] = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %40 "main" %4 %5 %6 %8 %25
+OpSource GLSL 450
+OpName %4 "_ub"
+OpName %5 "_uc"
+OpName %6 "_ud"
+OpName %8 "_ue"
+OpName %9 "defaultUniformsVS"
+OpMemberName %9 0 "_ua"
+OpName %11 ""
+OpName %16 "ANGLEDepthRangeParams"
+OpMemberName %16 0 "near"
+OpMemberName %16 1 "far"
+OpMemberName %16 2 "diff"
+OpMemberName %16 3 "reserved"
+OpName %17 "ANGLEUniformBlock"
+OpMemberName %17 0 "viewport"
+OpMemberName %17 1 "clipDistancesEnabled"
+OpMemberName %17 2 "xfbActiveUnpaused"
+OpMemberName %17 3 "xfbVerticesPerInstance"
+OpMemberName %17 4 "numSamples"
+OpMemberName %17 5 "xfbBufferOffsets"
+OpMemberName %17 6 "acbBufferOffsets"
+OpMemberName %17 7 "depthRange"
+OpName %19 "ANGLEUniforms"
+OpName %23 "gl_PerVertex"
+OpMemberName %23 0 "gl_Position"
+OpName %25 ""
+OpName %29 "_ua"
+OpName %28 "_uf"
+OpName %33 "_uf"
+OpName %32 "_ug"
+OpName %40 "main"
+OpName %42 "param"
+OpName %50 "param"
+OpName %53 "param"
+OpDecorate %4 Location 1
+OpDecorate %5 Location 2
+OpDecorate %6 Location 0
+OpDecorate %8 Location 1
+OpMemberDecorate %9 0 Offset 0
+OpDecorate %9 Block
+OpDecorate %11 DescriptorSet 0
+OpDecorate %11 Binding 1
+OpMemberDecorate %16 0 Offset 0
+OpMemberDecorate %16 1 Offset 4
+OpMemberDecorate %16 2 Offset 8
+OpMemberDecorate %16 3 Offset 12
+OpMemberDecorate %17 0 Offset 0
+OpMemberDecorate %17 1 Offset 16
+OpMemberDecorate %17 2 Offset 20
+OpMemberDecorate %17 3 Offset 24
+OpMemberDecorate %17 4 Offset 28
+OpMemberDecorate %17 5 Offset 32
+OpMemberDecorate %17 6 Offset 48
+OpMemberDecorate %17 7 Offset 64
+OpMemberDecorate %17 2 RelaxedPrecision
+OpMemberDecorate %17 4 RelaxedPrecision
+OpDecorate %17 Block
+OpDecorate %19 DescriptorSet 2
+OpDecorate %19 Binding 0
+OpMemberDecorate %23 0 BuiltIn Position
+OpDecorate %23 Block
+OpDecorate %28 RelaxedPrecision
+OpDecorate %29 RelaxedPrecision
+OpDecorate %31 RelaxedPrecision
+OpDecorate %32 RelaxedPrecision
+OpDecorate %33 RelaxedPrecision
+OpDecorate %35 RelaxedPrecision
+OpDecorate %36 RelaxedPrecision
+OpDecorate %37 RelaxedPrecision
+OpDecorate %44 RelaxedPrecision
+OpDecorate %52 RelaxedPrecision
+OpDecorate %55 RelaxedPrecision
+OpDecorate %56 RelaxedPrecision
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%9 = OpTypeStruct %2
+%12 = OpTypeInt 32 0
+%13 = OpTypeInt 32 1
+%14 = OpTypeVector %13 4
+%15 = OpTypeVector %12 4
+%16 = OpTypeStruct %1 %1 %1 %1
+%17 = OpTypeStruct %2 %12 %12 %13 %13 %14 %15 %16
+%21 = OpConstant %12 8
+%22 = OpTypeArray %1 %21
+%23 = OpTypeStruct %2
+%38 = OpTypeVoid
+%45 = OpConstant %12 0
+%58 = OpTypePointer Private %2
+%3 = OpTypePointer Input %2
+%59 = OpTypePointer Private %2
+%7 = OpTypePointer Output %2
+%10 = OpTypePointer Uniform %9
+%18 = OpTypePointer Uniform %17
+%24 = OpTypePointer Output %23
+%26 = OpTypePointer Function %2
+%46 = OpTypePointer Uniform %2
+%27 = OpTypeFunction %2 %26
+%39 = OpTypeFunction %38
+%4 = OpVariable %3 Input
+%5 = OpVariable %3 Input
+%6 = OpVariable %3 Input
+%8 = OpVariable %7 Output
+%11 = OpVariable %10 Uniform
+%19 = OpVariable %18 Uniform
+%20 = OpVariable %59 Private
+%25 = OpVariable %24 Output
+%60 = OpConstant %13 0
+%61 = OpConstant %1 0.5
+%28 = OpFunction %2 None %27
+%29 = OpFunctionParameter %26
+%30 = OpLabel
+%31 = OpLoad %2 %29
+OpReturnValue %31
+OpFunctionEnd
+%32 = OpFunction %2 None %27
+%33 = OpFunctionParameter %26
+%34 = OpLabel
+%35 = OpLoad %2 %33
+%36 = OpLoad %2 %33
+%37 = OpFAdd %2 %35 %36
+OpReturnValue %37
+OpFunctionEnd
+%40 = OpFunction %38 None %39
+%41 = OpLabel
+%42 = OpVariable %26 Function
+%50 = OpVariable %26 Function
+%53 = OpVariable %26 Function
+%43 = OpLoad %2 %4
+OpStore %42 %43
+%44 = OpFunctionCall %2 %28 %42
+%47 = OpAccessChain %46 %11 %45
+%48 = OpLoad %2 %47
+%49 = OpFAdd %2 %44 %48
+OpStore %8 %49
+%51 = OpLoad %2 %5
+OpStore %50 %51
+%52 = OpFunctionCall %2 %32 %50
+%54 = OpLoad %2 %6
+OpStore %53 %54
+%55 = OpFunctionCall %2 %28 %53
+%56 = OpFAdd %2 %52 %55
+%57 = OpAccessChain %7 %25 %45
+OpStore %57 %56
+%62 = OpAccessChain %7 %25 %60
+%63 = OpLoad %2 %62
+%64 = OpCompositeExtract %1 %63 0
+%65 = OpCompositeExtract %1 %63 1
+%66 = OpCompositeExtract %1 %63 2
+%67 = OpCompositeExtract %1 %63 3
+%69 = OpFNegate %1 %64
+%70 = OpFAdd %1 %66 %67
+%71 = OpFMul %1 %70 %61
+%68 = OpCompositeConstruct %2 %65 %69 %71 %67
+OpStore %62 %68
+OpReturn
+OpFunctionEnd
+)";
+
+TEST(DiffTest, DifferentDecorationsVertex) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 58
++; Bound: 73
+ ; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+-OpEntryPoint Vertex %40 "main" %4 %5 %6 %8 %20 %25
++OpEntryPoint Vertex %40 "main" %4 %5 %6 %8 %25
+ OpSource GLSL 450
+ OpName %4 "_ub"
+ OpName %5 "_uc"
+ OpName %6 "_ud"
+ OpName %8 "_ue"
+ OpName %9 "defaultUniformsVS"
+ OpMemberName %9 0 "_ua"
+ OpName %11 ""
+ OpName %16 "ANGLEDepthRangeParams"
+ OpMemberName %16 0 "near"
+ OpMemberName %16 1 "far"
+ OpMemberName %16 2 "diff"
+ OpMemberName %16 3 "reserved"
+ OpName %17 "ANGLEUniformBlock"
+ OpMemberName %17 0 "viewport"
+ OpMemberName %17 1 "clipDistancesEnabled"
+ OpMemberName %17 2 "xfbActiveUnpaused"
+ OpMemberName %17 3 "xfbVerticesPerInstance"
+ OpMemberName %17 4 "numSamples"
+ OpMemberName %17 5 "xfbBufferOffsets"
+ OpMemberName %17 6 "acbBufferOffsets"
+ OpMemberName %17 7 "depthRange"
+ OpName %19 "ANGLEUniforms"
+-OpName %20 "ANGLEXfbPosition"
+ OpName %23 "gl_PerVertex"
+ OpMemberName %23 0 "gl_Position"
+-OpMemberName %23 1 "gl_PointSize"
+-OpMemberName %23 2 "gl_ClipDistance"
+-OpMemberName %23 3 "gl_CullDistance"
+ OpName %25 ""
+ OpName %29 "_ua"
+ OpName %28 "_uf"
+ OpName %33 "_uf"
+ OpName %32 "_ug"
+ OpName %40 "main"
+ OpName %42 "param"
+ OpName %50 "param"
+ OpName %53 "param"
+-OpDecorate %4 Location 0
++OpDecorate %4 Location 1
+-OpDecorate %5 Location 1
++OpDecorate %5 Location 2
+-OpDecorate %6 Location 2
++OpDecorate %6 Location 0
+-OpDecorate %8 Location 0
++OpDecorate %8 Location 1
+ OpMemberDecorate %9 0 Offset 0
+ OpDecorate %9 Block
+ OpDecorate %11 DescriptorSet 0
+-OpDecorate %11 Binding 0
++OpDecorate %11 Binding 1
+ OpMemberDecorate %16 0 Offset 0
+ OpMemberDecorate %16 1 Offset 4
+ OpMemberDecorate %16 2 Offset 8
+ OpMemberDecorate %16 3 Offset 12
+ OpMemberDecorate %17 0 Offset 0
+ OpMemberDecorate %17 1 Offset 16
+ OpMemberDecorate %17 2 Offset 20
+ OpMemberDecorate %17 3 Offset 24
+ OpMemberDecorate %17 4 Offset 28
+ OpMemberDecorate %17 5 Offset 32
+ OpMemberDecorate %17 6 Offset 48
+ OpMemberDecorate %17 7 Offset 64
+ OpMemberDecorate %17 2 RelaxedPrecision
+ OpMemberDecorate %17 4 RelaxedPrecision
+ OpDecorate %17 Block
+-OpDecorate %19 DescriptorSet 0
++OpDecorate %19 DescriptorSet 2
+-OpDecorate %19 Binding 1
++OpDecorate %19 Binding 0
+-OpDecorate %20 Location 1
+ OpMemberDecorate %23 0 BuiltIn Position
+-OpMemberDecorate %23 1 BuiltIn PointSize
+-OpMemberDecorate %23 2 BuiltIn ClipDistance
+-OpMemberDecorate %23 3 BuiltIn CullDistance
+ OpDecorate %23 Block
+ OpDecorate %28 RelaxedPrecision
+ OpDecorate %29 RelaxedPrecision
+ OpDecorate %31 RelaxedPrecision
+ OpDecorate %32 RelaxedPrecision
+ OpDecorate %33 RelaxedPrecision
+ OpDecorate %35 RelaxedPrecision
+ OpDecorate %36 RelaxedPrecision
+ OpDecorate %37 RelaxedPrecision
+ OpDecorate %44 RelaxedPrecision
+ OpDecorate %52 RelaxedPrecision
+ OpDecorate %55 RelaxedPrecision
+ OpDecorate %56 RelaxedPrecision
+ %1 = OpTypeFloat 32
+ %2 = OpTypeVector %1 4
+ %9 = OpTypeStruct %2
+ %12 = OpTypeInt 32 0
+ %13 = OpTypeInt 32 1
+ %14 = OpTypeVector %13 4
+ %15 = OpTypeVector %12 4
+ %16 = OpTypeStruct %1 %1 %1 %1
+ %17 = OpTypeStruct %2 %12 %12 %13 %13 %14 %15 %16
+ %21 = OpConstant %12 8
+ %22 = OpTypeArray %1 %21
+-%23 = OpTypeStruct %2 %1 %22 %22
++%23 = OpTypeStruct %2
+ %38 = OpTypeVoid
+ %45 = OpConstant %12 0
++%59 = OpTypePointer Private %2
+ %3 = OpTypePointer Input %2
++%60 = OpTypePointer Private %2
+ %7 = OpTypePointer Output %2
+ %10 = OpTypePointer Uniform %9
+ %18 = OpTypePointer Uniform %17
+ %24 = OpTypePointer Output %23
+ %26 = OpTypePointer Function %2
+ %46 = OpTypePointer Uniform %2
+ %27 = OpTypeFunction %2 %26
+ %39 = OpTypeFunction %38
+ %4 = OpVariable %3 Input
+ %5 = OpVariable %3 Input
+ %6 = OpVariable %3 Input
+ %8 = OpVariable %7 Output
+ %11 = OpVariable %10 Uniform
+ %19 = OpVariable %18 Uniform
+-%20 = OpVariable %7 Output
++%58 = OpVariable %60 Private
+ %25 = OpVariable %24 Output
++%61 = OpConstant %13 0
++%62 = OpConstant %1 0.5
+ %28 = OpFunction %2 None %27
+ %29 = OpFunctionParameter %26
+ %30 = OpLabel
+ %31 = OpLoad %2 %29
+ OpReturnValue %31
+ OpFunctionEnd
+ %32 = OpFunction %2 None %27
+ %33 = OpFunctionParameter %26
+ %34 = OpLabel
+ %35 = OpLoad %2 %33
+ %36 = OpLoad %2 %33
+ %37 = OpFAdd %2 %35 %36
+ OpReturnValue %37
+ OpFunctionEnd
+ %40 = OpFunction %38 None %39
+ %41 = OpLabel
+ %42 = OpVariable %26 Function
+ %50 = OpVariable %26 Function
+ %53 = OpVariable %26 Function
+ %43 = OpLoad %2 %4
+ OpStore %42 %43
+ %44 = OpFunctionCall %2 %28 %42
+ %47 = OpAccessChain %46 %11 %45
+ %48 = OpLoad %2 %47
+ %49 = OpFAdd %2 %44 %48
+ OpStore %8 %49
+ %51 = OpLoad %2 %5
+ OpStore %50 %51
+ %52 = OpFunctionCall %2 %32 %50
+ %54 = OpLoad %2 %6
+ OpStore %53 %54
+ %55 = OpFunctionCall %2 %28 %53
+ %56 = OpFAdd %2 %52 %55
+ %57 = OpAccessChain %7 %25 %45
+ OpStore %57 %56
++%63 = OpAccessChain %7 %25 %61
++%64 = OpLoad %2 %63
++%65 = OpCompositeExtract %1 %64 0
++%66 = OpCompositeExtract %1 %64 1
++%67 = OpCompositeExtract %1 %64 2
++%68 = OpCompositeExtract %1 %64 3
++%70 = OpFNegate %1 %65
++%71 = OpFAdd %1 %67 %68
++%72 = OpFMul %1 %71 %62
++%69 = OpCompositeConstruct %2 %66 %70 %72 %68
++OpStore %63 %69
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, DifferentDecorationsVertexNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %40 "main" %4 %5 %6 %8 %20 %25
+OpSource GLSL 450
+OpDecorate %4 Location 0
+OpDecorate %5 Location 1
+OpDecorate %6 Location 2
+OpDecorate %8 Location 0
+OpMemberDecorate %9 0 Offset 0
+OpDecorate %9 Block
+OpDecorate %11 DescriptorSet 0
+OpDecorate %11 Binding 0
+OpMemberDecorate %16 0 Offset 0
+OpMemberDecorate %16 1 Offset 4
+OpMemberDecorate %16 2 Offset 8
+OpMemberDecorate %16 3 Offset 12
+OpMemberDecorate %17 0 Offset 0
+OpMemberDecorate %17 1 Offset 16
+OpMemberDecorate %17 2 Offset 20
+OpMemberDecorate %17 3 Offset 24
+OpMemberDecorate %17 4 Offset 28
+OpMemberDecorate %17 5 Offset 32
+OpMemberDecorate %17 6 Offset 48
+OpMemberDecorate %17 7 Offset 64
+OpMemberDecorate %17 2 RelaxedPrecision
+OpMemberDecorate %17 4 RelaxedPrecision
+OpDecorate %17 Block
+OpDecorate %19 DescriptorSet 0
+OpDecorate %19 Binding 1
+OpDecorate %20 Location 1
+OpMemberDecorate %23 0 BuiltIn Position
+OpMemberDecorate %23 1 BuiltIn PointSize
+OpMemberDecorate %23 2 BuiltIn ClipDistance
+OpMemberDecorate %23 3 BuiltIn CullDistance
+OpDecorate %23 Block
+OpDecorate %28 RelaxedPrecision
+OpDecorate %29 RelaxedPrecision
+OpDecorate %31 RelaxedPrecision
+OpDecorate %32 RelaxedPrecision
+OpDecorate %33 RelaxedPrecision
+OpDecorate %35 RelaxedPrecision
+OpDecorate %36 RelaxedPrecision
+OpDecorate %37 RelaxedPrecision
+OpDecorate %44 RelaxedPrecision
+OpDecorate %52 RelaxedPrecision
+OpDecorate %55 RelaxedPrecision
+OpDecorate %56 RelaxedPrecision
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%9 = OpTypeStruct %2
+%12 = OpTypeInt 32 0
+%13 = OpTypeInt 32 1
+%14 = OpTypeVector %13 4
+%15 = OpTypeVector %12 4
+%16 = OpTypeStruct %1 %1 %1 %1
+%17 = OpTypeStruct %2 %12 %12 %13 %13 %14 %15 %16
+%21 = OpConstant %12 8
+%22 = OpTypeArray %1 %21
+%23 = OpTypeStruct %2 %1 %22 %22
+%38 = OpTypeVoid
+%45 = OpConstant %12 0
+%3 = OpTypePointer Input %2
+%7 = OpTypePointer Output %2
+%10 = OpTypePointer Uniform %9
+%18 = OpTypePointer Uniform %17
+%24 = OpTypePointer Output %23
+%26 = OpTypePointer Function %2
+%46 = OpTypePointer Uniform %2
+%27 = OpTypeFunction %2 %26
+%39 = OpTypeFunction %38
+%4 = OpVariable %3 Input
+%5 = OpVariable %3 Input
+%6 = OpVariable %3 Input
+%8 = OpVariable %7 Output
+%11 = OpVariable %10 Uniform
+%19 = OpVariable %18 Uniform
+%20 = OpVariable %7 Output
+%25 = OpVariable %24 Output
+%28 = OpFunction %2 None %27
+%29 = OpFunctionParameter %26
+%30 = OpLabel
+%31 = OpLoad %2 %29
+OpReturnValue %31
+OpFunctionEnd
+%32 = OpFunction %2 None %27
+%33 = OpFunctionParameter %26
+%34 = OpLabel
+%35 = OpLoad %2 %33
+%36 = OpLoad %2 %33
+%37 = OpFAdd %2 %35 %36
+OpReturnValue %37
+OpFunctionEnd
+%40 = OpFunction %38 None %39
+%41 = OpLabel
+%42 = OpVariable %26 Function
+%50 = OpVariable %26 Function
+%53 = OpVariable %26 Function
+%43 = OpLoad %2 %4
+OpStore %42 %43
+%44 = OpFunctionCall %2 %28 %42
+%47 = OpAccessChain %46 %11 %45
+%48 = OpLoad %2 %47
+%49 = OpFAdd %2 %44 %48
+OpStore %8 %49
+%51 = OpLoad %2 %5
+OpStore %50 %51
+%52 = OpFunctionCall %2 %32 %50
+%54 = OpLoad %2 %6
+OpStore %53 %54
+%55 = OpFunctionCall %2 %28 %53
+%56 = OpFAdd %2 %52 %55
+%57 = OpAccessChain %7 %25 %45
+OpStore %57 %56
+OpReturn
+OpFunctionEnd
+
+)";
+  constexpr char kDstNoDebug[] = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %40 "main" %4 %5 %6 %8 %25
+OpSource GLSL 450
+OpDecorate %4 Location 1
+OpDecorate %5 Location 2
+OpDecorate %6 Location 0
+OpDecorate %8 Location 1
+OpMemberDecorate %9 0 Offset 0
+OpDecorate %9 Block
+OpDecorate %11 DescriptorSet 0
+OpDecorate %11 Binding 1
+OpMemberDecorate %16 0 Offset 0
+OpMemberDecorate %16 1 Offset 4
+OpMemberDecorate %16 2 Offset 8
+OpMemberDecorate %16 3 Offset 12
+OpMemberDecorate %17 0 Offset 0
+OpMemberDecorate %17 1 Offset 16
+OpMemberDecorate %17 2 Offset 20
+OpMemberDecorate %17 3 Offset 24
+OpMemberDecorate %17 4 Offset 28
+OpMemberDecorate %17 5 Offset 32
+OpMemberDecorate %17 6 Offset 48
+OpMemberDecorate %17 7 Offset 64
+OpMemberDecorate %17 2 RelaxedPrecision
+OpMemberDecorate %17 4 RelaxedPrecision
+OpDecorate %17 Block
+OpDecorate %19 DescriptorSet 2
+OpDecorate %19 Binding 0
+OpMemberDecorate %23 0 BuiltIn Position
+OpDecorate %23 Block
+OpDecorate %28 RelaxedPrecision
+OpDecorate %29 RelaxedPrecision
+OpDecorate %31 RelaxedPrecision
+OpDecorate %32 RelaxedPrecision
+OpDecorate %33 RelaxedPrecision
+OpDecorate %35 RelaxedPrecision
+OpDecorate %36 RelaxedPrecision
+OpDecorate %37 RelaxedPrecision
+OpDecorate %44 RelaxedPrecision
+OpDecorate %52 RelaxedPrecision
+OpDecorate %55 RelaxedPrecision
+OpDecorate %56 RelaxedPrecision
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%9 = OpTypeStruct %2
+%12 = OpTypeInt 32 0
+%13 = OpTypeInt 32 1
+%14 = OpTypeVector %13 4
+%15 = OpTypeVector %12 4
+%16 = OpTypeStruct %1 %1 %1 %1
+%17 = OpTypeStruct %2 %12 %12 %13 %13 %14 %15 %16
+%21 = OpConstant %12 8
+%22 = OpTypeArray %1 %21
+%23 = OpTypeStruct %2
+%38 = OpTypeVoid
+%45 = OpConstant %12 0
+%58 = OpTypePointer Private %2
+%3 = OpTypePointer Input %2
+%59 = OpTypePointer Private %2
+%7 = OpTypePointer Output %2
+%10 = OpTypePointer Uniform %9
+%18 = OpTypePointer Uniform %17
+%24 = OpTypePointer Output %23
+%26 = OpTypePointer Function %2
+%46 = OpTypePointer Uniform %2
+%27 = OpTypeFunction %2 %26
+%39 = OpTypeFunction %38
+%4 = OpVariable %3 Input
+%5 = OpVariable %3 Input
+%6 = OpVariable %3 Input
+%8 = OpVariable %7 Output
+%11 = OpVariable %10 Uniform
+%19 = OpVariable %18 Uniform
+%20 = OpVariable %59 Private
+%25 = OpVariable %24 Output
+%60 = OpConstant %13 0
+%61 = OpConstant %1 0.5
+%28 = OpFunction %2 None %27
+%29 = OpFunctionParameter %26
+%30 = OpLabel
+%31 = OpLoad %2 %29
+OpReturnValue %31
+OpFunctionEnd
+%32 = OpFunction %2 None %27
+%33 = OpFunctionParameter %26
+%34 = OpLabel
+%35 = OpLoad %2 %33
+%36 = OpLoad %2 %33
+%37 = OpFAdd %2 %35 %36
+OpReturnValue %37
+OpFunctionEnd
+%40 = OpFunction %38 None %39
+%41 = OpLabel
+%42 = OpVariable %26 Function
+%50 = OpVariable %26 Function
+%53 = OpVariable %26 Function
+%43 = OpLoad %2 %4
+OpStore %42 %43
+%44 = OpFunctionCall %2 %28 %42
+%47 = OpAccessChain %46 %11 %45
+%48 = OpLoad %2 %47
+%49 = OpFAdd %2 %44 %48
+OpStore %8 %49
+%51 = OpLoad %2 %5
+OpStore %50 %51
+%52 = OpFunctionCall %2 %32 %50
+%54 = OpLoad %2 %6
+OpStore %53 %54
+%55 = OpFunctionCall %2 %28 %53
+%56 = OpFAdd %2 %52 %55
+%57 = OpAccessChain %7 %25 %45
+OpStore %57 %56
+%62 = OpAccessChain %7 %25 %60
+%63 = OpLoad %2 %62
+%64 = OpCompositeExtract %1 %63 0
+%65 = OpCompositeExtract %1 %63 1
+%66 = OpCompositeExtract %1 %63 2
+%67 = OpCompositeExtract %1 %63 3
+%69 = OpFNegate %1 %64
+%70 = OpFAdd %1 %66 %67
+%71 = OpFMul %1 %70 %61
+%68 = OpCompositeConstruct %2 %65 %69 %71 %67
+OpStore %62 %68
+OpReturn
+OpFunctionEnd
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 58
++; Bound: 79
+ ; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+-OpEntryPoint Vertex %40 "main" %4 %5 %6 %8 %20 %25
++OpEntryPoint Vertex %40 "main" %5 %6 %4 %20 %25
+ OpSource GLSL 450
+ OpDecorate %4 Location 0
+ OpDecorate %5 Location 1
+ OpDecorate %6 Location 2
+-OpDecorate %8 Location 0
+ OpMemberDecorate %9 0 Offset 0
+ OpDecorate %9 Block
+-OpDecorate %11 DescriptorSet 0
++OpDecorate %11 DescriptorSet 2
+ OpDecorate %11 Binding 0
+ OpMemberDecorate %16 0 Offset 0
+ OpMemberDecorate %16 1 Offset 4
+ OpMemberDecorate %16 2 Offset 8
+ OpMemberDecorate %16 3 Offset 12
+ OpMemberDecorate %17 0 Offset 0
+ OpMemberDecorate %17 1 Offset 16
+ OpMemberDecorate %17 2 Offset 20
+ OpMemberDecorate %17 3 Offset 24
+ OpMemberDecorate %17 4 Offset 28
+ OpMemberDecorate %17 5 Offset 32
+ OpMemberDecorate %17 6 Offset 48
+ OpMemberDecorate %17 7 Offset 64
+ OpMemberDecorate %17 2 RelaxedPrecision
+ OpMemberDecorate %17 4 RelaxedPrecision
+ OpDecorate %17 Block
+ OpDecorate %19 DescriptorSet 0
+ OpDecorate %19 Binding 1
+ OpDecorate %20 Location 1
+ OpMemberDecorate %23 0 BuiltIn Position
+-OpMemberDecorate %23 1 BuiltIn PointSize
+-OpMemberDecorate %23 2 BuiltIn ClipDistance
+-OpMemberDecorate %23 3 BuiltIn CullDistance
+ OpDecorate %23 Block
+ OpDecorate %28 RelaxedPrecision
+-OpDecorate %29 RelaxedPrecision
++OpDecorate %59 RelaxedPrecision
+ OpDecorate %31 RelaxedPrecision
+ OpDecorate %32 RelaxedPrecision
+-OpDecorate %33 RelaxedPrecision
++OpDecorate %60 RelaxedPrecision
+ OpDecorate %35 RelaxedPrecision
+ OpDecorate %36 RelaxedPrecision
+ OpDecorate %37 RelaxedPrecision
+ OpDecorate %44 RelaxedPrecision
+ OpDecorate %52 RelaxedPrecision
+ OpDecorate %55 RelaxedPrecision
+ OpDecorate %56 RelaxedPrecision
+ %1 = OpTypeFloat 32
+ %2 = OpTypeVector %1 4
+ %9 = OpTypeStruct %2
+ %12 = OpTypeInt 32 0
+ %13 = OpTypeInt 32 1
+ %14 = OpTypeVector %13 4
+ %15 = OpTypeVector %12 4
+ %16 = OpTypeStruct %1 %1 %1 %1
+ %17 = OpTypeStruct %2 %12 %12 %13 %13 %14 %15 %16
+ %21 = OpConstant %12 8
+ %22 = OpTypeArray %1 %21
+-%23 = OpTypeStruct %2 %1 %22 %22
++%23 = OpTypeStruct %2
+ %38 = OpTypeVoid
+ %45 = OpConstant %12 0
++%65 = OpTypePointer Private %2
+ %3 = OpTypePointer Input %2
++%66 = OpTypePointer Private %2
+ %7 = OpTypePointer Output %2
+ %10 = OpTypePointer Uniform %9
+ %18 = OpTypePointer Uniform %17
+ %24 = OpTypePointer Output %23
+ %26 = OpTypePointer Function %2
+ %46 = OpTypePointer Uniform %2
+ %27 = OpTypeFunction %2 %26
+ %39 = OpTypeFunction %38
+ %4 = OpVariable %3 Input
+ %5 = OpVariable %3 Input
+ %6 = OpVariable %3 Input
+-%8 = OpVariable %7 Output
+-%11 = OpVariable %10 Uniform
++%11 = OpVariable %18 Uniform
+-%19 = OpVariable %18 Uniform
++%19 = OpVariable %10 Uniform
+ %20 = OpVariable %7 Output
++%58 = OpVariable %66 Private
+ %25 = OpVariable %24 Output
++%67 = OpConstant %13 0
++%68 = OpConstant %1 0.5
+ %28 = OpFunction %2 None %27
+-%29 = OpFunctionParameter %26
++%59 = OpFunctionParameter %26
+ %30 = OpLabel
+-%31 = OpLoad %2 %29
++%31 = OpLoad %2 %59
+ OpReturnValue %31
+ OpFunctionEnd
+ %32 = OpFunction %2 None %27
+-%33 = OpFunctionParameter %26
++%60 = OpFunctionParameter %26
+ %34 = OpLabel
+-%35 = OpLoad %2 %33
++%35 = OpLoad %2 %60
+-%36 = OpLoad %2 %33
++%36 = OpLoad %2 %60
+ %37 = OpFAdd %2 %35 %36
+ OpReturnValue %37
+ OpFunctionEnd
+ %40 = OpFunction %38 None %39
+ %41 = OpLabel
+ %42 = OpVariable %26 Function
+ %50 = OpVariable %26 Function
+ %53 = OpVariable %26 Function
+-%43 = OpLoad %2 %4
++%61 = OpLoad %2 %5
+-OpStore %42 %43
++OpStore %42 %61
+ %44 = OpFunctionCall %2 %28 %42
+-%47 = OpAccessChain %46 %11 %45
++%62 = OpAccessChain %46 %19 %45
+-%48 = OpLoad %2 %47
++%48 = OpLoad %2 %62
+ %49 = OpFAdd %2 %44 %48
+-OpStore %8 %49
++OpStore %20 %49
+-%51 = OpLoad %2 %5
++%63 = OpLoad %2 %6
+-OpStore %50 %51
++OpStore %50 %63
+ %52 = OpFunctionCall %2 %32 %50
+-%54 = OpLoad %2 %6
++%64 = OpLoad %2 %4
+-OpStore %53 %54
++OpStore %53 %64
+ %55 = OpFunctionCall %2 %28 %53
+ %56 = OpFAdd %2 %52 %55
+ %57 = OpAccessChain %7 %25 %45
+ OpStore %57 %56
++%69 = OpAccessChain %7 %25 %67
++%70 = OpLoad %2 %69
++%71 = OpCompositeExtract %1 %70 0
++%72 = OpCompositeExtract %1 %70 1
++%73 = OpCompositeExtract %1 %70 2
++%74 = OpCompositeExtract %1 %70 3
++%76 = OpFNegate %1 %71
++%77 = OpFAdd %1 %73 %74
++%78 = OpFMul %1 %77 %68
++%75 = OpCompositeConstruct %2 %72 %76 %78 %74
++OpStore %69 %75
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+TEST(DiffTest, DifferentDecorationsVertexIgnoreSetBinding) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 58
++; Bound: 73
+ ; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+-OpEntryPoint Vertex %40 "main" %4 %5 %6 %8 %20 %25
++OpEntryPoint Vertex %40 "main" %4 %5 %6 %8 %25
+ OpSource GLSL 450
+ OpName %4 "_ub"
+ OpName %5 "_uc"
+ OpName %6 "_ud"
+ OpName %8 "_ue"
+ OpName %9 "defaultUniformsVS"
+ OpMemberName %9 0 "_ua"
+ OpName %11 ""
+ OpName %16 "ANGLEDepthRangeParams"
+ OpMemberName %16 0 "near"
+ OpMemberName %16 1 "far"
+ OpMemberName %16 2 "diff"
+ OpMemberName %16 3 "reserved"
+ OpName %17 "ANGLEUniformBlock"
+ OpMemberName %17 0 "viewport"
+ OpMemberName %17 1 "clipDistancesEnabled"
+ OpMemberName %17 2 "xfbActiveUnpaused"
+ OpMemberName %17 3 "xfbVerticesPerInstance"
+ OpMemberName %17 4 "numSamples"
+ OpMemberName %17 5 "xfbBufferOffsets"
+ OpMemberName %17 6 "acbBufferOffsets"
+ OpMemberName %17 7 "depthRange"
+ OpName %19 "ANGLEUniforms"
+-OpName %20 "ANGLEXfbPosition"
+ OpName %23 "gl_PerVertex"
+ OpMemberName %23 0 "gl_Position"
+-OpMemberName %23 1 "gl_PointSize"
+-OpMemberName %23 2 "gl_ClipDistance"
+-OpMemberName %23 3 "gl_CullDistance"
+ OpName %25 ""
+ OpName %29 "_ua"
+ OpName %28 "_uf"
+ OpName %33 "_uf"
+ OpName %32 "_ug"
+ OpName %40 "main"
+ OpName %42 "param"
+ OpName %50 "param"
+ OpName %53 "param"
+-OpDecorate %4 Location 0
++OpDecorate %4 Location 1
+-OpDecorate %5 Location 1
++OpDecorate %5 Location 2
+-OpDecorate %6 Location 2
++OpDecorate %6 Location 0
+-OpDecorate %8 Location 0
++OpDecorate %8 Location 1
+ OpMemberDecorate %9 0 Offset 0
+ OpDecorate %9 Block
+ OpDecorate %11 DescriptorSet 0
+-OpDecorate %11 Binding 0
++OpDecorate %11 Binding 1
+ OpMemberDecorate %16 0 Offset 0
+ OpMemberDecorate %16 1 Offset 4
+ OpMemberDecorate %16 2 Offset 8
+ OpMemberDecorate %16 3 Offset 12
+ OpMemberDecorate %17 0 Offset 0
+ OpMemberDecorate %17 1 Offset 16
+ OpMemberDecorate %17 2 Offset 20
+ OpMemberDecorate %17 3 Offset 24
+ OpMemberDecorate %17 4 Offset 28
+ OpMemberDecorate %17 5 Offset 32
+ OpMemberDecorate %17 6 Offset 48
+ OpMemberDecorate %17 7 Offset 64
+ OpMemberDecorate %17 2 RelaxedPrecision
+ OpMemberDecorate %17 4 RelaxedPrecision
+ OpDecorate %17 Block
+-OpDecorate %19 DescriptorSet 0
++OpDecorate %19 DescriptorSet 2
+-OpDecorate %19 Binding 1
++OpDecorate %19 Binding 0
+-OpDecorate %20 Location 1
+ OpMemberDecorate %23 0 BuiltIn Position
+-OpMemberDecorate %23 1 BuiltIn PointSize
+-OpMemberDecorate %23 2 BuiltIn ClipDistance
+-OpMemberDecorate %23 3 BuiltIn CullDistance
+ OpDecorate %23 Block
+ OpDecorate %28 RelaxedPrecision
+ OpDecorate %29 RelaxedPrecision
+ OpDecorate %31 RelaxedPrecision
+ OpDecorate %32 RelaxedPrecision
+ OpDecorate %33 RelaxedPrecision
+ OpDecorate %35 RelaxedPrecision
+ OpDecorate %36 RelaxedPrecision
+ OpDecorate %37 RelaxedPrecision
+ OpDecorate %44 RelaxedPrecision
+ OpDecorate %52 RelaxedPrecision
+ OpDecorate %55 RelaxedPrecision
+ OpDecorate %56 RelaxedPrecision
+ %1 = OpTypeFloat 32
+ %2 = OpTypeVector %1 4
+ %9 = OpTypeStruct %2
+ %12 = OpTypeInt 32 0
+ %13 = OpTypeInt 32 1
+ %14 = OpTypeVector %13 4
+ %15 = OpTypeVector %12 4
+ %16 = OpTypeStruct %1 %1 %1 %1
+ %17 = OpTypeStruct %2 %12 %12 %13 %13 %14 %15 %16
+ %21 = OpConstant %12 8
+ %22 = OpTypeArray %1 %21
+-%23 = OpTypeStruct %2 %1 %22 %22
++%23 = OpTypeStruct %2
+ %38 = OpTypeVoid
+ %45 = OpConstant %12 0
++%59 = OpTypePointer Private %2
+ %3 = OpTypePointer Input %2
++%60 = OpTypePointer Private %2
+ %7 = OpTypePointer Output %2
+ %10 = OpTypePointer Uniform %9
+ %18 = OpTypePointer Uniform %17
+ %24 = OpTypePointer Output %23
+ %26 = OpTypePointer Function %2
+ %46 = OpTypePointer Uniform %2
+ %27 = OpTypeFunction %2 %26
+ %39 = OpTypeFunction %38
+ %4 = OpVariable %3 Input
+ %5 = OpVariable %3 Input
+ %6 = OpVariable %3 Input
+ %8 = OpVariable %7 Output
+ %11 = OpVariable %10 Uniform
+ %19 = OpVariable %18 Uniform
+-%20 = OpVariable %7 Output
++%58 = OpVariable %60 Private
+ %25 = OpVariable %24 Output
++%61 = OpConstant %13 0
++%62 = OpConstant %1 0.5
+ %28 = OpFunction %2 None %27
+ %29 = OpFunctionParameter %26
+ %30 = OpLabel
+ %31 = OpLoad %2 %29
+ OpReturnValue %31
+ OpFunctionEnd
+ %32 = OpFunction %2 None %27
+ %33 = OpFunctionParameter %26
+ %34 = OpLabel
+ %35 = OpLoad %2 %33
+ %36 = OpLoad %2 %33
+ %37 = OpFAdd %2 %35 %36
+ OpReturnValue %37
+ OpFunctionEnd
+ %40 = OpFunction %38 None %39
+ %41 = OpLabel
+ %42 = OpVariable %26 Function
+ %50 = OpVariable %26 Function
+ %53 = OpVariable %26 Function
+ %43 = OpLoad %2 %4
+ OpStore %42 %43
+ %44 = OpFunctionCall %2 %28 %42
+ %47 = OpAccessChain %46 %11 %45
+ %48 = OpLoad %2 %47
+ %49 = OpFAdd %2 %44 %48
+ OpStore %8 %49
+ %51 = OpLoad %2 %5
+ OpStore %50 %51
+ %52 = OpFunctionCall %2 %32 %50
+ %54 = OpLoad %2 %6
+ OpStore %53 %54
+ %55 = OpFunctionCall %2 %28 %53
+ %56 = OpFAdd %2 %52 %55
+ %57 = OpAccessChain %7 %25 %45
+ OpStore %57 %56
++%63 = OpAccessChain %7 %25 %61
++%64 = OpLoad %2 %63
++%65 = OpCompositeExtract %1 %64 0
++%66 = OpCompositeExtract %1 %64 1
++%67 = OpCompositeExtract %1 %64 2
++%68 = OpCompositeExtract %1 %64 3
++%70 = OpFNegate %1 %65
++%71 = OpFAdd %1 %67 %68
++%72 = OpFMul %1 %71 %62
++%69 = OpCompositeConstruct %2 %66 %70 %72 %68
++OpStore %63 %69
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  options.ignore_set_binding = true;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, DifferentDecorationsVertexIgnoreSetBindingLocation) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 58
++; Bound: 73
+ ; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+-OpEntryPoint Vertex %40 "main" %4 %5 %6 %8 %20 %25
++OpEntryPoint Vertex %40 "main" %4 %5 %6 %8 %25
+ OpSource GLSL 450
+ OpName %4 "_ub"
+ OpName %5 "_uc"
+ OpName %6 "_ud"
+ OpName %8 "_ue"
+ OpName %9 "defaultUniformsVS"
+ OpMemberName %9 0 "_ua"
+ OpName %11 ""
+ OpName %16 "ANGLEDepthRangeParams"
+ OpMemberName %16 0 "near"
+ OpMemberName %16 1 "far"
+ OpMemberName %16 2 "diff"
+ OpMemberName %16 3 "reserved"
+ OpName %17 "ANGLEUniformBlock"
+ OpMemberName %17 0 "viewport"
+ OpMemberName %17 1 "clipDistancesEnabled"
+ OpMemberName %17 2 "xfbActiveUnpaused"
+ OpMemberName %17 3 "xfbVerticesPerInstance"
+ OpMemberName %17 4 "numSamples"
+ OpMemberName %17 5 "xfbBufferOffsets"
+ OpMemberName %17 6 "acbBufferOffsets"
+ OpMemberName %17 7 "depthRange"
+ OpName %19 "ANGLEUniforms"
+-OpName %20 "ANGLEXfbPosition"
+ OpName %23 "gl_PerVertex"
+ OpMemberName %23 0 "gl_Position"
+-OpMemberName %23 1 "gl_PointSize"
+-OpMemberName %23 2 "gl_ClipDistance"
+-OpMemberName %23 3 "gl_CullDistance"
+ OpName %25 ""
+ OpName %29 "_ua"
+ OpName %28 "_uf"
+ OpName %33 "_uf"
+ OpName %32 "_ug"
+ OpName %40 "main"
+ OpName %42 "param"
+ OpName %50 "param"
+ OpName %53 "param"
+-OpDecorate %4 Location 0
++OpDecorate %4 Location 1
+-OpDecorate %5 Location 1
++OpDecorate %5 Location 2
+-OpDecorate %6 Location 2
++OpDecorate %6 Location 0
+-OpDecorate %8 Location 0
++OpDecorate %8 Location 1
+ OpMemberDecorate %9 0 Offset 0
+ OpDecorate %9 Block
+ OpDecorate %11 DescriptorSet 0
+-OpDecorate %11 Binding 0
++OpDecorate %11 Binding 1
+ OpMemberDecorate %16 0 Offset 0
+ OpMemberDecorate %16 1 Offset 4
+ OpMemberDecorate %16 2 Offset 8
+ OpMemberDecorate %16 3 Offset 12
+ OpMemberDecorate %17 0 Offset 0
+ OpMemberDecorate %17 1 Offset 16
+ OpMemberDecorate %17 2 Offset 20
+ OpMemberDecorate %17 3 Offset 24
+ OpMemberDecorate %17 4 Offset 28
+ OpMemberDecorate %17 5 Offset 32
+ OpMemberDecorate %17 6 Offset 48
+ OpMemberDecorate %17 7 Offset 64
+ OpMemberDecorate %17 2 RelaxedPrecision
+ OpMemberDecorate %17 4 RelaxedPrecision
+ OpDecorate %17 Block
+-OpDecorate %19 DescriptorSet 0
++OpDecorate %19 DescriptorSet 2
+-OpDecorate %19 Binding 1
++OpDecorate %19 Binding 0
+-OpDecorate %20 Location 1
+ OpMemberDecorate %23 0 BuiltIn Position
+-OpMemberDecorate %23 1 BuiltIn PointSize
+-OpMemberDecorate %23 2 BuiltIn ClipDistance
+-OpMemberDecorate %23 3 BuiltIn CullDistance
+ OpDecorate %23 Block
+ OpDecorate %28 RelaxedPrecision
+ OpDecorate %29 RelaxedPrecision
+ OpDecorate %31 RelaxedPrecision
+ OpDecorate %32 RelaxedPrecision
+ OpDecorate %33 RelaxedPrecision
+ OpDecorate %35 RelaxedPrecision
+ OpDecorate %36 RelaxedPrecision
+ OpDecorate %37 RelaxedPrecision
+ OpDecorate %44 RelaxedPrecision
+ OpDecorate %52 RelaxedPrecision
+ OpDecorate %55 RelaxedPrecision
+ OpDecorate %56 RelaxedPrecision
+ %1 = OpTypeFloat 32
+ %2 = OpTypeVector %1 4
+ %9 = OpTypeStruct %2
+ %12 = OpTypeInt 32 0
+ %13 = OpTypeInt 32 1
+ %14 = OpTypeVector %13 4
+ %15 = OpTypeVector %12 4
+ %16 = OpTypeStruct %1 %1 %1 %1
+ %17 = OpTypeStruct %2 %12 %12 %13 %13 %14 %15 %16
+ %21 = OpConstant %12 8
+ %22 = OpTypeArray %1 %21
+-%23 = OpTypeStruct %2 %1 %22 %22
++%23 = OpTypeStruct %2
+ %38 = OpTypeVoid
+ %45 = OpConstant %12 0
++%59 = OpTypePointer Private %2
+ %3 = OpTypePointer Input %2
++%60 = OpTypePointer Private %2
+ %7 = OpTypePointer Output %2
+ %10 = OpTypePointer Uniform %9
+ %18 = OpTypePointer Uniform %17
+ %24 = OpTypePointer Output %23
+ %26 = OpTypePointer Function %2
+ %46 = OpTypePointer Uniform %2
+ %27 = OpTypeFunction %2 %26
+ %39 = OpTypeFunction %38
+ %4 = OpVariable %3 Input
+ %5 = OpVariable %3 Input
+ %6 = OpVariable %3 Input
+ %8 = OpVariable %7 Output
+ %11 = OpVariable %10 Uniform
+ %19 = OpVariable %18 Uniform
+-%20 = OpVariable %7 Output
++%58 = OpVariable %60 Private
+ %25 = OpVariable %24 Output
++%61 = OpConstant %13 0
++%62 = OpConstant %1 0.5
+ %28 = OpFunction %2 None %27
+ %29 = OpFunctionParameter %26
+ %30 = OpLabel
+ %31 = OpLoad %2 %29
+ OpReturnValue %31
+ OpFunctionEnd
+ %32 = OpFunction %2 None %27
+ %33 = OpFunctionParameter %26
+ %34 = OpLabel
+ %35 = OpLoad %2 %33
+ %36 = OpLoad %2 %33
+ %37 = OpFAdd %2 %35 %36
+ OpReturnValue %37
+ OpFunctionEnd
+ %40 = OpFunction %38 None %39
+ %41 = OpLabel
+ %42 = OpVariable %26 Function
+ %50 = OpVariable %26 Function
+ %53 = OpVariable %26 Function
+ %43 = OpLoad %2 %4
+ OpStore %42 %43
+ %44 = OpFunctionCall %2 %28 %42
+ %47 = OpAccessChain %46 %11 %45
+ %48 = OpLoad %2 %47
+ %49 = OpFAdd %2 %44 %48
+ OpStore %8 %49
+ %51 = OpLoad %2 %5
+ OpStore %50 %51
+ %52 = OpFunctionCall %2 %32 %50
+ %54 = OpLoad %2 %6
+ OpStore %53 %54
+ %55 = OpFunctionCall %2 %28 %53
+ %56 = OpFAdd %2 %52 %55
+ %57 = OpAccessChain %7 %25 %45
+ OpStore %57 %56
++%63 = OpAccessChain %7 %25 %61
++%64 = OpLoad %2 %63
++%65 = OpCompositeExtract %1 %64 0
++%66 = OpCompositeExtract %1 %64 1
++%67 = OpCompositeExtract %1 %64 2
++%68 = OpCompositeExtract %1 %64 3
++%70 = OpFNegate %1 %65
++%71 = OpFAdd %1 %67 %68
++%72 = OpFMul %1 %71 %62
++%69 = OpCompositeConstruct %2 %66 %70 %72 %68
++OpStore %63 %69
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  options.ignore_set_binding = true;
+  options.ignore_location = true;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/different_decorations_vertex_dst.spvasm b/test/diff/diff_files/different_decorations_vertex_dst.spvasm
new file mode 100644
index 0000000..33c6a9c
--- /dev/null
+++ b/test/diff/diff_files/different_decorations_vertex_dst.spvasm
@@ -0,0 +1,159 @@
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %40 "main" %4 %5 %6 %8 %25
+OpSource GLSL 450
+OpName %4 "_ub"
+OpName %5 "_uc"
+OpName %6 "_ud"
+OpName %8 "_ue"
+OpName %9 "defaultUniformsVS"
+OpMemberName %9 0 "_ua"
+OpName %11 ""
+OpName %16 "ANGLEDepthRangeParams"
+OpMemberName %16 0 "near"
+OpMemberName %16 1 "far"
+OpMemberName %16 2 "diff"
+OpMemberName %16 3 "reserved"
+OpName %17 "ANGLEUniformBlock"
+OpMemberName %17 0 "viewport"
+OpMemberName %17 1 "clipDistancesEnabled"
+OpMemberName %17 2 "xfbActiveUnpaused"
+OpMemberName %17 3 "xfbVerticesPerInstance"
+OpMemberName %17 4 "numSamples"
+OpMemberName %17 5 "xfbBufferOffsets"
+OpMemberName %17 6 "acbBufferOffsets"
+OpMemberName %17 7 "depthRange"
+OpName %19 "ANGLEUniforms"
+OpName %23 "gl_PerVertex"
+OpMemberName %23 0 "gl_Position"
+OpName %25 ""
+OpName %29 "_ua"
+OpName %28 "_uf"
+OpName %33 "_uf"
+OpName %32 "_ug"
+OpName %40 "main"
+OpName %42 "param"
+OpName %50 "param"
+OpName %53 "param"
+OpDecorate %4 Location 1
+OpDecorate %5 Location 2
+OpDecorate %6 Location 0
+OpDecorate %8 Location 1
+OpMemberDecorate %9 0 Offset 0
+OpDecorate %9 Block
+OpDecorate %11 DescriptorSet 0
+OpDecorate %11 Binding 1
+OpMemberDecorate %16 0 Offset 0
+OpMemberDecorate %16 1 Offset 4
+OpMemberDecorate %16 2 Offset 8
+OpMemberDecorate %16 3 Offset 12
+OpMemberDecorate %17 0 Offset 0
+OpMemberDecorate %17 1 Offset 16
+OpMemberDecorate %17 2 Offset 20
+OpMemberDecorate %17 3 Offset 24
+OpMemberDecorate %17 4 Offset 28
+OpMemberDecorate %17 5 Offset 32
+OpMemberDecorate %17 6 Offset 48
+OpMemberDecorate %17 7 Offset 64
+OpMemberDecorate %17 2 RelaxedPrecision
+OpMemberDecorate %17 4 RelaxedPrecision
+OpDecorate %17 Block
+OpDecorate %19 DescriptorSet 2
+OpDecorate %19 Binding 0
+OpMemberDecorate %23 0 BuiltIn Position
+OpDecorate %23 Block
+OpDecorate %28 RelaxedPrecision
+OpDecorate %29 RelaxedPrecision
+OpDecorate %31 RelaxedPrecision
+OpDecorate %32 RelaxedPrecision
+OpDecorate %33 RelaxedPrecision
+OpDecorate %35 RelaxedPrecision
+OpDecorate %36 RelaxedPrecision
+OpDecorate %37 RelaxedPrecision
+OpDecorate %44 RelaxedPrecision
+OpDecorate %52 RelaxedPrecision
+OpDecorate %55 RelaxedPrecision
+OpDecorate %56 RelaxedPrecision
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%9 = OpTypeStruct %2
+%12 = OpTypeInt 32 0
+%13 = OpTypeInt 32 1
+%14 = OpTypeVector %13 4
+%15 = OpTypeVector %12 4
+%16 = OpTypeStruct %1 %1 %1 %1
+%17 = OpTypeStruct %2 %12 %12 %13 %13 %14 %15 %16
+%21 = OpConstant %12 8
+%22 = OpTypeArray %1 %21
+%23 = OpTypeStruct %2
+%38 = OpTypeVoid
+%45 = OpConstant %12 0
+%58 = OpTypePointer Private %2
+%3 = OpTypePointer Input %2
+%59 = OpTypePointer Private %2
+%7 = OpTypePointer Output %2
+%10 = OpTypePointer Uniform %9
+%18 = OpTypePointer Uniform %17
+%24 = OpTypePointer Output %23
+%26 = OpTypePointer Function %2
+%46 = OpTypePointer Uniform %2
+%27 = OpTypeFunction %2 %26
+%39 = OpTypeFunction %38
+%4 = OpVariable %3 Input
+%5 = OpVariable %3 Input
+%6 = OpVariable %3 Input
+%8 = OpVariable %7 Output
+%11 = OpVariable %10 Uniform
+%19 = OpVariable %18 Uniform
+%20 = OpVariable %59 Private
+%25 = OpVariable %24 Output
+%60 = OpConstant %13 0
+%61 = OpConstant %1 0.5
+%28 = OpFunction %2 None %27
+%29 = OpFunctionParameter %26
+%30 = OpLabel
+%31 = OpLoad %2 %29
+OpReturnValue %31
+OpFunctionEnd
+%32 = OpFunction %2 None %27
+%33 = OpFunctionParameter %26
+%34 = OpLabel
+%35 = OpLoad %2 %33
+%36 = OpLoad %2 %33
+%37 = OpFAdd %2 %35 %36
+OpReturnValue %37
+OpFunctionEnd
+%40 = OpFunction %38 None %39
+%41 = OpLabel
+%42 = OpVariable %26 Function
+%50 = OpVariable %26 Function
+%53 = OpVariable %26 Function
+%43 = OpLoad %2 %4
+OpStore %42 %43
+%44 = OpFunctionCall %2 %28 %42
+%47 = OpAccessChain %46 %11 %45
+%48 = OpLoad %2 %47
+%49 = OpFAdd %2 %44 %48
+OpStore %8 %49
+%51 = OpLoad %2 %5
+OpStore %50 %51
+%52 = OpFunctionCall %2 %32 %50
+%54 = OpLoad %2 %6
+OpStore %53 %54
+%55 = OpFunctionCall %2 %28 %53
+%56 = OpFAdd %2 %52 %55
+%57 = OpAccessChain %7 %25 %45
+OpStore %57 %56
+%62 = OpAccessChain %7 %25 %60
+%63 = OpLoad %2 %62
+%64 = OpCompositeExtract %1 %63 0
+%65 = OpCompositeExtract %1 %63 1
+%66 = OpCompositeExtract %1 %63 2
+%67 = OpCompositeExtract %1 %63 3
+%69 = OpFNegate %1 %64
+%70 = OpFAdd %1 %66 %67
+%71 = OpFMul %1 %70 %61
+%68 = OpCompositeConstruct %2 %65 %69 %71 %67
+OpStore %62 %68
+OpReturn
+OpFunctionEnd
diff --git a/test/diff/diff_files/different_decorations_vertex_src.spvasm b/test/diff/diff_files/different_decorations_vertex_src.spvasm
new file mode 100644
index 0000000..ce1680c
--- /dev/null
+++ b/test/diff/diff_files/different_decorations_vertex_src.spvasm
@@ -0,0 +1,155 @@
+;; Test where variable set/binding/location decorations are different between
+;; src and dst vertex shaders.
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %40 "main" %4 %5 %6 %8 %20 %25
+OpSource GLSL 450
+OpName %4 "_ub"
+OpName %5 "_uc"
+OpName %6 "_ud"
+OpName %8 "_ue"
+OpName %9 "defaultUniformsVS"
+OpMemberName %9 0 "_ua"
+OpName %11 ""
+OpName %16 "ANGLEDepthRangeParams"
+OpMemberName %16 0 "near"
+OpMemberName %16 1 "far"
+OpMemberName %16 2 "diff"
+OpMemberName %16 3 "reserved"
+OpName %17 "ANGLEUniformBlock"
+OpMemberName %17 0 "viewport"
+OpMemberName %17 1 "clipDistancesEnabled"
+OpMemberName %17 2 "xfbActiveUnpaused"
+OpMemberName %17 3 "xfbVerticesPerInstance"
+OpMemberName %17 4 "numSamples"
+OpMemberName %17 5 "xfbBufferOffsets"
+OpMemberName %17 6 "acbBufferOffsets"
+OpMemberName %17 7 "depthRange"
+OpName %19 "ANGLEUniforms"
+OpName %20 "ANGLEXfbPosition"
+OpName %23 "gl_PerVertex"
+OpMemberName %23 0 "gl_Position"
+OpMemberName %23 1 "gl_PointSize"
+OpMemberName %23 2 "gl_ClipDistance"
+OpMemberName %23 3 "gl_CullDistance"
+OpName %25 ""
+OpName %29 "_ua"
+OpName %28 "_uf"
+OpName %33 "_uf"
+OpName %32 "_ug"
+OpName %40 "main"
+OpName %42 "param"
+OpName %50 "param"
+OpName %53 "param"
+OpDecorate %4 Location 0
+OpDecorate %5 Location 1
+OpDecorate %6 Location 2
+OpDecorate %8 Location 0
+OpMemberDecorate %9 0 Offset 0
+OpDecorate %9 Block
+OpDecorate %11 DescriptorSet 0
+OpDecorate %11 Binding 0
+OpMemberDecorate %16 0 Offset 0
+OpMemberDecorate %16 1 Offset 4
+OpMemberDecorate %16 2 Offset 8
+OpMemberDecorate %16 3 Offset 12
+OpMemberDecorate %17 0 Offset 0
+OpMemberDecorate %17 1 Offset 16
+OpMemberDecorate %17 2 Offset 20
+OpMemberDecorate %17 3 Offset 24
+OpMemberDecorate %17 4 Offset 28
+OpMemberDecorate %17 5 Offset 32
+OpMemberDecorate %17 6 Offset 48
+OpMemberDecorate %17 7 Offset 64
+OpMemberDecorate %17 2 RelaxedPrecision
+OpMemberDecorate %17 4 RelaxedPrecision
+OpDecorate %17 Block
+OpDecorate %19 DescriptorSet 0
+OpDecorate %19 Binding 1
+OpDecorate %20 Location 1
+OpMemberDecorate %23 0 BuiltIn Position
+OpMemberDecorate %23 1 BuiltIn PointSize
+OpMemberDecorate %23 2 BuiltIn ClipDistance
+OpMemberDecorate %23 3 BuiltIn CullDistance
+OpDecorate %23 Block
+OpDecorate %28 RelaxedPrecision
+OpDecorate %29 RelaxedPrecision
+OpDecorate %31 RelaxedPrecision
+OpDecorate %32 RelaxedPrecision
+OpDecorate %33 RelaxedPrecision
+OpDecorate %35 RelaxedPrecision
+OpDecorate %36 RelaxedPrecision
+OpDecorate %37 RelaxedPrecision
+OpDecorate %44 RelaxedPrecision
+OpDecorate %52 RelaxedPrecision
+OpDecorate %55 RelaxedPrecision
+OpDecorate %56 RelaxedPrecision
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%9 = OpTypeStruct %2
+%12 = OpTypeInt 32 0
+%13 = OpTypeInt 32 1
+%14 = OpTypeVector %13 4
+%15 = OpTypeVector %12 4
+%16 = OpTypeStruct %1 %1 %1 %1
+%17 = OpTypeStruct %2 %12 %12 %13 %13 %14 %15 %16
+%21 = OpConstant %12 8
+%22 = OpTypeArray %1 %21
+%23 = OpTypeStruct %2 %1 %22 %22
+%38 = OpTypeVoid
+%45 = OpConstant %12 0
+%3 = OpTypePointer Input %2
+%7 = OpTypePointer Output %2
+%10 = OpTypePointer Uniform %9
+%18 = OpTypePointer Uniform %17
+%24 = OpTypePointer Output %23
+%26 = OpTypePointer Function %2
+%46 = OpTypePointer Uniform %2
+%27 = OpTypeFunction %2 %26
+%39 = OpTypeFunction %38
+%4 = OpVariable %3 Input
+%5 = OpVariable %3 Input
+%6 = OpVariable %3 Input
+%8 = OpVariable %7 Output
+%11 = OpVariable %10 Uniform
+%19 = OpVariable %18 Uniform
+%20 = OpVariable %7 Output
+%25 = OpVariable %24 Output
+%28 = OpFunction %2 None %27
+%29 = OpFunctionParameter %26
+%30 = OpLabel
+%31 = OpLoad %2 %29
+OpReturnValue %31
+OpFunctionEnd
+%32 = OpFunction %2 None %27
+%33 = OpFunctionParameter %26
+%34 = OpLabel
+%35 = OpLoad %2 %33
+%36 = OpLoad %2 %33
+%37 = OpFAdd %2 %35 %36
+OpReturnValue %37
+OpFunctionEnd
+%40 = OpFunction %38 None %39
+%41 = OpLabel
+%42 = OpVariable %26 Function
+%50 = OpVariable %26 Function
+%53 = OpVariable %26 Function
+%43 = OpLoad %2 %4
+OpStore %42 %43
+%44 = OpFunctionCall %2 %28 %42
+%47 = OpAccessChain %46 %11 %45
+%48 = OpLoad %2 %47
+%49 = OpFAdd %2 %44 %48
+OpStore %8 %49
+%51 = OpLoad %2 %5
+OpStore %50 %51
+%52 = OpFunctionCall %2 %32 %50
+%54 = OpLoad %2 %6
+OpStore %53 %54
+%55 = OpFunctionCall %2 %28 %53
+%56 = OpFAdd %2 %52 %55
+%57 = OpAccessChain %7 %25 %45
+OpStore %57 %56
+OpReturn
+OpFunctionEnd
+
diff --git a/test/diff/diff_files/different_function_parameter_count_autogen.cpp b/test/diff/diff_files/different_function_parameter_count_autogen.cpp
new file mode 100644
index 0000000..3a077fb
--- /dev/null
+++ b/test/diff/diff_files/different_function_parameter_count_autogen.cpp
@@ -0,0 +1,339 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Test where src and dst have a function with different parameter count.
+constexpr char kSrc[] = R"(; SPIR-V
+; Version: 1.0
+; Generator: Khronos Glslang Reference Front End; 10
+; Bound: 25
+; Schema: 0
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %20
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               OpName %4 "main"
+               OpName %11 "f(vf2;"
+               OpName %10 "v"
+               OpName %20 "o"
+               OpName %23 "param"
+               OpDecorate %20 RelaxedPrecision
+               OpDecorate %20 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 2
+          %8 = OpTypePointer Function %7
+          %9 = OpTypeFunction %7 %8
+         %14 = OpConstant %6 0.5
+         %15 = OpConstantComposite %7 %14 %14
+         %19 = OpTypePointer Output %7
+         %20 = OpVariable %19 Output
+         %21 = OpConstant %6 0
+         %22 = OpConstantComposite %7 %21 %21
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %23 = OpVariable %8 Function
+               OpStore %23 %22
+         %24 = OpFunctionCall %7 %11 %23
+               OpStore %20 %24
+               OpReturn
+               OpFunctionEnd
+         %11 = OpFunction %7 None %9
+         %10 = OpFunctionParameter %8
+         %12 = OpLabel
+         %13 = OpLoad %7 %10
+         %16 = OpFAdd %7 %13 %15
+               OpReturnValue %16
+               OpFunctionEnd)";
+constexpr char kDst[] = R"(; SPIR-V
+; Version: 1.0
+; Generator: Khronos Glslang Reference Front End; 10
+; Bound: 28
+; Schema: 0
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %20
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               OpName %4 "main"
+               OpName %12 "f(vf2;vf2;"
+               OpName %10 "v"
+               OpName %11 "v2"
+               OpName %20 "o"
+               OpName %25 "param"
+               OpName %26 "param"
+               OpDecorate %20 RelaxedPrecision
+               OpDecorate %20 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 2
+          %8 = OpTypePointer Function %7
+          %9 = OpTypeFunction %7 %8 %8
+         %19 = OpTypePointer Output %7
+         %20 = OpVariable %19 Output
+         %21 = OpConstant %6 0
+         %22 = OpConstantComposite %7 %21 %21
+         %23 = OpConstant %6 0.5
+         %24 = OpConstantComposite %7 %23 %23
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %25 = OpVariable %8 Function
+         %26 = OpVariable %8 Function
+               OpStore %25 %22
+               OpStore %26 %24
+         %27 = OpFunctionCall %7 %12 %25 %26
+               OpStore %20 %27
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %7 None %9
+         %10 = OpFunctionParameter %8
+         %11 = OpFunctionParameter %8
+         %13 = OpLabel
+         %14 = OpLoad %7 %10
+         %15 = OpLoad %7 %11
+         %16 = OpFAdd %7 %14 %15
+               OpReturnValue %16
+               OpFunctionEnd
+
+)";
+
+TEST(DiffTest, DifferentFunctionParameterCount) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 25
++; Bound: 33
+ ; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %20
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 320
+ OpName %4 "main"
+-OpName %11 "f(vf2;"
++OpName %11 "f(vf2;vf2;"
+ OpName %10 "v"
++OpName %26 "v2"
+ OpName %20 "o"
+ OpName %23 "param"
++OpName %31 "param"
+ OpDecorate %20 RelaxedPrecision
+ OpDecorate %20 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypeVector %6 2
+ %8 = OpTypePointer Function %7
+-%9 = OpTypeFunction %7 %8
++%25 = OpTypeFunction %7 %8 %8
+ %14 = OpConstant %6 0.5
+ %15 = OpConstantComposite %7 %14 %14
+ %19 = OpTypePointer Output %7
+ %20 = OpVariable %19 Output
+ %21 = OpConstant %6 0
+ %22 = OpConstantComposite %7 %21 %21
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %23 = OpVariable %8 Function
++%31 = OpVariable %8 Function
+ OpStore %23 %22
+-%24 = OpFunctionCall %7 %11 %23
++OpStore %31 %15
++%32 = OpFunctionCall %7 %11 %23 %31
+-OpStore %20 %24
++OpStore %20 %32
+ OpReturn
+ OpFunctionEnd
+-%11 = OpFunction %7 None %9
++%11 = OpFunction %7 None %25
+ %10 = OpFunctionParameter %8
++%26 = OpFunctionParameter %8
+ %12 = OpLabel
+ %13 = OpLoad %7 %10
+-%16 = OpFAdd %7 %13 %15
++%27 = OpLoad %7 %26
++%28 = OpFAdd %7 %13 %27
+-OpReturnValue %16
++OpReturnValue %28
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, DifferentFunctionParameterCountNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(; SPIR-V
+; Version: 1.0
+; Generator: Khronos Glslang Reference Front End; 10
+; Bound: 25
+; Schema: 0
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %20
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               OpDecorate %20 RelaxedPrecision
+               OpDecorate %20 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 2
+          %8 = OpTypePointer Function %7
+          %9 = OpTypeFunction %7 %8
+         %14 = OpConstant %6 0.5
+         %15 = OpConstantComposite %7 %14 %14
+         %19 = OpTypePointer Output %7
+         %20 = OpVariable %19 Output
+         %21 = OpConstant %6 0
+         %22 = OpConstantComposite %7 %21 %21
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %23 = OpVariable %8 Function
+               OpStore %23 %22
+         %24 = OpFunctionCall %7 %11 %23
+               OpStore %20 %24
+               OpReturn
+               OpFunctionEnd
+         %11 = OpFunction %7 None %9
+         %10 = OpFunctionParameter %8
+         %12 = OpLabel
+         %13 = OpLoad %7 %10
+         %16 = OpFAdd %7 %13 %15
+               OpReturnValue %16
+               OpFunctionEnd
+)";
+  constexpr char kDstNoDebug[] = R"(; SPIR-V
+; Version: 1.0
+; Generator: Khronos Glslang Reference Front End; 10
+; Bound: 28
+; Schema: 0
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %20
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               OpDecorate %20 RelaxedPrecision
+               OpDecorate %20 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 2
+          %8 = OpTypePointer Function %7
+          %9 = OpTypeFunction %7 %8 %8
+         %19 = OpTypePointer Output %7
+         %20 = OpVariable %19 Output
+         %21 = OpConstant %6 0
+         %22 = OpConstantComposite %7 %21 %21
+         %23 = OpConstant %6 0.5
+         %24 = OpConstantComposite %7 %23 %23
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %25 = OpVariable %8 Function
+         %26 = OpVariable %8 Function
+               OpStore %25 %22
+               OpStore %26 %24
+         %27 = OpFunctionCall %7 %12 %25 %26
+               OpStore %20 %27
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %7 None %9
+         %10 = OpFunctionParameter %8
+         %11 = OpFunctionParameter %8
+         %13 = OpLabel
+         %14 = OpLoad %7 %10
+         %15 = OpLoad %7 %11
+         %16 = OpFAdd %7 %14 %15
+               OpReturnValue %16
+               OpFunctionEnd
+
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 25
++; Bound: 34
+ ; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %20
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 320
+ OpDecorate %20 RelaxedPrecision
+ OpDecorate %20 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypeVector %6 2
+ %8 = OpTypePointer Function %7
+-%9 = OpTypeFunction %7 %8
++%25 = OpTypeFunction %7 %8 %8
+ %14 = OpConstant %6 0.5
+ %15 = OpConstantComposite %7 %14 %14
+ %19 = OpTypePointer Output %7
+ %20 = OpVariable %19 Output
+ %21 = OpConstant %6 0
+ %22 = OpConstantComposite %7 %21 %21
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %23 = OpVariable %8 Function
++%32 = OpVariable %8 Function
+ OpStore %23 %22
+-%24 = OpFunctionCall %7 %11 %23
++OpStore %32 %15
++%33 = OpFunctionCall %7 %11 %23 %32
+-OpStore %20 %24
++OpStore %20 %33
+ OpReturn
+ OpFunctionEnd
+-%11 = OpFunction %7 None %9
++%11 = OpFunction %7 None %25
+-%10 = OpFunctionParameter %8
++%26 = OpFunctionParameter %8
++%27 = OpFunctionParameter %8
+ %12 = OpLabel
+-%13 = OpLoad %7 %10
++%13 = OpLoad %7 %26
+-%16 = OpFAdd %7 %13 %15
++%28 = OpLoad %7 %27
++%29 = OpFAdd %7 %13 %28
+-OpReturnValue %16
++OpReturnValue %29
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/different_function_parameter_count_dst.spvasm b/test/diff/diff_files/different_function_parameter_count_dst.spvasm
new file mode 100644
index 0000000..9244260
--- /dev/null
+++ b/test/diff/diff_files/different_function_parameter_count_dst.spvasm
@@ -0,0 +1,52 @@
+; SPIR-V
+; Version: 1.0
+; Generator: Khronos Glslang Reference Front End; 10
+; Bound: 28
+; Schema: 0
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %20
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               OpName %4 "main"
+               OpName %12 "f(vf2;vf2;"
+               OpName %10 "v"
+               OpName %11 "v2"
+               OpName %20 "o"
+               OpName %25 "param"
+               OpName %26 "param"
+               OpDecorate %20 RelaxedPrecision
+               OpDecorate %20 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 2
+          %8 = OpTypePointer Function %7
+          %9 = OpTypeFunction %7 %8 %8
+         %19 = OpTypePointer Output %7
+         %20 = OpVariable %19 Output
+         %21 = OpConstant %6 0
+         %22 = OpConstantComposite %7 %21 %21
+         %23 = OpConstant %6 0.5
+         %24 = OpConstantComposite %7 %23 %23
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %25 = OpVariable %8 Function
+         %26 = OpVariable %8 Function
+               OpStore %25 %22
+               OpStore %26 %24
+         %27 = OpFunctionCall %7 %12 %25 %26
+               OpStore %20 %27
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %7 None %9
+         %10 = OpFunctionParameter %8
+         %11 = OpFunctionParameter %8
+         %13 = OpLabel
+         %14 = OpLoad %7 %10
+         %15 = OpLoad %7 %11
+         %16 = OpFAdd %7 %14 %15
+               OpReturnValue %16
+               OpFunctionEnd
+
diff --git a/test/diff/diff_files/different_function_parameter_count_src.spvasm b/test/diff/diff_files/different_function_parameter_count_src.spvasm
new file mode 100644
index 0000000..6ee2408
--- /dev/null
+++ b/test/diff/diff_files/different_function_parameter_count_src.spvasm
@@ -0,0 +1,46 @@
+;; Test where src and dst have a function with different parameter count.
+; SPIR-V
+; Version: 1.0
+; Generator: Khronos Glslang Reference Front End; 10
+; Bound: 25
+; Schema: 0
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %20
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               OpName %4 "main"
+               OpName %11 "f(vf2;"
+               OpName %10 "v"
+               OpName %20 "o"
+               OpName %23 "param"
+               OpDecorate %20 RelaxedPrecision
+               OpDecorate %20 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 2
+          %8 = OpTypePointer Function %7
+          %9 = OpTypeFunction %7 %8
+         %14 = OpConstant %6 0.5
+         %15 = OpConstantComposite %7 %14 %14
+         %19 = OpTypePointer Output %7
+         %20 = OpVariable %19 Output
+         %21 = OpConstant %6 0
+         %22 = OpConstantComposite %7 %21 %21
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %23 = OpVariable %8 Function
+               OpStore %23 %22
+         %24 = OpFunctionCall %7 %11 %23
+               OpStore %20 %24
+               OpReturn
+               OpFunctionEnd
+         %11 = OpFunction %7 None %9
+         %10 = OpFunctionParameter %8
+         %12 = OpLabel
+         %13 = OpLoad %7 %10
+         %16 = OpFAdd %7 %13 %15
+               OpReturnValue %16
+               OpFunctionEnd
diff --git a/test/diff/diff_files/extra_if_block_autogen.cpp b/test/diff/diff_files/extra_if_block_autogen.cpp
new file mode 100644
index 0000000..4f91319
--- /dev/null
+++ b/test/diff/diff_files/extra_if_block_autogen.cpp
@@ -0,0 +1,867 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Test where src has an extra if block in one function, and dst has an extra
+// if block in another function.
+constexpr char kSrc[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %63 %68
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "f1("
+               OpName %10 "f2("
+               OpName %13 "v"
+               OpName %16 "Buffer"
+               OpMemberName %16 0 "flag1"
+               OpMemberName %16 1 "flag2"
+               OpName %18 ""
+               OpName %45 "v"
+               OpName %63 "color"
+               OpName %68 "v"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %13 RelaxedPrecision
+               OpMemberDecorate %16 0 RelaxedPrecision
+               OpMemberDecorate %16 0 Offset 0
+               OpMemberDecorate %16 1 RelaxedPrecision
+               OpMemberDecorate %16 1 Offset 4
+               OpDecorate %16 Block
+               OpDecorate %18 DescriptorSet 0
+               OpDecorate %18 Binding 0
+               OpDecorate %23 RelaxedPrecision
+               OpDecorate %30 RelaxedPrecision
+               OpDecorate %31 RelaxedPrecision
+               OpDecorate %34 RelaxedPrecision
+               OpDecorate %35 RelaxedPrecision
+               OpDecorate %36 RelaxedPrecision
+               OpDecorate %37 RelaxedPrecision
+               OpDecorate %38 RelaxedPrecision
+               OpDecorate %39 RelaxedPrecision
+               OpDecorate %40 RelaxedPrecision
+               OpDecorate %41 RelaxedPrecision
+               OpDecorate %42 RelaxedPrecision
+               OpDecorate %45 RelaxedPrecision
+               OpDecorate %47 RelaxedPrecision
+               OpDecorate %48 RelaxedPrecision
+               OpDecorate %50 RelaxedPrecision
+               OpDecorate %51 RelaxedPrecision
+               OpDecorate %54 RelaxedPrecision
+               OpDecorate %55 RelaxedPrecision
+               OpDecorate %56 RelaxedPrecision
+               OpDecorate %57 RelaxedPrecision
+               OpDecorate %58 RelaxedPrecision
+               OpDecorate %63 RelaxedPrecision
+               OpDecorate %63 Location 0
+               OpDecorate %64 RelaxedPrecision
+               OpDecorate %65 RelaxedPrecision
+               OpDecorate %66 RelaxedPrecision
+               OpDecorate %68 RelaxedPrecision
+               OpDecorate %68 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeFunction %6
+         %12 = OpTypePointer Function %6
+         %14 = OpConstant %6 0
+         %15 = OpTypeInt 32 0
+         %16 = OpTypeStruct %15 %15
+         %17 = OpTypePointer Uniform %16
+         %18 = OpVariable %17 Uniform
+         %19 = OpTypeInt 32 1
+         %20 = OpConstant %19 0
+         %21 = OpTypePointer Uniform %15
+         %24 = OpConstant %15 0
+         %25 = OpTypeBool
+         %29 = OpConstant %6 1
+         %32 = OpConstant %19 1
+         %49 = OpConstant %6 10
+         %52 = OpConstant %6 0.5
+         %53 = OpConstant %6 0.699999988
+         %61 = OpTypeVector %6 4
+         %62 = OpTypePointer Output %61
+         %63 = OpVariable %62 Output
+         %67 = OpTypePointer Input %6
+         %68 = OpVariable %67 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %64 = OpFunctionCall %6 %8
+         %65 = OpFunctionCall %6 %10
+         %66 = OpCompositeConstruct %61 %64 %65 %14 %29
+               OpStore %63 %66
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %6 None %7
+          %9 = OpLabel
+         %13 = OpVariable %12 Function
+               OpStore %13 %14
+         %22 = OpAccessChain %21 %18 %20
+         %23 = OpLoad %15 %22
+         %26 = OpINotEqual %25 %23 %24
+               OpSelectionMerge %28 None
+               OpBranchConditional %26 %27 %28
+         %27 = OpLabel
+         %30 = OpLoad %6 %13
+         %31 = OpFAdd %6 %30 %29
+               OpStore %13 %31
+               OpBranch %28
+         %28 = OpLabel
+         %33 = OpAccessChain %21 %18 %32
+         %34 = OpLoad %15 %33
+         %35 = OpConvertUToF %6 %34
+         %36 = OpExtInst %6 %1 Log2 %35
+         %37 = OpLoad %6 %13
+         %38 = OpFAdd %6 %37 %36
+               OpStore %13 %38
+         %39 = OpLoad %6 %13
+         %40 = OpLoad %6 %13
+         %41 = OpExtInst %6 %1 Sqrt %40
+         %42 = OpFSub %6 %39 %41
+               OpReturnValue %42
+               OpFunctionEnd
+         %10 = OpFunction %6 None %7
+         %11 = OpLabel
+         %45 = OpVariable %12 Function
+         %46 = OpAccessChain %21 %18 %20
+         %47 = OpLoad %15 %46
+         %48 = OpConvertUToF %6 %47
+         %50 = OpFDiv %6 %48 %49
+               OpStore %45 %50
+         %51 = OpLoad %6 %45
+         %54 = OpExtInst %6 %1 FClamp %51 %52 %53
+         %55 = OpLoad %6 %45
+         %56 = OpFMul %6 %55 %54
+               OpStore %45 %56
+         %57 = OpLoad %6 %45
+         %58 = OpExtInst %6 %1 Exp %57
+               OpReturnValue %58
+               OpFunctionEnd
+)";
+constexpr char kDst[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %63 %69
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "f1("
+               OpName %10 "f2("
+               OpName %13 "v"
+               OpName %16 "Buffer"
+               OpMemberName %16 0 "flag1"
+               OpMemberName %16 1 "flag2"
+               OpName %18 ""
+               OpName %34 "v"
+               OpName %63 "color"
+               OpName %69 "v"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %13 RelaxedPrecision
+               OpMemberDecorate %16 0 RelaxedPrecision
+               OpMemberDecorate %16 0 Offset 0
+               OpMemberDecorate %16 1 RelaxedPrecision
+               OpMemberDecorate %16 1 Offset 4
+               OpDecorate %16 Block
+               OpDecorate %18 DescriptorSet 0
+               OpDecorate %18 Binding 0
+               OpDecorate %23 RelaxedPrecision
+               OpDecorate %24 RelaxedPrecision
+               OpDecorate %25 RelaxedPrecision
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %27 RelaxedPrecision
+               OpDecorate %28 RelaxedPrecision
+               OpDecorate %29 RelaxedPrecision
+               OpDecorate %30 RelaxedPrecision
+               OpDecorate %31 RelaxedPrecision
+               OpDecorate %34 RelaxedPrecision
+               OpDecorate %37 RelaxedPrecision
+               OpDecorate %38 RelaxedPrecision
+               OpDecorate %40 RelaxedPrecision
+               OpDecorate %41 RelaxedPrecision
+               OpDecorate %44 RelaxedPrecision
+               OpDecorate %45 RelaxedPrecision
+               OpDecorate %46 RelaxedPrecision
+               OpDecorate %48 RelaxedPrecision
+               OpDecorate %55 RelaxedPrecision
+               OpDecorate %56 RelaxedPrecision
+               OpDecorate %57 RelaxedPrecision
+               OpDecorate %58 RelaxedPrecision
+               OpDecorate %63 RelaxedPrecision
+               OpDecorate %63 Location 0
+               OpDecorate %64 RelaxedPrecision
+               OpDecorate %65 RelaxedPrecision
+               OpDecorate %67 RelaxedPrecision
+               OpDecorate %69 RelaxedPrecision
+               OpDecorate %69 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeFunction %6
+         %12 = OpTypePointer Function %6
+         %14 = OpConstant %6 0
+         %15 = OpTypeInt 32 0
+         %16 = OpTypeStruct %15 %15
+         %17 = OpTypePointer Uniform %16
+         %18 = OpVariable %17 Uniform
+         %19 = OpTypeInt 32 1
+         %20 = OpConstant %19 1
+         %21 = OpTypePointer Uniform %15
+         %35 = OpConstant %19 0
+         %39 = OpConstant %6 10
+         %42 = OpConstant %6 0.5
+         %43 = OpConstant %6 0.699999988
+         %49 = OpConstant %15 0
+         %50 = OpTypeBool
+         %54 = OpConstant %6 0.100000001
+         %61 = OpTypeVector %6 4
+         %62 = OpTypePointer Output %61
+         %63 = OpVariable %62 Output
+         %66 = OpConstant %6 1
+         %68 = OpTypePointer Input %6
+         %69 = OpVariable %68 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %64 = OpFunctionCall %6 %8
+         %65 = OpFunctionCall %6 %10
+         %67 = OpCompositeConstruct %61 %64 %65 %14 %66
+               OpStore %63 %67
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %6 None %7
+          %9 = OpLabel
+         %13 = OpVariable %12 Function
+               OpStore %13 %14
+         %22 = OpAccessChain %21 %18 %20
+         %23 = OpLoad %15 %22
+         %24 = OpConvertUToF %6 %23
+         %25 = OpExtInst %6 %1 Log2 %24
+         %26 = OpLoad %6 %13
+         %27 = OpFAdd %6 %26 %25
+               OpStore %13 %27
+         %28 = OpLoad %6 %13
+         %29 = OpLoad %6 %13
+         %30 = OpExtInst %6 %1 Sqrt %29
+         %31 = OpFSub %6 %28 %30
+               OpReturnValue %31
+               OpFunctionEnd
+         %10 = OpFunction %6 None %7
+         %11 = OpLabel
+         %34 = OpVariable %12 Function
+         %36 = OpAccessChain %21 %18 %35
+         %37 = OpLoad %15 %36
+         %38 = OpConvertUToF %6 %37
+         %40 = OpFDiv %6 %38 %39
+               OpStore %34 %40
+         %41 = OpLoad %6 %34
+         %44 = OpExtInst %6 %1 FClamp %41 %42 %43
+         %45 = OpLoad %6 %34
+         %46 = OpFMul %6 %45 %44
+               OpStore %34 %46
+         %47 = OpAccessChain %21 %18 %20
+         %48 = OpLoad %15 %47
+         %51 = OpINotEqual %50 %48 %49
+               OpSelectionMerge %53 None
+               OpBranchConditional %51 %52 %53
+         %52 = OpLabel
+         %55 = OpLoad %6 %34
+         %56 = OpFSub %6 %55 %54
+               OpStore %34 %56
+               OpBranch %53
+         %53 = OpLabel
+         %57 = OpLoad %6 %34
+         %58 = OpExtInst %6 %1 Exp %57
+               OpReturnValue %58
+               OpFunctionEnd
+
+)";
+
+TEST(DiffTest, ExtraIfBlock) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 69
++; Bound: 81
+ ; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %63 %68
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %8 "f1("
+ OpName %10 "f2("
+ OpName %13 "v"
+ OpName %16 "Buffer"
+ OpMemberName %16 0 "flag1"
+ OpMemberName %16 1 "flag2"
+ OpName %18 ""
+ OpName %45 "v"
+ OpName %63 "color"
+ OpName %68 "v"
+ OpDecorate %8 RelaxedPrecision
+ OpDecorate %10 RelaxedPrecision
+ OpDecorate %13 RelaxedPrecision
+ OpMemberDecorate %16 0 RelaxedPrecision
+ OpMemberDecorate %16 0 Offset 0
+ OpMemberDecorate %16 1 RelaxedPrecision
+ OpMemberDecorate %16 1 Offset 4
+ OpDecorate %16 Block
+ OpDecorate %18 DescriptorSet 0
+ OpDecorate %18 Binding 0
+-OpDecorate %23 RelaxedPrecision
+-OpDecorate %30 RelaxedPrecision
+-OpDecorate %31 RelaxedPrecision
+ OpDecorate %34 RelaxedPrecision
+ OpDecorate %35 RelaxedPrecision
+ OpDecorate %36 RelaxedPrecision
+ OpDecorate %37 RelaxedPrecision
+ OpDecorate %38 RelaxedPrecision
+ OpDecorate %39 RelaxedPrecision
+ OpDecorate %40 RelaxedPrecision
+ OpDecorate %41 RelaxedPrecision
+ OpDecorate %42 RelaxedPrecision
+ OpDecorate %45 RelaxedPrecision
+ OpDecorate %47 RelaxedPrecision
+ OpDecorate %48 RelaxedPrecision
+ OpDecorate %50 RelaxedPrecision
+ OpDecorate %51 RelaxedPrecision
+ OpDecorate %54 RelaxedPrecision
+ OpDecorate %55 RelaxedPrecision
+ OpDecorate %56 RelaxedPrecision
++OpDecorate %72 RelaxedPrecision
+ OpDecorate %57 RelaxedPrecision
++OpDecorate %77 RelaxedPrecision
++OpDecorate %78 RelaxedPrecision
+ OpDecorate %58 RelaxedPrecision
+ OpDecorate %63 RelaxedPrecision
+ OpDecorate %63 Location 0
+ OpDecorate %64 RelaxedPrecision
+ OpDecorate %65 RelaxedPrecision
+ OpDecorate %66 RelaxedPrecision
+ OpDecorate %68 RelaxedPrecision
+ OpDecorate %68 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypeFunction %6
+ %12 = OpTypePointer Function %6
+ %14 = OpConstant %6 0
+ %15 = OpTypeInt 32 0
+ %16 = OpTypeStruct %15 %15
+ %17 = OpTypePointer Uniform %16
+ %18 = OpVariable %17 Uniform
+ %19 = OpTypeInt 32 1
+ %20 = OpConstant %19 0
+ %21 = OpTypePointer Uniform %15
+ %24 = OpConstant %15 0
+ %25 = OpTypeBool
+ %29 = OpConstant %6 1
+ %32 = OpConstant %19 1
+ %49 = OpConstant %6 10
+ %52 = OpConstant %6 0.5
++%76 = OpConstant %6 0.100000001
+ %53 = OpConstant %6 0.699999988
+ %61 = OpTypeVector %6 4
+ %62 = OpTypePointer Output %61
+ %63 = OpVariable %62 Output
+ %67 = OpTypePointer Input %6
+ %68 = OpVariable %67 Input
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %64 = OpFunctionCall %6 %8
+ %65 = OpFunctionCall %6 %10
+ %66 = OpCompositeConstruct %61 %64 %65 %14 %29
+ OpStore %63 %66
+ OpReturn
+ OpFunctionEnd
+ %8 = OpFunction %6 None %7
+ %9 = OpLabel
+ %13 = OpVariable %12 Function
+ OpStore %13 %14
+-%22 = OpAccessChain %21 %18 %20
+-%23 = OpLoad %15 %22
+-%26 = OpINotEqual %25 %23 %24
+-OpSelectionMerge %28 None
+-OpBranchConditional %26 %27 %28
+-%27 = OpLabel
+-%30 = OpLoad %6 %13
+-%31 = OpFAdd %6 %30 %29
+-OpStore %13 %31
+-OpBranch %28
+-%28 = OpLabel
+ %33 = OpAccessChain %21 %18 %32
+ %34 = OpLoad %15 %33
+ %35 = OpConvertUToF %6 %34
+ %36 = OpExtInst %6 %1 Log2 %35
+ %37 = OpLoad %6 %13
+ %38 = OpFAdd %6 %37 %36
+ OpStore %13 %38
+ %39 = OpLoad %6 %13
+ %40 = OpLoad %6 %13
+ %41 = OpExtInst %6 %1 Sqrt %40
+ %42 = OpFSub %6 %39 %41
+ OpReturnValue %42
+ OpFunctionEnd
+ %10 = OpFunction %6 None %7
+ %11 = OpLabel
+ %45 = OpVariable %12 Function
+ %46 = OpAccessChain %21 %18 %20
+ %47 = OpLoad %15 %46
+ %48 = OpConvertUToF %6 %47
+ %50 = OpFDiv %6 %48 %49
+ OpStore %45 %50
+ %51 = OpLoad %6 %45
+ %54 = OpExtInst %6 %1 FClamp %51 %52 %53
+ %55 = OpLoad %6 %45
+ %56 = OpFMul %6 %55 %54
+ OpStore %45 %56
++%71 = OpAccessChain %21 %18 %32
++%72 = OpLoad %15 %71
++%73 = OpINotEqual %25 %72 %24
++OpSelectionMerge %75 None
++OpBranchConditional %73 %74 %75
++%74 = OpLabel
+ %57 = OpLoad %6 %45
++%77 = OpFSub %6 %57 %76
++OpStore %45 %77
++OpBranch %75
++%75 = OpLabel
++%78 = OpLoad %6 %45
+-%58 = OpExtInst %6 %1 Exp %57
++%58 = OpExtInst %6 %1 Exp %78
+ OpReturnValue %58
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, ExtraIfBlockNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %63 %68
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %13 RelaxedPrecision
+               OpMemberDecorate %16 0 RelaxedPrecision
+               OpMemberDecorate %16 0 Offset 0
+               OpMemberDecorate %16 1 RelaxedPrecision
+               OpMemberDecorate %16 1 Offset 4
+               OpDecorate %16 Block
+               OpDecorate %18 DescriptorSet 0
+               OpDecorate %18 Binding 0
+               OpDecorate %23 RelaxedPrecision
+               OpDecorate %30 RelaxedPrecision
+               OpDecorate %31 RelaxedPrecision
+               OpDecorate %34 RelaxedPrecision
+               OpDecorate %35 RelaxedPrecision
+               OpDecorate %36 RelaxedPrecision
+               OpDecorate %37 RelaxedPrecision
+               OpDecorate %38 RelaxedPrecision
+               OpDecorate %39 RelaxedPrecision
+               OpDecorate %40 RelaxedPrecision
+               OpDecorate %41 RelaxedPrecision
+               OpDecorate %42 RelaxedPrecision
+               OpDecorate %45 RelaxedPrecision
+               OpDecorate %47 RelaxedPrecision
+               OpDecorate %48 RelaxedPrecision
+               OpDecorate %50 RelaxedPrecision
+               OpDecorate %51 RelaxedPrecision
+               OpDecorate %54 RelaxedPrecision
+               OpDecorate %55 RelaxedPrecision
+               OpDecorate %56 RelaxedPrecision
+               OpDecorate %57 RelaxedPrecision
+               OpDecorate %58 RelaxedPrecision
+               OpDecorate %63 RelaxedPrecision
+               OpDecorate %63 Location 0
+               OpDecorate %64 RelaxedPrecision
+               OpDecorate %65 RelaxedPrecision
+               OpDecorate %66 RelaxedPrecision
+               OpDecorate %68 RelaxedPrecision
+               OpDecorate %68 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeFunction %6
+         %12 = OpTypePointer Function %6
+         %14 = OpConstant %6 0
+         %15 = OpTypeInt 32 0
+         %16 = OpTypeStruct %15 %15
+         %17 = OpTypePointer Uniform %16
+         %18 = OpVariable %17 Uniform
+         %19 = OpTypeInt 32 1
+         %20 = OpConstant %19 0
+         %21 = OpTypePointer Uniform %15
+         %24 = OpConstant %15 0
+         %25 = OpTypeBool
+         %29 = OpConstant %6 1
+         %32 = OpConstant %19 1
+         %49 = OpConstant %6 10
+         %52 = OpConstant %6 0.5
+         %53 = OpConstant %6 0.699999988
+         %61 = OpTypeVector %6 4
+         %62 = OpTypePointer Output %61
+         %63 = OpVariable %62 Output
+         %67 = OpTypePointer Input %6
+         %68 = OpVariable %67 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %64 = OpFunctionCall %6 %8
+         %65 = OpFunctionCall %6 %10
+         %66 = OpCompositeConstruct %61 %64 %65 %14 %29
+               OpStore %63 %66
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %6 None %7
+          %9 = OpLabel
+         %13 = OpVariable %12 Function
+               OpStore %13 %14
+         %22 = OpAccessChain %21 %18 %20
+         %23 = OpLoad %15 %22
+         %26 = OpINotEqual %25 %23 %24
+               OpSelectionMerge %28 None
+               OpBranchConditional %26 %27 %28
+         %27 = OpLabel
+         %30 = OpLoad %6 %13
+         %31 = OpFAdd %6 %30 %29
+               OpStore %13 %31
+               OpBranch %28
+         %28 = OpLabel
+         %33 = OpAccessChain %21 %18 %32
+         %34 = OpLoad %15 %33
+         %35 = OpConvertUToF %6 %34
+         %36 = OpExtInst %6 %1 Log2 %35
+         %37 = OpLoad %6 %13
+         %38 = OpFAdd %6 %37 %36
+               OpStore %13 %38
+         %39 = OpLoad %6 %13
+         %40 = OpLoad %6 %13
+         %41 = OpExtInst %6 %1 Sqrt %40
+         %42 = OpFSub %6 %39 %41
+               OpReturnValue %42
+               OpFunctionEnd
+         %10 = OpFunction %6 None %7
+         %11 = OpLabel
+         %45 = OpVariable %12 Function
+         %46 = OpAccessChain %21 %18 %20
+         %47 = OpLoad %15 %46
+         %48 = OpConvertUToF %6 %47
+         %50 = OpFDiv %6 %48 %49
+               OpStore %45 %50
+         %51 = OpLoad %6 %45
+         %54 = OpExtInst %6 %1 FClamp %51 %52 %53
+         %55 = OpLoad %6 %45
+         %56 = OpFMul %6 %55 %54
+               OpStore %45 %56
+         %57 = OpLoad %6 %45
+         %58 = OpExtInst %6 %1 Exp %57
+               OpReturnValue %58
+               OpFunctionEnd
+
+)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %63 %69
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %13 RelaxedPrecision
+               OpMemberDecorate %16 0 RelaxedPrecision
+               OpMemberDecorate %16 0 Offset 0
+               OpMemberDecorate %16 1 RelaxedPrecision
+               OpMemberDecorate %16 1 Offset 4
+               OpDecorate %16 Block
+               OpDecorate %18 DescriptorSet 0
+               OpDecorate %18 Binding 0
+               OpDecorate %23 RelaxedPrecision
+               OpDecorate %24 RelaxedPrecision
+               OpDecorate %25 RelaxedPrecision
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %27 RelaxedPrecision
+               OpDecorate %28 RelaxedPrecision
+               OpDecorate %29 RelaxedPrecision
+               OpDecorate %30 RelaxedPrecision
+               OpDecorate %31 RelaxedPrecision
+               OpDecorate %34 RelaxedPrecision
+               OpDecorate %37 RelaxedPrecision
+               OpDecorate %38 RelaxedPrecision
+               OpDecorate %40 RelaxedPrecision
+               OpDecorate %41 RelaxedPrecision
+               OpDecorate %44 RelaxedPrecision
+               OpDecorate %45 RelaxedPrecision
+               OpDecorate %46 RelaxedPrecision
+               OpDecorate %48 RelaxedPrecision
+               OpDecorate %55 RelaxedPrecision
+               OpDecorate %56 RelaxedPrecision
+               OpDecorate %57 RelaxedPrecision
+               OpDecorate %58 RelaxedPrecision
+               OpDecorate %63 RelaxedPrecision
+               OpDecorate %63 Location 0
+               OpDecorate %64 RelaxedPrecision
+               OpDecorate %65 RelaxedPrecision
+               OpDecorate %67 RelaxedPrecision
+               OpDecorate %69 RelaxedPrecision
+               OpDecorate %69 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeFunction %6
+         %12 = OpTypePointer Function %6
+         %14 = OpConstant %6 0
+         %15 = OpTypeInt 32 0
+         %16 = OpTypeStruct %15 %15
+         %17 = OpTypePointer Uniform %16
+         %18 = OpVariable %17 Uniform
+         %19 = OpTypeInt 32 1
+         %20 = OpConstant %19 1
+         %21 = OpTypePointer Uniform %15
+         %35 = OpConstant %19 0
+         %39 = OpConstant %6 10
+         %42 = OpConstant %6 0.5
+         %43 = OpConstant %6 0.699999988
+         %49 = OpConstant %15 0
+         %50 = OpTypeBool
+         %54 = OpConstant %6 0.100000001
+         %61 = OpTypeVector %6 4
+         %62 = OpTypePointer Output %61
+         %63 = OpVariable %62 Output
+         %66 = OpConstant %6 1
+         %68 = OpTypePointer Input %6
+         %69 = OpVariable %68 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %64 = OpFunctionCall %6 %8
+         %65 = OpFunctionCall %6 %10
+         %67 = OpCompositeConstruct %61 %64 %65 %14 %66
+               OpStore %63 %67
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %6 None %7
+          %9 = OpLabel
+         %13 = OpVariable %12 Function
+               OpStore %13 %14
+         %22 = OpAccessChain %21 %18 %20
+         %23 = OpLoad %15 %22
+         %24 = OpConvertUToF %6 %23
+         %25 = OpExtInst %6 %1 Log2 %24
+         %26 = OpLoad %6 %13
+         %27 = OpFAdd %6 %26 %25
+               OpStore %13 %27
+         %28 = OpLoad %6 %13
+         %29 = OpLoad %6 %13
+         %30 = OpExtInst %6 %1 Sqrt %29
+         %31 = OpFSub %6 %28 %30
+               OpReturnValue %31
+               OpFunctionEnd
+         %10 = OpFunction %6 None %7
+         %11 = OpLabel
+         %34 = OpVariable %12 Function
+         %36 = OpAccessChain %21 %18 %35
+         %37 = OpLoad %15 %36
+         %38 = OpConvertUToF %6 %37
+         %40 = OpFDiv %6 %38 %39
+               OpStore %34 %40
+         %41 = OpLoad %6 %34
+         %44 = OpExtInst %6 %1 FClamp %41 %42 %43
+         %45 = OpLoad %6 %34
+         %46 = OpFMul %6 %45 %44
+               OpStore %34 %46
+         %47 = OpAccessChain %21 %18 %20
+         %48 = OpLoad %15 %47
+         %51 = OpINotEqual %50 %48 %49
+               OpSelectionMerge %53 None
+               OpBranchConditional %51 %52 %53
+         %52 = OpLabel
+         %55 = OpLoad %6 %34
+         %56 = OpFSub %6 %55 %54
+               OpStore %34 %56
+               OpBranch %53
+         %53 = OpLabel
+         %57 = OpLoad %6 %34
+         %58 = OpExtInst %6 %1 Exp %57
+               OpReturnValue %58
+               OpFunctionEnd
+
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 69
++; Bound: 81
+ ; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %63 %68
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpDecorate %8 RelaxedPrecision
+ OpDecorate %10 RelaxedPrecision
+ OpDecorate %13 RelaxedPrecision
+ OpMemberDecorate %16 0 RelaxedPrecision
+ OpMemberDecorate %16 0 Offset 0
+ OpMemberDecorate %16 1 RelaxedPrecision
+ OpMemberDecorate %16 1 Offset 4
+ OpDecorate %16 Block
+ OpDecorate %18 DescriptorSet 0
+ OpDecorate %18 Binding 0
+-OpDecorate %23 RelaxedPrecision
+-OpDecorate %30 RelaxedPrecision
+-OpDecorate %31 RelaxedPrecision
+ OpDecorate %34 RelaxedPrecision
+ OpDecorate %35 RelaxedPrecision
+ OpDecorate %36 RelaxedPrecision
+ OpDecorate %37 RelaxedPrecision
+ OpDecorate %38 RelaxedPrecision
+ OpDecorate %39 RelaxedPrecision
+ OpDecorate %40 RelaxedPrecision
+ OpDecorate %41 RelaxedPrecision
+ OpDecorate %42 RelaxedPrecision
+ OpDecorate %45 RelaxedPrecision
+ OpDecorate %47 RelaxedPrecision
+ OpDecorate %48 RelaxedPrecision
+ OpDecorate %50 RelaxedPrecision
+ OpDecorate %51 RelaxedPrecision
+ OpDecorate %54 RelaxedPrecision
+ OpDecorate %55 RelaxedPrecision
+ OpDecorate %56 RelaxedPrecision
++OpDecorate %72 RelaxedPrecision
+ OpDecorate %57 RelaxedPrecision
++OpDecorate %77 RelaxedPrecision
++OpDecorate %78 RelaxedPrecision
+ OpDecorate %58 RelaxedPrecision
+ OpDecorate %63 RelaxedPrecision
+ OpDecorate %63 Location 0
+ OpDecorate %64 RelaxedPrecision
+ OpDecorate %65 RelaxedPrecision
+ OpDecorate %66 RelaxedPrecision
+ OpDecorate %68 RelaxedPrecision
+ OpDecorate %68 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypeFunction %6
+ %12 = OpTypePointer Function %6
+ %14 = OpConstant %6 0
+ %15 = OpTypeInt 32 0
+ %16 = OpTypeStruct %15 %15
+ %17 = OpTypePointer Uniform %16
+ %18 = OpVariable %17 Uniform
+ %19 = OpTypeInt 32 1
+ %20 = OpConstant %19 0
+ %21 = OpTypePointer Uniform %15
+ %24 = OpConstant %15 0
+ %25 = OpTypeBool
+ %29 = OpConstant %6 1
+ %32 = OpConstant %19 1
+ %49 = OpConstant %6 10
+ %52 = OpConstant %6 0.5
++%76 = OpConstant %6 0.100000001
+ %53 = OpConstant %6 0.699999988
+ %61 = OpTypeVector %6 4
+ %62 = OpTypePointer Output %61
+ %63 = OpVariable %62 Output
+ %67 = OpTypePointer Input %6
+ %68 = OpVariable %67 Input
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %64 = OpFunctionCall %6 %8
+ %65 = OpFunctionCall %6 %10
+ %66 = OpCompositeConstruct %61 %64 %65 %14 %29
+ OpStore %63 %66
+ OpReturn
+ OpFunctionEnd
+ %8 = OpFunction %6 None %7
+ %9 = OpLabel
+ %13 = OpVariable %12 Function
+ OpStore %13 %14
+-%22 = OpAccessChain %21 %18 %20
+-%23 = OpLoad %15 %22
+-%26 = OpINotEqual %25 %23 %24
+-OpSelectionMerge %28 None
+-OpBranchConditional %26 %27 %28
+-%27 = OpLabel
+-%30 = OpLoad %6 %13
+-%31 = OpFAdd %6 %30 %29
+-OpStore %13 %31
+-OpBranch %28
+-%28 = OpLabel
+ %33 = OpAccessChain %21 %18 %32
+ %34 = OpLoad %15 %33
+ %35 = OpConvertUToF %6 %34
+ %36 = OpExtInst %6 %1 Log2 %35
+ %37 = OpLoad %6 %13
+ %38 = OpFAdd %6 %37 %36
+ OpStore %13 %38
+ %39 = OpLoad %6 %13
+ %40 = OpLoad %6 %13
+ %41 = OpExtInst %6 %1 Sqrt %40
+ %42 = OpFSub %6 %39 %41
+ OpReturnValue %42
+ OpFunctionEnd
+ %10 = OpFunction %6 None %7
+ %11 = OpLabel
+ %45 = OpVariable %12 Function
+ %46 = OpAccessChain %21 %18 %20
+ %47 = OpLoad %15 %46
+ %48 = OpConvertUToF %6 %47
+ %50 = OpFDiv %6 %48 %49
+ OpStore %45 %50
+ %51 = OpLoad %6 %45
+ %54 = OpExtInst %6 %1 FClamp %51 %52 %53
+ %55 = OpLoad %6 %45
+ %56 = OpFMul %6 %55 %54
+ OpStore %45 %56
++%71 = OpAccessChain %21 %18 %32
++%72 = OpLoad %15 %71
++%73 = OpINotEqual %25 %72 %24
++OpSelectionMerge %75 None
++OpBranchConditional %73 %74 %75
++%74 = OpLabel
+ %57 = OpLoad %6 %45
++%77 = OpFSub %6 %57 %76
++OpStore %45 %77
++OpBranch %75
++%75 = OpLabel
++%78 = OpLoad %6 %45
+-%58 = OpExtInst %6 %1 Exp %57
++%58 = OpExtInst %6 %1 Exp %78
+ OpReturnValue %58
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/extra_if_block_dst.spvasm b/test/diff/diff_files/extra_if_block_dst.spvasm
new file mode 100644
index 0000000..79bda83
--- /dev/null
+++ b/test/diff/diff_files/extra_if_block_dst.spvasm
@@ -0,0 +1,136 @@
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %63 %69
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "f1("
+               OpName %10 "f2("
+               OpName %13 "v"
+               OpName %16 "Buffer"
+               OpMemberName %16 0 "flag1"
+               OpMemberName %16 1 "flag2"
+               OpName %18 ""
+               OpName %34 "v"
+               OpName %63 "color"
+               OpName %69 "v"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %13 RelaxedPrecision
+               OpMemberDecorate %16 0 RelaxedPrecision
+               OpMemberDecorate %16 0 Offset 0
+               OpMemberDecorate %16 1 RelaxedPrecision
+               OpMemberDecorate %16 1 Offset 4
+               OpDecorate %16 Block
+               OpDecorate %18 DescriptorSet 0
+               OpDecorate %18 Binding 0
+               OpDecorate %23 RelaxedPrecision
+               OpDecorate %24 RelaxedPrecision
+               OpDecorate %25 RelaxedPrecision
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %27 RelaxedPrecision
+               OpDecorate %28 RelaxedPrecision
+               OpDecorate %29 RelaxedPrecision
+               OpDecorate %30 RelaxedPrecision
+               OpDecorate %31 RelaxedPrecision
+               OpDecorate %34 RelaxedPrecision
+               OpDecorate %37 RelaxedPrecision
+               OpDecorate %38 RelaxedPrecision
+               OpDecorate %40 RelaxedPrecision
+               OpDecorate %41 RelaxedPrecision
+               OpDecorate %44 RelaxedPrecision
+               OpDecorate %45 RelaxedPrecision
+               OpDecorate %46 RelaxedPrecision
+               OpDecorate %48 RelaxedPrecision
+               OpDecorate %55 RelaxedPrecision
+               OpDecorate %56 RelaxedPrecision
+               OpDecorate %57 RelaxedPrecision
+               OpDecorate %58 RelaxedPrecision
+               OpDecorate %63 RelaxedPrecision
+               OpDecorate %63 Location 0
+               OpDecorate %64 RelaxedPrecision
+               OpDecorate %65 RelaxedPrecision
+               OpDecorate %67 RelaxedPrecision
+               OpDecorate %69 RelaxedPrecision
+               OpDecorate %69 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeFunction %6
+         %12 = OpTypePointer Function %6
+         %14 = OpConstant %6 0
+         %15 = OpTypeInt 32 0
+         %16 = OpTypeStruct %15 %15
+         %17 = OpTypePointer Uniform %16
+         %18 = OpVariable %17 Uniform
+         %19 = OpTypeInt 32 1
+         %20 = OpConstant %19 1
+         %21 = OpTypePointer Uniform %15
+         %35 = OpConstant %19 0
+         %39 = OpConstant %6 10
+         %42 = OpConstant %6 0.5
+         %43 = OpConstant %6 0.699999988
+         %49 = OpConstant %15 0
+         %50 = OpTypeBool
+         %54 = OpConstant %6 0.100000001
+         %61 = OpTypeVector %6 4
+         %62 = OpTypePointer Output %61
+         %63 = OpVariable %62 Output
+         %66 = OpConstant %6 1
+         %68 = OpTypePointer Input %6
+         %69 = OpVariable %68 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %64 = OpFunctionCall %6 %8
+         %65 = OpFunctionCall %6 %10
+         %67 = OpCompositeConstruct %61 %64 %65 %14 %66
+               OpStore %63 %67
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %6 None %7
+          %9 = OpLabel
+         %13 = OpVariable %12 Function
+               OpStore %13 %14
+         %22 = OpAccessChain %21 %18 %20
+         %23 = OpLoad %15 %22
+         %24 = OpConvertUToF %6 %23
+         %25 = OpExtInst %6 %1 Log2 %24
+         %26 = OpLoad %6 %13
+         %27 = OpFAdd %6 %26 %25
+               OpStore %13 %27
+         %28 = OpLoad %6 %13
+         %29 = OpLoad %6 %13
+         %30 = OpExtInst %6 %1 Sqrt %29
+         %31 = OpFSub %6 %28 %30
+               OpReturnValue %31
+               OpFunctionEnd
+         %10 = OpFunction %6 None %7
+         %11 = OpLabel
+         %34 = OpVariable %12 Function
+         %36 = OpAccessChain %21 %18 %35
+         %37 = OpLoad %15 %36
+         %38 = OpConvertUToF %6 %37
+         %40 = OpFDiv %6 %38 %39
+               OpStore %34 %40
+         %41 = OpLoad %6 %34
+         %44 = OpExtInst %6 %1 FClamp %41 %42 %43
+         %45 = OpLoad %6 %34
+         %46 = OpFMul %6 %45 %44
+               OpStore %34 %46
+         %47 = OpAccessChain %21 %18 %20
+         %48 = OpLoad %15 %47
+         %51 = OpINotEqual %50 %48 %49
+               OpSelectionMerge %53 None
+               OpBranchConditional %51 %52 %53
+         %52 = OpLabel
+         %55 = OpLoad %6 %34
+         %56 = OpFSub %6 %55 %54
+               OpStore %34 %56
+               OpBranch %53
+         %53 = OpLabel
+         %57 = OpLoad %6 %34
+         %58 = OpExtInst %6 %1 Exp %57
+               OpReturnValue %58
+               OpFunctionEnd
+
diff --git a/test/diff/diff_files/extra_if_block_src.spvasm b/test/diff/diff_files/extra_if_block_src.spvasm
new file mode 100644
index 0000000..1b43ccb
--- /dev/null
+++ b/test/diff/diff_files/extra_if_block_src.spvasm
@@ -0,0 +1,137 @@
+;; Test where src has an extra if block in one function, and dst has an extra
+;; if block in another function.
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %63 %68
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "f1("
+               OpName %10 "f2("
+               OpName %13 "v"
+               OpName %16 "Buffer"
+               OpMemberName %16 0 "flag1"
+               OpMemberName %16 1 "flag2"
+               OpName %18 ""
+               OpName %45 "v"
+               OpName %63 "color"
+               OpName %68 "v"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %13 RelaxedPrecision
+               OpMemberDecorate %16 0 RelaxedPrecision
+               OpMemberDecorate %16 0 Offset 0
+               OpMemberDecorate %16 1 RelaxedPrecision
+               OpMemberDecorate %16 1 Offset 4
+               OpDecorate %16 Block
+               OpDecorate %18 DescriptorSet 0
+               OpDecorate %18 Binding 0
+               OpDecorate %23 RelaxedPrecision
+               OpDecorate %30 RelaxedPrecision
+               OpDecorate %31 RelaxedPrecision
+               OpDecorate %34 RelaxedPrecision
+               OpDecorate %35 RelaxedPrecision
+               OpDecorate %36 RelaxedPrecision
+               OpDecorate %37 RelaxedPrecision
+               OpDecorate %38 RelaxedPrecision
+               OpDecorate %39 RelaxedPrecision
+               OpDecorate %40 RelaxedPrecision
+               OpDecorate %41 RelaxedPrecision
+               OpDecorate %42 RelaxedPrecision
+               OpDecorate %45 RelaxedPrecision
+               OpDecorate %47 RelaxedPrecision
+               OpDecorate %48 RelaxedPrecision
+               OpDecorate %50 RelaxedPrecision
+               OpDecorate %51 RelaxedPrecision
+               OpDecorate %54 RelaxedPrecision
+               OpDecorate %55 RelaxedPrecision
+               OpDecorate %56 RelaxedPrecision
+               OpDecorate %57 RelaxedPrecision
+               OpDecorate %58 RelaxedPrecision
+               OpDecorate %63 RelaxedPrecision
+               OpDecorate %63 Location 0
+               OpDecorate %64 RelaxedPrecision
+               OpDecorate %65 RelaxedPrecision
+               OpDecorate %66 RelaxedPrecision
+               OpDecorate %68 RelaxedPrecision
+               OpDecorate %68 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeFunction %6
+         %12 = OpTypePointer Function %6
+         %14 = OpConstant %6 0
+         %15 = OpTypeInt 32 0
+         %16 = OpTypeStruct %15 %15
+         %17 = OpTypePointer Uniform %16
+         %18 = OpVariable %17 Uniform
+         %19 = OpTypeInt 32 1
+         %20 = OpConstant %19 0
+         %21 = OpTypePointer Uniform %15
+         %24 = OpConstant %15 0
+         %25 = OpTypeBool
+         %29 = OpConstant %6 1
+         %32 = OpConstant %19 1
+         %49 = OpConstant %6 10
+         %52 = OpConstant %6 0.5
+         %53 = OpConstant %6 0.699999988
+         %61 = OpTypeVector %6 4
+         %62 = OpTypePointer Output %61
+         %63 = OpVariable %62 Output
+         %67 = OpTypePointer Input %6
+         %68 = OpVariable %67 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %64 = OpFunctionCall %6 %8
+         %65 = OpFunctionCall %6 %10
+         %66 = OpCompositeConstruct %61 %64 %65 %14 %29
+               OpStore %63 %66
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %6 None %7
+          %9 = OpLabel
+         %13 = OpVariable %12 Function
+               OpStore %13 %14
+         %22 = OpAccessChain %21 %18 %20
+         %23 = OpLoad %15 %22
+         %26 = OpINotEqual %25 %23 %24
+               OpSelectionMerge %28 None
+               OpBranchConditional %26 %27 %28
+         %27 = OpLabel
+         %30 = OpLoad %6 %13
+         %31 = OpFAdd %6 %30 %29
+               OpStore %13 %31
+               OpBranch %28
+         %28 = OpLabel
+         %33 = OpAccessChain %21 %18 %32
+         %34 = OpLoad %15 %33
+         %35 = OpConvertUToF %6 %34
+         %36 = OpExtInst %6 %1 Log2 %35
+         %37 = OpLoad %6 %13
+         %38 = OpFAdd %6 %37 %36
+               OpStore %13 %38
+         %39 = OpLoad %6 %13
+         %40 = OpLoad %6 %13
+         %41 = OpExtInst %6 %1 Sqrt %40
+         %42 = OpFSub %6 %39 %41
+               OpReturnValue %42
+               OpFunctionEnd
+         %10 = OpFunction %6 None %7
+         %11 = OpLabel
+         %45 = OpVariable %12 Function
+         %46 = OpAccessChain %21 %18 %20
+         %47 = OpLoad %15 %46
+         %48 = OpConvertUToF %6 %47
+         %50 = OpFDiv %6 %48 %49
+               OpStore %45 %50
+         %51 = OpLoad %6 %45
+         %54 = OpExtInst %6 %1 FClamp %51 %52 %53
+         %55 = OpLoad %6 %45
+         %56 = OpFMul %6 %55 %54
+               OpStore %45 %56
+         %57 = OpLoad %6 %45
+         %58 = OpExtInst %6 %1 Exp %57
+               OpReturnValue %58
+               OpFunctionEnd
+
diff --git a/test/diff/diff_files/generate_tests.py b/test/diff/diff_files/generate_tests.py
new file mode 100755
index 0000000..cc3175d
--- /dev/null
+++ b/test/diff/diff_files/generate_tests.py
@@ -0,0 +1,304 @@
+#! /usr/bin/python3
+#
+# Copyright (c) 2022 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.
+
+import glob
+import os
+import subprocess
+import sys
+
+# A handful of relevant tests are hand-picked to generate extra unit tests with
+# specific options of spirv-diff.
+IGNORE_SET_BINDING_TESTS = ['different_decorations_vertex']
+IGNORE_LOCATION_TESTS = ['different_decorations_fragment']
+IGNORE_DECORATIONS_TESTS = ['different_decorations_vertex', 'different_decorations_fragment']
+DUMP_IDS_TESTS = ['basic', 'int_vs_uint_constants', 'multiple_same_entry_points', 'small_functions_small_diffs']
+
+LICENSE = u"""Copyright (c) 2022 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.
+"""
+
+TEMPLATE_TEST_FILE = u"""// GENERATED FILE - DO NOT EDIT.
+// Generated by {script_name}
+//
+{license}
+
+#include "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {{
+namespace diff {{
+namespace {{
+
+{test_comment}
+constexpr char kSrc[] = R"({src_spirv})";
+constexpr char kDst[] = R"({dst_spirv})";
+
+TEST(DiffTest, {test_name}) {{
+  constexpr char kDiff[] = R"({diff_spirv})";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}}
+
+TEST(DiffTest, {test_name}NoDebug) {{
+  constexpr char kSrcNoDebug[] = R"({src_spirv_no_debug})";
+  constexpr char kDstNoDebug[] = R"({dst_spirv_no_debug})";
+  constexpr char kDiff[] = R"({diff_spirv_no_debug})";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}}
+{extra_tests}
+}}  // namespace
+}}  // namespace diff
+}}  // namespace spvtools
+"""
+
+TEMPLATE_TEST_FUNC = u"""
+TEST(DiffTest, {test_name}{test_tag}) {{
+  constexpr char kDiff[] = R"({diff_spirv})";
+  Options options;
+  {test_options}
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}}
+"""
+
+TEMPLATE_TEST_FILES_CMAKE = u"""# GENERATED FILE - DO NOT EDIT.
+# Generated by {script_name}
+#
+{license}
+
+list(APPEND DIFF_TEST_FILES
+{test_files}
+)
+"""
+
+VARIANT_NONE = 0
+VARIANT_IGNORE_SET_BINDING = 1
+VARIANT_IGNORE_LOCATION = 2
+VARIANT_IGNORE_DECORATIONS = 3
+VARIANT_DUMP_IDS = 4
+
+def print_usage():
+    print("Usage: {} <path-to-spirv-diff>".format(sys.argv[0]))
+
+def remove_debug_info(in_path):
+    tmp_dir = '.no_dbg'
+
+    if not os.path.exists(tmp_dir):
+        os.makedirs(tmp_dir)
+
+    (in_basename, in_ext) = os.path.splitext(in_path)
+    out_name = in_basename + '_no_dbg' + in_ext
+    out_path = os.path.join(tmp_dir, out_name)
+
+    with open(in_path, 'r') as fin:
+        with open(out_path, 'w') as fout:
+            for line in fin:
+                ops = line.strip().split()
+                op = ops[0] if len(ops) > 0 else ''
+                if (op != ';;' and op != 'OpName' and op != 'OpMemberName' and op != 'OpString' and
+                    op != 'OpLine' and op != 'OpNoLine' and op != 'OpModuleProcessed'):
+                    fout.write(line)
+
+    return out_path
+
+def make_src_file(test_name):
+    return '{}_src.spvasm'.format(test_name)
+
+def make_dst_file(test_name):
+    return '{}_dst.spvasm'.format(test_name)
+
+def make_cpp_file(test_name):
+    return '{}_autogen.cpp'.format(test_name)
+
+def make_camel_case(test_name):
+    return test_name.replace('_', ' ').title().replace(' ', '')
+
+def make_comment(text, comment_prefix):
+    return '\n'.join([comment_prefix + (' ' if line.strip() else '') + line for line in text.splitlines()])
+
+def read_file(file_name):
+    with open(file_name, 'r') as f:
+        content = f.read()
+
+    # Use unix line endings.
+    content = content.replace('\r\n', '\n')
+
+    return content
+
+def parse_test_comment(src_spirv_file_name, src_spirv):
+    src_spirv_lines = src_spirv.splitlines()
+    comment_line_count = 0
+    while comment_line_count < len(src_spirv_lines):
+        if not src_spirv_lines[comment_line_count].strip().startswith(';;'):
+            break
+        comment_line_count += 1
+
+    if comment_line_count == 0:
+        print("Expected comment on test file '{}'.  See README.md next to this file.".format(src_spirv_file_name))
+        sys.exit(1)
+
+    comment_block = src_spirv_lines[:comment_line_count]
+    spirv_block = src_spirv_lines[comment_line_count:]
+
+    comment_block = ['// ' + line.replace(';;', '').strip() for line in comment_block]
+
+    return '\n'.join(spirv_block), '\n'.join(comment_block)
+
+def run_diff_tool(diff_tool, src_file, dst_file, variant):
+    args = [diff_tool]
+
+    if variant == VARIANT_IGNORE_SET_BINDING or variant == VARIANT_IGNORE_DECORATIONS:
+        args.append('--ignore-set-binding')
+
+    if variant == VARIANT_IGNORE_LOCATION or variant == VARIANT_IGNORE_DECORATIONS:
+        args.append('--ignore-location')
+
+    if variant == VARIANT_DUMP_IDS:
+        args.append('--with-id-map')
+
+    args.append('--no-color')
+    args.append('--no-indent')
+
+    args.append(src_file)
+    args.append(dst_file)
+
+    success = True
+    print(' '.join(args))
+    process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
+    out, err = process.communicate()
+
+    if process.returncode != 0:
+        print(err)
+        sys.exit(process.returncode)
+
+    # Use unix line endings.
+    out = out.replace('\r\n', '\n')
+
+    return out
+
+def generate_extra_test(diff_tool, src_file, dst_file, variant, test_name_camel_case, test_tag, test_options):
+    diff = run_diff_tool(diff_tool, src_file, dst_file, variant)
+    return TEMPLATE_TEST_FUNC.format(
+        test_name = test_name_camel_case,
+        test_tag = test_tag,
+        test_options = test_options,
+        diff_spirv = diff)
+
+def generate_test(diff_tool, test_name):
+    src_file = make_src_file(test_name)
+    dst_file = make_dst_file(test_name)
+    src_file_no_debug = remove_debug_info(src_file)
+    dst_file_no_debug = remove_debug_info(dst_file)
+
+    src_spirv = read_file(src_file)
+    dst_spirv = read_file(dst_file)
+    src_spirv_no_debug = read_file(src_file_no_debug)
+    dst_spirv_no_debug = read_file(dst_file_no_debug)
+
+    test_name_camel_case = make_camel_case(test_name)
+
+    diff_spirv = run_diff_tool(diff_tool, src_file, dst_file, VARIANT_NONE)
+    diff_spirv_no_debug = run_diff_tool(diff_tool, src_file_no_debug, dst_file_no_debug, VARIANT_NONE)
+
+    extra_tests = []
+
+    if test_name in IGNORE_SET_BINDING_TESTS:
+        extra_tests.append(generate_extra_test(diff_tool, src_file, dst_file, VARIANT_IGNORE_SET_BINDING,
+            test_name_camel_case, 'IgnoreSetBinding', 'options.ignore_set_binding = true;'))
+
+    if test_name in IGNORE_LOCATION_TESTS:
+        extra_tests.append(generate_extra_test(diff_tool, src_file, dst_file, VARIANT_IGNORE_LOCATION,
+            test_name_camel_case, 'IgnoreLocation', 'options.ignore_location = true;'))
+
+    if test_name in IGNORE_DECORATIONS_TESTS:
+        extra_tests.append(generate_extra_test(diff_tool, src_file, dst_file, VARIANT_IGNORE_DECORATIONS,
+            test_name_camel_case, 'IgnoreSetBindingLocation',
+            '\n  '.join(['options.ignore_set_binding = true;', 'options.ignore_location = true;'])))
+
+    if test_name in DUMP_IDS_TESTS:
+        extra_tests.append(generate_extra_test(diff_tool, src_file, dst_file, VARIANT_DUMP_IDS,
+            test_name_camel_case, 'DumpIds', 'options.dump_id_map = true;'))
+
+    src_spirv, test_comment = parse_test_comment(src_file, src_spirv)
+
+    test_file = TEMPLATE_TEST_FILE.format(
+            script_name = os.path.basename(__file__),
+            license = make_comment(LICENSE, '//'),
+            test_comment = test_comment,
+            test_name = test_name_camel_case,
+            src_spirv = src_spirv,
+            dst_spirv = dst_spirv,
+            diff_spirv = diff_spirv,
+            src_spirv_no_debug = src_spirv_no_debug,
+            dst_spirv_no_debug = dst_spirv_no_debug,
+            diff_spirv_no_debug = diff_spirv_no_debug,
+            extra_tests = ''.join(extra_tests))
+
+    test_file_name = make_cpp_file(test_name)
+    with open(test_file_name, 'wb') as fout:
+        fout.write(str.encode(test_file))
+
+    return test_file_name
+
+def generate_tests(diff_tool, test_names):
+    return [generate_test(diff_tool, test_name) for test_name in test_names]
+
+def generate_cmake(test_files):
+    cmake = TEMPLATE_TEST_FILES_CMAKE.format(
+            script_name = os.path.basename(__file__),
+            license = make_comment(LICENSE, '#'),
+            test_files = '\n'.join(['"diff_files/{}"'.format(f) for f in test_files]))
+
+    with open('diff_test_files_autogen.cmake', 'wb') as fout:
+        fout.write(str.encode(cmake))
+
+def main():
+
+    if len(sys.argv) != 2:
+        print_usage()
+        return 1
+
+    diff_tool = sys.argv[1]
+    if not os.path.exists(diff_tool):
+        print("No such file: {}".format(diff_tool))
+        print_usage()
+        return 1
+
+    diff_tool = os.path.realpath(diff_tool)
+    os.chdir(os.path.dirname(__file__))
+
+    test_names = sorted([f[:-11] for f in glob.glob("*_src.spvasm")])
+
+    test_files = generate_tests(diff_tool, test_names)
+
+    generate_cmake(test_files)
+
+    return 0
+
+if __name__ == '__main__':
+    sys.exit(main())
diff --git a/test/diff/diff_files/index_signedness_autogen.cpp b/test/diff/diff_files/index_signedness_autogen.cpp
new file mode 100644
index 0000000..ab650be
--- /dev/null
+++ b/test/diff/diff_files/index_signedness_autogen.cpp
@@ -0,0 +1,733 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Test where signedness of indices are different between src and dst.
+constexpr char kSrc[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %13 "BufferOut"
+               OpMemberName %13 0 "o1"
+               OpMemberName %13 1 "o2"
+               OpMemberName %13 2 "o3"
+               OpName %15 ""
+               OpName %22 "BufferIn"
+               OpMemberName %22 0 "i1"
+               OpMemberName %22 1 "i2"
+               OpName %24 ""
+               OpDecorate %8 ArrayStride 4
+               OpDecorate %9 ArrayStride 4
+               OpDecorate %11 ArrayStride 4
+               OpDecorate %12 ArrayStride 8
+               OpMemberDecorate %13 0 Offset 0
+               OpMemberDecorate %13 1 Offset 12
+               OpMemberDecorate %13 2 Offset 24
+               OpDecorate %13 BufferBlock
+               OpDecorate %15 DescriptorSet 0
+               OpDecorate %15 Binding 1
+               OpDecorate %18 ArrayStride 16
+               OpDecorate %19 ArrayStride 48
+               OpDecorate %21 ArrayStride 16
+               OpMemberDecorate %22 0 Offset 0
+               OpMemberDecorate %22 1 Offset 96
+               OpDecorate %22 Block
+               OpDecorate %24 DescriptorSet 0
+               OpDecorate %24 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 0
+          %7 = OpConstant %6 3
+          %8 = OpTypeArray %6 %7
+          %9 = OpTypeArray %6 %7
+         %10 = OpConstant %6 2
+         %11 = OpTypeArray %6 %10
+         %12 = OpTypeArray %11 %10
+         %13 = OpTypeStruct %8 %9 %12
+         %14 = OpTypePointer Uniform %13
+         %15 = OpVariable %14 Uniform
+         %16 = OpTypeInt 32 1
+         %17 = OpConstant %16 0
+         %18 = OpTypeArray %6 %7
+         %19 = OpTypeArray %18 %10
+         %20 = OpConstant %6 4
+         %21 = OpTypeArray %6 %20
+         %22 = OpTypeStruct %19 %21
+         %23 = OpTypePointer Uniform %22
+         %24 = OpVariable %23 Uniform
+         %25 = OpTypePointer Uniform %6
+         %28 = OpConstant %6 1
+         %31 = OpConstant %16 1
+         %34 = OpConstant %6 0
+         %37 = OpConstant %16 2
+         %61 = OpConstant %16 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %26 = OpAccessChain %25 %24 %17 %17 %17
+         %27 = OpLoad %6 %26
+         %29 = OpIAdd %6 %27 %28
+         %30 = OpAccessChain %25 %15 %17 %17
+               OpStore %30 %29
+         %32 = OpAccessChain %25 %24 %17 %31 %17
+         %33 = OpLoad %6 %32
+         %35 = OpIAdd %6 %33 %34
+         %36 = OpAccessChain %25 %15 %17 %31
+               OpStore %36 %35
+         %38 = OpAccessChain %25 %24 %17 %31 %31
+         %39 = OpLoad %6 %38
+         %40 = OpIAdd %6 %39 %10
+         %41 = OpAccessChain %25 %15 %17 %37
+               OpStore %41 %40
+         %42 = OpAccessChain %25 %24 %17 %17 %37
+         %43 = OpLoad %6 %42
+         %44 = OpAccessChain %25 %15 %31 %17
+               OpStore %44 %43
+         %45 = OpAccessChain %25 %24 %17 %17 %31
+         %46 = OpLoad %6 %45
+         %47 = OpIMul %6 %46 %7
+         %48 = OpAccessChain %25 %15 %31 %31
+               OpStore %48 %47
+         %49 = OpAccessChain %25 %24 %17 %31 %37
+         %50 = OpLoad %6 %49
+         %51 = OpAccessChain %25 %15 %31 %37
+               OpStore %51 %50
+         %52 = OpAccessChain %25 %24 %31 %17
+         %53 = OpLoad %6 %52
+         %54 = OpAccessChain %25 %15 %37 %17 %17
+               OpStore %54 %53
+         %55 = OpAccessChain %25 %24 %31 %31
+         %56 = OpLoad %6 %55
+         %57 = OpAccessChain %25 %15 %37 %17 %31
+               OpStore %57 %56
+         %58 = OpAccessChain %25 %24 %31 %37
+         %59 = OpLoad %6 %58
+         %60 = OpAccessChain %25 %15 %37 %31 %17
+               OpStore %60 %59
+         %62 = OpAccessChain %25 %24 %31 %61
+         %63 = OpLoad %6 %62
+         %64 = OpAccessChain %25 %15 %37 %31 %31
+               OpStore %64 %63
+               OpReturn
+               OpFunctionEnd
+)";
+constexpr char kDst[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %13 "BufferOut"
+               OpMemberName %13 0 "o1"
+               OpMemberName %13 1 "o2"
+               OpMemberName %13 2 "o3"
+               OpName %15 ""
+               OpName %22 "BufferIn"
+               OpMemberName %22 0 "i1"
+               OpMemberName %22 1 "i2"
+               OpName %24 ""
+               OpDecorate %8 ArrayStride 4
+               OpDecorate %9 ArrayStride 4
+               OpDecorate %11 ArrayStride 4
+               OpDecorate %12 ArrayStride 8
+               OpMemberDecorate %13 0 Offset 0
+               OpMemberDecorate %13 1 Offset 12
+               OpMemberDecorate %13 2 Offset 24
+               OpDecorate %13 BufferBlock
+               OpDecorate %15 DescriptorSet 0
+               OpDecorate %15 Binding 1
+               OpDecorate %18 ArrayStride 16
+               OpDecorate %19 ArrayStride 48
+               OpDecorate %21 ArrayStride 16
+               OpMemberDecorate %22 0 Offset 0
+               OpMemberDecorate %22 1 Offset 96
+               OpDecorate %22 Block
+               OpDecorate %24 DescriptorSet 0
+               OpDecorate %24 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 0
+         %16 = OpTypeInt 32 1
+          %7 = OpConstant %16 3
+          %8 = OpTypeArray %6 %7
+          %9 = OpTypeArray %6 %7
+         %10 = OpConstant %16 2
+         %11 = OpTypeArray %6 %10
+         %12 = OpTypeArray %11 %10
+         %13 = OpTypeStruct %8 %9 %12
+         %14 = OpTypePointer Uniform %13
+         %15 = OpVariable %14 Uniform
+         %18 = OpTypeArray %6 %7
+         %19 = OpTypeArray %18 %10
+         %20 = OpConstant %16 4
+         %21 = OpTypeArray %6 %20
+         %22 = OpTypeStruct %19 %21
+         %23 = OpTypePointer Uniform %22
+         %24 = OpVariable %23 Uniform
+         %25 = OpTypePointer Uniform %6
+         %17 = OpConstant %16 0
+         %28 = OpConstant %16 1
+         %31 = OpConstant %6 1
+         %34 = OpConstant %6 0
+         %37 = OpConstant %6 2
+         %61 = OpConstant %6 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %26 = OpAccessChain %25 %24 %17 %17 %17
+         %27 = OpLoad %6 %26
+         %29 = OpIAdd %6 %27 %28
+         %30 = OpAccessChain %25 %15 %17 %17
+               OpStore %30 %29
+         %32 = OpAccessChain %25 %24 %17 %31 %17
+         %33 = OpLoad %6 %32
+         %35 = OpIAdd %6 %33 %34
+         %36 = OpAccessChain %25 %15 %17 %31
+               OpStore %36 %35
+         %38 = OpAccessChain %25 %24 %17 %31 %31
+         %39 = OpLoad %6 %38
+         %40 = OpIAdd %6 %39 %37
+         %41 = OpAccessChain %25 %15 %17 %10
+               OpStore %41 %40
+         %42 = OpAccessChain %25 %24 %17 %17 %10
+         %43 = OpLoad %6 %42
+         %44 = OpAccessChain %25 %15 %31 %17
+               OpStore %44 %43
+         %45 = OpAccessChain %25 %24 %17 %17 %31
+         %46 = OpLoad %6 %45
+         %47 = OpIMul %6 %46 %7
+         %48 = OpAccessChain %25 %15 %31 %31
+               OpStore %48 %47
+         %49 = OpAccessChain %25 %24 %17 %31 %10
+         %50 = OpLoad %6 %49
+         %51 = OpAccessChain %25 %15 %31 %10
+               OpStore %51 %50
+         %52 = OpAccessChain %25 %24 %31 %17
+         %53 = OpLoad %6 %52
+         %54 = OpAccessChain %25 %15 %37 %17 %17
+               OpStore %54 %53
+         %55 = OpAccessChain %25 %24 %31 %31
+         %56 = OpLoad %6 %55
+         %57 = OpAccessChain %25 %15 %37 %17 %31
+               OpStore %57 %56
+         %58 = OpAccessChain %25 %24 %31 %37
+         %59 = OpLoad %6 %58
+         %60 = OpAccessChain %25 %15 %37 %31 %17
+               OpStore %60 %59
+         %62 = OpAccessChain %25 %24 %31 %61
+         %63 = OpLoad %6 %62
+         %64 = OpAccessChain %25 %15 %37 %31 %31
+               OpStore %64 %63
+               OpReturn
+               OpFunctionEnd
+
+)";
+
+TEST(DiffTest, IndexSignedness) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+ ; Bound: 65
+ ; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %4 "main"
+ OpExecutionMode %4 LocalSize 1 1 1
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %13 "BufferOut"
+ OpMemberName %13 0 "o1"
+ OpMemberName %13 1 "o2"
+ OpMemberName %13 2 "o3"
+ OpName %15 ""
+ OpName %22 "BufferIn"
+ OpMemberName %22 0 "i1"
+ OpMemberName %22 1 "i2"
+ OpName %24 ""
+ OpDecorate %8 ArrayStride 4
+ OpDecorate %9 ArrayStride 4
+ OpDecorate %11 ArrayStride 4
+ OpDecorate %12 ArrayStride 8
+ OpMemberDecorate %13 0 Offset 0
+ OpMemberDecorate %13 1 Offset 12
+ OpMemberDecorate %13 2 Offset 24
+ OpDecorate %13 BufferBlock
+ OpDecorate %15 DescriptorSet 0
+ OpDecorate %15 Binding 1
+ OpDecorate %18 ArrayStride 16
+ OpDecorate %19 ArrayStride 48
+ OpDecorate %21 ArrayStride 16
+ OpMemberDecorate %22 0 Offset 0
+ OpMemberDecorate %22 1 Offset 96
+ OpDecorate %22 Block
+ OpDecorate %24 DescriptorSet 0
+ OpDecorate %24 Binding 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 0
+ %7 = OpConstant %6 3
+-%8 = OpTypeArray %6 %7
++%8 = OpTypeArray %6 %61
+-%9 = OpTypeArray %6 %7
++%9 = OpTypeArray %6 %61
+ %10 = OpConstant %6 2
+-%11 = OpTypeArray %6 %10
++%11 = OpTypeArray %6 %37
+-%12 = OpTypeArray %11 %10
++%12 = OpTypeArray %11 %37
+ %13 = OpTypeStruct %8 %9 %12
+ %14 = OpTypePointer Uniform %13
+ %15 = OpVariable %14 Uniform
+ %16 = OpTypeInt 32 1
+ %17 = OpConstant %16 0
+-%18 = OpTypeArray %6 %7
++%18 = OpTypeArray %6 %61
+-%19 = OpTypeArray %18 %10
++%19 = OpTypeArray %18 %37
+-%20 = OpConstant %6 4
++%20 = OpConstant %16 4
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypeStruct %19 %21
+ %23 = OpTypePointer Uniform %22
+ %24 = OpVariable %23 Uniform
+ %25 = OpTypePointer Uniform %6
+ %28 = OpConstant %6 1
+ %31 = OpConstant %16 1
+ %34 = OpConstant %6 0
+ %37 = OpConstant %16 2
+ %61 = OpConstant %16 3
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %26 = OpAccessChain %25 %24 %17 %17 %17
+ %27 = OpLoad %6 %26
+-%29 = OpIAdd %6 %27 %28
++%29 = OpIAdd %6 %27 %31
+ %30 = OpAccessChain %25 %15 %17 %17
+ OpStore %30 %29
+-%32 = OpAccessChain %25 %24 %17 %31 %17
++%32 = OpAccessChain %25 %24 %17 %28 %17
+ %33 = OpLoad %6 %32
+ %35 = OpIAdd %6 %33 %34
+-%36 = OpAccessChain %25 %15 %17 %31
++%36 = OpAccessChain %25 %15 %17 %28
+ OpStore %36 %35
+-%38 = OpAccessChain %25 %24 %17 %31 %31
++%38 = OpAccessChain %25 %24 %17 %28 %28
+ %39 = OpLoad %6 %38
+ %40 = OpIAdd %6 %39 %10
+ %41 = OpAccessChain %25 %15 %17 %37
+ OpStore %41 %40
+ %42 = OpAccessChain %25 %24 %17 %17 %37
+ %43 = OpLoad %6 %42
+-%44 = OpAccessChain %25 %15 %31 %17
++%44 = OpAccessChain %25 %15 %28 %17
+ OpStore %44 %43
+-%45 = OpAccessChain %25 %24 %17 %17 %31
++%45 = OpAccessChain %25 %24 %17 %17 %28
+ %46 = OpLoad %6 %45
+-%47 = OpIMul %6 %46 %7
++%47 = OpIMul %6 %46 %61
+-%48 = OpAccessChain %25 %15 %31 %31
++%48 = OpAccessChain %25 %15 %28 %28
+ OpStore %48 %47
+-%49 = OpAccessChain %25 %24 %17 %31 %37
++%49 = OpAccessChain %25 %24 %17 %28 %37
+ %50 = OpLoad %6 %49
+-%51 = OpAccessChain %25 %15 %31 %37
++%51 = OpAccessChain %25 %15 %28 %37
+ OpStore %51 %50
+-%52 = OpAccessChain %25 %24 %31 %17
++%52 = OpAccessChain %25 %24 %28 %17
+ %53 = OpLoad %6 %52
+-%54 = OpAccessChain %25 %15 %37 %17 %17
++%54 = OpAccessChain %25 %15 %10 %17 %17
+ OpStore %54 %53
+-%55 = OpAccessChain %25 %24 %31 %31
++%55 = OpAccessChain %25 %24 %28 %28
+ %56 = OpLoad %6 %55
+-%57 = OpAccessChain %25 %15 %37 %17 %31
++%57 = OpAccessChain %25 %15 %10 %17 %28
+ OpStore %57 %56
+-%58 = OpAccessChain %25 %24 %31 %37
++%58 = OpAccessChain %25 %24 %28 %10
+ %59 = OpLoad %6 %58
+-%60 = OpAccessChain %25 %15 %37 %31 %17
++%60 = OpAccessChain %25 %15 %10 %28 %17
+ OpStore %60 %59
+-%62 = OpAccessChain %25 %24 %31 %61
++%62 = OpAccessChain %25 %24 %28 %7
+ %63 = OpLoad %6 %62
+-%64 = OpAccessChain %25 %15 %37 %31 %31
++%64 = OpAccessChain %25 %15 %10 %28 %28
+ OpStore %64 %63
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, IndexSignednessNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpDecorate %8 ArrayStride 4
+               OpDecorate %9 ArrayStride 4
+               OpDecorate %11 ArrayStride 4
+               OpDecorate %12 ArrayStride 8
+               OpMemberDecorate %13 0 Offset 0
+               OpMemberDecorate %13 1 Offset 12
+               OpMemberDecorate %13 2 Offset 24
+               OpDecorate %13 BufferBlock
+               OpDecorate %15 DescriptorSet 0
+               OpDecorate %15 Binding 1
+               OpDecorate %18 ArrayStride 16
+               OpDecorate %19 ArrayStride 48
+               OpDecorate %21 ArrayStride 16
+               OpMemberDecorate %22 0 Offset 0
+               OpMemberDecorate %22 1 Offset 96
+               OpDecorate %22 Block
+               OpDecorate %24 DescriptorSet 0
+               OpDecorate %24 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 0
+          %7 = OpConstant %6 3
+          %8 = OpTypeArray %6 %7
+          %9 = OpTypeArray %6 %7
+         %10 = OpConstant %6 2
+         %11 = OpTypeArray %6 %10
+         %12 = OpTypeArray %11 %10
+         %13 = OpTypeStruct %8 %9 %12
+         %14 = OpTypePointer Uniform %13
+         %15 = OpVariable %14 Uniform
+         %16 = OpTypeInt 32 1
+         %17 = OpConstant %16 0
+         %18 = OpTypeArray %6 %7
+         %19 = OpTypeArray %18 %10
+         %20 = OpConstant %6 4
+         %21 = OpTypeArray %6 %20
+         %22 = OpTypeStruct %19 %21
+         %23 = OpTypePointer Uniform %22
+         %24 = OpVariable %23 Uniform
+         %25 = OpTypePointer Uniform %6
+         %28 = OpConstant %6 1
+         %31 = OpConstant %16 1
+         %34 = OpConstant %6 0
+         %37 = OpConstant %16 2
+         %61 = OpConstant %16 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %26 = OpAccessChain %25 %24 %17 %17 %17
+         %27 = OpLoad %6 %26
+         %29 = OpIAdd %6 %27 %28
+         %30 = OpAccessChain %25 %15 %17 %17
+               OpStore %30 %29
+         %32 = OpAccessChain %25 %24 %17 %31 %17
+         %33 = OpLoad %6 %32
+         %35 = OpIAdd %6 %33 %34
+         %36 = OpAccessChain %25 %15 %17 %31
+               OpStore %36 %35
+         %38 = OpAccessChain %25 %24 %17 %31 %31
+         %39 = OpLoad %6 %38
+         %40 = OpIAdd %6 %39 %10
+         %41 = OpAccessChain %25 %15 %17 %37
+               OpStore %41 %40
+         %42 = OpAccessChain %25 %24 %17 %17 %37
+         %43 = OpLoad %6 %42
+         %44 = OpAccessChain %25 %15 %31 %17
+               OpStore %44 %43
+         %45 = OpAccessChain %25 %24 %17 %17 %31
+         %46 = OpLoad %6 %45
+         %47 = OpIMul %6 %46 %7
+         %48 = OpAccessChain %25 %15 %31 %31
+               OpStore %48 %47
+         %49 = OpAccessChain %25 %24 %17 %31 %37
+         %50 = OpLoad %6 %49
+         %51 = OpAccessChain %25 %15 %31 %37
+               OpStore %51 %50
+         %52 = OpAccessChain %25 %24 %31 %17
+         %53 = OpLoad %6 %52
+         %54 = OpAccessChain %25 %15 %37 %17 %17
+               OpStore %54 %53
+         %55 = OpAccessChain %25 %24 %31 %31
+         %56 = OpLoad %6 %55
+         %57 = OpAccessChain %25 %15 %37 %17 %31
+               OpStore %57 %56
+         %58 = OpAccessChain %25 %24 %31 %37
+         %59 = OpLoad %6 %58
+         %60 = OpAccessChain %25 %15 %37 %31 %17
+               OpStore %60 %59
+         %62 = OpAccessChain %25 %24 %31 %61
+         %63 = OpLoad %6 %62
+         %64 = OpAccessChain %25 %15 %37 %31 %31
+               OpStore %64 %63
+               OpReturn
+               OpFunctionEnd
+
+)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpDecorate %8 ArrayStride 4
+               OpDecorate %9 ArrayStride 4
+               OpDecorate %11 ArrayStride 4
+               OpDecorate %12 ArrayStride 8
+               OpMemberDecorate %13 0 Offset 0
+               OpMemberDecorate %13 1 Offset 12
+               OpMemberDecorate %13 2 Offset 24
+               OpDecorate %13 BufferBlock
+               OpDecorate %15 DescriptorSet 0
+               OpDecorate %15 Binding 1
+               OpDecorate %18 ArrayStride 16
+               OpDecorate %19 ArrayStride 48
+               OpDecorate %21 ArrayStride 16
+               OpMemberDecorate %22 0 Offset 0
+               OpMemberDecorate %22 1 Offset 96
+               OpDecorate %22 Block
+               OpDecorate %24 DescriptorSet 0
+               OpDecorate %24 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 0
+         %16 = OpTypeInt 32 1
+          %7 = OpConstant %16 3
+          %8 = OpTypeArray %6 %7
+          %9 = OpTypeArray %6 %7
+         %10 = OpConstant %16 2
+         %11 = OpTypeArray %6 %10
+         %12 = OpTypeArray %11 %10
+         %13 = OpTypeStruct %8 %9 %12
+         %14 = OpTypePointer Uniform %13
+         %15 = OpVariable %14 Uniform
+         %18 = OpTypeArray %6 %7
+         %19 = OpTypeArray %18 %10
+         %20 = OpConstant %16 4
+         %21 = OpTypeArray %6 %20
+         %22 = OpTypeStruct %19 %21
+         %23 = OpTypePointer Uniform %22
+         %24 = OpVariable %23 Uniform
+         %25 = OpTypePointer Uniform %6
+         %17 = OpConstant %16 0
+         %28 = OpConstant %16 1
+         %31 = OpConstant %6 1
+         %34 = OpConstant %6 0
+         %37 = OpConstant %6 2
+         %61 = OpConstant %6 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %26 = OpAccessChain %25 %24 %17 %17 %17
+         %27 = OpLoad %6 %26
+         %29 = OpIAdd %6 %27 %28
+         %30 = OpAccessChain %25 %15 %17 %17
+               OpStore %30 %29
+         %32 = OpAccessChain %25 %24 %17 %31 %17
+         %33 = OpLoad %6 %32
+         %35 = OpIAdd %6 %33 %34
+         %36 = OpAccessChain %25 %15 %17 %31
+               OpStore %36 %35
+         %38 = OpAccessChain %25 %24 %17 %31 %31
+         %39 = OpLoad %6 %38
+         %40 = OpIAdd %6 %39 %37
+         %41 = OpAccessChain %25 %15 %17 %10
+               OpStore %41 %40
+         %42 = OpAccessChain %25 %24 %17 %17 %10
+         %43 = OpLoad %6 %42
+         %44 = OpAccessChain %25 %15 %31 %17
+               OpStore %44 %43
+         %45 = OpAccessChain %25 %24 %17 %17 %31
+         %46 = OpLoad %6 %45
+         %47 = OpIMul %6 %46 %7
+         %48 = OpAccessChain %25 %15 %31 %31
+               OpStore %48 %47
+         %49 = OpAccessChain %25 %24 %17 %31 %10
+         %50 = OpLoad %6 %49
+         %51 = OpAccessChain %25 %15 %31 %10
+               OpStore %51 %50
+         %52 = OpAccessChain %25 %24 %31 %17
+         %53 = OpLoad %6 %52
+         %54 = OpAccessChain %25 %15 %37 %17 %17
+               OpStore %54 %53
+         %55 = OpAccessChain %25 %24 %31 %31
+         %56 = OpLoad %6 %55
+         %57 = OpAccessChain %25 %15 %37 %17 %31
+               OpStore %57 %56
+         %58 = OpAccessChain %25 %24 %31 %37
+         %59 = OpLoad %6 %58
+         %60 = OpAccessChain %25 %15 %37 %31 %17
+               OpStore %60 %59
+         %62 = OpAccessChain %25 %24 %31 %61
+         %63 = OpLoad %6 %62
+         %64 = OpAccessChain %25 %15 %37 %31 %31
+               OpStore %64 %63
+               OpReturn
+               OpFunctionEnd
+
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+ ; Bound: 65
+ ; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %4 "main"
+ OpExecutionMode %4 LocalSize 1 1 1
+ OpSource ESSL 310
+ OpDecorate %8 ArrayStride 4
+ OpDecorate %9 ArrayStride 4
+ OpDecorate %11 ArrayStride 4
+ OpDecorate %12 ArrayStride 8
+ OpMemberDecorate %13 0 Offset 0
+ OpMemberDecorate %13 1 Offset 12
+ OpMemberDecorate %13 2 Offset 24
+ OpDecorate %13 BufferBlock
+ OpDecorate %15 DescriptorSet 0
+ OpDecorate %15 Binding 1
+ OpDecorate %18 ArrayStride 16
+ OpDecorate %19 ArrayStride 48
+ OpDecorate %21 ArrayStride 16
+ OpMemberDecorate %22 0 Offset 0
+ OpMemberDecorate %22 1 Offset 96
+ OpDecorate %22 Block
+ OpDecorate %24 DescriptorSet 0
+ OpDecorate %24 Binding 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 0
+ %7 = OpConstant %6 3
+-%8 = OpTypeArray %6 %7
++%8 = OpTypeArray %6 %61
+-%9 = OpTypeArray %6 %7
++%9 = OpTypeArray %6 %61
+ %10 = OpConstant %6 2
+-%11 = OpTypeArray %6 %10
++%11 = OpTypeArray %6 %37
+-%12 = OpTypeArray %11 %10
++%12 = OpTypeArray %11 %37
+ %13 = OpTypeStruct %8 %9 %12
+ %14 = OpTypePointer Uniform %13
+ %15 = OpVariable %14 Uniform
+ %16 = OpTypeInt 32 1
+ %17 = OpConstant %16 0
+-%18 = OpTypeArray %6 %7
++%18 = OpTypeArray %6 %61
+-%19 = OpTypeArray %18 %10
++%19 = OpTypeArray %18 %37
+-%20 = OpConstant %6 4
++%20 = OpConstant %16 4
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypeStruct %19 %21
+ %23 = OpTypePointer Uniform %22
+ %24 = OpVariable %23 Uniform
+ %25 = OpTypePointer Uniform %6
+ %28 = OpConstant %6 1
+ %31 = OpConstant %16 1
+ %34 = OpConstant %6 0
+ %37 = OpConstant %16 2
+ %61 = OpConstant %16 3
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %26 = OpAccessChain %25 %24 %17 %17 %17
+ %27 = OpLoad %6 %26
+-%29 = OpIAdd %6 %27 %28
++%29 = OpIAdd %6 %27 %31
+ %30 = OpAccessChain %25 %15 %17 %17
+ OpStore %30 %29
+-%32 = OpAccessChain %25 %24 %17 %31 %17
++%32 = OpAccessChain %25 %24 %17 %28 %17
+ %33 = OpLoad %6 %32
+ %35 = OpIAdd %6 %33 %34
+-%36 = OpAccessChain %25 %15 %17 %31
++%36 = OpAccessChain %25 %15 %17 %28
+ OpStore %36 %35
+-%38 = OpAccessChain %25 %24 %17 %31 %31
++%38 = OpAccessChain %25 %24 %17 %28 %28
+ %39 = OpLoad %6 %38
+ %40 = OpIAdd %6 %39 %10
+ %41 = OpAccessChain %25 %15 %17 %37
+ OpStore %41 %40
+ %42 = OpAccessChain %25 %24 %17 %17 %37
+ %43 = OpLoad %6 %42
+-%44 = OpAccessChain %25 %15 %31 %17
++%44 = OpAccessChain %25 %15 %28 %17
+ OpStore %44 %43
+-%45 = OpAccessChain %25 %24 %17 %17 %31
++%45 = OpAccessChain %25 %24 %17 %17 %28
+ %46 = OpLoad %6 %45
+-%47 = OpIMul %6 %46 %7
++%47 = OpIMul %6 %46 %61
+-%48 = OpAccessChain %25 %15 %31 %31
++%48 = OpAccessChain %25 %15 %28 %28
+ OpStore %48 %47
+-%49 = OpAccessChain %25 %24 %17 %31 %37
++%49 = OpAccessChain %25 %24 %17 %28 %37
+ %50 = OpLoad %6 %49
+-%51 = OpAccessChain %25 %15 %31 %37
++%51 = OpAccessChain %25 %15 %28 %37
+ OpStore %51 %50
+-%52 = OpAccessChain %25 %24 %31 %17
++%52 = OpAccessChain %25 %24 %28 %17
+ %53 = OpLoad %6 %52
+-%54 = OpAccessChain %25 %15 %37 %17 %17
++%54 = OpAccessChain %25 %15 %10 %17 %17
+ OpStore %54 %53
+-%55 = OpAccessChain %25 %24 %31 %31
++%55 = OpAccessChain %25 %24 %28 %28
+ %56 = OpLoad %6 %55
+-%57 = OpAccessChain %25 %15 %37 %17 %31
++%57 = OpAccessChain %25 %15 %10 %17 %28
+ OpStore %57 %56
+-%58 = OpAccessChain %25 %24 %31 %37
++%58 = OpAccessChain %25 %24 %28 %10
+ %59 = OpLoad %6 %58
+-%60 = OpAccessChain %25 %15 %37 %31 %17
++%60 = OpAccessChain %25 %15 %10 %28 %17
+ OpStore %60 %59
+-%62 = OpAccessChain %25 %24 %31 %61
++%62 = OpAccessChain %25 %24 %28 %7
+ %63 = OpLoad %6 %62
+-%64 = OpAccessChain %25 %15 %37 %31 %31
++%64 = OpAccessChain %25 %15 %10 %28 %28
+ OpStore %64 %63
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/index_signedness_dst.spvasm b/test/diff/diff_files/index_signedness_dst.spvasm
new file mode 100644
index 0000000..08c1939
--- /dev/null
+++ b/test/diff/diff_files/index_signedness_dst.spvasm
@@ -0,0 +1,110 @@
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %13 "BufferOut"
+               OpMemberName %13 0 "o1"
+               OpMemberName %13 1 "o2"
+               OpMemberName %13 2 "o3"
+               OpName %15 ""
+               OpName %22 "BufferIn"
+               OpMemberName %22 0 "i1"
+               OpMemberName %22 1 "i2"
+               OpName %24 ""
+               OpDecorate %8 ArrayStride 4
+               OpDecorate %9 ArrayStride 4
+               OpDecorate %11 ArrayStride 4
+               OpDecorate %12 ArrayStride 8
+               OpMemberDecorate %13 0 Offset 0
+               OpMemberDecorate %13 1 Offset 12
+               OpMemberDecorate %13 2 Offset 24
+               OpDecorate %13 BufferBlock
+               OpDecorate %15 DescriptorSet 0
+               OpDecorate %15 Binding 1
+               OpDecorate %18 ArrayStride 16
+               OpDecorate %19 ArrayStride 48
+               OpDecorate %21 ArrayStride 16
+               OpMemberDecorate %22 0 Offset 0
+               OpMemberDecorate %22 1 Offset 96
+               OpDecorate %22 Block
+               OpDecorate %24 DescriptorSet 0
+               OpDecorate %24 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 0
+         %16 = OpTypeInt 32 1
+          %7 = OpConstant %16 3
+          %8 = OpTypeArray %6 %7
+          %9 = OpTypeArray %6 %7
+         %10 = OpConstant %16 2
+         %11 = OpTypeArray %6 %10
+         %12 = OpTypeArray %11 %10
+         %13 = OpTypeStruct %8 %9 %12
+         %14 = OpTypePointer Uniform %13
+         %15 = OpVariable %14 Uniform
+         %18 = OpTypeArray %6 %7
+         %19 = OpTypeArray %18 %10
+         %20 = OpConstant %16 4
+         %21 = OpTypeArray %6 %20
+         %22 = OpTypeStruct %19 %21
+         %23 = OpTypePointer Uniform %22
+         %24 = OpVariable %23 Uniform
+         %25 = OpTypePointer Uniform %6
+         %17 = OpConstant %16 0
+         %28 = OpConstant %16 1
+         %31 = OpConstant %6 1
+         %34 = OpConstant %6 0
+         %37 = OpConstant %6 2
+         %61 = OpConstant %6 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %26 = OpAccessChain %25 %24 %17 %17 %17
+         %27 = OpLoad %6 %26
+         %29 = OpIAdd %6 %27 %28
+         %30 = OpAccessChain %25 %15 %17 %17
+               OpStore %30 %29
+         %32 = OpAccessChain %25 %24 %17 %31 %17
+         %33 = OpLoad %6 %32
+         %35 = OpIAdd %6 %33 %34
+         %36 = OpAccessChain %25 %15 %17 %31
+               OpStore %36 %35
+         %38 = OpAccessChain %25 %24 %17 %31 %31
+         %39 = OpLoad %6 %38
+         %40 = OpIAdd %6 %39 %37
+         %41 = OpAccessChain %25 %15 %17 %10
+               OpStore %41 %40
+         %42 = OpAccessChain %25 %24 %17 %17 %10
+         %43 = OpLoad %6 %42
+         %44 = OpAccessChain %25 %15 %31 %17
+               OpStore %44 %43
+         %45 = OpAccessChain %25 %24 %17 %17 %31
+         %46 = OpLoad %6 %45
+         %47 = OpIMul %6 %46 %7
+         %48 = OpAccessChain %25 %15 %31 %31
+               OpStore %48 %47
+         %49 = OpAccessChain %25 %24 %17 %31 %10
+         %50 = OpLoad %6 %49
+         %51 = OpAccessChain %25 %15 %31 %10
+               OpStore %51 %50
+         %52 = OpAccessChain %25 %24 %31 %17
+         %53 = OpLoad %6 %52
+         %54 = OpAccessChain %25 %15 %37 %17 %17
+               OpStore %54 %53
+         %55 = OpAccessChain %25 %24 %31 %31
+         %56 = OpLoad %6 %55
+         %57 = OpAccessChain %25 %15 %37 %17 %31
+               OpStore %57 %56
+         %58 = OpAccessChain %25 %24 %31 %37
+         %59 = OpLoad %6 %58
+         %60 = OpAccessChain %25 %15 %37 %31 %17
+               OpStore %60 %59
+         %62 = OpAccessChain %25 %24 %31 %61
+         %63 = OpLoad %6 %62
+         %64 = OpAccessChain %25 %15 %37 %31 %31
+               OpStore %64 %63
+               OpReturn
+               OpFunctionEnd
+
diff --git a/test/diff/diff_files/index_signedness_src.spvasm b/test/diff/diff_files/index_signedness_src.spvasm
new file mode 100644
index 0000000..06dfd18
--- /dev/null
+++ b/test/diff/diff_files/index_signedness_src.spvasm
@@ -0,0 +1,111 @@
+;; Test where signedness of indices are different between src and dst.
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %13 "BufferOut"
+               OpMemberName %13 0 "o1"
+               OpMemberName %13 1 "o2"
+               OpMemberName %13 2 "o3"
+               OpName %15 ""
+               OpName %22 "BufferIn"
+               OpMemberName %22 0 "i1"
+               OpMemberName %22 1 "i2"
+               OpName %24 ""
+               OpDecorate %8 ArrayStride 4
+               OpDecorate %9 ArrayStride 4
+               OpDecorate %11 ArrayStride 4
+               OpDecorate %12 ArrayStride 8
+               OpMemberDecorate %13 0 Offset 0
+               OpMemberDecorate %13 1 Offset 12
+               OpMemberDecorate %13 2 Offset 24
+               OpDecorate %13 BufferBlock
+               OpDecorate %15 DescriptorSet 0
+               OpDecorate %15 Binding 1
+               OpDecorate %18 ArrayStride 16
+               OpDecorate %19 ArrayStride 48
+               OpDecorate %21 ArrayStride 16
+               OpMemberDecorate %22 0 Offset 0
+               OpMemberDecorate %22 1 Offset 96
+               OpDecorate %22 Block
+               OpDecorate %24 DescriptorSet 0
+               OpDecorate %24 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 0
+          %7 = OpConstant %6 3
+          %8 = OpTypeArray %6 %7
+          %9 = OpTypeArray %6 %7
+         %10 = OpConstant %6 2
+         %11 = OpTypeArray %6 %10
+         %12 = OpTypeArray %11 %10
+         %13 = OpTypeStruct %8 %9 %12
+         %14 = OpTypePointer Uniform %13
+         %15 = OpVariable %14 Uniform
+         %16 = OpTypeInt 32 1
+         %17 = OpConstant %16 0
+         %18 = OpTypeArray %6 %7
+         %19 = OpTypeArray %18 %10
+         %20 = OpConstant %6 4
+         %21 = OpTypeArray %6 %20
+         %22 = OpTypeStruct %19 %21
+         %23 = OpTypePointer Uniform %22
+         %24 = OpVariable %23 Uniform
+         %25 = OpTypePointer Uniform %6
+         %28 = OpConstant %6 1
+         %31 = OpConstant %16 1
+         %34 = OpConstant %6 0
+         %37 = OpConstant %16 2
+         %61 = OpConstant %16 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %26 = OpAccessChain %25 %24 %17 %17 %17
+         %27 = OpLoad %6 %26
+         %29 = OpIAdd %6 %27 %28
+         %30 = OpAccessChain %25 %15 %17 %17
+               OpStore %30 %29
+         %32 = OpAccessChain %25 %24 %17 %31 %17
+         %33 = OpLoad %6 %32
+         %35 = OpIAdd %6 %33 %34
+         %36 = OpAccessChain %25 %15 %17 %31
+               OpStore %36 %35
+         %38 = OpAccessChain %25 %24 %17 %31 %31
+         %39 = OpLoad %6 %38
+         %40 = OpIAdd %6 %39 %10
+         %41 = OpAccessChain %25 %15 %17 %37
+               OpStore %41 %40
+         %42 = OpAccessChain %25 %24 %17 %17 %37
+         %43 = OpLoad %6 %42
+         %44 = OpAccessChain %25 %15 %31 %17
+               OpStore %44 %43
+         %45 = OpAccessChain %25 %24 %17 %17 %31
+         %46 = OpLoad %6 %45
+         %47 = OpIMul %6 %46 %7
+         %48 = OpAccessChain %25 %15 %31 %31
+               OpStore %48 %47
+         %49 = OpAccessChain %25 %24 %17 %31 %37
+         %50 = OpLoad %6 %49
+         %51 = OpAccessChain %25 %15 %31 %37
+               OpStore %51 %50
+         %52 = OpAccessChain %25 %24 %31 %17
+         %53 = OpLoad %6 %52
+         %54 = OpAccessChain %25 %15 %37 %17 %17
+               OpStore %54 %53
+         %55 = OpAccessChain %25 %24 %31 %31
+         %56 = OpLoad %6 %55
+         %57 = OpAccessChain %25 %15 %37 %17 %31
+               OpStore %57 %56
+         %58 = OpAccessChain %25 %24 %31 %37
+         %59 = OpLoad %6 %58
+         %60 = OpAccessChain %25 %15 %37 %31 %17
+               OpStore %60 %59
+         %62 = OpAccessChain %25 %24 %31 %61
+         %63 = OpLoad %6 %62
+         %64 = OpAccessChain %25 %15 %37 %31 %31
+               OpStore %64 %63
+               OpReturn
+               OpFunctionEnd
+
diff --git a/test/diff/diff_files/int_vs_uint_constants_autogen.cpp b/test/diff/diff_files/int_vs_uint_constants_autogen.cpp
new file mode 100644
index 0000000..187722e
--- /dev/null
+++ b/test/diff/diff_files/int_vs_uint_constants_autogen.cpp
@@ -0,0 +1,396 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Tests that identical integer constants are matched, regardless of int or
+// uint.  This helps compare output from different generators that default to
+// int or uint for constants such as those passed to OpAccessChain.
+constexpr char kSrc[] = R"(; SPIR-V
+; Version: 1.0
+; Generator: Google ANGLE Shader Compiler; 0
+; Bound: 27
+; Schema: 0
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %22 "main" %4 %19
+OpSource GLSL 450
+OpName %4 "_ua_position"
+OpName %17 "gl_PerVertex"
+OpMemberName %17 0 "gl_Position"
+OpMemberName %17 1 "gl_PointSize"
+OpMemberName %17 2 "gl_ClipDistance"
+OpMemberName %17 3 "gl_CullDistance"
+OpName %19 ""
+OpName %22 "main"
+OpDecorate %4 Location 0
+OpMemberDecorate %17 1 RelaxedPrecision
+OpMemberDecorate %17 0 BuiltIn Position
+OpMemberDecorate %17 1 BuiltIn PointSize
+OpMemberDecorate %17 2 BuiltIn ClipDistance
+OpMemberDecorate %17 3 BuiltIn CullDistance
+OpDecorate %17 Block
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%5 = OpTypeInt 32 0
+%8 = OpTypeVector %5 4
+%15 = OpConstant %5 8
+%16 = OpTypeArray %1 %15
+%17 = OpTypeStruct %2 %1 %16 %16
+%20 = OpTypeVoid
+%25 = OpConstant %5 0
+%3 = OpTypePointer Input %2
+%13 = OpTypePointer Output %2
+%18 = OpTypePointer Output %17
+%21 = OpTypeFunction %20
+%4 = OpVariable %3 Input
+%19 = OpVariable %18 Output
+%22 = OpFunction %20 None %21
+%23 = OpLabel
+%24 = OpLoad %2 %4
+%26 = OpAccessChain %13 %19 %25
+OpStore %26 %24
+OpReturn
+OpFunctionEnd)";
+constexpr char kDst[] = R"(; SPIR-V
+; Version: 1.0
+; Generator: Khronos Glslang Reference Front End; 10
+; Bound: 28
+; Schema: 0
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %4 "main" %13 %17
+OpSource GLSL 450
+OpName %4 "main"
+OpName %11 "gl_PerVertex"
+OpMemberName %11 0 "gl_Position"
+OpMemberName %11 1 "gl_PointSize"
+OpMemberName %11 2 "gl_ClipDistance"
+OpMemberName %11 3 "gl_CullDistance"
+OpName %13 ""
+OpName %17 "_ua_position"
+OpMemberDecorate %11 0 BuiltIn Position
+OpMemberDecorate %11 1 BuiltIn PointSize
+OpMemberDecorate %11 2 BuiltIn ClipDistance
+OpMemberDecorate %11 3 BuiltIn CullDistance
+OpDecorate %11 Block
+OpDecorate %17 Location 0
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%6 = OpTypeFloat 32
+%7 = OpTypeVector %6 4
+%8 = OpTypeInt 32 0
+%9 = OpConstant %8 1
+%10 = OpTypeArray %6 %9
+%11 = OpTypeStruct %7 %6 %10 %10
+%12 = OpTypePointer Output %11
+%13 = OpVariable %12 Output
+%14 = OpTypeInt 32 1
+%15 = OpConstant %14 0
+%16 = OpTypePointer Input %7
+%17 = OpVariable %16 Input
+%19 = OpTypePointer Output %7
+%4 = OpFunction %2 None %3
+%5 = OpLabel
+%18 = OpLoad %7 %17
+%20 = OpAccessChain %19 %13 %15
+OpStore %20 %18
+OpReturn
+OpFunctionEnd
+)";
+
+TEST(DiffTest, IntVsUintConstants) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 27
++; Bound: 31
+ ; Schema: 0
+ OpCapability Shader
++%27 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+-OpEntryPoint Vertex %22 "main" %4 %19
++OpEntryPoint Vertex %22 "main" %19 %4
+ OpSource GLSL 450
+ OpName %4 "_ua_position"
+ OpName %17 "gl_PerVertex"
+ OpMemberName %17 0 "gl_Position"
+ OpMemberName %17 1 "gl_PointSize"
+ OpMemberName %17 2 "gl_ClipDistance"
+ OpMemberName %17 3 "gl_CullDistance"
+ OpName %19 ""
+ OpName %22 "main"
+ OpDecorate %4 Location 0
+-OpMemberDecorate %17 1 RelaxedPrecision
+ OpMemberDecorate %17 0 BuiltIn Position
+ OpMemberDecorate %17 1 BuiltIn PointSize
+ OpMemberDecorate %17 2 BuiltIn ClipDistance
+ OpMemberDecorate %17 3 BuiltIn CullDistance
+ OpDecorate %17 Block
+ %1 = OpTypeFloat 32
+ %2 = OpTypeVector %1 4
+ %5 = OpTypeInt 32 0
+-%8 = OpTypeVector %5 4
+-%15 = OpConstant %5 8
+-%16 = OpTypeArray %1 %15
+-%17 = OpTypeStruct %2 %1 %16 %16
++%17 = OpTypeStruct %2 %1 %29 %29
+ %20 = OpTypeVoid
++%28 = OpConstant %5 1
++%29 = OpTypeArray %1 %28
+-%25 = OpConstant %5 0
++%25 = OpConstant %30 0
+ %3 = OpTypePointer Input %2
+ %13 = OpTypePointer Output %2
++%30 = OpTypeInt 32 1
+ %18 = OpTypePointer Output %17
+ %21 = OpTypeFunction %20
+ %4 = OpVariable %3 Input
+ %19 = OpVariable %18 Output
+ %22 = OpFunction %20 None %21
+ %23 = OpLabel
+ %24 = OpLoad %2 %4
+ %26 = OpAccessChain %13 %19 %25
+ OpStore %26 %24
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, IntVsUintConstantsNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(; SPIR-V
+; Version: 1.0
+; Generator: Google ANGLE Shader Compiler; 0
+; Bound: 27
+; Schema: 0
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %22 "main" %4 %19
+OpSource GLSL 450
+OpDecorate %4 Location 0
+OpMemberDecorate %17 1 RelaxedPrecision
+OpMemberDecorate %17 0 BuiltIn Position
+OpMemberDecorate %17 1 BuiltIn PointSize
+OpMemberDecorate %17 2 BuiltIn ClipDistance
+OpMemberDecorate %17 3 BuiltIn CullDistance
+OpDecorate %17 Block
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%5 = OpTypeInt 32 0
+%8 = OpTypeVector %5 4
+%15 = OpConstant %5 8
+%16 = OpTypeArray %1 %15
+%17 = OpTypeStruct %2 %1 %16 %16
+%20 = OpTypeVoid
+%25 = OpConstant %5 0
+%3 = OpTypePointer Input %2
+%13 = OpTypePointer Output %2
+%18 = OpTypePointer Output %17
+%21 = OpTypeFunction %20
+%4 = OpVariable %3 Input
+%19 = OpVariable %18 Output
+%22 = OpFunction %20 None %21
+%23 = OpLabel
+%24 = OpLoad %2 %4
+%26 = OpAccessChain %13 %19 %25
+OpStore %26 %24
+OpReturn
+OpFunctionEnd
+)";
+  constexpr char kDstNoDebug[] = R"(; SPIR-V
+; Version: 1.0
+; Generator: Khronos Glslang Reference Front End; 10
+; Bound: 28
+; Schema: 0
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %4 "main" %13 %17
+OpSource GLSL 450
+OpMemberDecorate %11 0 BuiltIn Position
+OpMemberDecorate %11 1 BuiltIn PointSize
+OpMemberDecorate %11 2 BuiltIn ClipDistance
+OpMemberDecorate %11 3 BuiltIn CullDistance
+OpDecorate %11 Block
+OpDecorate %17 Location 0
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%6 = OpTypeFloat 32
+%7 = OpTypeVector %6 4
+%8 = OpTypeInt 32 0
+%9 = OpConstant %8 1
+%10 = OpTypeArray %6 %9
+%11 = OpTypeStruct %7 %6 %10 %10
+%12 = OpTypePointer Output %11
+%13 = OpVariable %12 Output
+%14 = OpTypeInt 32 1
+%15 = OpConstant %14 0
+%16 = OpTypePointer Input %7
+%17 = OpVariable %16 Input
+%19 = OpTypePointer Output %7
+%4 = OpFunction %2 None %3
+%5 = OpLabel
+%18 = OpLoad %7 %17
+%20 = OpAccessChain %19 %13 %15
+OpStore %20 %18
+OpReturn
+OpFunctionEnd
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 27
++; Bound: 31
+ ; Schema: 0
+ OpCapability Shader
++%27 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+-OpEntryPoint Vertex %22 "main" %4 %19
++OpEntryPoint Vertex %22 "main" %19 %4
+ OpSource GLSL 450
+ OpDecorate %4 Location 0
+-OpMemberDecorate %17 1 RelaxedPrecision
+ OpMemberDecorate %17 0 BuiltIn Position
+ OpMemberDecorate %17 1 BuiltIn PointSize
+ OpMemberDecorate %17 2 BuiltIn ClipDistance
+ OpMemberDecorate %17 3 BuiltIn CullDistance
+ OpDecorate %17 Block
+ %1 = OpTypeFloat 32
+ %2 = OpTypeVector %1 4
+ %5 = OpTypeInt 32 0
+-%8 = OpTypeVector %5 4
+-%15 = OpConstant %5 8
+-%16 = OpTypeArray %1 %15
+-%17 = OpTypeStruct %2 %1 %16 %16
++%17 = OpTypeStruct %2 %1 %29 %29
+ %20 = OpTypeVoid
++%28 = OpConstant %5 1
++%29 = OpTypeArray %1 %28
+-%25 = OpConstant %5 0
++%25 = OpConstant %30 0
+ %3 = OpTypePointer Input %2
+ %13 = OpTypePointer Output %2
++%30 = OpTypeInt 32 1
+ %18 = OpTypePointer Output %17
+ %21 = OpTypeFunction %20
+ %4 = OpVariable %3 Input
+ %19 = OpVariable %18 Output
+ %22 = OpFunction %20 None %21
+ %23 = OpLabel
+ %24 = OpLoad %2 %4
+ %26 = OpAccessChain %13 %19 %25
+ OpStore %26 %24
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+TEST(DiffTest, IntVsUintConstantsDumpIds) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 27
++; Bound: 31
+ ; Schema: 0
+ OpCapability Shader
++%27 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+-OpEntryPoint Vertex %22 "main" %4 %19
++OpEntryPoint Vertex %22 "main" %19 %4
+ OpSource GLSL 450
+ OpName %4 "_ua_position"
+ OpName %17 "gl_PerVertex"
+ OpMemberName %17 0 "gl_Position"
+ OpMemberName %17 1 "gl_PointSize"
+ OpMemberName %17 2 "gl_ClipDistance"
+ OpMemberName %17 3 "gl_CullDistance"
+ OpName %19 ""
+ OpName %22 "main"
+ OpDecorate %4 Location 0
+-OpMemberDecorate %17 1 RelaxedPrecision
+ OpMemberDecorate %17 0 BuiltIn Position
+ OpMemberDecorate %17 1 BuiltIn PointSize
+ OpMemberDecorate %17 2 BuiltIn ClipDistance
+ OpMemberDecorate %17 3 BuiltIn CullDistance
+ OpDecorate %17 Block
+ %1 = OpTypeFloat 32
+ %2 = OpTypeVector %1 4
+ %5 = OpTypeInt 32 0
+-%8 = OpTypeVector %5 4
+-%15 = OpConstant %5 8
+-%16 = OpTypeArray %1 %15
+-%17 = OpTypeStruct %2 %1 %16 %16
++%17 = OpTypeStruct %2 %1 %29 %29
+ %20 = OpTypeVoid
++%28 = OpConstant %5 1
++%29 = OpTypeArray %1 %28
+-%25 = OpConstant %5 0
++%25 = OpConstant %30 0
+ %3 = OpTypePointer Input %2
+ %13 = OpTypePointer Output %2
++%30 = OpTypeInt 32 1
+ %18 = OpTypePointer Output %17
+ %21 = OpTypeFunction %20
+ %4 = OpVariable %3 Input
+ %19 = OpVariable %18 Output
+ %22 = OpFunction %20 None %21
+ %23 = OpLabel
+ %24 = OpLoad %2 %4
+ %26 = OpAccessChain %13 %19 %25
+ OpStore %26 %24
+ OpReturn
+ OpFunctionEnd
+ Src ->  Dst
+   1 ->    6 [TypeFloat]
+   2 ->    7 [TypeVector]
+   3 ->   16 [TypePointer]
+   4 ->   17 [Variable]
+   5 ->    8 [TypeInt]
+   8 ->   23 [TypeVector]
+  13 ->   19 [TypePointer]
+  15 ->   29 [Constant]
+  16 ->   30 [TypeArray]
+  17 ->   11 [TypeStruct]
+  18 ->   12 [TypePointer]
+  19 ->   13 [Variable]
+  20 ->    2 [TypeVoid]
+  21 ->    3 [TypeFunction]
+  22 ->    4 [Function]
+  23 ->    5 [Label]
+  24 ->   18 [Load]
+  25 ->   15 [Constant]
+  26 ->   20 [AccessChain]
+)";
+  Options options;
+  options.dump_id_map = true;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/int_vs_uint_constants_dst.spvasm b/test/diff/diff_files/int_vs_uint_constants_dst.spvasm
new file mode 100644
index 0000000..da2618b
--- /dev/null
+++ b/test/diff/diff_files/int_vs_uint_constants_dst.spvasm
@@ -0,0 +1,46 @@
+; SPIR-V
+; Version: 1.0
+; Generator: Khronos Glslang Reference Front End; 10
+; Bound: 28
+; Schema: 0
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %4 "main" %13 %17
+OpSource GLSL 450
+OpName %4 "main"
+OpName %11 "gl_PerVertex"
+OpMemberName %11 0 "gl_Position"
+OpMemberName %11 1 "gl_PointSize"
+OpMemberName %11 2 "gl_ClipDistance"
+OpMemberName %11 3 "gl_CullDistance"
+OpName %13 ""
+OpName %17 "_ua_position"
+OpMemberDecorate %11 0 BuiltIn Position
+OpMemberDecorate %11 1 BuiltIn PointSize
+OpMemberDecorate %11 2 BuiltIn ClipDistance
+OpMemberDecorate %11 3 BuiltIn CullDistance
+OpDecorate %11 Block
+OpDecorate %17 Location 0
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%6 = OpTypeFloat 32
+%7 = OpTypeVector %6 4
+%8 = OpTypeInt 32 0
+%9 = OpConstant %8 1
+%10 = OpTypeArray %6 %9
+%11 = OpTypeStruct %7 %6 %10 %10
+%12 = OpTypePointer Output %11
+%13 = OpVariable %12 Output
+%14 = OpTypeInt 32 1
+%15 = OpConstant %14 0
+%16 = OpTypePointer Input %7
+%17 = OpVariable %16 Input
+%19 = OpTypePointer Output %7
+%4 = OpFunction %2 None %3
+%5 = OpLabel
+%18 = OpLoad %7 %17
+%20 = OpAccessChain %19 %13 %15
+OpStore %20 %18
+OpReturn
+OpFunctionEnd
diff --git a/test/diff/diff_files/int_vs_uint_constants_src.spvasm b/test/diff/diff_files/int_vs_uint_constants_src.spvasm
new file mode 100644
index 0000000..214b49b
--- /dev/null
+++ b/test/diff/diff_files/int_vs_uint_constants_src.spvasm
@@ -0,0 +1,49 @@
+;; Tests that identical integer constants are matched, regardless of int or
+;; uint.  This helps compare output from different generators that default to
+;; int or uint for constants such as those passed to OpAccessChain.
+; SPIR-V
+; Version: 1.0
+; Generator: Google ANGLE Shader Compiler; 0
+; Bound: 27
+; Schema: 0
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %22 "main" %4 %19
+OpSource GLSL 450
+OpName %4 "_ua_position"
+OpName %17 "gl_PerVertex"
+OpMemberName %17 0 "gl_Position"
+OpMemberName %17 1 "gl_PointSize"
+OpMemberName %17 2 "gl_ClipDistance"
+OpMemberName %17 3 "gl_CullDistance"
+OpName %19 ""
+OpName %22 "main"
+OpDecorate %4 Location 0
+OpMemberDecorate %17 1 RelaxedPrecision
+OpMemberDecorate %17 0 BuiltIn Position
+OpMemberDecorate %17 1 BuiltIn PointSize
+OpMemberDecorate %17 2 BuiltIn ClipDistance
+OpMemberDecorate %17 3 BuiltIn CullDistance
+OpDecorate %17 Block
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%5 = OpTypeInt 32 0
+%8 = OpTypeVector %5 4
+%15 = OpConstant %5 8
+%16 = OpTypeArray %1 %15
+%17 = OpTypeStruct %2 %1 %16 %16
+%20 = OpTypeVoid
+%25 = OpConstant %5 0
+%3 = OpTypePointer Input %2
+%13 = OpTypePointer Output %2
+%18 = OpTypePointer Output %17
+%21 = OpTypeFunction %20
+%4 = OpVariable %3 Input
+%19 = OpVariable %18 Output
+%22 = OpFunction %20 None %21
+%23 = OpLabel
+%24 = OpLoad %2 %4
+%26 = OpAccessChain %13 %19 %25
+OpStore %26 %24
+OpReturn
+OpFunctionEnd
diff --git a/test/diff/diff_files/large_functions_large_diffs_autogen.cpp b/test/diff/diff_files/large_functions_large_diffs_autogen.cpp
new file mode 100644
index 0000000..12cb621
--- /dev/null
+++ b/test/diff/diff_files/large_functions_large_diffs_autogen.cpp
@@ -0,0 +1,1534 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Test where src and dst have a few large functions with large differences.
+constexpr char kSrc[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main" %15
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "f1("
+               OpName %8 "f2("
+               OpName %12 "x"
+               OpName %15 "gl_LocalInvocationID"
+               OpName %20 "y"
+               OpName %27 "image"
+               OpName %44 "sum"
+               OpName %46 "i"
+               OpName %56 "j"
+               OpName %80 "BufferOut"
+               OpMemberName %80 0 "o_uv4"
+               OpMemberName %80 1 "o_v3"
+               OpMemberName %80 2 "o_i"
+               OpName %82 ""
+               OpName %88 "BufferIn"
+               OpMemberName %88 0 "i_u"
+               OpMemberName %88 1 "i_v4"
+               OpMemberName %88 2 "i_f"
+               OpName %90 ""
+               OpName %101 "i"
+               OpName %128 "image2"
+               OpDecorate %15 BuiltIn LocalInvocationId
+               OpDecorate %27 DescriptorSet 0
+               OpDecorate %27 Binding 2
+               OpMemberDecorate %80 0 Offset 0
+               OpMemberDecorate %80 1 Offset 16
+               OpMemberDecorate %80 2 Offset 28
+               OpDecorate %80 BufferBlock
+               OpDecorate %82 DescriptorSet 0
+               OpDecorate %82 Binding 1
+               OpMemberDecorate %88 0 Offset 0
+               OpMemberDecorate %88 1 RowMajor
+               OpMemberDecorate %88 1 Offset 16
+               OpMemberDecorate %88 1 MatrixStride 16
+               OpMemberDecorate %88 2 Offset 80
+               OpDecorate %88 Block
+               OpDecorate %90 DescriptorSet 0
+               OpDecorate %90 Binding 0
+               OpDecorate %128 DescriptorSet 0
+               OpDecorate %128 Binding 3
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeInt 32 0
+         %11 = OpTypePointer Function %10
+         %13 = OpTypeVector %10 3
+         %14 = OpTypePointer Input %13
+         %15 = OpVariable %14 Input
+         %16 = OpConstant %10 0
+         %17 = OpTypePointer Input %10
+         %21 = OpConstant %10 1
+         %24 = OpTypeInt 32 1
+         %25 = OpTypeImage %24 2D 0 0 0 2 R32i
+         %26 = OpTypePointer UniformConstant %25
+         %27 = OpVariable %26 UniformConstant
+         %29 = OpTypeVector %10 2
+         %32 = OpTypeVector %24 2
+         %38 = OpTypeVector %24 4
+         %40 = OpConstant %10 2
+         %41 = OpConstant %10 3400
+         %42 = OpConstant %10 264
+         %43 = OpTypePointer Function %24
+         %45 = OpConstant %24 0
+         %53 = OpConstant %24 2
+         %54 = OpTypeBool
+         %73 = OpConstant %24 1
+         %77 = OpTypeVector %10 4
+         %78 = OpTypeFloat 32
+         %79 = OpTypeVector %78 3
+         %80 = OpTypeStruct %77 %79 %24
+         %81 = OpTypePointer Uniform %80
+         %82 = OpVariable %81 Uniform
+         %84 = OpTypePointer Uniform %24
+         %86 = OpTypeVector %78 4
+         %87 = OpTypeMatrix %86 4
+         %88 = OpTypeStruct %10 %87 %78
+         %89 = OpTypePointer Uniform %88
+         %90 = OpVariable %89 Uniform
+         %91 = OpTypePointer Uniform %87
+         %94 = OpTypePointer Uniform %77
+        %108 = OpConstant %24 3
+        %110 = OpTypePointer Uniform %79
+        %113 = OpTypePointer Uniform %78
+        %128 = OpVariable %26 UniformConstant
+        %130 = OpConstantComposite %32 %45 %45
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+        %136 = OpFunctionCall %2 %6
+        %137 = OpFunctionCall %2 %8
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %12 = OpVariable %11 Function
+         %20 = OpVariable %11 Function
+         %44 = OpVariable %43 Function
+         %46 = OpVariable %43 Function
+         %56 = OpVariable %43 Function
+         %18 = OpAccessChain %17 %15 %16
+         %19 = OpLoad %10 %18
+               OpStore %12 %19
+         %22 = OpAccessChain %17 %15 %21
+         %23 = OpLoad %10 %22
+               OpStore %20 %23
+         %28 = OpLoad %25 %27
+         %30 = OpLoad %13 %15
+         %31 = OpVectorShuffle %29 %30 %30 0 1
+         %33 = OpBitcast %32 %31
+         %34 = OpLoad %10 %12
+         %35 = OpLoad %10 %20
+         %36 = OpIAdd %10 %34 %35
+         %37 = OpBitcast %24 %36
+         %39 = OpCompositeConstruct %38 %37 %37 %37 %37
+               OpImageWrite %28 %33 %39
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+               OpStore %44 %45
+               OpStore %46 %45
+               OpBranch %47
+         %47 = OpLabel
+               OpLoopMerge %49 %50 None
+               OpBranch %51
+         %51 = OpLabel
+         %52 = OpLoad %24 %46
+         %55 = OpSLessThan %54 %52 %53
+               OpBranchConditional %55 %48 %49
+         %48 = OpLabel
+               OpStore %56 %45
+               OpBranch %57
+         %57 = OpLabel
+               OpLoopMerge %59 %60 None
+               OpBranch %61
+         %61 = OpLabel
+         %62 = OpLoad %24 %56
+         %63 = OpSLessThan %54 %62 %53
+               OpBranchConditional %63 %58 %59
+         %58 = OpLabel
+         %64 = OpLoad %25 %27
+         %65 = OpLoad %24 %46
+         %66 = OpLoad %24 %56
+         %67 = OpCompositeConstruct %32 %65 %66
+         %68 = OpImageRead %38 %64 %67
+         %69 = OpCompositeExtract %24 %68 0
+         %70 = OpLoad %24 %44
+         %71 = OpIMul %24 %70 %69
+               OpStore %44 %71
+               OpBranch %60
+         %60 = OpLabel
+         %72 = OpLoad %24 %56
+         %74 = OpIAdd %24 %72 %73
+               OpStore %56 %74
+               OpBranch %57
+         %59 = OpLabel
+               OpBranch %50
+         %50 = OpLabel
+         %75 = OpLoad %24 %46
+         %76 = OpIAdd %24 %75 %73
+               OpStore %46 %76
+               OpBranch %47
+         %49 = OpLabel
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+         %83 = OpLoad %24 %44
+         %85 = OpAccessChain %84 %82 %53
+               OpStore %85 %83
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+        %101 = OpVariable %43 Function
+         %92 = OpAccessChain %91 %90 %73
+         %93 = OpLoad %87 %92
+         %95 = OpAccessChain %94 %82 %45
+         %96 = OpLoad %77 %95
+         %97 = OpConvertUToF %86 %96
+         %98 = OpMatrixTimesVector %86 %93 %97
+         %99 = OpConvertFToU %77 %98
+        %100 = OpAccessChain %94 %82 %45
+               OpStore %100 %99
+               OpStore %101 %45
+               OpBranch %102
+        %102 = OpLabel
+               OpLoopMerge %104 %105 None
+               OpBranch %106
+        %106 = OpLabel
+        %107 = OpLoad %24 %101
+        %109 = OpSLessThan %54 %107 %108
+               OpBranchConditional %109 %103 %104
+        %103 = OpLabel
+        %111 = OpAccessChain %110 %82 %73
+        %112 = OpLoad %79 %111
+        %114 = OpAccessChain %113 %90 %53
+        %115 = OpLoad %78 %114
+        %116 = OpVectorTimesScalar %79 %112 %115
+        %117 = OpConvertFToU %13 %116
+        %118 = OpCompositeExtract %10 %117 0
+        %119 = OpCompositeExtract %10 %117 1
+        %120 = OpCompositeExtract %10 %117 2
+        %121 = OpCompositeConstruct %77 %118 %119 %120 %16
+        %122 = OpAccessChain %94 %82 %45
+        %123 = OpLoad %77 %122
+        %124 = OpIAdd %77 %123 %121
+        %125 = OpAccessChain %94 %82 %45
+               OpStore %125 %124
+               OpBranch %105
+        %105 = OpLabel
+        %126 = OpLoad %24 %101
+        %127 = OpIAdd %24 %126 %73
+               OpStore %101 %127
+               OpBranch %102
+        %104 = OpLabel
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+        %129 = OpLoad %25 %128
+        %131 = OpImageRead %38 %129 %130
+        %132 = OpCompositeExtract %24 %131 0
+        %133 = OpConvertSToF %78 %132
+        %134 = OpCompositeConstruct %79 %133 %133 %133
+        %135 = OpAccessChain %110 %82 %73
+               OpStore %135 %134
+               OpReturn
+               OpFunctionEnd
+)";
+constexpr char kDst[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main" %15 %110
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "f1("
+               OpName %8 "f2("
+               OpName %12 "x"
+               OpName %15 "gl_GlobalInvocationID"
+               OpName %20 "z"
+               OpName %26 "i"
+               OpName %40 "BufferOut"
+               OpMemberName %40 0 "o_uv4"
+               OpMemberName %40 1 "o_v3"
+               OpMemberName %40 2 "o_i"
+               OpName %42 ""
+               OpName %63 "image2"
+               OpName %79 "image"
+               OpName %89 "i"
+               OpName %110 "gl_LocalInvocationID"
+               OpName %127 "BufferIn"
+               OpMemberName %127 0 "i_u"
+               OpMemberName %127 1 "i_v4"
+               OpMemberName %127 2 "i_f"
+               OpName %129 ""
+               OpDecorate %15 BuiltIn GlobalInvocationId
+               OpMemberDecorate %40 0 Offset 0
+               OpMemberDecorate %40 1 Offset 16
+               OpMemberDecorate %40 2 Offset 28
+               OpDecorate %40 BufferBlock
+               OpDecorate %42 DescriptorSet 0
+               OpDecorate %42 Binding 1
+               OpDecorate %63 DescriptorSet 0
+               OpDecorate %63 Binding 3
+               OpDecorate %79 DescriptorSet 0
+               OpDecorate %79 Binding 2
+               OpDecorate %110 BuiltIn LocalInvocationId
+               OpMemberDecorate %127 0 Offset 0
+               OpMemberDecorate %127 1 RowMajor
+               OpMemberDecorate %127 1 Offset 16
+               OpMemberDecorate %127 1 MatrixStride 16
+               OpMemberDecorate %127 2 Offset 80
+               OpDecorate %127 Block
+               OpDecorate %129 DescriptorSet 0
+               OpDecorate %129 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeInt 32 0
+         %11 = OpTypePointer Function %10
+         %13 = OpTypeVector %10 3
+         %14 = OpTypePointer Input %13
+         %15 = OpVariable %14 Input
+         %16 = OpConstant %10 0
+         %17 = OpTypePointer Input %10
+         %21 = OpConstant %10 1
+         %24 = OpTypeInt 32 1
+         %25 = OpTypePointer Function %24
+         %27 = OpConstant %24 0
+         %34 = OpConstant %24 2
+         %35 = OpTypeBool
+         %37 = OpTypeVector %10 4
+         %38 = OpTypeFloat 32
+         %39 = OpTypeVector %38 3
+         %40 = OpTypeStruct %37 %39 %24
+         %41 = OpTypePointer Uniform %40
+         %42 = OpVariable %41 Uniform
+         %46 = OpTypeVector %10 2
+         %48 = OpTypePointer Uniform %37
+         %53 = OpTypePointer Uniform %10
+         %59 = OpConstant %24 1
+         %61 = OpTypeImage %24 2D 0 0 0 2 R32i
+         %62 = OpTypePointer UniformConstant %61
+         %63 = OpVariable %62 UniformConstant
+         %69 = OpTypeVector %24 2
+         %71 = OpTypeVector %24 4
+         %74 = OpTypePointer Uniform %24
+         %76 = OpConstant %10 2
+         %77 = OpConstant %10 3400
+         %78 = OpConstant %10 264
+         %79 = OpVariable %62 UniformConstant
+         %96 = OpConstant %24 3
+        %103 = OpConstantComposite %69 %27 %27
+        %107 = OpTypePointer Uniform %38
+        %110 = OpVariable %14 Input
+        %113 = OpTypeVector %38 2
+        %125 = OpTypeVector %38 4
+        %126 = OpTypeMatrix %125 4
+        %127 = OpTypeStruct %10 %126 %38
+        %128 = OpTypePointer Uniform %127
+        %129 = OpVariable %128 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+        %123 = OpFunctionCall %2 %8
+        %124 = OpFunctionCall %2 %6
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %12 = OpVariable %11 Function
+         %20 = OpVariable %11 Function
+         %26 = OpVariable %25 Function
+         %18 = OpAccessChain %17 %15 %16
+         %19 = OpLoad %10 %18
+               OpStore %12 %19
+         %22 = OpAccessChain %17 %15 %21
+         %23 = OpLoad %10 %22
+               OpStore %20 %23
+               OpStore %26 %27
+               OpBranch %28
+         %28 = OpLabel
+               OpLoopMerge %30 %31 None
+               OpBranch %32
+         %32 = OpLabel
+         %33 = OpLoad %24 %26
+         %36 = OpSLessThan %35 %33 %34
+               OpBranchConditional %36 %29 %30
+         %29 = OpLabel
+         %43 = OpLoad %10 %12
+         %44 = OpLoad %10 %20
+         %45 = OpIAdd %10 %43 %44
+         %47 = OpCompositeConstruct %46 %45 %45
+         %49 = OpAccessChain %48 %42 %27
+         %50 = OpLoad %37 %49
+         %51 = OpVectorShuffle %46 %50 %50 0 1
+         %52 = OpIAdd %46 %51 %47
+         %54 = OpAccessChain %53 %42 %27 %16
+         %55 = OpCompositeExtract %10 %52 0
+               OpStore %54 %55
+         %56 = OpAccessChain %53 %42 %27 %21
+         %57 = OpCompositeExtract %10 %52 1
+               OpStore %56 %57
+               OpBranch %31
+         %31 = OpLabel
+         %58 = OpLoad %24 %26
+         %60 = OpIAdd %24 %58 %59
+               OpStore %26 %60
+               OpBranch %28
+         %30 = OpLabel
+         %64 = OpLoad %61 %63
+         %65 = OpLoad %10 %12
+         %66 = OpBitcast %24 %65
+         %67 = OpLoad %10 %20
+         %68 = OpBitcast %24 %67
+         %70 = OpCompositeConstruct %69 %66 %68
+         %72 = OpImageRead %71 %64 %70
+         %73 = OpCompositeExtract %24 %72 1
+         %75 = OpAccessChain %74 %42 %34
+               OpStore %75 %73
+               OpMemoryBarrier %76 %77
+               OpControlBarrier %76 %76 %78
+         %80 = OpLoad %61 %79
+         %81 = OpLoad %10 %20
+         %82 = OpBitcast %24 %81
+         %83 = OpLoad %10 %12
+         %84 = OpBitcast %24 %83
+         %85 = OpCompositeConstruct %69 %82 %84
+         %86 = OpAccessChain %74 %42 %34
+         %87 = OpLoad %24 %86
+         %88 = OpCompositeConstruct %71 %87 %27 %27 %27
+               OpImageWrite %80 %85 %88
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+         %89 = OpVariable %25 Function
+               OpStore %89 %27
+               OpBranch %90
+         %90 = OpLabel
+               OpLoopMerge %92 %93 None
+               OpBranch %94
+         %94 = OpLabel
+         %95 = OpLoad %24 %89
+         %97 = OpSLessThan %35 %95 %96
+               OpBranchConditional %97 %91 %92
+         %91 = OpLabel
+         %98 = OpLoad %24 %89
+         %99 = OpIEqual %35 %98 %27
+               OpSelectionMerge %101 None
+               OpBranchConditional %99 %100 %109
+        %100 = OpLabel
+        %102 = OpLoad %61 %63
+        %104 = OpImageRead %71 %102 %103
+        %105 = OpCompositeExtract %24 %104 0
+        %106 = OpConvertSToF %38 %105
+        %108 = OpAccessChain %107 %42 %59 %16
+               OpStore %108 %106
+               OpBranch %101
+        %109 = OpLabel
+        %111 = OpLoad %13 %110
+        %112 = OpConvertUToF %39 %111
+        %114 = OpCompositeExtract %38 %112 0
+        %115 = OpCompositeExtract %38 %112 1
+        %116 = OpCompositeConstruct %113 %114 %115
+        %117 = OpAccessChain %107 %42 %59 %21
+        %118 = OpCompositeExtract %38 %116 0
+               OpStore %117 %118
+        %119 = OpAccessChain %107 %42 %59 %76
+        %120 = OpCompositeExtract %38 %116 1
+               OpStore %119 %120
+               OpBranch %101
+        %101 = OpLabel
+               OpBranch %93
+         %93 = OpLabel
+        %121 = OpLoad %24 %89
+        %122 = OpIAdd %24 %121 %59
+               OpStore %89 %122
+               OpBranch %90
+         %92 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+)";
+
+TEST(DiffTest, LargeFunctionsLargeDiffs) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 138
++; Bound: 190
+ ; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+-OpEntryPoint GLCompute %4 "main" %15
++OpEntryPoint GLCompute %4 "main" %138 %15
+ OpExecutionMode %4 LocalSize 1 1 1
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %6 "f1("
+ OpName %8 "f2("
+ OpName %12 "x"
++OpName %138 "gl_GlobalInvocationID"
+ OpName %15 "gl_LocalInvocationID"
+-OpName %20 "y"
++OpName %20 "z"
+ OpName %27 "image"
+-OpName %44 "sum"
++OpName %44 "i"
+-OpName %46 "i"
+-OpName %56 "j"
+ OpName %80 "BufferOut"
+ OpMemberName %80 0 "o_uv4"
+ OpMemberName %80 1 "o_v3"
+ OpMemberName %80 2 "o_i"
+ OpName %82 ""
+ OpName %88 "BufferIn"
+ OpMemberName %88 0 "i_u"
+ OpMemberName %88 1 "i_v4"
+ OpMemberName %88 2 "i_f"
+ OpName %90 ""
+ OpName %101 "i"
+ OpName %128 "image2"
++OpDecorate %138 BuiltIn GlobalInvocationId
+ OpDecorate %15 BuiltIn LocalInvocationId
+ OpDecorate %27 DescriptorSet 0
+ OpDecorate %27 Binding 2
+ OpMemberDecorate %80 0 Offset 0
+ OpMemberDecorate %80 1 Offset 16
+ OpMemberDecorate %80 2 Offset 28
+ OpDecorate %80 BufferBlock
+ OpDecorate %82 DescriptorSet 0
+ OpDecorate %82 Binding 1
+ OpMemberDecorate %88 0 Offset 0
+ OpMemberDecorate %88 1 RowMajor
+ OpMemberDecorate %88 1 Offset 16
+ OpMemberDecorate %88 1 MatrixStride 16
+ OpMemberDecorate %88 2 Offset 80
+ OpDecorate %88 Block
+ OpDecorate %90 DescriptorSet 0
+ OpDecorate %90 Binding 0
+ OpDecorate %128 DescriptorSet 0
+ OpDecorate %128 Binding 3
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %10 = OpTypeInt 32 0
+ %11 = OpTypePointer Function %10
+ %13 = OpTypeVector %10 3
+ %14 = OpTypePointer Input %13
++%138 = OpVariable %14 Input
+ %15 = OpVariable %14 Input
+ %16 = OpConstant %10 0
+ %17 = OpTypePointer Input %10
+ %21 = OpConstant %10 1
+ %24 = OpTypeInt 32 1
+ %25 = OpTypeImage %24 2D 0 0 0 2 R32i
+ %26 = OpTypePointer UniformConstant %25
+ %27 = OpVariable %26 UniformConstant
+ %29 = OpTypeVector %10 2
+ %32 = OpTypeVector %24 2
+ %38 = OpTypeVector %24 4
+ %40 = OpConstant %10 2
+ %41 = OpConstant %10 3400
+ %42 = OpConstant %10 264
+ %43 = OpTypePointer Function %24
+ %45 = OpConstant %24 0
++%149 = OpTypePointer Uniform %10
+ %53 = OpConstant %24 2
+ %54 = OpTypeBool
+ %73 = OpConstant %24 1
+ %77 = OpTypeVector %10 4
+ %78 = OpTypeFloat 32
+ %79 = OpTypeVector %78 3
+ %80 = OpTypeStruct %77 %79 %24
+ %81 = OpTypePointer Uniform %80
+ %82 = OpVariable %81 Uniform
+ %84 = OpTypePointer Uniform %24
+ %86 = OpTypeVector %78 4
+ %87 = OpTypeMatrix %86 4
+ %88 = OpTypeStruct %10 %87 %78
+ %89 = OpTypePointer Uniform %88
+ %90 = OpVariable %89 Uniform
+-%91 = OpTypePointer Uniform %87
++%179 = OpTypeVector %78 2
+ %94 = OpTypePointer Uniform %77
+ %108 = OpConstant %24 3
+-%110 = OpTypePointer Uniform %79
+ %113 = OpTypePointer Uniform %78
+ %128 = OpVariable %26 UniformConstant
+ %130 = OpConstantComposite %32 %45 %45
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+-%136 = OpFunctionCall %2 %6
+ %137 = OpFunctionCall %2 %8
++%189 = OpFunctionCall %2 %6
+ OpReturn
+ OpFunctionEnd
+ %6 = OpFunction %2 None %3
+ %7 = OpLabel
+ %12 = OpVariable %11 Function
+ %20 = OpVariable %11 Function
+ %44 = OpVariable %43 Function
+-%46 = OpVariable %43 Function
+-%56 = OpVariable %43 Function
+-%18 = OpAccessChain %17 %15 %16
++%139 = OpAccessChain %17 %138 %16
+-%19 = OpLoad %10 %18
++%19 = OpLoad %10 %139
+ OpStore %12 %19
+-%22 = OpAccessChain %17 %15 %21
++%140 = OpAccessChain %17 %138 %21
+-%23 = OpLoad %10 %22
++%23 = OpLoad %10 %140
+ OpStore %20 %23
+-%28 = OpLoad %25 %27
+-%30 = OpLoad %13 %15
+-%31 = OpVectorShuffle %29 %30 %30 0 1
+-%33 = OpBitcast %32 %31
+-%34 = OpLoad %10 %12
+-%35 = OpLoad %10 %20
+-%36 = OpIAdd %10 %34 %35
+-%37 = OpBitcast %24 %36
+-%39 = OpCompositeConstruct %38 %37 %37 %37 %37
+-OpImageWrite %28 %33 %39
+-OpMemoryBarrier %40 %41
+-OpControlBarrier %40 %40 %42
+ OpStore %44 %45
+-OpStore %46 %45
+ OpBranch %47
+ %47 = OpLabel
+-OpLoopMerge %49 %50 None
++OpLoopMerge %49 %59 None
+ OpBranch %51
+ %51 = OpLabel
+-%52 = OpLoad %24 %46
++%52 = OpLoad %24 %44
+ %55 = OpSLessThan %54 %52 %53
+ OpBranchConditional %55 %48 %49
+ %48 = OpLabel
+-OpStore %56 %45
+-OpBranch %57
+-%57 = OpLabel
+-OpLoopMerge %59 %60 None
+-OpBranch %61
+-%61 = OpLabel
+-%62 = OpLoad %24 %56
+-%63 = OpSLessThan %54 %62 %53
+-OpBranchConditional %63 %58 %59
+-%58 = OpLabel
+-%64 = OpLoad %25 %27
+-%65 = OpLoad %24 %46
+-%66 = OpLoad %24 %56
+-%67 = OpCompositeConstruct %32 %65 %66
+-%68 = OpImageRead %38 %64 %67
+-%69 = OpCompositeExtract %24 %68 0
+-%70 = OpLoad %24 %44
+-%71 = OpIMul %24 %70 %69
++%141 = OpLoad %10 %12
++%142 = OpLoad %10 %20
++%143 = OpIAdd %10 %141 %142
++%144 = OpCompositeConstruct %29 %143 %143
++%145 = OpAccessChain %94 %82 %45
++%146 = OpLoad %77 %145
++%147 = OpVectorShuffle %29 %146 %146 0 1
++%148 = OpIAdd %29 %147 %144
++%150 = OpAccessChain %149 %82 %45 %16
++%151 = OpCompositeExtract %10 %148 0
+-OpStore %44 %71
++OpStore %150 %151
+-OpBranch %60
+-%60 = OpLabel
+-%72 = OpLoad %24 %56
+-%74 = OpIAdd %24 %72 %73
++%152 = OpAccessChain %149 %82 %45 %21
++%153 = OpCompositeExtract %10 %148 1
+-OpStore %56 %74
++OpStore %152 %153
+-OpBranch %57
++OpBranch %59
+ %59 = OpLabel
+-OpBranch %50
+-%50 = OpLabel
+-%75 = OpLoad %24 %46
++%75 = OpLoad %24 %44
+ %76 = OpIAdd %24 %75 %73
+-OpStore %46 %76
++OpStore %44 %76
+ OpBranch %47
+ %49 = OpLabel
++%154 = OpLoad %25 %128
++%155 = OpLoad %10 %12
++%156 = OpBitcast %24 %155
++%157 = OpLoad %10 %20
++%158 = OpBitcast %24 %157
++%159 = OpCompositeConstruct %32 %156 %158
++%160 = OpImageRead %38 %154 %159
++%161 = OpCompositeExtract %24 %160 1
++%162 = OpAccessChain %84 %82 %53
++OpStore %162 %161
+ OpMemoryBarrier %40 %41
+ OpControlBarrier %40 %40 %42
+-%83 = OpLoad %24 %44
++%163 = OpLoad %25 %27
++%164 = OpLoad %10 %20
++%165 = OpBitcast %24 %164
++%166 = OpLoad %10 %12
++%167 = OpBitcast %24 %166
++%168 = OpCompositeConstruct %32 %165 %167
+ %85 = OpAccessChain %84 %82 %53
+-OpStore %85 %83
++%169 = OpLoad %24 %85
++%170 = OpCompositeConstruct %38 %169 %45 %45 %45
++OpImageWrite %163 %168 %170
+ OpReturn
+ OpFunctionEnd
+ %8 = OpFunction %2 None %3
+ %9 = OpLabel
+ %101 = OpVariable %43 Function
+-%92 = OpAccessChain %91 %90 %73
+-%93 = OpLoad %87 %92
+-%95 = OpAccessChain %94 %82 %45
+-%96 = OpLoad %77 %95
+-%97 = OpConvertUToF %86 %96
+-%98 = OpMatrixTimesVector %86 %93 %97
+-%99 = OpConvertFToU %77 %98
+-%100 = OpAccessChain %94 %82 %45
+-OpStore %100 %99
++OpStore %101 %45
+-OpStore %101 %45
+ OpBranch %102
+ %102 = OpLabel
+-OpLoopMerge %104 %105 None
++OpLoopMerge %171 %172 None
+ OpBranch %106
+ %106 = OpLabel
+ %107 = OpLoad %24 %101
+ %109 = OpSLessThan %54 %107 %108
+-OpBranchConditional %109 %103 %104
++OpBranchConditional %109 %103 %171
+ %103 = OpLabel
+-%111 = OpAccessChain %110 %82 %73
+-%112 = OpLoad %79 %111
+-%114 = OpAccessChain %113 %90 %53
+-%115 = OpLoad %78 %114
+-%116 = OpVectorTimesScalar %79 %112 %115
+-%117 = OpConvertFToU %13 %116
+-%118 = OpCompositeExtract %10 %117 0
+-%119 = OpCompositeExtract %10 %117 1
+-%120 = OpCompositeExtract %10 %117 2
+-%121 = OpCompositeConstruct %77 %118 %119 %120 %16
+-%122 = OpAccessChain %94 %82 %45
+-%123 = OpLoad %77 %122
+-%124 = OpIAdd %77 %123 %121
+-%125 = OpAccessChain %94 %82 %45
+-OpStore %125 %124
+-OpBranch %105
+-%105 = OpLabel
+ %126 = OpLoad %24 %101
+-%127 = OpIAdd %24 %126 %73
++%173 = OpIEqual %54 %126 %45
++OpSelectionMerge %174 None
++OpBranchConditional %173 %104 %176
++%176 = OpLabel
++%177 = OpLoad %13 %15
++%178 = OpConvertUToF %79 %177
++%180 = OpCompositeExtract %78 %178 0
++%181 = OpCompositeExtract %78 %178 1
++%182 = OpCompositeConstruct %179 %180 %181
++%183 = OpAccessChain %113 %82 %73 %21
++%184 = OpCompositeExtract %78 %182 0
+-OpStore %101 %127
++OpStore %183 %184
++%185 = OpAccessChain %113 %82 %73 %40
++%186 = OpCompositeExtract %78 %182 1
++OpStore %185 %186
+-OpBranch %102
++OpBranch %174
+ %104 = OpLabel
+-OpMemoryBarrier %40 %41
+-OpControlBarrier %40 %40 %42
+ %129 = OpLoad %25 %128
+ %131 = OpImageRead %38 %129 %130
+ %132 = OpCompositeExtract %24 %131 0
+ %133 = OpConvertSToF %78 %132
+-%134 = OpCompositeConstruct %79 %133 %133 %133
+-%135 = OpAccessChain %110 %82 %73
++%175 = OpAccessChain %113 %82 %73 %16
+-OpStore %135 %134
++OpStore %175 %133
++OpBranch %174
++%174 = OpLabel
++OpBranch %172
++%172 = OpLabel
++%187 = OpLoad %24 %101
++%188 = OpIAdd %24 %187 %73
++OpStore %101 %188
++OpBranch %102
++%171 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, LargeFunctionsLargeDiffsNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main" %15
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpDecorate %15 BuiltIn LocalInvocationId
+               OpDecorate %27 DescriptorSet 0
+               OpDecorate %27 Binding 2
+               OpMemberDecorate %80 0 Offset 0
+               OpMemberDecorate %80 1 Offset 16
+               OpMemberDecorate %80 2 Offset 28
+               OpDecorate %80 BufferBlock
+               OpDecorate %82 DescriptorSet 0
+               OpDecorate %82 Binding 1
+               OpMemberDecorate %88 0 Offset 0
+               OpMemberDecorate %88 1 RowMajor
+               OpMemberDecorate %88 1 Offset 16
+               OpMemberDecorate %88 1 MatrixStride 16
+               OpMemberDecorate %88 2 Offset 80
+               OpDecorate %88 Block
+               OpDecorate %90 DescriptorSet 0
+               OpDecorate %90 Binding 0
+               OpDecorate %128 DescriptorSet 0
+               OpDecorate %128 Binding 3
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeInt 32 0
+         %11 = OpTypePointer Function %10
+         %13 = OpTypeVector %10 3
+         %14 = OpTypePointer Input %13
+         %15 = OpVariable %14 Input
+         %16 = OpConstant %10 0
+         %17 = OpTypePointer Input %10
+         %21 = OpConstant %10 1
+         %24 = OpTypeInt 32 1
+         %25 = OpTypeImage %24 2D 0 0 0 2 R32i
+         %26 = OpTypePointer UniformConstant %25
+         %27 = OpVariable %26 UniformConstant
+         %29 = OpTypeVector %10 2
+         %32 = OpTypeVector %24 2
+         %38 = OpTypeVector %24 4
+         %40 = OpConstant %10 2
+         %41 = OpConstant %10 3400
+         %42 = OpConstant %10 264
+         %43 = OpTypePointer Function %24
+         %45 = OpConstant %24 0
+         %53 = OpConstant %24 2
+         %54 = OpTypeBool
+         %73 = OpConstant %24 1
+         %77 = OpTypeVector %10 4
+         %78 = OpTypeFloat 32
+         %79 = OpTypeVector %78 3
+         %80 = OpTypeStruct %77 %79 %24
+         %81 = OpTypePointer Uniform %80
+         %82 = OpVariable %81 Uniform
+         %84 = OpTypePointer Uniform %24
+         %86 = OpTypeVector %78 4
+         %87 = OpTypeMatrix %86 4
+         %88 = OpTypeStruct %10 %87 %78
+         %89 = OpTypePointer Uniform %88
+         %90 = OpVariable %89 Uniform
+         %91 = OpTypePointer Uniform %87
+         %94 = OpTypePointer Uniform %77
+        %108 = OpConstant %24 3
+        %110 = OpTypePointer Uniform %79
+        %113 = OpTypePointer Uniform %78
+        %128 = OpVariable %26 UniformConstant
+        %130 = OpConstantComposite %32 %45 %45
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+        %136 = OpFunctionCall %2 %6
+        %137 = OpFunctionCall %2 %8
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %12 = OpVariable %11 Function
+         %20 = OpVariable %11 Function
+         %44 = OpVariable %43 Function
+         %46 = OpVariable %43 Function
+         %56 = OpVariable %43 Function
+         %18 = OpAccessChain %17 %15 %16
+         %19 = OpLoad %10 %18
+               OpStore %12 %19
+         %22 = OpAccessChain %17 %15 %21
+         %23 = OpLoad %10 %22
+               OpStore %20 %23
+         %28 = OpLoad %25 %27
+         %30 = OpLoad %13 %15
+         %31 = OpVectorShuffle %29 %30 %30 0 1
+         %33 = OpBitcast %32 %31
+         %34 = OpLoad %10 %12
+         %35 = OpLoad %10 %20
+         %36 = OpIAdd %10 %34 %35
+         %37 = OpBitcast %24 %36
+         %39 = OpCompositeConstruct %38 %37 %37 %37 %37
+               OpImageWrite %28 %33 %39
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+               OpStore %44 %45
+               OpStore %46 %45
+               OpBranch %47
+         %47 = OpLabel
+               OpLoopMerge %49 %50 None
+               OpBranch %51
+         %51 = OpLabel
+         %52 = OpLoad %24 %46
+         %55 = OpSLessThan %54 %52 %53
+               OpBranchConditional %55 %48 %49
+         %48 = OpLabel
+               OpStore %56 %45
+               OpBranch %57
+         %57 = OpLabel
+               OpLoopMerge %59 %60 None
+               OpBranch %61
+         %61 = OpLabel
+         %62 = OpLoad %24 %56
+         %63 = OpSLessThan %54 %62 %53
+               OpBranchConditional %63 %58 %59
+         %58 = OpLabel
+         %64 = OpLoad %25 %27
+         %65 = OpLoad %24 %46
+         %66 = OpLoad %24 %56
+         %67 = OpCompositeConstruct %32 %65 %66
+         %68 = OpImageRead %38 %64 %67
+         %69 = OpCompositeExtract %24 %68 0
+         %70 = OpLoad %24 %44
+         %71 = OpIMul %24 %70 %69
+               OpStore %44 %71
+               OpBranch %60
+         %60 = OpLabel
+         %72 = OpLoad %24 %56
+         %74 = OpIAdd %24 %72 %73
+               OpStore %56 %74
+               OpBranch %57
+         %59 = OpLabel
+               OpBranch %50
+         %50 = OpLabel
+         %75 = OpLoad %24 %46
+         %76 = OpIAdd %24 %75 %73
+               OpStore %46 %76
+               OpBranch %47
+         %49 = OpLabel
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+         %83 = OpLoad %24 %44
+         %85 = OpAccessChain %84 %82 %53
+               OpStore %85 %83
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+        %101 = OpVariable %43 Function
+         %92 = OpAccessChain %91 %90 %73
+         %93 = OpLoad %87 %92
+         %95 = OpAccessChain %94 %82 %45
+         %96 = OpLoad %77 %95
+         %97 = OpConvertUToF %86 %96
+         %98 = OpMatrixTimesVector %86 %93 %97
+         %99 = OpConvertFToU %77 %98
+        %100 = OpAccessChain %94 %82 %45
+               OpStore %100 %99
+               OpStore %101 %45
+               OpBranch %102
+        %102 = OpLabel
+               OpLoopMerge %104 %105 None
+               OpBranch %106
+        %106 = OpLabel
+        %107 = OpLoad %24 %101
+        %109 = OpSLessThan %54 %107 %108
+               OpBranchConditional %109 %103 %104
+        %103 = OpLabel
+        %111 = OpAccessChain %110 %82 %73
+        %112 = OpLoad %79 %111
+        %114 = OpAccessChain %113 %90 %53
+        %115 = OpLoad %78 %114
+        %116 = OpVectorTimesScalar %79 %112 %115
+        %117 = OpConvertFToU %13 %116
+        %118 = OpCompositeExtract %10 %117 0
+        %119 = OpCompositeExtract %10 %117 1
+        %120 = OpCompositeExtract %10 %117 2
+        %121 = OpCompositeConstruct %77 %118 %119 %120 %16
+        %122 = OpAccessChain %94 %82 %45
+        %123 = OpLoad %77 %122
+        %124 = OpIAdd %77 %123 %121
+        %125 = OpAccessChain %94 %82 %45
+               OpStore %125 %124
+               OpBranch %105
+        %105 = OpLabel
+        %126 = OpLoad %24 %101
+        %127 = OpIAdd %24 %126 %73
+               OpStore %101 %127
+               OpBranch %102
+        %104 = OpLabel
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+        %129 = OpLoad %25 %128
+        %131 = OpImageRead %38 %129 %130
+        %132 = OpCompositeExtract %24 %131 0
+        %133 = OpConvertSToF %78 %132
+        %134 = OpCompositeConstruct %79 %133 %133 %133
+        %135 = OpAccessChain %110 %82 %73
+               OpStore %135 %134
+               OpReturn
+               OpFunctionEnd
+
+)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main" %15 %110
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpDecorate %15 BuiltIn GlobalInvocationId
+               OpMemberDecorate %40 0 Offset 0
+               OpMemberDecorate %40 1 Offset 16
+               OpMemberDecorate %40 2 Offset 28
+               OpDecorate %40 BufferBlock
+               OpDecorate %42 DescriptorSet 0
+               OpDecorate %42 Binding 1
+               OpDecorate %63 DescriptorSet 0
+               OpDecorate %63 Binding 3
+               OpDecorate %79 DescriptorSet 0
+               OpDecorate %79 Binding 2
+               OpDecorate %110 BuiltIn LocalInvocationId
+               OpMemberDecorate %127 0 Offset 0
+               OpMemberDecorate %127 1 RowMajor
+               OpMemberDecorate %127 1 Offset 16
+               OpMemberDecorate %127 1 MatrixStride 16
+               OpMemberDecorate %127 2 Offset 80
+               OpDecorate %127 Block
+               OpDecorate %129 DescriptorSet 0
+               OpDecorate %129 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeInt 32 0
+         %11 = OpTypePointer Function %10
+         %13 = OpTypeVector %10 3
+         %14 = OpTypePointer Input %13
+         %15 = OpVariable %14 Input
+         %16 = OpConstant %10 0
+         %17 = OpTypePointer Input %10
+         %21 = OpConstant %10 1
+         %24 = OpTypeInt 32 1
+         %25 = OpTypePointer Function %24
+         %27 = OpConstant %24 0
+         %34 = OpConstant %24 2
+         %35 = OpTypeBool
+         %37 = OpTypeVector %10 4
+         %38 = OpTypeFloat 32
+         %39 = OpTypeVector %38 3
+         %40 = OpTypeStruct %37 %39 %24
+         %41 = OpTypePointer Uniform %40
+         %42 = OpVariable %41 Uniform
+         %46 = OpTypeVector %10 2
+         %48 = OpTypePointer Uniform %37
+         %53 = OpTypePointer Uniform %10
+         %59 = OpConstant %24 1
+         %61 = OpTypeImage %24 2D 0 0 0 2 R32i
+         %62 = OpTypePointer UniformConstant %61
+         %63 = OpVariable %62 UniformConstant
+         %69 = OpTypeVector %24 2
+         %71 = OpTypeVector %24 4
+         %74 = OpTypePointer Uniform %24
+         %76 = OpConstant %10 2
+         %77 = OpConstant %10 3400
+         %78 = OpConstant %10 264
+         %79 = OpVariable %62 UniformConstant
+         %96 = OpConstant %24 3
+        %103 = OpConstantComposite %69 %27 %27
+        %107 = OpTypePointer Uniform %38
+        %110 = OpVariable %14 Input
+        %113 = OpTypeVector %38 2
+        %125 = OpTypeVector %38 4
+        %126 = OpTypeMatrix %125 4
+        %127 = OpTypeStruct %10 %126 %38
+        %128 = OpTypePointer Uniform %127
+        %129 = OpVariable %128 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+        %123 = OpFunctionCall %2 %8
+        %124 = OpFunctionCall %2 %6
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %12 = OpVariable %11 Function
+         %20 = OpVariable %11 Function
+         %26 = OpVariable %25 Function
+         %18 = OpAccessChain %17 %15 %16
+         %19 = OpLoad %10 %18
+               OpStore %12 %19
+         %22 = OpAccessChain %17 %15 %21
+         %23 = OpLoad %10 %22
+               OpStore %20 %23
+               OpStore %26 %27
+               OpBranch %28
+         %28 = OpLabel
+               OpLoopMerge %30 %31 None
+               OpBranch %32
+         %32 = OpLabel
+         %33 = OpLoad %24 %26
+         %36 = OpSLessThan %35 %33 %34
+               OpBranchConditional %36 %29 %30
+         %29 = OpLabel
+         %43 = OpLoad %10 %12
+         %44 = OpLoad %10 %20
+         %45 = OpIAdd %10 %43 %44
+         %47 = OpCompositeConstruct %46 %45 %45
+         %49 = OpAccessChain %48 %42 %27
+         %50 = OpLoad %37 %49
+         %51 = OpVectorShuffle %46 %50 %50 0 1
+         %52 = OpIAdd %46 %51 %47
+         %54 = OpAccessChain %53 %42 %27 %16
+         %55 = OpCompositeExtract %10 %52 0
+               OpStore %54 %55
+         %56 = OpAccessChain %53 %42 %27 %21
+         %57 = OpCompositeExtract %10 %52 1
+               OpStore %56 %57
+               OpBranch %31
+         %31 = OpLabel
+         %58 = OpLoad %24 %26
+         %60 = OpIAdd %24 %58 %59
+               OpStore %26 %60
+               OpBranch %28
+         %30 = OpLabel
+         %64 = OpLoad %61 %63
+         %65 = OpLoad %10 %12
+         %66 = OpBitcast %24 %65
+         %67 = OpLoad %10 %20
+         %68 = OpBitcast %24 %67
+         %70 = OpCompositeConstruct %69 %66 %68
+         %72 = OpImageRead %71 %64 %70
+         %73 = OpCompositeExtract %24 %72 1
+         %75 = OpAccessChain %74 %42 %34
+               OpStore %75 %73
+               OpMemoryBarrier %76 %77
+               OpControlBarrier %76 %76 %78
+         %80 = OpLoad %61 %79
+         %81 = OpLoad %10 %20
+         %82 = OpBitcast %24 %81
+         %83 = OpLoad %10 %12
+         %84 = OpBitcast %24 %83
+         %85 = OpCompositeConstruct %69 %82 %84
+         %86 = OpAccessChain %74 %42 %34
+         %87 = OpLoad %24 %86
+         %88 = OpCompositeConstruct %71 %87 %27 %27 %27
+               OpImageWrite %80 %85 %88
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+         %89 = OpVariable %25 Function
+               OpStore %89 %27
+               OpBranch %90
+         %90 = OpLabel
+               OpLoopMerge %92 %93 None
+               OpBranch %94
+         %94 = OpLabel
+         %95 = OpLoad %24 %89
+         %97 = OpSLessThan %35 %95 %96
+               OpBranchConditional %97 %91 %92
+         %91 = OpLabel
+         %98 = OpLoad %24 %89
+         %99 = OpIEqual %35 %98 %27
+               OpSelectionMerge %101 None
+               OpBranchConditional %99 %100 %109
+        %100 = OpLabel
+        %102 = OpLoad %61 %63
+        %104 = OpImageRead %71 %102 %103
+        %105 = OpCompositeExtract %24 %104 0
+        %106 = OpConvertSToF %38 %105
+        %108 = OpAccessChain %107 %42 %59 %16
+               OpStore %108 %106
+               OpBranch %101
+        %109 = OpLabel
+        %111 = OpLoad %13 %110
+        %112 = OpConvertUToF %39 %111
+        %114 = OpCompositeExtract %38 %112 0
+        %115 = OpCompositeExtract %38 %112 1
+        %116 = OpCompositeConstruct %113 %114 %115
+        %117 = OpAccessChain %107 %42 %59 %21
+        %118 = OpCompositeExtract %38 %116 0
+               OpStore %117 %118
+        %119 = OpAccessChain %107 %42 %59 %76
+        %120 = OpCompositeExtract %38 %116 1
+               OpStore %119 %120
+               OpBranch %101
+        %101 = OpLabel
+               OpBranch %93
+         %93 = OpLabel
+        %121 = OpLoad %24 %89
+        %122 = OpIAdd %24 %121 %59
+               OpStore %89 %122
+               OpBranch %90
+         %92 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 138
++; Bound: 220
+ ; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+-OpEntryPoint GLCompute %4 "main" %15
++OpEntryPoint GLCompute %4 "main" %143 %15
+ OpExecutionMode %4 LocalSize 1 1 1
+ OpSource ESSL 310
++OpDecorate %143 BuiltIn GlobalInvocationId
+ OpDecorate %15 BuiltIn LocalInvocationId
+ OpDecorate %27 DescriptorSet 0
+ OpDecorate %27 Binding 2
+ OpMemberDecorate %80 0 Offset 0
+ OpMemberDecorate %80 1 Offset 16
+ OpMemberDecorate %80 2 Offset 28
+ OpDecorate %80 BufferBlock
+ OpDecorate %82 DescriptorSet 0
+ OpDecorate %82 Binding 1
+ OpMemberDecorate %88 0 Offset 0
+ OpMemberDecorate %88 1 RowMajor
+ OpMemberDecorate %88 1 Offset 16
+ OpMemberDecorate %88 1 MatrixStride 16
+ OpMemberDecorate %88 2 Offset 80
+ OpDecorate %88 Block
+ OpDecorate %90 DescriptorSet 0
+ OpDecorate %90 Binding 0
+ OpDecorate %128 DescriptorSet 0
+ OpDecorate %128 Binding 3
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %10 = OpTypeInt 32 0
+ %11 = OpTypePointer Function %10
+ %13 = OpTypeVector %10 3
+ %14 = OpTypePointer Input %13
++%143 = OpVariable %14 Input
+ %15 = OpVariable %14 Input
+ %16 = OpConstant %10 0
+ %17 = OpTypePointer Input %10
+ %21 = OpConstant %10 1
+ %24 = OpTypeInt 32 1
+ %25 = OpTypeImage %24 2D 0 0 0 2 R32i
+ %26 = OpTypePointer UniformConstant %25
+ %27 = OpVariable %26 UniformConstant
+ %29 = OpTypeVector %10 2
+ %32 = OpTypeVector %24 2
+ %38 = OpTypeVector %24 4
+ %40 = OpConstant %10 2
+ %41 = OpConstant %10 3400
+ %42 = OpConstant %10 264
+ %43 = OpTypePointer Function %24
+ %45 = OpConstant %24 0
++%165 = OpTypePointer Uniform %10
+ %53 = OpConstant %24 2
+ %54 = OpTypeBool
+ %73 = OpConstant %24 1
+ %77 = OpTypeVector %10 4
+ %78 = OpTypeFloat 32
+ %79 = OpTypeVector %78 3
+ %80 = OpTypeStruct %77 %79 %24
+ %81 = OpTypePointer Uniform %80
+ %82 = OpVariable %81 Uniform
+ %84 = OpTypePointer Uniform %24
+ %86 = OpTypeVector %78 4
+ %87 = OpTypeMatrix %86 4
+ %88 = OpTypeStruct %10 %87 %78
+ %89 = OpTypePointer Uniform %88
+ %90 = OpVariable %89 Uniform
+-%91 = OpTypePointer Uniform %87
++%210 = OpTypeVector %78 2
+ %94 = OpTypePointer Uniform %77
+ %108 = OpConstant %24 3
+-%110 = OpTypePointer Uniform %79
+ %113 = OpTypePointer Uniform %78
+ %128 = OpVariable %26 UniformConstant
+ %130 = OpConstantComposite %32 %45 %45
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+-%136 = OpFunctionCall %2 %6
++%136 = OpFunctionCall %2 %140
+-%137 = OpFunctionCall %2 %8
++%137 = OpFunctionCall %2 %138
+ OpReturn
+ OpFunctionEnd
+-%6 = OpFunction %2 None %3
+-%7 = OpLabel
+-%12 = OpVariable %11 Function
+-%20 = OpVariable %11 Function
+-%44 = OpVariable %43 Function
+-%46 = OpVariable %43 Function
+-%56 = OpVariable %43 Function
+-%18 = OpAccessChain %17 %15 %16
+-%19 = OpLoad %10 %18
+-OpStore %12 %19
+-%22 = OpAccessChain %17 %15 %21
+-%23 = OpLoad %10 %22
+-OpStore %20 %23
+-%28 = OpLoad %25 %27
+-%30 = OpLoad %13 %15
+-%31 = OpVectorShuffle %29 %30 %30 0 1
+-%33 = OpBitcast %32 %31
+-%34 = OpLoad %10 %12
+-%35 = OpLoad %10 %20
+-%36 = OpIAdd %10 %34 %35
+-%37 = OpBitcast %24 %36
+-%39 = OpCompositeConstruct %38 %37 %37 %37 %37
+-OpImageWrite %28 %33 %39
+-OpMemoryBarrier %40 %41
+-OpControlBarrier %40 %40 %42
+-OpStore %44 %45
+-OpStore %46 %45
+-OpBranch %47
+-%47 = OpLabel
+-OpLoopMerge %49 %50 None
+-OpBranch %51
+-%51 = OpLabel
+-%52 = OpLoad %24 %46
+-%55 = OpSLessThan %54 %52 %53
+-OpBranchConditional %55 %48 %49
+-%48 = OpLabel
+-OpStore %56 %45
+-OpBranch %57
+-%57 = OpLabel
+-OpLoopMerge %59 %60 None
+-OpBranch %61
+-%61 = OpLabel
+-%62 = OpLoad %24 %56
+-%63 = OpSLessThan %54 %62 %53
+-OpBranchConditional %63 %58 %59
+-%58 = OpLabel
+-%64 = OpLoad %25 %27
+-%65 = OpLoad %24 %46
+-%66 = OpLoad %24 %56
+-%67 = OpCompositeConstruct %32 %65 %66
+-%68 = OpImageRead %38 %64 %67
+-%69 = OpCompositeExtract %24 %68 0
+-%70 = OpLoad %24 %44
+-%71 = OpIMul %24 %70 %69
+-OpStore %44 %71
+-OpBranch %60
+-%60 = OpLabel
+-%72 = OpLoad %24 %56
+-%74 = OpIAdd %24 %72 %73
+-OpStore %56 %74
+-OpBranch %57
+-%59 = OpLabel
+-OpBranch %50
+-%50 = OpLabel
+-%75 = OpLoad %24 %46
+-%76 = OpIAdd %24 %75 %73
+-OpStore %46 %76
+-OpBranch %47
+-%49 = OpLabel
+-OpMemoryBarrier %40 %41
+-OpControlBarrier %40 %40 %42
+-%83 = OpLoad %24 %44
+-%85 = OpAccessChain %84 %82 %53
+-OpStore %85 %83
+-OpReturn
+-OpFunctionEnd
+-%8 = OpFunction %2 None %3
+-%9 = OpLabel
+-%101 = OpVariable %43 Function
+-%92 = OpAccessChain %91 %90 %73
+-%93 = OpLoad %87 %92
+-%95 = OpAccessChain %94 %82 %45
+-%96 = OpLoad %77 %95
+-%97 = OpConvertUToF %86 %96
+-%98 = OpMatrixTimesVector %86 %93 %97
+-%99 = OpConvertFToU %77 %98
+-%100 = OpAccessChain %94 %82 %45
+-OpStore %100 %99
+-OpStore %101 %45
+-OpBranch %102
+-%102 = OpLabel
+-OpLoopMerge %104 %105 None
+-OpBranch %106
+-%106 = OpLabel
+-%107 = OpLoad %24 %101
+-%109 = OpSLessThan %54 %107 %108
+-OpBranchConditional %109 %103 %104
+-%103 = OpLabel
+-%111 = OpAccessChain %110 %82 %73
+-%112 = OpLoad %79 %111
+-%114 = OpAccessChain %113 %90 %53
+-%115 = OpLoad %78 %114
+-%116 = OpVectorTimesScalar %79 %112 %115
+-%117 = OpConvertFToU %13 %116
+-%118 = OpCompositeExtract %10 %117 0
+-%119 = OpCompositeExtract %10 %117 1
+-%120 = OpCompositeExtract %10 %117 2
+-%121 = OpCompositeConstruct %77 %118 %119 %120 %16
+-%122 = OpAccessChain %94 %82 %45
+-%123 = OpLoad %77 %122
+-%124 = OpIAdd %77 %123 %121
+-%125 = OpAccessChain %94 %82 %45
+-OpStore %125 %124
+-OpBranch %105
+-%105 = OpLabel
+-%126 = OpLoad %24 %101
+-%127 = OpIAdd %24 %126 %73
+-OpStore %101 %127
+-OpBranch %102
+-%104 = OpLabel
+-OpMemoryBarrier %40 %41
+-OpControlBarrier %40 %40 %42
+-%129 = OpLoad %25 %128
+-%131 = OpImageRead %38 %129 %130
+-%132 = OpCompositeExtract %24 %131 0
+-%133 = OpConvertSToF %78 %132
+-%134 = OpCompositeConstruct %79 %133 %133 %133
+-%135 = OpAccessChain %110 %82 %73
+-OpStore %135 %134
+-OpReturn
+-OpFunctionEnd
++%138 = OpFunction %2 None %3
++%139 = OpLabel
++%142 = OpVariable %11 Function
++%146 = OpVariable %11 Function
++%149 = OpVariable %43 Function
++%144 = OpAccessChain %17 %143 %16
++%145 = OpLoad %10 %144
++OpStore %142 %145
++%147 = OpAccessChain %17 %143 %21
++%148 = OpLoad %10 %147
++OpStore %146 %148
++OpStore %149 %45
++OpBranch %150
++%150 = OpLabel
++OpLoopMerge %152 %153 None
++OpBranch %154
++%154 = OpLabel
++%155 = OpLoad %24 %149
++%156 = OpSLessThan %54 %155 %53
++OpBranchConditional %156 %151 %152
++%151 = OpLabel
++%157 = OpLoad %10 %142
++%158 = OpLoad %10 %146
++%159 = OpIAdd %10 %157 %158
++%160 = OpCompositeConstruct %29 %159 %159
++%161 = OpAccessChain %94 %82 %45
++%162 = OpLoad %77 %161
++%163 = OpVectorShuffle %29 %162 %162 0 1
++%164 = OpIAdd %29 %163 %160
++%166 = OpAccessChain %165 %82 %45 %16
++%167 = OpCompositeExtract %10 %164 0
++OpStore %166 %167
++%168 = OpAccessChain %165 %82 %45 %21
++%169 = OpCompositeExtract %10 %164 1
++OpStore %168 %169
++OpBranch %153
++%153 = OpLabel
++%170 = OpLoad %24 %149
++%171 = OpIAdd %24 %170 %73
++OpStore %149 %171
++OpBranch %150
++%152 = OpLabel
++%172 = OpLoad %25 %128
++%173 = OpLoad %10 %142
++%174 = OpBitcast %24 %173
++%175 = OpLoad %10 %146
++%176 = OpBitcast %24 %175
++%177 = OpCompositeConstruct %32 %174 %176
++%178 = OpImageRead %38 %172 %177
++%179 = OpCompositeExtract %24 %178 1
++%180 = OpAccessChain %84 %82 %53
++OpStore %180 %179
++OpMemoryBarrier %40 %41
++OpControlBarrier %40 %40 %42
++%181 = OpLoad %25 %27
++%182 = OpLoad %10 %146
++%183 = OpBitcast %24 %182
++%184 = OpLoad %10 %142
++%185 = OpBitcast %24 %184
++%186 = OpCompositeConstruct %32 %183 %185
++%187 = OpAccessChain %84 %82 %53
++%188 = OpLoad %24 %187
++%189 = OpCompositeConstruct %38 %188 %45 %45 %45
++OpImageWrite %181 %186 %189
++OpReturn
++OpFunctionEnd
++%140 = OpFunction %2 None %3
++%141 = OpLabel
++%190 = OpVariable %43 Function
++OpStore %190 %45
++OpBranch %191
++%191 = OpLabel
++OpLoopMerge %193 %194 None
++OpBranch %195
++%195 = OpLabel
++%196 = OpLoad %24 %190
++%197 = OpSLessThan %54 %196 %108
++OpBranchConditional %197 %192 %193
++%192 = OpLabel
++%198 = OpLoad %24 %190
++%199 = OpIEqual %54 %198 %45
++OpSelectionMerge %201 None
++OpBranchConditional %199 %200 %207
++%207 = OpLabel
++%208 = OpLoad %13 %15
++%209 = OpConvertUToF %79 %208
++%211 = OpCompositeExtract %78 %209 0
++%212 = OpCompositeExtract %78 %209 1
++%213 = OpCompositeConstruct %210 %211 %212
++%214 = OpAccessChain %113 %82 %73 %21
++%215 = OpCompositeExtract %78 %213 0
++OpStore %214 %215
++%216 = OpAccessChain %113 %82 %73 %40
++%217 = OpCompositeExtract %78 %213 1
++OpStore %216 %217
++OpBranch %201
++%200 = OpLabel
++%202 = OpLoad %25 %128
++%203 = OpImageRead %38 %202 %130
++%204 = OpCompositeExtract %24 %203 0
++%205 = OpConvertSToF %78 %204
++%206 = OpAccessChain %113 %82 %73 %16
++OpStore %206 %205
++OpBranch %201
++%201 = OpLabel
++OpBranch %194
++%194 = OpLabel
++%218 = OpLoad %24 %190
++%219 = OpIAdd %24 %218 %73
++OpStore %190 %219
++OpBranch %191
++%193 = OpLabel
++OpReturn
++OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/large_functions_large_diffs_dst.spvasm b/test/diff/diff_files/large_functions_large_diffs_dst.spvasm
new file mode 100644
index 0000000..be7c1d5
--- /dev/null
+++ b/test/diff/diff_files/large_functions_large_diffs_dst.spvasm
@@ -0,0 +1,213 @@
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main" %15 %110
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "f1("
+               OpName %8 "f2("
+               OpName %12 "x"
+               OpName %15 "gl_GlobalInvocationID"
+               OpName %20 "z"
+               OpName %26 "i"
+               OpName %40 "BufferOut"
+               OpMemberName %40 0 "o_uv4"
+               OpMemberName %40 1 "o_v3"
+               OpMemberName %40 2 "o_i"
+               OpName %42 ""
+               OpName %63 "image2"
+               OpName %79 "image"
+               OpName %89 "i"
+               OpName %110 "gl_LocalInvocationID"
+               OpName %127 "BufferIn"
+               OpMemberName %127 0 "i_u"
+               OpMemberName %127 1 "i_v4"
+               OpMemberName %127 2 "i_f"
+               OpName %129 ""
+               OpDecorate %15 BuiltIn GlobalInvocationId
+               OpMemberDecorate %40 0 Offset 0
+               OpMemberDecorate %40 1 Offset 16
+               OpMemberDecorate %40 2 Offset 28
+               OpDecorate %40 BufferBlock
+               OpDecorate %42 DescriptorSet 0
+               OpDecorate %42 Binding 1
+               OpDecorate %63 DescriptorSet 0
+               OpDecorate %63 Binding 3
+               OpDecorate %79 DescriptorSet 0
+               OpDecorate %79 Binding 2
+               OpDecorate %110 BuiltIn LocalInvocationId
+               OpMemberDecorate %127 0 Offset 0
+               OpMemberDecorate %127 1 RowMajor
+               OpMemberDecorate %127 1 Offset 16
+               OpMemberDecorate %127 1 MatrixStride 16
+               OpMemberDecorate %127 2 Offset 80
+               OpDecorate %127 Block
+               OpDecorate %129 DescriptorSet 0
+               OpDecorate %129 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeInt 32 0
+         %11 = OpTypePointer Function %10
+         %13 = OpTypeVector %10 3
+         %14 = OpTypePointer Input %13
+         %15 = OpVariable %14 Input
+         %16 = OpConstant %10 0
+         %17 = OpTypePointer Input %10
+         %21 = OpConstant %10 1
+         %24 = OpTypeInt 32 1
+         %25 = OpTypePointer Function %24
+         %27 = OpConstant %24 0
+         %34 = OpConstant %24 2
+         %35 = OpTypeBool
+         %37 = OpTypeVector %10 4
+         %38 = OpTypeFloat 32
+         %39 = OpTypeVector %38 3
+         %40 = OpTypeStruct %37 %39 %24
+         %41 = OpTypePointer Uniform %40
+         %42 = OpVariable %41 Uniform
+         %46 = OpTypeVector %10 2
+         %48 = OpTypePointer Uniform %37
+         %53 = OpTypePointer Uniform %10
+         %59 = OpConstant %24 1
+         %61 = OpTypeImage %24 2D 0 0 0 2 R32i
+         %62 = OpTypePointer UniformConstant %61
+         %63 = OpVariable %62 UniformConstant
+         %69 = OpTypeVector %24 2
+         %71 = OpTypeVector %24 4
+         %74 = OpTypePointer Uniform %24
+         %76 = OpConstant %10 2
+         %77 = OpConstant %10 3400
+         %78 = OpConstant %10 264
+         %79 = OpVariable %62 UniformConstant
+         %96 = OpConstant %24 3
+        %103 = OpConstantComposite %69 %27 %27
+        %107 = OpTypePointer Uniform %38
+        %110 = OpVariable %14 Input
+        %113 = OpTypeVector %38 2
+        %125 = OpTypeVector %38 4
+        %126 = OpTypeMatrix %125 4
+        %127 = OpTypeStruct %10 %126 %38
+        %128 = OpTypePointer Uniform %127
+        %129 = OpVariable %128 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+        %123 = OpFunctionCall %2 %8
+        %124 = OpFunctionCall %2 %6
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %12 = OpVariable %11 Function
+         %20 = OpVariable %11 Function
+         %26 = OpVariable %25 Function
+         %18 = OpAccessChain %17 %15 %16
+         %19 = OpLoad %10 %18
+               OpStore %12 %19
+         %22 = OpAccessChain %17 %15 %21
+         %23 = OpLoad %10 %22
+               OpStore %20 %23
+               OpStore %26 %27
+               OpBranch %28
+         %28 = OpLabel
+               OpLoopMerge %30 %31 None
+               OpBranch %32
+         %32 = OpLabel
+         %33 = OpLoad %24 %26
+         %36 = OpSLessThan %35 %33 %34
+               OpBranchConditional %36 %29 %30
+         %29 = OpLabel
+         %43 = OpLoad %10 %12
+         %44 = OpLoad %10 %20
+         %45 = OpIAdd %10 %43 %44
+         %47 = OpCompositeConstruct %46 %45 %45
+         %49 = OpAccessChain %48 %42 %27
+         %50 = OpLoad %37 %49
+         %51 = OpVectorShuffle %46 %50 %50 0 1
+         %52 = OpIAdd %46 %51 %47
+         %54 = OpAccessChain %53 %42 %27 %16
+         %55 = OpCompositeExtract %10 %52 0
+               OpStore %54 %55
+         %56 = OpAccessChain %53 %42 %27 %21
+         %57 = OpCompositeExtract %10 %52 1
+               OpStore %56 %57
+               OpBranch %31
+         %31 = OpLabel
+         %58 = OpLoad %24 %26
+         %60 = OpIAdd %24 %58 %59
+               OpStore %26 %60
+               OpBranch %28
+         %30 = OpLabel
+         %64 = OpLoad %61 %63
+         %65 = OpLoad %10 %12
+         %66 = OpBitcast %24 %65
+         %67 = OpLoad %10 %20
+         %68 = OpBitcast %24 %67
+         %70 = OpCompositeConstruct %69 %66 %68
+         %72 = OpImageRead %71 %64 %70
+         %73 = OpCompositeExtract %24 %72 1
+         %75 = OpAccessChain %74 %42 %34
+               OpStore %75 %73
+               OpMemoryBarrier %76 %77
+               OpControlBarrier %76 %76 %78
+         %80 = OpLoad %61 %79
+         %81 = OpLoad %10 %20
+         %82 = OpBitcast %24 %81
+         %83 = OpLoad %10 %12
+         %84 = OpBitcast %24 %83
+         %85 = OpCompositeConstruct %69 %82 %84
+         %86 = OpAccessChain %74 %42 %34
+         %87 = OpLoad %24 %86
+         %88 = OpCompositeConstruct %71 %87 %27 %27 %27
+               OpImageWrite %80 %85 %88
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+         %89 = OpVariable %25 Function
+               OpStore %89 %27
+               OpBranch %90
+         %90 = OpLabel
+               OpLoopMerge %92 %93 None
+               OpBranch %94
+         %94 = OpLabel
+         %95 = OpLoad %24 %89
+         %97 = OpSLessThan %35 %95 %96
+               OpBranchConditional %97 %91 %92
+         %91 = OpLabel
+         %98 = OpLoad %24 %89
+         %99 = OpIEqual %35 %98 %27
+               OpSelectionMerge %101 None
+               OpBranchConditional %99 %100 %109
+        %100 = OpLabel
+        %102 = OpLoad %61 %63
+        %104 = OpImageRead %71 %102 %103
+        %105 = OpCompositeExtract %24 %104 0
+        %106 = OpConvertSToF %38 %105
+        %108 = OpAccessChain %107 %42 %59 %16
+               OpStore %108 %106
+               OpBranch %101
+        %109 = OpLabel
+        %111 = OpLoad %13 %110
+        %112 = OpConvertUToF %39 %111
+        %114 = OpCompositeExtract %38 %112 0
+        %115 = OpCompositeExtract %38 %112 1
+        %116 = OpCompositeConstruct %113 %114 %115
+        %117 = OpAccessChain %107 %42 %59 %21
+        %118 = OpCompositeExtract %38 %116 0
+               OpStore %117 %118
+        %119 = OpAccessChain %107 %42 %59 %76
+        %120 = OpCompositeExtract %38 %116 1
+               OpStore %119 %120
+               OpBranch %101
+        %101 = OpLabel
+               OpBranch %93
+         %93 = OpLabel
+        %121 = OpLoad %24 %89
+        %122 = OpIAdd %24 %121 %59
+               OpStore %89 %122
+               OpBranch %90
+         %92 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
diff --git a/test/diff/diff_files/large_functions_large_diffs_src.spvasm b/test/diff/diff_files/large_functions_large_diffs_src.spvasm
new file mode 100644
index 0000000..8f0aa61
--- /dev/null
+++ b/test/diff/diff_files/large_functions_large_diffs_src.spvasm
@@ -0,0 +1,230 @@
+;; Test where src and dst have a few large functions with large differences.
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main" %15
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "f1("
+               OpName %8 "f2("
+               OpName %12 "x"
+               OpName %15 "gl_LocalInvocationID"
+               OpName %20 "y"
+               OpName %27 "image"
+               OpName %44 "sum"
+               OpName %46 "i"
+               OpName %56 "j"
+               OpName %80 "BufferOut"
+               OpMemberName %80 0 "o_uv4"
+               OpMemberName %80 1 "o_v3"
+               OpMemberName %80 2 "o_i"
+               OpName %82 ""
+               OpName %88 "BufferIn"
+               OpMemberName %88 0 "i_u"
+               OpMemberName %88 1 "i_v4"
+               OpMemberName %88 2 "i_f"
+               OpName %90 ""
+               OpName %101 "i"
+               OpName %128 "image2"
+               OpDecorate %15 BuiltIn LocalInvocationId
+               OpDecorate %27 DescriptorSet 0
+               OpDecorate %27 Binding 2
+               OpMemberDecorate %80 0 Offset 0
+               OpMemberDecorate %80 1 Offset 16
+               OpMemberDecorate %80 2 Offset 28
+               OpDecorate %80 BufferBlock
+               OpDecorate %82 DescriptorSet 0
+               OpDecorate %82 Binding 1
+               OpMemberDecorate %88 0 Offset 0
+               OpMemberDecorate %88 1 RowMajor
+               OpMemberDecorate %88 1 Offset 16
+               OpMemberDecorate %88 1 MatrixStride 16
+               OpMemberDecorate %88 2 Offset 80
+               OpDecorate %88 Block
+               OpDecorate %90 DescriptorSet 0
+               OpDecorate %90 Binding 0
+               OpDecorate %128 DescriptorSet 0
+               OpDecorate %128 Binding 3
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeInt 32 0
+         %11 = OpTypePointer Function %10
+         %13 = OpTypeVector %10 3
+         %14 = OpTypePointer Input %13
+         %15 = OpVariable %14 Input
+         %16 = OpConstant %10 0
+         %17 = OpTypePointer Input %10
+         %21 = OpConstant %10 1
+         %24 = OpTypeInt 32 1
+         %25 = OpTypeImage %24 2D 0 0 0 2 R32i
+         %26 = OpTypePointer UniformConstant %25
+         %27 = OpVariable %26 UniformConstant
+         %29 = OpTypeVector %10 2
+         %32 = OpTypeVector %24 2
+         %38 = OpTypeVector %24 4
+         %40 = OpConstant %10 2
+         %41 = OpConstant %10 3400
+         %42 = OpConstant %10 264
+         %43 = OpTypePointer Function %24
+         %45 = OpConstant %24 0
+         %53 = OpConstant %24 2
+         %54 = OpTypeBool
+         %73 = OpConstant %24 1
+         %77 = OpTypeVector %10 4
+         %78 = OpTypeFloat 32
+         %79 = OpTypeVector %78 3
+         %80 = OpTypeStruct %77 %79 %24
+         %81 = OpTypePointer Uniform %80
+         %82 = OpVariable %81 Uniform
+         %84 = OpTypePointer Uniform %24
+         %86 = OpTypeVector %78 4
+         %87 = OpTypeMatrix %86 4
+         %88 = OpTypeStruct %10 %87 %78
+         %89 = OpTypePointer Uniform %88
+         %90 = OpVariable %89 Uniform
+         %91 = OpTypePointer Uniform %87
+         %94 = OpTypePointer Uniform %77
+        %108 = OpConstant %24 3
+        %110 = OpTypePointer Uniform %79
+        %113 = OpTypePointer Uniform %78
+        %128 = OpVariable %26 UniformConstant
+        %130 = OpConstantComposite %32 %45 %45
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+        %136 = OpFunctionCall %2 %6
+        %137 = OpFunctionCall %2 %8
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %12 = OpVariable %11 Function
+         %20 = OpVariable %11 Function
+         %44 = OpVariable %43 Function
+         %46 = OpVariable %43 Function
+         %56 = OpVariable %43 Function
+         %18 = OpAccessChain %17 %15 %16
+         %19 = OpLoad %10 %18
+               OpStore %12 %19
+         %22 = OpAccessChain %17 %15 %21
+         %23 = OpLoad %10 %22
+               OpStore %20 %23
+         %28 = OpLoad %25 %27
+         %30 = OpLoad %13 %15
+         %31 = OpVectorShuffle %29 %30 %30 0 1
+         %33 = OpBitcast %32 %31
+         %34 = OpLoad %10 %12
+         %35 = OpLoad %10 %20
+         %36 = OpIAdd %10 %34 %35
+         %37 = OpBitcast %24 %36
+         %39 = OpCompositeConstruct %38 %37 %37 %37 %37
+               OpImageWrite %28 %33 %39
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+               OpStore %44 %45
+               OpStore %46 %45
+               OpBranch %47
+         %47 = OpLabel
+               OpLoopMerge %49 %50 None
+               OpBranch %51
+         %51 = OpLabel
+         %52 = OpLoad %24 %46
+         %55 = OpSLessThan %54 %52 %53
+               OpBranchConditional %55 %48 %49
+         %48 = OpLabel
+               OpStore %56 %45
+               OpBranch %57
+         %57 = OpLabel
+               OpLoopMerge %59 %60 None
+               OpBranch %61
+         %61 = OpLabel
+         %62 = OpLoad %24 %56
+         %63 = OpSLessThan %54 %62 %53
+               OpBranchConditional %63 %58 %59
+         %58 = OpLabel
+         %64 = OpLoad %25 %27
+         %65 = OpLoad %24 %46
+         %66 = OpLoad %24 %56
+         %67 = OpCompositeConstruct %32 %65 %66
+         %68 = OpImageRead %38 %64 %67
+         %69 = OpCompositeExtract %24 %68 0
+         %70 = OpLoad %24 %44
+         %71 = OpIMul %24 %70 %69
+               OpStore %44 %71
+               OpBranch %60
+         %60 = OpLabel
+         %72 = OpLoad %24 %56
+         %74 = OpIAdd %24 %72 %73
+               OpStore %56 %74
+               OpBranch %57
+         %59 = OpLabel
+               OpBranch %50
+         %50 = OpLabel
+         %75 = OpLoad %24 %46
+         %76 = OpIAdd %24 %75 %73
+               OpStore %46 %76
+               OpBranch %47
+         %49 = OpLabel
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+         %83 = OpLoad %24 %44
+         %85 = OpAccessChain %84 %82 %53
+               OpStore %85 %83
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+        %101 = OpVariable %43 Function
+         %92 = OpAccessChain %91 %90 %73
+         %93 = OpLoad %87 %92
+         %95 = OpAccessChain %94 %82 %45
+         %96 = OpLoad %77 %95
+         %97 = OpConvertUToF %86 %96
+         %98 = OpMatrixTimesVector %86 %93 %97
+         %99 = OpConvertFToU %77 %98
+        %100 = OpAccessChain %94 %82 %45
+               OpStore %100 %99
+               OpStore %101 %45
+               OpBranch %102
+        %102 = OpLabel
+               OpLoopMerge %104 %105 None
+               OpBranch %106
+        %106 = OpLabel
+        %107 = OpLoad %24 %101
+        %109 = OpSLessThan %54 %107 %108
+               OpBranchConditional %109 %103 %104
+        %103 = OpLabel
+        %111 = OpAccessChain %110 %82 %73
+        %112 = OpLoad %79 %111
+        %114 = OpAccessChain %113 %90 %53
+        %115 = OpLoad %78 %114
+        %116 = OpVectorTimesScalar %79 %112 %115
+        %117 = OpConvertFToU %13 %116
+        %118 = OpCompositeExtract %10 %117 0
+        %119 = OpCompositeExtract %10 %117 1
+        %120 = OpCompositeExtract %10 %117 2
+        %121 = OpCompositeConstruct %77 %118 %119 %120 %16
+        %122 = OpAccessChain %94 %82 %45
+        %123 = OpLoad %77 %122
+        %124 = OpIAdd %77 %123 %121
+        %125 = OpAccessChain %94 %82 %45
+               OpStore %125 %124
+               OpBranch %105
+        %105 = OpLabel
+        %126 = OpLoad %24 %101
+        %127 = OpIAdd %24 %126 %73
+               OpStore %101 %127
+               OpBranch %102
+        %104 = OpLabel
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+        %129 = OpLoad %25 %128
+        %131 = OpImageRead %38 %129 %130
+        %132 = OpCompositeExtract %24 %131 0
+        %133 = OpConvertSToF %78 %132
+        %134 = OpCompositeConstruct %79 %133 %133 %133
+        %135 = OpAccessChain %110 %82 %73
+               OpStore %135 %134
+               OpReturn
+               OpFunctionEnd
+
diff --git a/test/diff/diff_files/large_functions_small_diffs_autogen.cpp b/test/diff/diff_files/large_functions_small_diffs_autogen.cpp
new file mode 100644
index 0000000..02838d9
--- /dev/null
+++ b/test/diff/diff_files/large_functions_small_diffs_autogen.cpp
@@ -0,0 +1,1364 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Test where src and dst have a few large functions with small differences.
+constexpr char kSrc[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main" %15
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "f1("
+               OpName %8 "f2("
+               OpName %12 "x"
+               OpName %15 "gl_LocalInvocationID"
+               OpName %20 "y"
+               OpName %27 "image"
+               OpName %44 "sum"
+               OpName %46 "i"
+               OpName %56 "j"
+               OpName %80 "BufferOut"
+               OpMemberName %80 0 "o_uv4"
+               OpMemberName %80 1 "o_v3"
+               OpMemberName %80 2 "o_i"
+               OpName %82 ""
+               OpName %88 "BufferIn"
+               OpMemberName %88 0 "i_u"
+               OpMemberName %88 1 "i_v4"
+               OpMemberName %88 2 "i_f"
+               OpName %90 ""
+               OpName %101 "i"
+               OpDecorate %15 BuiltIn LocalInvocationId
+               OpDecorate %27 DescriptorSet 0
+               OpDecorate %27 Binding 2
+               OpMemberDecorate %80 0 Offset 0
+               OpMemberDecorate %80 1 Offset 16
+               OpMemberDecorate %80 2 Offset 28
+               OpDecorate %80 BufferBlock
+               OpDecorate %82 DescriptorSet 0
+               OpDecorate %82 Binding 1
+               OpMemberDecorate %88 0 Offset 0
+               OpMemberDecorate %88 1 RowMajor
+               OpMemberDecorate %88 1 Offset 16
+               OpMemberDecorate %88 1 MatrixStride 16
+               OpMemberDecorate %88 2 Offset 80
+               OpDecorate %88 Block
+               OpDecorate %90 DescriptorSet 0
+               OpDecorate %90 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeInt 32 0
+         %11 = OpTypePointer Function %10
+         %13 = OpTypeVector %10 3
+         %14 = OpTypePointer Input %13
+         %15 = OpVariable %14 Input
+         %16 = OpConstant %10 0
+         %17 = OpTypePointer Input %10
+         %21 = OpConstant %10 1
+         %24 = OpTypeInt 32 1
+         %25 = OpTypeImage %24 2D 0 0 0 2 R32i
+         %26 = OpTypePointer UniformConstant %25
+         %27 = OpVariable %26 UniformConstant
+         %29 = OpTypeVector %10 2
+         %32 = OpTypeVector %24 2
+         %38 = OpTypeVector %24 4
+         %40 = OpConstant %10 2
+         %41 = OpConstant %10 3400
+         %42 = OpConstant %10 264
+         %43 = OpTypePointer Function %24
+         %45 = OpConstant %24 0
+         %53 = OpConstant %24 2
+         %54 = OpTypeBool
+         %73 = OpConstant %24 1
+         %77 = OpTypeVector %10 4
+         %78 = OpTypeFloat 32
+         %79 = OpTypeVector %78 3
+         %80 = OpTypeStruct %77 %79 %24
+         %81 = OpTypePointer Uniform %80
+         %82 = OpVariable %81 Uniform
+         %84 = OpTypePointer Uniform %24
+         %86 = OpTypeVector %78 4
+         %87 = OpTypeMatrix %86 4
+         %88 = OpTypeStruct %10 %87 %78
+         %89 = OpTypePointer Uniform %88
+         %90 = OpVariable %89 Uniform
+         %91 = OpTypePointer Uniform %87
+         %94 = OpTypePointer Uniform %77
+        %108 = OpConstant %24 3
+        %110 = OpTypePointer Uniform %79
+        %113 = OpTypePointer Uniform %78
+        %129 = OpConstantComposite %32 %45 %45
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+        %135 = OpFunctionCall %2 %6
+        %136 = OpFunctionCall %2 %8
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %12 = OpVariable %11 Function
+         %20 = OpVariable %11 Function
+         %44 = OpVariable %43 Function
+         %46 = OpVariable %43 Function
+         %56 = OpVariable %43 Function
+         %18 = OpAccessChain %17 %15 %16
+         %19 = OpLoad %10 %18
+               OpStore %12 %19
+         %22 = OpAccessChain %17 %15 %21
+         %23 = OpLoad %10 %22
+               OpStore %20 %23
+         %28 = OpLoad %25 %27
+         %30 = OpLoad %13 %15
+         %31 = OpVectorShuffle %29 %30 %30 0 1
+         %33 = OpBitcast %32 %31
+         %34 = OpLoad %10 %12
+         %35 = OpLoad %10 %20
+         %36 = OpIAdd %10 %34 %35
+         %37 = OpBitcast %24 %36
+         %39 = OpCompositeConstruct %38 %37 %37 %37 %37
+               OpImageWrite %28 %33 %39
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+               OpStore %44 %45
+               OpStore %46 %45
+               OpBranch %47
+         %47 = OpLabel
+               OpLoopMerge %49 %50 None
+               OpBranch %51
+         %51 = OpLabel
+         %52 = OpLoad %24 %46
+         %55 = OpSLessThan %54 %52 %53
+               OpBranchConditional %55 %48 %49
+         %48 = OpLabel
+               OpStore %56 %45
+               OpBranch %57
+         %57 = OpLabel
+               OpLoopMerge %59 %60 None
+               OpBranch %61
+         %61 = OpLabel
+         %62 = OpLoad %24 %56
+         %63 = OpSLessThan %54 %62 %53
+               OpBranchConditional %63 %58 %59
+         %58 = OpLabel
+         %64 = OpLoad %25 %27
+         %65 = OpLoad %24 %46
+         %66 = OpLoad %24 %56
+         %67 = OpCompositeConstruct %32 %65 %66
+         %68 = OpImageRead %38 %64 %67
+         %69 = OpCompositeExtract %24 %68 0
+         %70 = OpLoad %24 %44
+         %71 = OpIAdd %24 %70 %69
+               OpStore %44 %71
+               OpBranch %60
+         %60 = OpLabel
+         %72 = OpLoad %24 %56
+         %74 = OpIAdd %24 %72 %73
+               OpStore %56 %74
+               OpBranch %57
+         %59 = OpLabel
+               OpBranch %50
+         %50 = OpLabel
+         %75 = OpLoad %24 %46
+         %76 = OpIAdd %24 %75 %73
+               OpStore %46 %76
+               OpBranch %47
+         %49 = OpLabel
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+         %83 = OpLoad %24 %44
+         %85 = OpAccessChain %84 %82 %53
+               OpStore %85 %83
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+        %101 = OpVariable %43 Function
+         %92 = OpAccessChain %91 %90 %73
+         %93 = OpLoad %87 %92
+         %95 = OpAccessChain %94 %82 %45
+         %96 = OpLoad %77 %95
+         %97 = OpConvertUToF %86 %96
+         %98 = OpMatrixTimesVector %86 %93 %97
+         %99 = OpConvertFToU %77 %98
+        %100 = OpAccessChain %94 %82 %45
+               OpStore %100 %99
+               OpStore %101 %45
+               OpBranch %102
+        %102 = OpLabel
+               OpLoopMerge %104 %105 None
+               OpBranch %106
+        %106 = OpLabel
+        %107 = OpLoad %24 %101
+        %109 = OpSLessThan %54 %107 %108
+               OpBranchConditional %109 %103 %104
+        %103 = OpLabel
+        %111 = OpAccessChain %110 %82 %73
+        %112 = OpLoad %79 %111
+        %114 = OpAccessChain %113 %90 %53
+        %115 = OpLoad %78 %114
+        %116 = OpVectorTimesScalar %79 %112 %115
+        %117 = OpConvertFToU %13 %116
+        %118 = OpCompositeExtract %10 %117 0
+        %119 = OpCompositeExtract %10 %117 1
+        %120 = OpCompositeExtract %10 %117 2
+        %121 = OpCompositeConstruct %77 %118 %119 %120 %16
+        %122 = OpAccessChain %94 %82 %45
+        %123 = OpLoad %77 %122
+        %124 = OpIAdd %77 %123 %121
+        %125 = OpAccessChain %94 %82 %45
+               OpStore %125 %124
+               OpBranch %105
+        %105 = OpLabel
+        %126 = OpLoad %24 %101
+        %127 = OpIAdd %24 %126 %73
+               OpStore %101 %127
+               OpBranch %102
+        %104 = OpLabel
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+        %128 = OpLoad %25 %27
+        %130 = OpImageRead %38 %128 %129
+        %131 = OpCompositeExtract %24 %130 0
+        %132 = OpConvertSToF %78 %131
+        %133 = OpCompositeConstruct %79 %132 %132 %132
+        %134 = OpAccessChain %110 %82 %73
+               OpStore %134 %133
+               OpReturn
+               OpFunctionEnd
+)";
+constexpr char kDst[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main" %15
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "f1("
+               OpName %8 "f2("
+               OpName %12 "x"
+               OpName %15 "gl_LocalInvocationID"
+               OpName %20 "y"
+               OpName %27 "image"
+               OpName %44 "sum"
+               OpName %46 "i"
+               OpName %56 "j"
+               OpName %80 "BufferOut"
+               OpMemberName %80 0 "o_uv4"
+               OpMemberName %80 1 "o_v3"
+               OpMemberName %80 2 "o_i"
+               OpName %82 ""
+               OpName %88 "BufferIn"
+               OpMemberName %88 0 "i_u"
+               OpMemberName %88 1 "i_v4"
+               OpMemberName %88 2 "i_f"
+               OpName %90 ""
+               OpName %101 "i"
+               OpName %128 "image2"
+               OpDecorate %15 BuiltIn LocalInvocationId
+               OpDecorate %27 DescriptorSet 0
+               OpDecorate %27 Binding 2
+               OpMemberDecorate %80 0 Offset 0
+               OpMemberDecorate %80 1 Offset 16
+               OpMemberDecorate %80 2 Offset 28
+               OpDecorate %80 BufferBlock
+               OpDecorate %82 DescriptorSet 0
+               OpDecorate %82 Binding 1
+               OpMemberDecorate %88 0 Offset 0
+               OpMemberDecorate %88 1 RowMajor
+               OpMemberDecorate %88 1 Offset 16
+               OpMemberDecorate %88 1 MatrixStride 16
+               OpMemberDecorate %88 2 Offset 80
+               OpDecorate %88 Block
+               OpDecorate %90 DescriptorSet 0
+               OpDecorate %90 Binding 0
+               OpDecorate %128 DescriptorSet 0
+               OpDecorate %128 Binding 3
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeInt 32 0
+         %11 = OpTypePointer Function %10
+         %13 = OpTypeVector %10 3
+         %14 = OpTypePointer Input %13
+         %15 = OpVariable %14 Input
+         %16 = OpConstant %10 0
+         %17 = OpTypePointer Input %10
+         %21 = OpConstant %10 1
+         %24 = OpTypeInt 32 1
+         %25 = OpTypeImage %24 2D 0 0 0 2 R32i
+         %26 = OpTypePointer UniformConstant %25
+         %27 = OpVariable %26 UniformConstant
+         %29 = OpTypeVector %10 2
+         %32 = OpTypeVector %24 2
+         %38 = OpTypeVector %24 4
+         %40 = OpConstant %10 2
+         %41 = OpConstant %10 3400
+         %42 = OpConstant %10 264
+         %43 = OpTypePointer Function %24
+         %45 = OpConstant %24 0
+         %53 = OpConstant %24 2
+         %54 = OpTypeBool
+         %73 = OpConstant %24 1
+         %77 = OpTypeVector %10 4
+         %78 = OpTypeFloat 32
+         %79 = OpTypeVector %78 3
+         %80 = OpTypeStruct %77 %79 %24
+         %81 = OpTypePointer Uniform %80
+         %82 = OpVariable %81 Uniform
+         %84 = OpTypePointer Uniform %24
+         %86 = OpTypeVector %78 4
+         %87 = OpTypeMatrix %86 4
+         %88 = OpTypeStruct %10 %87 %78
+         %89 = OpTypePointer Uniform %88
+         %90 = OpVariable %89 Uniform
+         %91 = OpTypePointer Uniform %87
+         %94 = OpTypePointer Uniform %77
+        %108 = OpConstant %24 3
+        %110 = OpTypePointer Uniform %79
+        %113 = OpTypePointer Uniform %78
+        %128 = OpVariable %26 UniformConstant
+        %130 = OpConstantComposite %32 %45 %45
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+        %136 = OpFunctionCall %2 %6
+        %137 = OpFunctionCall %2 %8
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %12 = OpVariable %11 Function
+         %20 = OpVariable %11 Function
+         %44 = OpVariable %43 Function
+         %46 = OpVariable %43 Function
+         %56 = OpVariable %43 Function
+         %18 = OpAccessChain %17 %15 %16
+         %19 = OpLoad %10 %18
+               OpStore %12 %19
+         %22 = OpAccessChain %17 %15 %21
+         %23 = OpLoad %10 %22
+               OpStore %20 %23
+         %28 = OpLoad %25 %27
+         %30 = OpLoad %13 %15
+         %31 = OpVectorShuffle %29 %30 %30 0 1
+         %33 = OpBitcast %32 %31
+         %34 = OpLoad %10 %12
+         %35 = OpLoad %10 %20
+         %36 = OpIAdd %10 %34 %35
+         %37 = OpBitcast %24 %36
+         %39 = OpCompositeConstruct %38 %37 %37 %37 %37
+               OpImageWrite %28 %33 %39
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+               OpStore %44 %45
+               OpStore %46 %45
+               OpBranch %47
+         %47 = OpLabel
+               OpLoopMerge %49 %50 None
+               OpBranch %51
+         %51 = OpLabel
+         %52 = OpLoad %24 %46
+         %55 = OpSLessThan %54 %52 %53
+               OpBranchConditional %55 %48 %49
+         %48 = OpLabel
+               OpStore %56 %45
+               OpBranch %57
+         %57 = OpLabel
+               OpLoopMerge %59 %60 None
+               OpBranch %61
+         %61 = OpLabel
+         %62 = OpLoad %24 %56
+         %63 = OpSLessThan %54 %62 %53
+               OpBranchConditional %63 %58 %59
+         %58 = OpLabel
+         %64 = OpLoad %25 %27
+         %65 = OpLoad %24 %46
+         %66 = OpLoad %24 %56
+         %67 = OpCompositeConstruct %32 %65 %66
+         %68 = OpImageRead %38 %64 %67
+         %69 = OpCompositeExtract %24 %68 0
+         %70 = OpLoad %24 %44
+         %71 = OpIMul %24 %70 %69
+               OpStore %44 %71
+               OpBranch %60
+         %60 = OpLabel
+         %72 = OpLoad %24 %56
+         %74 = OpIAdd %24 %72 %73
+               OpStore %56 %74
+               OpBranch %57
+         %59 = OpLabel
+               OpBranch %50
+         %50 = OpLabel
+         %75 = OpLoad %24 %46
+         %76 = OpIAdd %24 %75 %73
+               OpStore %46 %76
+               OpBranch %47
+         %49 = OpLabel
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+         %83 = OpLoad %24 %44
+         %85 = OpAccessChain %84 %82 %53
+               OpStore %85 %83
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+        %101 = OpVariable %43 Function
+         %92 = OpAccessChain %91 %90 %73
+         %93 = OpLoad %87 %92
+         %95 = OpAccessChain %94 %82 %45
+         %96 = OpLoad %77 %95
+         %97 = OpConvertUToF %86 %96
+         %98 = OpMatrixTimesVector %86 %93 %97
+         %99 = OpConvertFToU %77 %98
+        %100 = OpAccessChain %94 %82 %45
+               OpStore %100 %99
+               OpStore %101 %45
+               OpBranch %102
+        %102 = OpLabel
+               OpLoopMerge %104 %105 None
+               OpBranch %106
+        %106 = OpLabel
+        %107 = OpLoad %24 %101
+        %109 = OpSLessThan %54 %107 %108
+               OpBranchConditional %109 %103 %104
+        %103 = OpLabel
+        %111 = OpAccessChain %110 %82 %73
+        %112 = OpLoad %79 %111
+        %114 = OpAccessChain %113 %90 %53
+        %115 = OpLoad %78 %114
+        %116 = OpVectorTimesScalar %79 %112 %115
+        %117 = OpConvertFToU %13 %116
+        %118 = OpCompositeExtract %10 %117 0
+        %119 = OpCompositeExtract %10 %117 1
+        %120 = OpCompositeExtract %10 %117 2
+        %121 = OpCompositeConstruct %77 %118 %119 %120 %16
+        %122 = OpAccessChain %94 %82 %45
+        %123 = OpLoad %77 %122
+        %124 = OpIAdd %77 %123 %121
+        %125 = OpAccessChain %94 %82 %45
+               OpStore %125 %124
+               OpBranch %105
+        %105 = OpLabel
+        %126 = OpLoad %24 %101
+        %127 = OpIAdd %24 %126 %73
+               OpStore %101 %127
+               OpBranch %102
+        %104 = OpLabel
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+        %129 = OpLoad %25 %128
+        %131 = OpImageRead %38 %129 %130
+        %132 = OpCompositeExtract %24 %131 0
+        %133 = OpConvertSToF %78 %132
+        %134 = OpCompositeConstruct %79 %133 %133 %133
+        %135 = OpAccessChain %110 %82 %73
+               OpStore %135 %134
+               OpReturn
+               OpFunctionEnd
+
+)";
+
+TEST(DiffTest, LargeFunctionsSmallDiffs) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 137
++; Bound: 140
+ ; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %4 "main" %15
+ OpExecutionMode %4 LocalSize 1 1 1
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %6 "f1("
+ OpName %8 "f2("
+ OpName %12 "x"
+ OpName %15 "gl_LocalInvocationID"
+ OpName %20 "y"
+ OpName %27 "image"
+ OpName %44 "sum"
+ OpName %46 "i"
+ OpName %56 "j"
+ OpName %80 "BufferOut"
+ OpMemberName %80 0 "o_uv4"
+ OpMemberName %80 1 "o_v3"
+ OpMemberName %80 2 "o_i"
+ OpName %82 ""
+ OpName %88 "BufferIn"
+ OpMemberName %88 0 "i_u"
+ OpMemberName %88 1 "i_v4"
+ OpMemberName %88 2 "i_f"
+ OpName %90 ""
+ OpName %101 "i"
++OpName %138 "image2"
+ OpDecorate %15 BuiltIn LocalInvocationId
+ OpDecorate %27 DescriptorSet 0
+ OpDecorate %27 Binding 2
+ OpMemberDecorate %80 0 Offset 0
+ OpMemberDecorate %80 1 Offset 16
+ OpMemberDecorate %80 2 Offset 28
+ OpDecorate %80 BufferBlock
+ OpDecorate %82 DescriptorSet 0
+ OpDecorate %82 Binding 1
+ OpMemberDecorate %88 0 Offset 0
+ OpMemberDecorate %88 1 RowMajor
+ OpMemberDecorate %88 1 Offset 16
+ OpMemberDecorate %88 1 MatrixStride 16
+ OpMemberDecorate %88 2 Offset 80
+ OpDecorate %88 Block
+ OpDecorate %90 DescriptorSet 0
+ OpDecorate %90 Binding 0
++OpDecorate %138 DescriptorSet 0
++OpDecorate %138 Binding 3
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %10 = OpTypeInt 32 0
+ %11 = OpTypePointer Function %10
+ %13 = OpTypeVector %10 3
+ %14 = OpTypePointer Input %13
+ %15 = OpVariable %14 Input
+ %16 = OpConstant %10 0
+ %17 = OpTypePointer Input %10
+ %21 = OpConstant %10 1
+ %24 = OpTypeInt 32 1
+ %25 = OpTypeImage %24 2D 0 0 0 2 R32i
+ %26 = OpTypePointer UniformConstant %25
+ %27 = OpVariable %26 UniformConstant
+ %29 = OpTypeVector %10 2
+ %32 = OpTypeVector %24 2
+ %38 = OpTypeVector %24 4
+ %40 = OpConstant %10 2
+ %41 = OpConstant %10 3400
+ %42 = OpConstant %10 264
+ %43 = OpTypePointer Function %24
+ %45 = OpConstant %24 0
+ %53 = OpConstant %24 2
+ %54 = OpTypeBool
+ %73 = OpConstant %24 1
+ %77 = OpTypeVector %10 4
+ %78 = OpTypeFloat 32
+ %79 = OpTypeVector %78 3
+ %80 = OpTypeStruct %77 %79 %24
+ %81 = OpTypePointer Uniform %80
+ %82 = OpVariable %81 Uniform
+ %84 = OpTypePointer Uniform %24
+ %86 = OpTypeVector %78 4
+ %87 = OpTypeMatrix %86 4
+ %88 = OpTypeStruct %10 %87 %78
+ %89 = OpTypePointer Uniform %88
+ %90 = OpVariable %89 Uniform
+ %91 = OpTypePointer Uniform %87
+ %94 = OpTypePointer Uniform %77
+ %108 = OpConstant %24 3
+ %110 = OpTypePointer Uniform %79
+ %113 = OpTypePointer Uniform %78
++%138 = OpVariable %26 UniformConstant
+ %129 = OpConstantComposite %32 %45 %45
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %135 = OpFunctionCall %2 %6
+ %136 = OpFunctionCall %2 %8
+ OpReturn
+ OpFunctionEnd
+ %6 = OpFunction %2 None %3
+ %7 = OpLabel
+ %12 = OpVariable %11 Function
+ %20 = OpVariable %11 Function
+ %44 = OpVariable %43 Function
+ %46 = OpVariable %43 Function
+ %56 = OpVariable %43 Function
+ %18 = OpAccessChain %17 %15 %16
+ %19 = OpLoad %10 %18
+ OpStore %12 %19
+ %22 = OpAccessChain %17 %15 %21
+ %23 = OpLoad %10 %22
+ OpStore %20 %23
+ %28 = OpLoad %25 %27
+ %30 = OpLoad %13 %15
+ %31 = OpVectorShuffle %29 %30 %30 0 1
+ %33 = OpBitcast %32 %31
+ %34 = OpLoad %10 %12
+ %35 = OpLoad %10 %20
+ %36 = OpIAdd %10 %34 %35
+ %37 = OpBitcast %24 %36
+ %39 = OpCompositeConstruct %38 %37 %37 %37 %37
+ OpImageWrite %28 %33 %39
+ OpMemoryBarrier %40 %41
+ OpControlBarrier %40 %40 %42
+ OpStore %44 %45
+ OpStore %46 %45
+ OpBranch %47
+ %47 = OpLabel
+ OpLoopMerge %49 %50 None
+ OpBranch %51
+ %51 = OpLabel
+ %52 = OpLoad %24 %46
+ %55 = OpSLessThan %54 %52 %53
+ OpBranchConditional %55 %48 %49
+ %48 = OpLabel
+ OpStore %56 %45
+ OpBranch %57
+ %57 = OpLabel
+ OpLoopMerge %59 %60 None
+ OpBranch %61
+ %61 = OpLabel
+ %62 = OpLoad %24 %56
+ %63 = OpSLessThan %54 %62 %53
+ OpBranchConditional %63 %58 %59
+ %58 = OpLabel
+ %64 = OpLoad %25 %27
+ %65 = OpLoad %24 %46
+ %66 = OpLoad %24 %56
+ %67 = OpCompositeConstruct %32 %65 %66
+ %68 = OpImageRead %38 %64 %67
+ %69 = OpCompositeExtract %24 %68 0
+ %70 = OpLoad %24 %44
+-%71 = OpIAdd %24 %70 %69
++%137 = OpIMul %24 %70 %69
+-OpStore %44 %71
++OpStore %44 %137
+ OpBranch %60
+ %60 = OpLabel
+ %72 = OpLoad %24 %56
+ %74 = OpIAdd %24 %72 %73
+ OpStore %56 %74
+ OpBranch %57
+ %59 = OpLabel
+ OpBranch %50
+ %50 = OpLabel
+ %75 = OpLoad %24 %46
+ %76 = OpIAdd %24 %75 %73
+ OpStore %46 %76
+ OpBranch %47
+ %49 = OpLabel
+ OpMemoryBarrier %40 %41
+ OpControlBarrier %40 %40 %42
+ %83 = OpLoad %24 %44
+ %85 = OpAccessChain %84 %82 %53
+ OpStore %85 %83
+ OpReturn
+ OpFunctionEnd
+ %8 = OpFunction %2 None %3
+ %9 = OpLabel
+ %101 = OpVariable %43 Function
+ %92 = OpAccessChain %91 %90 %73
+ %93 = OpLoad %87 %92
+ %95 = OpAccessChain %94 %82 %45
+ %96 = OpLoad %77 %95
+ %97 = OpConvertUToF %86 %96
+ %98 = OpMatrixTimesVector %86 %93 %97
+ %99 = OpConvertFToU %77 %98
+ %100 = OpAccessChain %94 %82 %45
+ OpStore %100 %99
+ OpStore %101 %45
+ OpBranch %102
+ %102 = OpLabel
+ OpLoopMerge %104 %105 None
+ OpBranch %106
+ %106 = OpLabel
+ %107 = OpLoad %24 %101
+ %109 = OpSLessThan %54 %107 %108
+ OpBranchConditional %109 %103 %104
+ %103 = OpLabel
+ %111 = OpAccessChain %110 %82 %73
+ %112 = OpLoad %79 %111
+ %114 = OpAccessChain %113 %90 %53
+ %115 = OpLoad %78 %114
+ %116 = OpVectorTimesScalar %79 %112 %115
+ %117 = OpConvertFToU %13 %116
+ %118 = OpCompositeExtract %10 %117 0
+ %119 = OpCompositeExtract %10 %117 1
+ %120 = OpCompositeExtract %10 %117 2
+ %121 = OpCompositeConstruct %77 %118 %119 %120 %16
+ %122 = OpAccessChain %94 %82 %45
+ %123 = OpLoad %77 %122
+ %124 = OpIAdd %77 %123 %121
+ %125 = OpAccessChain %94 %82 %45
+ OpStore %125 %124
+ OpBranch %105
+ %105 = OpLabel
+ %126 = OpLoad %24 %101
+ %127 = OpIAdd %24 %126 %73
+ OpStore %101 %127
+ OpBranch %102
+ %104 = OpLabel
+ OpMemoryBarrier %40 %41
+ OpControlBarrier %40 %40 %42
+-%128 = OpLoad %25 %27
++%139 = OpLoad %25 %138
+-%130 = OpImageRead %38 %128 %129
++%130 = OpImageRead %38 %139 %129
+ %131 = OpCompositeExtract %24 %130 0
+ %132 = OpConvertSToF %78 %131
+ %133 = OpCompositeConstruct %79 %132 %132 %132
+ %134 = OpAccessChain %110 %82 %73
+ OpStore %134 %133
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, LargeFunctionsSmallDiffsNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main" %15
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpDecorate %15 BuiltIn LocalInvocationId
+               OpDecorate %27 DescriptorSet 0
+               OpDecorate %27 Binding 2
+               OpMemberDecorate %80 0 Offset 0
+               OpMemberDecorate %80 1 Offset 16
+               OpMemberDecorate %80 2 Offset 28
+               OpDecorate %80 BufferBlock
+               OpDecorate %82 DescriptorSet 0
+               OpDecorate %82 Binding 1
+               OpMemberDecorate %88 0 Offset 0
+               OpMemberDecorate %88 1 RowMajor
+               OpMemberDecorate %88 1 Offset 16
+               OpMemberDecorate %88 1 MatrixStride 16
+               OpMemberDecorate %88 2 Offset 80
+               OpDecorate %88 Block
+               OpDecorate %90 DescriptorSet 0
+               OpDecorate %90 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeInt 32 0
+         %11 = OpTypePointer Function %10
+         %13 = OpTypeVector %10 3
+         %14 = OpTypePointer Input %13
+         %15 = OpVariable %14 Input
+         %16 = OpConstant %10 0
+         %17 = OpTypePointer Input %10
+         %21 = OpConstant %10 1
+         %24 = OpTypeInt 32 1
+         %25 = OpTypeImage %24 2D 0 0 0 2 R32i
+         %26 = OpTypePointer UniformConstant %25
+         %27 = OpVariable %26 UniformConstant
+         %29 = OpTypeVector %10 2
+         %32 = OpTypeVector %24 2
+         %38 = OpTypeVector %24 4
+         %40 = OpConstant %10 2
+         %41 = OpConstant %10 3400
+         %42 = OpConstant %10 264
+         %43 = OpTypePointer Function %24
+         %45 = OpConstant %24 0
+         %53 = OpConstant %24 2
+         %54 = OpTypeBool
+         %73 = OpConstant %24 1
+         %77 = OpTypeVector %10 4
+         %78 = OpTypeFloat 32
+         %79 = OpTypeVector %78 3
+         %80 = OpTypeStruct %77 %79 %24
+         %81 = OpTypePointer Uniform %80
+         %82 = OpVariable %81 Uniform
+         %84 = OpTypePointer Uniform %24
+         %86 = OpTypeVector %78 4
+         %87 = OpTypeMatrix %86 4
+         %88 = OpTypeStruct %10 %87 %78
+         %89 = OpTypePointer Uniform %88
+         %90 = OpVariable %89 Uniform
+         %91 = OpTypePointer Uniform %87
+         %94 = OpTypePointer Uniform %77
+        %108 = OpConstant %24 3
+        %110 = OpTypePointer Uniform %79
+        %113 = OpTypePointer Uniform %78
+        %129 = OpConstantComposite %32 %45 %45
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+        %135 = OpFunctionCall %2 %6
+        %136 = OpFunctionCall %2 %8
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %12 = OpVariable %11 Function
+         %20 = OpVariable %11 Function
+         %44 = OpVariable %43 Function
+         %46 = OpVariable %43 Function
+         %56 = OpVariable %43 Function
+         %18 = OpAccessChain %17 %15 %16
+         %19 = OpLoad %10 %18
+               OpStore %12 %19
+         %22 = OpAccessChain %17 %15 %21
+         %23 = OpLoad %10 %22
+               OpStore %20 %23
+         %28 = OpLoad %25 %27
+         %30 = OpLoad %13 %15
+         %31 = OpVectorShuffle %29 %30 %30 0 1
+         %33 = OpBitcast %32 %31
+         %34 = OpLoad %10 %12
+         %35 = OpLoad %10 %20
+         %36 = OpIAdd %10 %34 %35
+         %37 = OpBitcast %24 %36
+         %39 = OpCompositeConstruct %38 %37 %37 %37 %37
+               OpImageWrite %28 %33 %39
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+               OpStore %44 %45
+               OpStore %46 %45
+               OpBranch %47
+         %47 = OpLabel
+               OpLoopMerge %49 %50 None
+               OpBranch %51
+         %51 = OpLabel
+         %52 = OpLoad %24 %46
+         %55 = OpSLessThan %54 %52 %53
+               OpBranchConditional %55 %48 %49
+         %48 = OpLabel
+               OpStore %56 %45
+               OpBranch %57
+         %57 = OpLabel
+               OpLoopMerge %59 %60 None
+               OpBranch %61
+         %61 = OpLabel
+         %62 = OpLoad %24 %56
+         %63 = OpSLessThan %54 %62 %53
+               OpBranchConditional %63 %58 %59
+         %58 = OpLabel
+         %64 = OpLoad %25 %27
+         %65 = OpLoad %24 %46
+         %66 = OpLoad %24 %56
+         %67 = OpCompositeConstruct %32 %65 %66
+         %68 = OpImageRead %38 %64 %67
+         %69 = OpCompositeExtract %24 %68 0
+         %70 = OpLoad %24 %44
+         %71 = OpIAdd %24 %70 %69
+               OpStore %44 %71
+               OpBranch %60
+         %60 = OpLabel
+         %72 = OpLoad %24 %56
+         %74 = OpIAdd %24 %72 %73
+               OpStore %56 %74
+               OpBranch %57
+         %59 = OpLabel
+               OpBranch %50
+         %50 = OpLabel
+         %75 = OpLoad %24 %46
+         %76 = OpIAdd %24 %75 %73
+               OpStore %46 %76
+               OpBranch %47
+         %49 = OpLabel
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+         %83 = OpLoad %24 %44
+         %85 = OpAccessChain %84 %82 %53
+               OpStore %85 %83
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+        %101 = OpVariable %43 Function
+         %92 = OpAccessChain %91 %90 %73
+         %93 = OpLoad %87 %92
+         %95 = OpAccessChain %94 %82 %45
+         %96 = OpLoad %77 %95
+         %97 = OpConvertUToF %86 %96
+         %98 = OpMatrixTimesVector %86 %93 %97
+         %99 = OpConvertFToU %77 %98
+        %100 = OpAccessChain %94 %82 %45
+               OpStore %100 %99
+               OpStore %101 %45
+               OpBranch %102
+        %102 = OpLabel
+               OpLoopMerge %104 %105 None
+               OpBranch %106
+        %106 = OpLabel
+        %107 = OpLoad %24 %101
+        %109 = OpSLessThan %54 %107 %108
+               OpBranchConditional %109 %103 %104
+        %103 = OpLabel
+        %111 = OpAccessChain %110 %82 %73
+        %112 = OpLoad %79 %111
+        %114 = OpAccessChain %113 %90 %53
+        %115 = OpLoad %78 %114
+        %116 = OpVectorTimesScalar %79 %112 %115
+        %117 = OpConvertFToU %13 %116
+        %118 = OpCompositeExtract %10 %117 0
+        %119 = OpCompositeExtract %10 %117 1
+        %120 = OpCompositeExtract %10 %117 2
+        %121 = OpCompositeConstruct %77 %118 %119 %120 %16
+        %122 = OpAccessChain %94 %82 %45
+        %123 = OpLoad %77 %122
+        %124 = OpIAdd %77 %123 %121
+        %125 = OpAccessChain %94 %82 %45
+               OpStore %125 %124
+               OpBranch %105
+        %105 = OpLabel
+        %126 = OpLoad %24 %101
+        %127 = OpIAdd %24 %126 %73
+               OpStore %101 %127
+               OpBranch %102
+        %104 = OpLabel
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+        %128 = OpLoad %25 %27
+        %130 = OpImageRead %38 %128 %129
+        %131 = OpCompositeExtract %24 %130 0
+        %132 = OpConvertSToF %78 %131
+        %133 = OpCompositeConstruct %79 %132 %132 %132
+        %134 = OpAccessChain %110 %82 %73
+               OpStore %134 %133
+               OpReturn
+               OpFunctionEnd
+
+)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main" %15
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpDecorate %15 BuiltIn LocalInvocationId
+               OpDecorate %27 DescriptorSet 0
+               OpDecorate %27 Binding 2
+               OpMemberDecorate %80 0 Offset 0
+               OpMemberDecorate %80 1 Offset 16
+               OpMemberDecorate %80 2 Offset 28
+               OpDecorate %80 BufferBlock
+               OpDecorate %82 DescriptorSet 0
+               OpDecorate %82 Binding 1
+               OpMemberDecorate %88 0 Offset 0
+               OpMemberDecorate %88 1 RowMajor
+               OpMemberDecorate %88 1 Offset 16
+               OpMemberDecorate %88 1 MatrixStride 16
+               OpMemberDecorate %88 2 Offset 80
+               OpDecorate %88 Block
+               OpDecorate %90 DescriptorSet 0
+               OpDecorate %90 Binding 0
+               OpDecorate %128 DescriptorSet 0
+               OpDecorate %128 Binding 3
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeInt 32 0
+         %11 = OpTypePointer Function %10
+         %13 = OpTypeVector %10 3
+         %14 = OpTypePointer Input %13
+         %15 = OpVariable %14 Input
+         %16 = OpConstant %10 0
+         %17 = OpTypePointer Input %10
+         %21 = OpConstant %10 1
+         %24 = OpTypeInt 32 1
+         %25 = OpTypeImage %24 2D 0 0 0 2 R32i
+         %26 = OpTypePointer UniformConstant %25
+         %27 = OpVariable %26 UniformConstant
+         %29 = OpTypeVector %10 2
+         %32 = OpTypeVector %24 2
+         %38 = OpTypeVector %24 4
+         %40 = OpConstant %10 2
+         %41 = OpConstant %10 3400
+         %42 = OpConstant %10 264
+         %43 = OpTypePointer Function %24
+         %45 = OpConstant %24 0
+         %53 = OpConstant %24 2
+         %54 = OpTypeBool
+         %73 = OpConstant %24 1
+         %77 = OpTypeVector %10 4
+         %78 = OpTypeFloat 32
+         %79 = OpTypeVector %78 3
+         %80 = OpTypeStruct %77 %79 %24
+         %81 = OpTypePointer Uniform %80
+         %82 = OpVariable %81 Uniform
+         %84 = OpTypePointer Uniform %24
+         %86 = OpTypeVector %78 4
+         %87 = OpTypeMatrix %86 4
+         %88 = OpTypeStruct %10 %87 %78
+         %89 = OpTypePointer Uniform %88
+         %90 = OpVariable %89 Uniform
+         %91 = OpTypePointer Uniform %87
+         %94 = OpTypePointer Uniform %77
+        %108 = OpConstant %24 3
+        %110 = OpTypePointer Uniform %79
+        %113 = OpTypePointer Uniform %78
+        %128 = OpVariable %26 UniformConstant
+        %130 = OpConstantComposite %32 %45 %45
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+        %136 = OpFunctionCall %2 %6
+        %137 = OpFunctionCall %2 %8
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %12 = OpVariable %11 Function
+         %20 = OpVariable %11 Function
+         %44 = OpVariable %43 Function
+         %46 = OpVariable %43 Function
+         %56 = OpVariable %43 Function
+         %18 = OpAccessChain %17 %15 %16
+         %19 = OpLoad %10 %18
+               OpStore %12 %19
+         %22 = OpAccessChain %17 %15 %21
+         %23 = OpLoad %10 %22
+               OpStore %20 %23
+         %28 = OpLoad %25 %27
+         %30 = OpLoad %13 %15
+         %31 = OpVectorShuffle %29 %30 %30 0 1
+         %33 = OpBitcast %32 %31
+         %34 = OpLoad %10 %12
+         %35 = OpLoad %10 %20
+         %36 = OpIAdd %10 %34 %35
+         %37 = OpBitcast %24 %36
+         %39 = OpCompositeConstruct %38 %37 %37 %37 %37
+               OpImageWrite %28 %33 %39
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+               OpStore %44 %45
+               OpStore %46 %45
+               OpBranch %47
+         %47 = OpLabel
+               OpLoopMerge %49 %50 None
+               OpBranch %51
+         %51 = OpLabel
+         %52 = OpLoad %24 %46
+         %55 = OpSLessThan %54 %52 %53
+               OpBranchConditional %55 %48 %49
+         %48 = OpLabel
+               OpStore %56 %45
+               OpBranch %57
+         %57 = OpLabel
+               OpLoopMerge %59 %60 None
+               OpBranch %61
+         %61 = OpLabel
+         %62 = OpLoad %24 %56
+         %63 = OpSLessThan %54 %62 %53
+               OpBranchConditional %63 %58 %59
+         %58 = OpLabel
+         %64 = OpLoad %25 %27
+         %65 = OpLoad %24 %46
+         %66 = OpLoad %24 %56
+         %67 = OpCompositeConstruct %32 %65 %66
+         %68 = OpImageRead %38 %64 %67
+         %69 = OpCompositeExtract %24 %68 0
+         %70 = OpLoad %24 %44
+         %71 = OpIMul %24 %70 %69
+               OpStore %44 %71
+               OpBranch %60
+         %60 = OpLabel
+         %72 = OpLoad %24 %56
+         %74 = OpIAdd %24 %72 %73
+               OpStore %56 %74
+               OpBranch %57
+         %59 = OpLabel
+               OpBranch %50
+         %50 = OpLabel
+         %75 = OpLoad %24 %46
+         %76 = OpIAdd %24 %75 %73
+               OpStore %46 %76
+               OpBranch %47
+         %49 = OpLabel
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+         %83 = OpLoad %24 %44
+         %85 = OpAccessChain %84 %82 %53
+               OpStore %85 %83
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+        %101 = OpVariable %43 Function
+         %92 = OpAccessChain %91 %90 %73
+         %93 = OpLoad %87 %92
+         %95 = OpAccessChain %94 %82 %45
+         %96 = OpLoad %77 %95
+         %97 = OpConvertUToF %86 %96
+         %98 = OpMatrixTimesVector %86 %93 %97
+         %99 = OpConvertFToU %77 %98
+        %100 = OpAccessChain %94 %82 %45
+               OpStore %100 %99
+               OpStore %101 %45
+               OpBranch %102
+        %102 = OpLabel
+               OpLoopMerge %104 %105 None
+               OpBranch %106
+        %106 = OpLabel
+        %107 = OpLoad %24 %101
+        %109 = OpSLessThan %54 %107 %108
+               OpBranchConditional %109 %103 %104
+        %103 = OpLabel
+        %111 = OpAccessChain %110 %82 %73
+        %112 = OpLoad %79 %111
+        %114 = OpAccessChain %113 %90 %53
+        %115 = OpLoad %78 %114
+        %116 = OpVectorTimesScalar %79 %112 %115
+        %117 = OpConvertFToU %13 %116
+        %118 = OpCompositeExtract %10 %117 0
+        %119 = OpCompositeExtract %10 %117 1
+        %120 = OpCompositeExtract %10 %117 2
+        %121 = OpCompositeConstruct %77 %118 %119 %120 %16
+        %122 = OpAccessChain %94 %82 %45
+        %123 = OpLoad %77 %122
+        %124 = OpIAdd %77 %123 %121
+        %125 = OpAccessChain %94 %82 %45
+               OpStore %125 %124
+               OpBranch %105
+        %105 = OpLabel
+        %126 = OpLoad %24 %101
+        %127 = OpIAdd %24 %126 %73
+               OpStore %101 %127
+               OpBranch %102
+        %104 = OpLabel
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+        %129 = OpLoad %25 %128
+        %131 = OpImageRead %38 %129 %130
+        %132 = OpCompositeExtract %24 %131 0
+        %133 = OpConvertSToF %78 %132
+        %134 = OpCompositeConstruct %79 %133 %133 %133
+        %135 = OpAccessChain %110 %82 %73
+               OpStore %135 %134
+               OpReturn
+               OpFunctionEnd
+
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 137
++; Bound: 140
+ ; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %4 "main" %15
+ OpExecutionMode %4 LocalSize 1 1 1
+ OpSource ESSL 310
+ OpDecorate %15 BuiltIn LocalInvocationId
+ OpDecorate %27 DescriptorSet 0
+ OpDecorate %27 Binding 2
+ OpMemberDecorate %80 0 Offset 0
+ OpMemberDecorate %80 1 Offset 16
+ OpMemberDecorate %80 2 Offset 28
+ OpDecorate %80 BufferBlock
+ OpDecorate %82 DescriptorSet 0
+ OpDecorate %82 Binding 1
+ OpMemberDecorate %88 0 Offset 0
+ OpMemberDecorate %88 1 RowMajor
+ OpMemberDecorate %88 1 Offset 16
+ OpMemberDecorate %88 1 MatrixStride 16
+ OpMemberDecorate %88 2 Offset 80
+ OpDecorate %88 Block
+ OpDecorate %90 DescriptorSet 0
+ OpDecorate %90 Binding 0
++OpDecorate %138 DescriptorSet 0
++OpDecorate %138 Binding 3
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %10 = OpTypeInt 32 0
+ %11 = OpTypePointer Function %10
+ %13 = OpTypeVector %10 3
+ %14 = OpTypePointer Input %13
+ %15 = OpVariable %14 Input
+ %16 = OpConstant %10 0
+ %17 = OpTypePointer Input %10
+ %21 = OpConstant %10 1
+ %24 = OpTypeInt 32 1
+ %25 = OpTypeImage %24 2D 0 0 0 2 R32i
+ %26 = OpTypePointer UniformConstant %25
+ %27 = OpVariable %26 UniformConstant
+ %29 = OpTypeVector %10 2
+ %32 = OpTypeVector %24 2
+ %38 = OpTypeVector %24 4
+ %40 = OpConstant %10 2
+ %41 = OpConstant %10 3400
+ %42 = OpConstant %10 264
+ %43 = OpTypePointer Function %24
+ %45 = OpConstant %24 0
+ %53 = OpConstant %24 2
+ %54 = OpTypeBool
+ %73 = OpConstant %24 1
+ %77 = OpTypeVector %10 4
+ %78 = OpTypeFloat 32
+ %79 = OpTypeVector %78 3
+ %80 = OpTypeStruct %77 %79 %24
+ %81 = OpTypePointer Uniform %80
+ %82 = OpVariable %81 Uniform
+ %84 = OpTypePointer Uniform %24
+ %86 = OpTypeVector %78 4
+ %87 = OpTypeMatrix %86 4
+ %88 = OpTypeStruct %10 %87 %78
+ %89 = OpTypePointer Uniform %88
+ %90 = OpVariable %89 Uniform
+ %91 = OpTypePointer Uniform %87
+ %94 = OpTypePointer Uniform %77
+ %108 = OpConstant %24 3
+ %110 = OpTypePointer Uniform %79
+ %113 = OpTypePointer Uniform %78
++%138 = OpVariable %26 UniformConstant
+ %129 = OpConstantComposite %32 %45 %45
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %135 = OpFunctionCall %2 %6
+ %136 = OpFunctionCall %2 %8
+ OpReturn
+ OpFunctionEnd
+ %6 = OpFunction %2 None %3
+ %7 = OpLabel
+ %12 = OpVariable %11 Function
+ %20 = OpVariable %11 Function
+ %44 = OpVariable %43 Function
+ %46 = OpVariable %43 Function
+ %56 = OpVariable %43 Function
+ %18 = OpAccessChain %17 %15 %16
+ %19 = OpLoad %10 %18
+ OpStore %12 %19
+ %22 = OpAccessChain %17 %15 %21
+ %23 = OpLoad %10 %22
+ OpStore %20 %23
+ %28 = OpLoad %25 %27
+ %30 = OpLoad %13 %15
+ %31 = OpVectorShuffle %29 %30 %30 0 1
+ %33 = OpBitcast %32 %31
+ %34 = OpLoad %10 %12
+ %35 = OpLoad %10 %20
+ %36 = OpIAdd %10 %34 %35
+ %37 = OpBitcast %24 %36
+ %39 = OpCompositeConstruct %38 %37 %37 %37 %37
+ OpImageWrite %28 %33 %39
+ OpMemoryBarrier %40 %41
+ OpControlBarrier %40 %40 %42
+ OpStore %44 %45
+ OpStore %46 %45
+ OpBranch %47
+ %47 = OpLabel
+ OpLoopMerge %49 %50 None
+ OpBranch %51
+ %51 = OpLabel
+ %52 = OpLoad %24 %46
+ %55 = OpSLessThan %54 %52 %53
+ OpBranchConditional %55 %48 %49
+ %48 = OpLabel
+ OpStore %56 %45
+ OpBranch %57
+ %57 = OpLabel
+ OpLoopMerge %59 %60 None
+ OpBranch %61
+ %61 = OpLabel
+ %62 = OpLoad %24 %56
+ %63 = OpSLessThan %54 %62 %53
+ OpBranchConditional %63 %58 %59
+ %58 = OpLabel
+ %64 = OpLoad %25 %27
+ %65 = OpLoad %24 %46
+ %66 = OpLoad %24 %56
+ %67 = OpCompositeConstruct %32 %65 %66
+ %68 = OpImageRead %38 %64 %67
+ %69 = OpCompositeExtract %24 %68 0
+ %70 = OpLoad %24 %44
+-%71 = OpIAdd %24 %70 %69
++%137 = OpIMul %24 %70 %69
+-OpStore %44 %71
++OpStore %44 %137
+ OpBranch %60
+ %60 = OpLabel
+ %72 = OpLoad %24 %56
+ %74 = OpIAdd %24 %72 %73
+ OpStore %56 %74
+ OpBranch %57
+ %59 = OpLabel
+ OpBranch %50
+ %50 = OpLabel
+ %75 = OpLoad %24 %46
+ %76 = OpIAdd %24 %75 %73
+ OpStore %46 %76
+ OpBranch %47
+ %49 = OpLabel
+ OpMemoryBarrier %40 %41
+ OpControlBarrier %40 %40 %42
+ %83 = OpLoad %24 %44
+ %85 = OpAccessChain %84 %82 %53
+ OpStore %85 %83
+ OpReturn
+ OpFunctionEnd
+ %8 = OpFunction %2 None %3
+ %9 = OpLabel
+ %101 = OpVariable %43 Function
+ %92 = OpAccessChain %91 %90 %73
+ %93 = OpLoad %87 %92
+ %95 = OpAccessChain %94 %82 %45
+ %96 = OpLoad %77 %95
+ %97 = OpConvertUToF %86 %96
+ %98 = OpMatrixTimesVector %86 %93 %97
+ %99 = OpConvertFToU %77 %98
+ %100 = OpAccessChain %94 %82 %45
+ OpStore %100 %99
+ OpStore %101 %45
+ OpBranch %102
+ %102 = OpLabel
+ OpLoopMerge %104 %105 None
+ OpBranch %106
+ %106 = OpLabel
+ %107 = OpLoad %24 %101
+ %109 = OpSLessThan %54 %107 %108
+ OpBranchConditional %109 %103 %104
+ %103 = OpLabel
+ %111 = OpAccessChain %110 %82 %73
+ %112 = OpLoad %79 %111
+ %114 = OpAccessChain %113 %90 %53
+ %115 = OpLoad %78 %114
+ %116 = OpVectorTimesScalar %79 %112 %115
+ %117 = OpConvertFToU %13 %116
+ %118 = OpCompositeExtract %10 %117 0
+ %119 = OpCompositeExtract %10 %117 1
+ %120 = OpCompositeExtract %10 %117 2
+ %121 = OpCompositeConstruct %77 %118 %119 %120 %16
+ %122 = OpAccessChain %94 %82 %45
+ %123 = OpLoad %77 %122
+ %124 = OpIAdd %77 %123 %121
+ %125 = OpAccessChain %94 %82 %45
+ OpStore %125 %124
+ OpBranch %105
+ %105 = OpLabel
+ %126 = OpLoad %24 %101
+ %127 = OpIAdd %24 %126 %73
+ OpStore %101 %127
+ OpBranch %102
+ %104 = OpLabel
+ OpMemoryBarrier %40 %41
+ OpControlBarrier %40 %40 %42
+-%128 = OpLoad %25 %27
++%139 = OpLoad %25 %138
+-%130 = OpImageRead %38 %128 %129
++%130 = OpImageRead %38 %139 %129
+ %131 = OpCompositeExtract %24 %130 0
+ %132 = OpConvertSToF %78 %131
+ %133 = OpCompositeConstruct %79 %132 %132 %132
+ %134 = OpAccessChain %110 %82 %73
+ OpStore %134 %133
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/large_functions_small_diffs_dst.spvasm b/test/diff/diff_files/large_functions_small_diffs_dst.spvasm
new file mode 100644
index 0000000..f788e0b
--- /dev/null
+++ b/test/diff/diff_files/large_functions_small_diffs_dst.spvasm
@@ -0,0 +1,229 @@
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main" %15
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "f1("
+               OpName %8 "f2("
+               OpName %12 "x"
+               OpName %15 "gl_LocalInvocationID"
+               OpName %20 "y"
+               OpName %27 "image"
+               OpName %44 "sum"
+               OpName %46 "i"
+               OpName %56 "j"
+               OpName %80 "BufferOut"
+               OpMemberName %80 0 "o_uv4"
+               OpMemberName %80 1 "o_v3"
+               OpMemberName %80 2 "o_i"
+               OpName %82 ""
+               OpName %88 "BufferIn"
+               OpMemberName %88 0 "i_u"
+               OpMemberName %88 1 "i_v4"
+               OpMemberName %88 2 "i_f"
+               OpName %90 ""
+               OpName %101 "i"
+               OpName %128 "image2"
+               OpDecorate %15 BuiltIn LocalInvocationId
+               OpDecorate %27 DescriptorSet 0
+               OpDecorate %27 Binding 2
+               OpMemberDecorate %80 0 Offset 0
+               OpMemberDecorate %80 1 Offset 16
+               OpMemberDecorate %80 2 Offset 28
+               OpDecorate %80 BufferBlock
+               OpDecorate %82 DescriptorSet 0
+               OpDecorate %82 Binding 1
+               OpMemberDecorate %88 0 Offset 0
+               OpMemberDecorate %88 1 RowMajor
+               OpMemberDecorate %88 1 Offset 16
+               OpMemberDecorate %88 1 MatrixStride 16
+               OpMemberDecorate %88 2 Offset 80
+               OpDecorate %88 Block
+               OpDecorate %90 DescriptorSet 0
+               OpDecorate %90 Binding 0
+               OpDecorate %128 DescriptorSet 0
+               OpDecorate %128 Binding 3
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeInt 32 0
+         %11 = OpTypePointer Function %10
+         %13 = OpTypeVector %10 3
+         %14 = OpTypePointer Input %13
+         %15 = OpVariable %14 Input
+         %16 = OpConstant %10 0
+         %17 = OpTypePointer Input %10
+         %21 = OpConstant %10 1
+         %24 = OpTypeInt 32 1
+         %25 = OpTypeImage %24 2D 0 0 0 2 R32i
+         %26 = OpTypePointer UniformConstant %25
+         %27 = OpVariable %26 UniformConstant
+         %29 = OpTypeVector %10 2
+         %32 = OpTypeVector %24 2
+         %38 = OpTypeVector %24 4
+         %40 = OpConstant %10 2
+         %41 = OpConstant %10 3400
+         %42 = OpConstant %10 264
+         %43 = OpTypePointer Function %24
+         %45 = OpConstant %24 0
+         %53 = OpConstant %24 2
+         %54 = OpTypeBool
+         %73 = OpConstant %24 1
+         %77 = OpTypeVector %10 4
+         %78 = OpTypeFloat 32
+         %79 = OpTypeVector %78 3
+         %80 = OpTypeStruct %77 %79 %24
+         %81 = OpTypePointer Uniform %80
+         %82 = OpVariable %81 Uniform
+         %84 = OpTypePointer Uniform %24
+         %86 = OpTypeVector %78 4
+         %87 = OpTypeMatrix %86 4
+         %88 = OpTypeStruct %10 %87 %78
+         %89 = OpTypePointer Uniform %88
+         %90 = OpVariable %89 Uniform
+         %91 = OpTypePointer Uniform %87
+         %94 = OpTypePointer Uniform %77
+        %108 = OpConstant %24 3
+        %110 = OpTypePointer Uniform %79
+        %113 = OpTypePointer Uniform %78
+        %128 = OpVariable %26 UniformConstant
+        %130 = OpConstantComposite %32 %45 %45
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+        %136 = OpFunctionCall %2 %6
+        %137 = OpFunctionCall %2 %8
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %12 = OpVariable %11 Function
+         %20 = OpVariable %11 Function
+         %44 = OpVariable %43 Function
+         %46 = OpVariable %43 Function
+         %56 = OpVariable %43 Function
+         %18 = OpAccessChain %17 %15 %16
+         %19 = OpLoad %10 %18
+               OpStore %12 %19
+         %22 = OpAccessChain %17 %15 %21
+         %23 = OpLoad %10 %22
+               OpStore %20 %23
+         %28 = OpLoad %25 %27
+         %30 = OpLoad %13 %15
+         %31 = OpVectorShuffle %29 %30 %30 0 1
+         %33 = OpBitcast %32 %31
+         %34 = OpLoad %10 %12
+         %35 = OpLoad %10 %20
+         %36 = OpIAdd %10 %34 %35
+         %37 = OpBitcast %24 %36
+         %39 = OpCompositeConstruct %38 %37 %37 %37 %37
+               OpImageWrite %28 %33 %39
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+               OpStore %44 %45
+               OpStore %46 %45
+               OpBranch %47
+         %47 = OpLabel
+               OpLoopMerge %49 %50 None
+               OpBranch %51
+         %51 = OpLabel
+         %52 = OpLoad %24 %46
+         %55 = OpSLessThan %54 %52 %53
+               OpBranchConditional %55 %48 %49
+         %48 = OpLabel
+               OpStore %56 %45
+               OpBranch %57
+         %57 = OpLabel
+               OpLoopMerge %59 %60 None
+               OpBranch %61
+         %61 = OpLabel
+         %62 = OpLoad %24 %56
+         %63 = OpSLessThan %54 %62 %53
+               OpBranchConditional %63 %58 %59
+         %58 = OpLabel
+         %64 = OpLoad %25 %27
+         %65 = OpLoad %24 %46
+         %66 = OpLoad %24 %56
+         %67 = OpCompositeConstruct %32 %65 %66
+         %68 = OpImageRead %38 %64 %67
+         %69 = OpCompositeExtract %24 %68 0
+         %70 = OpLoad %24 %44
+         %71 = OpIMul %24 %70 %69
+               OpStore %44 %71
+               OpBranch %60
+         %60 = OpLabel
+         %72 = OpLoad %24 %56
+         %74 = OpIAdd %24 %72 %73
+               OpStore %56 %74
+               OpBranch %57
+         %59 = OpLabel
+               OpBranch %50
+         %50 = OpLabel
+         %75 = OpLoad %24 %46
+         %76 = OpIAdd %24 %75 %73
+               OpStore %46 %76
+               OpBranch %47
+         %49 = OpLabel
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+         %83 = OpLoad %24 %44
+         %85 = OpAccessChain %84 %82 %53
+               OpStore %85 %83
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+        %101 = OpVariable %43 Function
+         %92 = OpAccessChain %91 %90 %73
+         %93 = OpLoad %87 %92
+         %95 = OpAccessChain %94 %82 %45
+         %96 = OpLoad %77 %95
+         %97 = OpConvertUToF %86 %96
+         %98 = OpMatrixTimesVector %86 %93 %97
+         %99 = OpConvertFToU %77 %98
+        %100 = OpAccessChain %94 %82 %45
+               OpStore %100 %99
+               OpStore %101 %45
+               OpBranch %102
+        %102 = OpLabel
+               OpLoopMerge %104 %105 None
+               OpBranch %106
+        %106 = OpLabel
+        %107 = OpLoad %24 %101
+        %109 = OpSLessThan %54 %107 %108
+               OpBranchConditional %109 %103 %104
+        %103 = OpLabel
+        %111 = OpAccessChain %110 %82 %73
+        %112 = OpLoad %79 %111
+        %114 = OpAccessChain %113 %90 %53
+        %115 = OpLoad %78 %114
+        %116 = OpVectorTimesScalar %79 %112 %115
+        %117 = OpConvertFToU %13 %116
+        %118 = OpCompositeExtract %10 %117 0
+        %119 = OpCompositeExtract %10 %117 1
+        %120 = OpCompositeExtract %10 %117 2
+        %121 = OpCompositeConstruct %77 %118 %119 %120 %16
+        %122 = OpAccessChain %94 %82 %45
+        %123 = OpLoad %77 %122
+        %124 = OpIAdd %77 %123 %121
+        %125 = OpAccessChain %94 %82 %45
+               OpStore %125 %124
+               OpBranch %105
+        %105 = OpLabel
+        %126 = OpLoad %24 %101
+        %127 = OpIAdd %24 %126 %73
+               OpStore %101 %127
+               OpBranch %102
+        %104 = OpLabel
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+        %129 = OpLoad %25 %128
+        %131 = OpImageRead %38 %129 %130
+        %132 = OpCompositeExtract %24 %131 0
+        %133 = OpConvertSToF %78 %132
+        %134 = OpCompositeConstruct %79 %133 %133 %133
+        %135 = OpAccessChain %110 %82 %73
+               OpStore %135 %134
+               OpReturn
+               OpFunctionEnd
+
diff --git a/test/diff/diff_files/large_functions_small_diffs_src.spvasm b/test/diff/diff_files/large_functions_small_diffs_src.spvasm
new file mode 100644
index 0000000..78a9278
--- /dev/null
+++ b/test/diff/diff_files/large_functions_small_diffs_src.spvasm
@@ -0,0 +1,226 @@
+;; Test where src and dst have a few large functions with small differences.
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main" %15
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "f1("
+               OpName %8 "f2("
+               OpName %12 "x"
+               OpName %15 "gl_LocalInvocationID"
+               OpName %20 "y"
+               OpName %27 "image"
+               OpName %44 "sum"
+               OpName %46 "i"
+               OpName %56 "j"
+               OpName %80 "BufferOut"
+               OpMemberName %80 0 "o_uv4"
+               OpMemberName %80 1 "o_v3"
+               OpMemberName %80 2 "o_i"
+               OpName %82 ""
+               OpName %88 "BufferIn"
+               OpMemberName %88 0 "i_u"
+               OpMemberName %88 1 "i_v4"
+               OpMemberName %88 2 "i_f"
+               OpName %90 ""
+               OpName %101 "i"
+               OpDecorate %15 BuiltIn LocalInvocationId
+               OpDecorate %27 DescriptorSet 0
+               OpDecorate %27 Binding 2
+               OpMemberDecorate %80 0 Offset 0
+               OpMemberDecorate %80 1 Offset 16
+               OpMemberDecorate %80 2 Offset 28
+               OpDecorate %80 BufferBlock
+               OpDecorate %82 DescriptorSet 0
+               OpDecorate %82 Binding 1
+               OpMemberDecorate %88 0 Offset 0
+               OpMemberDecorate %88 1 RowMajor
+               OpMemberDecorate %88 1 Offset 16
+               OpMemberDecorate %88 1 MatrixStride 16
+               OpMemberDecorate %88 2 Offset 80
+               OpDecorate %88 Block
+               OpDecorate %90 DescriptorSet 0
+               OpDecorate %90 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeInt 32 0
+         %11 = OpTypePointer Function %10
+         %13 = OpTypeVector %10 3
+         %14 = OpTypePointer Input %13
+         %15 = OpVariable %14 Input
+         %16 = OpConstant %10 0
+         %17 = OpTypePointer Input %10
+         %21 = OpConstant %10 1
+         %24 = OpTypeInt 32 1
+         %25 = OpTypeImage %24 2D 0 0 0 2 R32i
+         %26 = OpTypePointer UniformConstant %25
+         %27 = OpVariable %26 UniformConstant
+         %29 = OpTypeVector %10 2
+         %32 = OpTypeVector %24 2
+         %38 = OpTypeVector %24 4
+         %40 = OpConstant %10 2
+         %41 = OpConstant %10 3400
+         %42 = OpConstant %10 264
+         %43 = OpTypePointer Function %24
+         %45 = OpConstant %24 0
+         %53 = OpConstant %24 2
+         %54 = OpTypeBool
+         %73 = OpConstant %24 1
+         %77 = OpTypeVector %10 4
+         %78 = OpTypeFloat 32
+         %79 = OpTypeVector %78 3
+         %80 = OpTypeStruct %77 %79 %24
+         %81 = OpTypePointer Uniform %80
+         %82 = OpVariable %81 Uniform
+         %84 = OpTypePointer Uniform %24
+         %86 = OpTypeVector %78 4
+         %87 = OpTypeMatrix %86 4
+         %88 = OpTypeStruct %10 %87 %78
+         %89 = OpTypePointer Uniform %88
+         %90 = OpVariable %89 Uniform
+         %91 = OpTypePointer Uniform %87
+         %94 = OpTypePointer Uniform %77
+        %108 = OpConstant %24 3
+        %110 = OpTypePointer Uniform %79
+        %113 = OpTypePointer Uniform %78
+        %129 = OpConstantComposite %32 %45 %45
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+        %135 = OpFunctionCall %2 %6
+        %136 = OpFunctionCall %2 %8
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %12 = OpVariable %11 Function
+         %20 = OpVariable %11 Function
+         %44 = OpVariable %43 Function
+         %46 = OpVariable %43 Function
+         %56 = OpVariable %43 Function
+         %18 = OpAccessChain %17 %15 %16
+         %19 = OpLoad %10 %18
+               OpStore %12 %19
+         %22 = OpAccessChain %17 %15 %21
+         %23 = OpLoad %10 %22
+               OpStore %20 %23
+         %28 = OpLoad %25 %27
+         %30 = OpLoad %13 %15
+         %31 = OpVectorShuffle %29 %30 %30 0 1
+         %33 = OpBitcast %32 %31
+         %34 = OpLoad %10 %12
+         %35 = OpLoad %10 %20
+         %36 = OpIAdd %10 %34 %35
+         %37 = OpBitcast %24 %36
+         %39 = OpCompositeConstruct %38 %37 %37 %37 %37
+               OpImageWrite %28 %33 %39
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+               OpStore %44 %45
+               OpStore %46 %45
+               OpBranch %47
+         %47 = OpLabel
+               OpLoopMerge %49 %50 None
+               OpBranch %51
+         %51 = OpLabel
+         %52 = OpLoad %24 %46
+         %55 = OpSLessThan %54 %52 %53
+               OpBranchConditional %55 %48 %49
+         %48 = OpLabel
+               OpStore %56 %45
+               OpBranch %57
+         %57 = OpLabel
+               OpLoopMerge %59 %60 None
+               OpBranch %61
+         %61 = OpLabel
+         %62 = OpLoad %24 %56
+         %63 = OpSLessThan %54 %62 %53
+               OpBranchConditional %63 %58 %59
+         %58 = OpLabel
+         %64 = OpLoad %25 %27
+         %65 = OpLoad %24 %46
+         %66 = OpLoad %24 %56
+         %67 = OpCompositeConstruct %32 %65 %66
+         %68 = OpImageRead %38 %64 %67
+         %69 = OpCompositeExtract %24 %68 0
+         %70 = OpLoad %24 %44
+         %71 = OpIAdd %24 %70 %69
+               OpStore %44 %71
+               OpBranch %60
+         %60 = OpLabel
+         %72 = OpLoad %24 %56
+         %74 = OpIAdd %24 %72 %73
+               OpStore %56 %74
+               OpBranch %57
+         %59 = OpLabel
+               OpBranch %50
+         %50 = OpLabel
+         %75 = OpLoad %24 %46
+         %76 = OpIAdd %24 %75 %73
+               OpStore %46 %76
+               OpBranch %47
+         %49 = OpLabel
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+         %83 = OpLoad %24 %44
+         %85 = OpAccessChain %84 %82 %53
+               OpStore %85 %83
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+        %101 = OpVariable %43 Function
+         %92 = OpAccessChain %91 %90 %73
+         %93 = OpLoad %87 %92
+         %95 = OpAccessChain %94 %82 %45
+         %96 = OpLoad %77 %95
+         %97 = OpConvertUToF %86 %96
+         %98 = OpMatrixTimesVector %86 %93 %97
+         %99 = OpConvertFToU %77 %98
+        %100 = OpAccessChain %94 %82 %45
+               OpStore %100 %99
+               OpStore %101 %45
+               OpBranch %102
+        %102 = OpLabel
+               OpLoopMerge %104 %105 None
+               OpBranch %106
+        %106 = OpLabel
+        %107 = OpLoad %24 %101
+        %109 = OpSLessThan %54 %107 %108
+               OpBranchConditional %109 %103 %104
+        %103 = OpLabel
+        %111 = OpAccessChain %110 %82 %73
+        %112 = OpLoad %79 %111
+        %114 = OpAccessChain %113 %90 %53
+        %115 = OpLoad %78 %114
+        %116 = OpVectorTimesScalar %79 %112 %115
+        %117 = OpConvertFToU %13 %116
+        %118 = OpCompositeExtract %10 %117 0
+        %119 = OpCompositeExtract %10 %117 1
+        %120 = OpCompositeExtract %10 %117 2
+        %121 = OpCompositeConstruct %77 %118 %119 %120 %16
+        %122 = OpAccessChain %94 %82 %45
+        %123 = OpLoad %77 %122
+        %124 = OpIAdd %77 %123 %121
+        %125 = OpAccessChain %94 %82 %45
+               OpStore %125 %124
+               OpBranch %105
+        %105 = OpLabel
+        %126 = OpLoad %24 %101
+        %127 = OpIAdd %24 %126 %73
+               OpStore %101 %127
+               OpBranch %102
+        %104 = OpLabel
+               OpMemoryBarrier %40 %41
+               OpControlBarrier %40 %40 %42
+        %128 = OpLoad %25 %27
+        %130 = OpImageRead %38 %128 %129
+        %131 = OpCompositeExtract %24 %130 0
+        %132 = OpConvertSToF %78 %131
+        %133 = OpCompositeConstruct %79 %132 %132 %132
+        %134 = OpAccessChain %110 %82 %73
+               OpStore %134 %133
+               OpReturn
+               OpFunctionEnd
+
diff --git a/test/diff/diff_files/multiple_different_entry_points_autogen.cpp b/test/diff/diff_files/multiple_different_entry_points_autogen.cpp
new file mode 100644
index 0000000..29d4b1d
--- /dev/null
+++ b/test/diff/diff_files/multiple_different_entry_points_autogen.cpp
@@ -0,0 +1,330 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Basic test for multiple entry points.  The entry points have different
+// execution models and so can be trivially matched.
+constexpr char kSrc[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %mainv "mainv" %vo %a
+               OpEntryPoint Fragment %mainf "mainf" %color %vi
+	       OpExecutionMode %mainf OriginUpperLeft
+               OpSource ESSL 310
+               OpName %mainv "mainv"
+               OpName %mainf "mainf"
+               OpName %a "a"
+               OpName %vo "v"
+               OpName %vi "v"
+               OpName %color "color"
+               OpDecorate %a Location 0
+               OpDecorate %vo Location 0
+               OpDecorate %vi Location 0
+               OpDecorate %color Location 0
+	       OpDecorate %color RelaxedPrecision
+	       OpDecorate %vi RelaxedPrecision
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %13 RelaxedPrecision
+
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+
+%_ptr_Input_float = OpTypePointer Input %float
+          %a = OpVariable %_ptr_Input_float Input
+%_ptr_Output_float = OpTypePointer Output %float
+         %vo = OpVariable %_ptr_Output_float Output
+         %vi = OpVariable %_ptr_Input_float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+      %color = OpVariable %_ptr_Output_v4float Output
+
+      %mainv = OpFunction %void None %3
+          %5 = OpLabel
+         %11 = OpLoad %float %a
+               OpStore %vo %11
+               OpReturn
+               OpFunctionEnd
+
+      %mainf = OpFunction %void None %3
+          %6 = OpLabel
+         %12 = OpLoad %float %vi
+	 %13 = OpCompositeConstruct %v4float %12 %12 %12 %12
+               OpStore %color %13
+               OpReturn
+               OpFunctionEnd)";
+constexpr char kDst[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %frag "frag" %vi %color
+               OpEntryPoint Vertex %vert "vert" %a %vo
+	       OpExecutionMode %frag OriginUpperLeft
+               OpSource ESSL 310
+               OpName %frag "frag"
+               OpName %vert "vert"
+               OpName %vo "v"
+               OpName %a "a"
+               OpName %color "color"
+               OpName %vi "v"
+               OpDecorate %vi Location 0
+               OpDecorate %color Location 0
+               OpDecorate %a Location 0
+               OpDecorate %vo Location 0
+	       OpDecorate %color RelaxedPrecision
+	       OpDecorate %vi RelaxedPrecision
+               OpDecorate %14 RelaxedPrecision
+               OpDecorate %17 RelaxedPrecision
+
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+
+%_ptr_Output_float = OpTypePointer Output %float
+         %vo = OpVariable %_ptr_Output_float Output
+%_ptr_Input_float = OpTypePointer Input %float
+          %a = OpVariable %_ptr_Input_float Input
+         %vi = OpVariable %_ptr_Input_float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+      %color = OpVariable %_ptr_Output_v4float Output
+
+       %frag = OpFunction %void None %3
+          %7 = OpLabel
+         %14 = OpLoad %float %vi
+	 %17 = OpCompositeConstruct %v4float %14 %14 %14 %14
+               OpStore %color %17
+               OpReturn
+               OpFunctionEnd
+
+       %vert = OpFunction %void None %3
+          %8 = OpLabel
+         %13 = OpLoad %float %a
+               OpStore %vo %13
+               OpReturn
+               OpFunctionEnd
+)";
+
+TEST(DiffTest, MultipleDifferentEntryPoints) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+ ; Bound: 20
+ ; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+-OpEntryPoint Vertex %2 "mainv" %4 %7
++OpEntryPoint Vertex %2 "vert" %7 %4
+-OpEntryPoint Fragment %8 "mainf" %9 %10
++OpEntryPoint Fragment %8 "frag" %10 %9
+ OpExecutionMode %8 OriginUpperLeft
+ OpSource ESSL 310
+-OpName %2 "mainv"
++OpName %2 "vert"
+-OpName %8 "mainf"
++OpName %8 "frag"
+ OpName %7 "a"
+ OpName %4 "v"
+ OpName %10 "v"
+ OpName %9 "color"
+ OpDecorate %7 Location 0
+ OpDecorate %4 Location 0
+ OpDecorate %10 Location 0
+ OpDecorate %9 Location 0
+ OpDecorate %9 RelaxedPrecision
+ OpDecorate %10 RelaxedPrecision
+ OpDecorate %12 RelaxedPrecision
+ OpDecorate %13 RelaxedPrecision
+ %14 = OpTypeVoid
+ %3 = OpTypeFunction %14
+ %15 = OpTypeFloat 32
+ %16 = OpTypeVector %15 4
+ %17 = OpTypePointer Input %15
+ %7 = OpVariable %17 Input
+ %18 = OpTypePointer Output %15
+ %4 = OpVariable %18 Output
+ %10 = OpVariable %17 Input
+ %19 = OpTypePointer Output %16
+ %9 = OpVariable %19 Output
+ %2 = OpFunction %14 None %3
+ %5 = OpLabel
+ %11 = OpLoad %15 %7
+ OpStore %4 %11
+ OpReturn
+ OpFunctionEnd
+ %8 = OpFunction %14 None %3
+ %6 = OpLabel
+ %12 = OpLoad %15 %10
+ %13 = OpCompositeConstruct %16 %12 %12 %12 %12
+ OpStore %9 %13
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, MultipleDifferentEntryPointsNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %mainv "mainv" %vo %a
+               OpEntryPoint Fragment %mainf "mainf" %color %vi
+	       OpExecutionMode %mainf OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %a Location 0
+               OpDecorate %vo Location 0
+               OpDecorate %vi Location 0
+               OpDecorate %color Location 0
+	       OpDecorate %color RelaxedPrecision
+	       OpDecorate %vi RelaxedPrecision
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %13 RelaxedPrecision
+
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+
+%_ptr_Input_float = OpTypePointer Input %float
+          %a = OpVariable %_ptr_Input_float Input
+%_ptr_Output_float = OpTypePointer Output %float
+         %vo = OpVariable %_ptr_Output_float Output
+         %vi = OpVariable %_ptr_Input_float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+      %color = OpVariable %_ptr_Output_v4float Output
+
+      %mainv = OpFunction %void None %3
+          %5 = OpLabel
+         %11 = OpLoad %float %a
+               OpStore %vo %11
+               OpReturn
+               OpFunctionEnd
+
+      %mainf = OpFunction %void None %3
+          %6 = OpLabel
+         %12 = OpLoad %float %vi
+	 %13 = OpCompositeConstruct %v4float %12 %12 %12 %12
+               OpStore %color %13
+               OpReturn
+               OpFunctionEnd
+)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %frag "frag" %vi %color
+               OpEntryPoint Vertex %vert "vert" %a %vo
+	       OpExecutionMode %frag OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %vi Location 0
+               OpDecorate %color Location 0
+               OpDecorate %a Location 0
+               OpDecorate %vo Location 0
+	       OpDecorate %color RelaxedPrecision
+	       OpDecorate %vi RelaxedPrecision
+               OpDecorate %14 RelaxedPrecision
+               OpDecorate %17 RelaxedPrecision
+
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+
+%_ptr_Output_float = OpTypePointer Output %float
+         %vo = OpVariable %_ptr_Output_float Output
+%_ptr_Input_float = OpTypePointer Input %float
+          %a = OpVariable %_ptr_Input_float Input
+         %vi = OpVariable %_ptr_Input_float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+      %color = OpVariable %_ptr_Output_v4float Output
+
+       %frag = OpFunction %void None %3
+          %7 = OpLabel
+         %14 = OpLoad %float %vi
+	 %17 = OpCompositeConstruct %v4float %14 %14 %14 %14
+               OpStore %color %17
+               OpReturn
+               OpFunctionEnd
+
+       %vert = OpFunction %void None %3
+          %8 = OpLabel
+         %13 = OpLoad %float %a
+               OpStore %vo %13
+               OpReturn
+               OpFunctionEnd
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+ ; Bound: 20
+ ; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+-OpEntryPoint Vertex %2 "mainv" %4 %7
++OpEntryPoint Vertex %2 "vert" %7 %4
+-OpEntryPoint Fragment %8 "mainf" %9 %10
++OpEntryPoint Fragment %8 "frag" %10 %9
+ OpExecutionMode %8 OriginUpperLeft
+ OpSource ESSL 310
+ OpDecorate %7 Location 0
+ OpDecorate %4 Location 0
+ OpDecorate %10 Location 0
+ OpDecorate %9 Location 0
+ OpDecorate %9 RelaxedPrecision
+ OpDecorate %10 RelaxedPrecision
+ OpDecorate %12 RelaxedPrecision
+ OpDecorate %13 RelaxedPrecision
+ %14 = OpTypeVoid
+ %3 = OpTypeFunction %14
+ %15 = OpTypeFloat 32
+ %16 = OpTypeVector %15 4
+ %17 = OpTypePointer Input %15
+ %7 = OpVariable %17 Input
+ %18 = OpTypePointer Output %15
+ %4 = OpVariable %18 Output
+ %10 = OpVariable %17 Input
+ %19 = OpTypePointer Output %16
+ %9 = OpVariable %19 Output
+ %2 = OpFunction %14 None %3
+ %5 = OpLabel
+ %11 = OpLoad %15 %7
+ OpStore %4 %11
+ OpReturn
+ OpFunctionEnd
+ %8 = OpFunction %14 None %3
+ %6 = OpLabel
+ %12 = OpLoad %15 %10
+ %13 = OpCompositeConstruct %16 %12 %12 %12 %12
+ OpStore %9 %13
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/multiple_different_entry_points_dst.spvasm b/test/diff/diff_files/multiple_different_entry_points_dst.spvasm
new file mode 100644
index 0000000..72cfe28
--- /dev/null
+++ b/test/diff/diff_files/multiple_different_entry_points_dst.spvasm
@@ -0,0 +1,49 @@
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %frag "frag" %vi %color
+               OpEntryPoint Vertex %vert "vert" %a %vo
+	       OpExecutionMode %frag OriginUpperLeft
+               OpSource ESSL 310
+               OpName %frag "frag"
+               OpName %vert "vert"
+               OpName %vo "v"
+               OpName %a "a"
+               OpName %color "color"
+               OpName %vi "v"
+               OpDecorate %vi Location 0
+               OpDecorate %color Location 0
+               OpDecorate %a Location 0
+               OpDecorate %vo Location 0
+	       OpDecorate %color RelaxedPrecision
+	       OpDecorate %vi RelaxedPrecision
+               OpDecorate %14 RelaxedPrecision
+               OpDecorate %17 RelaxedPrecision
+
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+
+%_ptr_Output_float = OpTypePointer Output %float
+         %vo = OpVariable %_ptr_Output_float Output
+%_ptr_Input_float = OpTypePointer Input %float
+          %a = OpVariable %_ptr_Input_float Input
+         %vi = OpVariable %_ptr_Input_float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+      %color = OpVariable %_ptr_Output_v4float Output
+
+       %frag = OpFunction %void None %3
+          %7 = OpLabel
+         %14 = OpLoad %float %vi
+	 %17 = OpCompositeConstruct %v4float %14 %14 %14 %14
+               OpStore %color %17
+               OpReturn
+               OpFunctionEnd
+
+       %vert = OpFunction %void None %3
+          %8 = OpLabel
+         %13 = OpLoad %float %a
+               OpStore %vo %13
+               OpReturn
+               OpFunctionEnd
diff --git a/test/diff/diff_files/multiple_different_entry_points_src.spvasm b/test/diff/diff_files/multiple_different_entry_points_src.spvasm
new file mode 100644
index 0000000..2119aa7
--- /dev/null
+++ b/test/diff/diff_files/multiple_different_entry_points_src.spvasm
@@ -0,0 +1,51 @@
+;; Basic test for multiple entry points.  The entry points have different
+;; execution models and so can be trivially matched.
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %mainv "mainv" %vo %a
+               OpEntryPoint Fragment %mainf "mainf" %color %vi
+	       OpExecutionMode %mainf OriginUpperLeft
+               OpSource ESSL 310
+               OpName %mainv "mainv"
+               OpName %mainf "mainf"
+               OpName %a "a"
+               OpName %vo "v"
+               OpName %vi "v"
+               OpName %color "color"
+               OpDecorate %a Location 0
+               OpDecorate %vo Location 0
+               OpDecorate %vi Location 0
+               OpDecorate %color Location 0
+	       OpDecorate %color RelaxedPrecision
+	       OpDecorate %vi RelaxedPrecision
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %13 RelaxedPrecision
+
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+
+%_ptr_Input_float = OpTypePointer Input %float
+          %a = OpVariable %_ptr_Input_float Input
+%_ptr_Output_float = OpTypePointer Output %float
+         %vo = OpVariable %_ptr_Output_float Output
+         %vi = OpVariable %_ptr_Input_float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+      %color = OpVariable %_ptr_Output_v4float Output
+
+      %mainv = OpFunction %void None %3
+          %5 = OpLabel
+         %11 = OpLoad %float %a
+               OpStore %vo %11
+               OpReturn
+               OpFunctionEnd
+
+      %mainf = OpFunction %void None %3
+          %6 = OpLabel
+         %12 = OpLoad %float %vi
+	 %13 = OpCompositeConstruct %v4float %12 %12 %12 %12
+               OpStore %color %13
+               OpReturn
+               OpFunctionEnd
diff --git a/test/diff/diff_files/multiple_same_entry_points_autogen.cpp b/test/diff/diff_files/multiple_same_entry_points_autogen.cpp
new file mode 100644
index 0000000..9d01166
--- /dev/null
+++ b/test/diff/diff_files/multiple_same_entry_points_autogen.cpp
@@ -0,0 +1,375 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Test for multiple entry points with the same execution model.
+constexpr char kSrc[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %4 "main1" %8 %10
+               OpEntryPoint Vertex %12 "main2" %13 %14 %15
+               OpSource ESSL 310
+               OpName %4 "main1"
+               OpName %12 "main2"
+               OpName %8 "v"
+               OpName %10 "a"
+               OpName %13 "v"
+               OpName %14 "a"
+               OpName %15 "b"
+               OpDecorate %8 Location 0
+               OpDecorate %10 Location 0
+               OpDecorate %13 Location 0
+               OpDecorate %14 Location 0
+               OpDecorate %15 Location 1
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+
+          %7 = OpTypePointer Output %6
+          %9 = OpTypePointer Input %6
+          %8 = OpVariable %7 Output
+         %10 = OpVariable %9 Input
+         %13 = OpVariable %7 Output
+         %14 = OpVariable %9 Input
+         %15 = OpVariable %9 Input
+
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpLoad %6 %10
+               OpStore %8 %11
+               OpReturn
+               OpFunctionEnd
+
+         %12 = OpFunction %2 None %3
+         %16 = OpLabel
+         %17 = OpLoad %6 %14
+         %18 = OpLoad %6 %15
+         %19 = OpFAdd %6 %17 %18
+               OpStore %13 %19
+               OpReturn
+               OpFunctionEnd)";
+constexpr char kDst[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %4 "main2" %13 %14 %15
+               OpEntryPoint Vertex %12 "main1" %8 %10
+               OpSource ESSL 310
+               OpName %12 "main1"
+               OpName %4 "main2"
+               OpName %8 "v"
+               OpName %10 "a"
+               OpName %13 "v"
+               OpName %14 "a"
+               OpName %15 "b"
+               OpDecorate %8 Location 0
+               OpDecorate %10 Location 0
+               OpDecorate %13 Location 0
+               OpDecorate %14 Location 0
+               OpDecorate %15 Location 1
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+
+          %7 = OpTypePointer Output %6
+          %9 = OpTypePointer Input %6
+          %8 = OpVariable %7 Output
+         %10 = OpVariable %9 Input
+         %13 = OpVariable %7 Output
+         %14 = OpVariable %9 Input
+         %15 = OpVariable %9 Input
+
+         %4 = OpFunction %2 None %3
+         %16 = OpLabel
+         %17 = OpLoad %6 %14
+         %18 = OpLoad %6 %15
+         %19 = OpFAdd %6 %17 %18
+               OpStore %13 %19
+               OpReturn
+               OpFunctionEnd
+
+         %12 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpLoad %6 %10
+               OpStore %8 %11
+               OpReturn
+               OpFunctionEnd
+)";
+
+TEST(DiffTest, MultipleSameEntryPoints) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+ ; Bound: 20
+ ; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
++OpEntryPoint Vertex %12 "main2" %13 %14 %15
+ OpEntryPoint Vertex %4 "main1" %8 %10
+-OpEntryPoint Vertex %12 "main2" %13 %14 %15
+ OpSource ESSL 310
+ OpName %4 "main1"
+ OpName %12 "main2"
+ OpName %8 "v"
+ OpName %10 "a"
+ OpName %13 "v"
+ OpName %14 "a"
+ OpName %15 "b"
+ OpDecorate %8 Location 0
+ OpDecorate %10 Location 0
+ OpDecorate %13 Location 0
+ OpDecorate %14 Location 0
+ OpDecorate %15 Location 1
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypePointer Output %6
+ %9 = OpTypePointer Input %6
+ %8 = OpVariable %7 Output
+ %10 = OpVariable %9 Input
+ %13 = OpVariable %7 Output
+ %14 = OpVariable %9 Input
+ %15 = OpVariable %9 Input
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %11 = OpLoad %6 %10
+ OpStore %8 %11
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %2 None %3
+ %16 = OpLabel
+ %17 = OpLoad %6 %14
+ %18 = OpLoad %6 %15
+ %19 = OpFAdd %6 %17 %18
+ OpStore %13 %19
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, MultipleSameEntryPointsNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %4 "main1" %8 %10
+               OpEntryPoint Vertex %12 "main2" %13 %14 %15
+               OpSource ESSL 310
+               OpDecorate %8 Location 0
+               OpDecorate %10 Location 0
+               OpDecorate %13 Location 0
+               OpDecorate %14 Location 0
+               OpDecorate %15 Location 1
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+
+          %7 = OpTypePointer Output %6
+          %9 = OpTypePointer Input %6
+          %8 = OpVariable %7 Output
+         %10 = OpVariable %9 Input
+         %13 = OpVariable %7 Output
+         %14 = OpVariable %9 Input
+         %15 = OpVariable %9 Input
+
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpLoad %6 %10
+               OpStore %8 %11
+               OpReturn
+               OpFunctionEnd
+
+         %12 = OpFunction %2 None %3
+         %16 = OpLabel
+         %17 = OpLoad %6 %14
+         %18 = OpLoad %6 %15
+         %19 = OpFAdd %6 %17 %18
+               OpStore %13 %19
+               OpReturn
+               OpFunctionEnd
+)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %4 "main2" %13 %14 %15
+               OpEntryPoint Vertex %12 "main1" %8 %10
+               OpSource ESSL 310
+               OpDecorate %8 Location 0
+               OpDecorate %10 Location 0
+               OpDecorate %13 Location 0
+               OpDecorate %14 Location 0
+               OpDecorate %15 Location 1
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+
+          %7 = OpTypePointer Output %6
+          %9 = OpTypePointer Input %6
+          %8 = OpVariable %7 Output
+         %10 = OpVariable %9 Input
+         %13 = OpVariable %7 Output
+         %14 = OpVariable %9 Input
+         %15 = OpVariable %9 Input
+
+         %4 = OpFunction %2 None %3
+         %16 = OpLabel
+         %17 = OpLoad %6 %14
+         %18 = OpLoad %6 %15
+         %19 = OpFAdd %6 %17 %18
+               OpStore %13 %19
+               OpReturn
+               OpFunctionEnd
+
+         %12 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpLoad %6 %10
+               OpStore %8 %11
+               OpReturn
+               OpFunctionEnd
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+ ; Bound: 20
+ ; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
++OpEntryPoint Vertex %12 "main2" %13 %14 %15
+ OpEntryPoint Vertex %4 "main1" %8 %10
+-OpEntryPoint Vertex %12 "main2" %13 %14 %15
+ OpSource ESSL 310
+ OpDecorate %8 Location 0
+ OpDecorate %10 Location 0
+ OpDecorate %13 Location 0
+ OpDecorate %14 Location 0
+ OpDecorate %15 Location 1
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypePointer Output %6
+ %9 = OpTypePointer Input %6
+ %8 = OpVariable %7 Output
+ %10 = OpVariable %9 Input
+ %13 = OpVariable %7 Output
+ %14 = OpVariable %9 Input
+ %15 = OpVariable %9 Input
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %11 = OpLoad %6 %10
+ OpStore %8 %11
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %2 None %3
+ %16 = OpLabel
+ %17 = OpLoad %6 %14
+ %18 = OpLoad %6 %15
+ %19 = OpFAdd %6 %17 %18
+ OpStore %13 %19
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+TEST(DiffTest, MultipleSameEntryPointsDumpIds) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+ ; Bound: 20
+ ; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
++OpEntryPoint Vertex %12 "main2" %13 %14 %15
+ OpEntryPoint Vertex %4 "main1" %8 %10
+-OpEntryPoint Vertex %12 "main2" %13 %14 %15
+ OpSource ESSL 310
+ OpName %4 "main1"
+ OpName %12 "main2"
+ OpName %8 "v"
+ OpName %10 "a"
+ OpName %13 "v"
+ OpName %14 "a"
+ OpName %15 "b"
+ OpDecorate %8 Location 0
+ OpDecorate %10 Location 0
+ OpDecorate %13 Location 0
+ OpDecorate %14 Location 0
+ OpDecorate %15 Location 1
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypePointer Output %6
+ %9 = OpTypePointer Input %6
+ %8 = OpVariable %7 Output
+ %10 = OpVariable %9 Input
+ %13 = OpVariable %7 Output
+ %14 = OpVariable %9 Input
+ %15 = OpVariable %9 Input
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %11 = OpLoad %6 %10
+ OpStore %8 %11
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %2 None %3
+ %16 = OpLabel
+ %17 = OpLoad %6 %14
+ %18 = OpLoad %6 %15
+ %19 = OpFAdd %6 %17 %18
+ OpStore %13 %19
+ OpReturn
+ OpFunctionEnd
+ Src ->  Dst
+   1 ->    1 [ExtInstImport]
+   2 ->    2 [TypeVoid]
+   3 ->    3 [TypeFunction]
+   4 ->   12 [Function]
+   5 ->    5 [Label]
+   6 ->    6 [TypeFloat]
+   7 ->    7 [TypePointer]
+   8 ->    8 [Variable]
+   9 ->    9 [TypePointer]
+  10 ->   10 [Variable]
+  11 ->   11 [Load]
+  12 ->    4 [Function]
+  13 ->   13 [Variable]
+  14 ->   14 [Variable]
+  15 ->   15 [Variable]
+  16 ->   16 [Label]
+  17 ->   17 [Load]
+  18 ->   18 [Load]
+  19 ->   19 [FAdd]
+)";
+  Options options;
+  options.dump_id_map = true;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/multiple_same_entry_points_dst.spvasm b/test/diff/diff_files/multiple_same_entry_points_dst.spvasm
new file mode 100644
index 0000000..e200722
--- /dev/null
+++ b/test/diff/diff_files/multiple_same_entry_points_dst.spvasm
@@ -0,0 +1,45 @@
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %4 "main2" %13 %14 %15
+               OpEntryPoint Vertex %12 "main1" %8 %10
+               OpSource ESSL 310
+               OpName %12 "main1"
+               OpName %4 "main2"
+               OpName %8 "v"
+               OpName %10 "a"
+               OpName %13 "v"
+               OpName %14 "a"
+               OpName %15 "b"
+               OpDecorate %8 Location 0
+               OpDecorate %10 Location 0
+               OpDecorate %13 Location 0
+               OpDecorate %14 Location 0
+               OpDecorate %15 Location 1
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+
+          %7 = OpTypePointer Output %6
+          %9 = OpTypePointer Input %6
+          %8 = OpVariable %7 Output
+         %10 = OpVariable %9 Input
+         %13 = OpVariable %7 Output
+         %14 = OpVariable %9 Input
+         %15 = OpVariable %9 Input
+
+         %4 = OpFunction %2 None %3
+         %16 = OpLabel
+         %17 = OpLoad %6 %14
+         %18 = OpLoad %6 %15
+         %19 = OpFAdd %6 %17 %18
+               OpStore %13 %19
+               OpReturn
+               OpFunctionEnd
+
+         %12 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpLoad %6 %10
+               OpStore %8 %11
+               OpReturn
+               OpFunctionEnd
diff --git a/test/diff/diff_files/multiple_same_entry_points_src.spvasm b/test/diff/diff_files/multiple_same_entry_points_src.spvasm
new file mode 100644
index 0000000..17001b5
--- /dev/null
+++ b/test/diff/diff_files/multiple_same_entry_points_src.spvasm
@@ -0,0 +1,46 @@
+;; Test for multiple entry points with the same execution model.
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %4 "main1" %8 %10
+               OpEntryPoint Vertex %12 "main2" %13 %14 %15
+               OpSource ESSL 310
+               OpName %4 "main1"
+               OpName %12 "main2"
+               OpName %8 "v"
+               OpName %10 "a"
+               OpName %13 "v"
+               OpName %14 "a"
+               OpName %15 "b"
+               OpDecorate %8 Location 0
+               OpDecorate %10 Location 0
+               OpDecorate %13 Location 0
+               OpDecorate %14 Location 0
+               OpDecorate %15 Location 1
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+
+          %7 = OpTypePointer Output %6
+          %9 = OpTypePointer Input %6
+          %8 = OpVariable %7 Output
+         %10 = OpVariable %9 Input
+         %13 = OpVariable %7 Output
+         %14 = OpVariable %9 Input
+         %15 = OpVariable %9 Input
+
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpLoad %6 %10
+               OpStore %8 %11
+               OpReturn
+               OpFunctionEnd
+
+         %12 = OpFunction %2 None %3
+         %16 = OpLabel
+         %17 = OpLoad %6 %14
+         %18 = OpLoad %6 %15
+         %19 = OpFAdd %6 %17 %18
+               OpStore %13 %19
+               OpReturn
+               OpFunctionEnd
diff --git a/test/diff/diff_files/reordered_if_blocks_autogen.cpp b/test/diff/diff_files/reordered_if_blocks_autogen.cpp
new file mode 100644
index 0000000..0788199
--- /dev/null
+++ b/test/diff/diff_files/reordered_if_blocks_autogen.cpp
@@ -0,0 +1,568 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Test where src and dst have the true and false blocks of an if reordered.
+constexpr char kSrc[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %8 %44
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "v"
+               OpName %44 "color"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %8 Location 0
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %18 RelaxedPrecision
+               OpDecorate %19 RelaxedPrecision
+               OpDecorate %20 RelaxedPrecision
+               OpDecorate %23 RelaxedPrecision
+               OpDecorate %24 RelaxedPrecision
+               OpDecorate %25 RelaxedPrecision
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %27 RelaxedPrecision
+               OpDecorate %28 RelaxedPrecision
+               OpDecorate %29 RelaxedPrecision
+               OpDecorate %30 RelaxedPrecision
+               OpDecorate %31 RelaxedPrecision
+               OpDecorate %33 RelaxedPrecision
+               OpDecorate %34 RelaxedPrecision
+               OpDecorate %35 RelaxedPrecision
+               OpDecorate %36 RelaxedPrecision
+               OpDecorate %37 RelaxedPrecision
+               OpDecorate %39 RelaxedPrecision
+               OpDecorate %40 RelaxedPrecision
+               OpDecorate %41 RelaxedPrecision
+               OpDecorate %42 RelaxedPrecision
+               OpDecorate %44 RelaxedPrecision
+               OpDecorate %44 Location 0
+               OpDecorate %45 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Input %6
+          %8 = OpVariable %7 Input
+         %10 = OpConstant %6 0
+         %11 = OpTypeBool
+         %15 = OpTypeVector %6 4
+         %16 = OpTypePointer Function %15
+         %21 = OpConstant %6 -0.5
+         %22 = OpConstant %6 -0.300000012
+         %38 = OpConstant %6 0.5
+         %43 = OpTypePointer Output %15
+         %44 = OpVariable %43 Output
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %9 = OpLoad %6 %8
+         %12 = OpFOrdLessThanEqual %11 %9 %10
+               OpSelectionMerge %14 None
+               OpBranchConditional %12 %13 %32
+         %13 = OpLabel
+         %18 = OpLoad %6 %8
+         %19 = OpExtInst %6 %1 Log %18
+         %20 = OpLoad %6 %8
+         %23 = OpExtInst %6 %1 FClamp %20 %21 %22
+         %24 = OpFMul %6 %19 %23
+         %25 = OpLoad %6 %8
+         %26 = OpExtInst %6 %1 Sin %25
+         %27 = OpLoad %6 %8
+         %28 = OpExtInst %6 %1 Cos %27
+         %29 = OpLoad %6 %8
+         %30 = OpExtInst %6 %1 Exp %29
+         %31 = OpCompositeConstruct %15 %24 %26 %28 %30
+               OpBranch %14
+         %32 = OpLabel
+         %33 = OpLoad %6 %8
+         %34 = OpExtInst %6 %1 Sqrt %33
+         %35 = OpLoad %6 %8
+         %36 = OpExtInst %6 %1 FSign %35
+         %37 = OpLoad %6 %8
+         %39 = OpExtInst %6 %1 FMax %37 %38
+         %40 = OpLoad %6 %8
+         %41 = OpExtInst %6 %1 Floor %40
+         %42 = OpCompositeConstruct %15 %34 %36 %39 %41
+               OpBranch %14
+         %14 = OpLabel
+         %45 = OpPhi %15 %31 %13 %42 %32
+               OpStore %44 %45
+               OpReturn
+               OpFunctionEnd)";
+constexpr char kDst[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %8 %44
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "v"
+               OpName %44 "color"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %8 Location 0
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %18 RelaxedPrecision
+               OpDecorate %19 RelaxedPrecision
+               OpDecorate %20 RelaxedPrecision
+               OpDecorate %21 RelaxedPrecision
+               OpDecorate %22 RelaxedPrecision
+               OpDecorate %24 RelaxedPrecision
+               OpDecorate %25 RelaxedPrecision
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %27 RelaxedPrecision
+               OpDecorate %29 RelaxedPrecision
+               OpDecorate %30 RelaxedPrecision
+               OpDecorate %31 RelaxedPrecision
+               OpDecorate %34 RelaxedPrecision
+               OpDecorate %35 RelaxedPrecision
+               OpDecorate %36 RelaxedPrecision
+               OpDecorate %37 RelaxedPrecision
+               OpDecorate %38 RelaxedPrecision
+               OpDecorate %39 RelaxedPrecision
+               OpDecorate %40 RelaxedPrecision
+               OpDecorate %41 RelaxedPrecision
+               OpDecorate %42 RelaxedPrecision
+               OpDecorate %44 RelaxedPrecision
+               OpDecorate %44 Location 0
+               OpDecorate %45 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Input %6
+          %8 = OpVariable %7 Input
+         %10 = OpConstant %6 0
+         %11 = OpTypeBool
+         %15 = OpTypeVector %6 4
+         %16 = OpTypePointer Function %15
+         %23 = OpConstant %6 0.5
+         %32 = OpConstant %6 -0.5
+         %33 = OpConstant %6 -0.300000012
+         %43 = OpTypePointer Output %15
+         %44 = OpVariable %43 Output
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %9 = OpLoad %6 %8
+         %12 = OpFOrdLessThanEqual %11 %9 %10
+               OpSelectionMerge %14 None
+               OpBranchConditional %12 %28 %13
+         %13 = OpLabel
+         %18 = OpLoad %6 %8
+         %19 = OpExtInst %6 %1 Sqrt %18
+         %20 = OpLoad %6 %8
+         %21 = OpExtInst %6 %1 FSign %20
+         %22 = OpLoad %6 %8
+         %24 = OpExtInst %6 %1 FMax %22 %23
+         %25 = OpLoad %6 %8
+         %26 = OpExtInst %6 %1 Floor %25
+         %27 = OpCompositeConstruct %15 %19 %21 %24 %26
+               OpBranch %14
+         %28 = OpLabel
+         %29 = OpLoad %6 %8
+         %30 = OpExtInst %6 %1 Log %29
+         %31 = OpLoad %6 %8
+         %34 = OpExtInst %6 %1 FClamp %31 %32 %33
+         %35 = OpFMul %6 %30 %34
+         %36 = OpLoad %6 %8
+         %37 = OpExtInst %6 %1 Sin %36
+         %38 = OpLoad %6 %8
+         %39 = OpExtInst %6 %1 Cos %38
+         %40 = OpLoad %6 %8
+         %41 = OpExtInst %6 %1 Exp %40
+         %42 = OpCompositeConstruct %15 %35 %37 %39 %41
+               OpBranch %14
+         %14 = OpLabel
+         %45 = OpPhi %15 %27 %13 %42 %28
+               OpStore %44 %45
+               OpReturn
+               OpFunctionEnd
+
+)";
+
+TEST(DiffTest, ReorderedIfBlocks) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 46
++; Bound: 47
+ ; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %8 %44
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %8 "v"
+ OpName %44 "color"
+ OpDecorate %8 RelaxedPrecision
+ OpDecorate %8 Location 0
+ OpDecorate %9 RelaxedPrecision
+ OpDecorate %18 RelaxedPrecision
+ OpDecorate %19 RelaxedPrecision
+ OpDecorate %20 RelaxedPrecision
+ OpDecorate %23 RelaxedPrecision
+ OpDecorate %24 RelaxedPrecision
+ OpDecorate %25 RelaxedPrecision
+ OpDecorate %26 RelaxedPrecision
+ OpDecorate %27 RelaxedPrecision
+ OpDecorate %28 RelaxedPrecision
+ OpDecorate %29 RelaxedPrecision
+ OpDecorate %30 RelaxedPrecision
+ OpDecorate %31 RelaxedPrecision
+ OpDecorate %33 RelaxedPrecision
+ OpDecorate %34 RelaxedPrecision
+ OpDecorate %35 RelaxedPrecision
+ OpDecorate %36 RelaxedPrecision
+ OpDecorate %37 RelaxedPrecision
+ OpDecorate %39 RelaxedPrecision
+ OpDecorate %40 RelaxedPrecision
+ OpDecorate %41 RelaxedPrecision
+ OpDecorate %42 RelaxedPrecision
+ OpDecorate %44 RelaxedPrecision
+ OpDecorate %44 Location 0
+ OpDecorate %45 RelaxedPrecision
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypePointer Input %6
+ %8 = OpVariable %7 Input
+ %10 = OpConstant %6 0
+ %11 = OpTypeBool
+ %15 = OpTypeVector %6 4
+ %16 = OpTypePointer Function %15
+ %21 = OpConstant %6 -0.5
+ %22 = OpConstant %6 -0.300000012
+ %38 = OpConstant %6 0.5
+ %43 = OpTypePointer Output %15
+ %44 = OpVariable %43 Output
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %9 = OpLoad %6 %8
+ %12 = OpFOrdLessThanEqual %11 %9 %10
+ OpSelectionMerge %14 None
+ OpBranchConditional %12 %13 %32
+ %32 = OpLabel
+ %33 = OpLoad %6 %8
+ %34 = OpExtInst %6 %1 Sqrt %33
+ %35 = OpLoad %6 %8
+ %36 = OpExtInst %6 %1 FSign %35
+ %37 = OpLoad %6 %8
+ %39 = OpExtInst %6 %1 FMax %37 %38
+ %40 = OpLoad %6 %8
+ %41 = OpExtInst %6 %1 Floor %40
+ %42 = OpCompositeConstruct %15 %34 %36 %39 %41
+ OpBranch %14
+ %13 = OpLabel
+ %18 = OpLoad %6 %8
+ %19 = OpExtInst %6 %1 Log %18
+ %20 = OpLoad %6 %8
+ %23 = OpExtInst %6 %1 FClamp %20 %21 %22
+ %24 = OpFMul %6 %19 %23
+ %25 = OpLoad %6 %8
+ %26 = OpExtInst %6 %1 Sin %25
+ %27 = OpLoad %6 %8
+ %28 = OpExtInst %6 %1 Cos %27
+ %29 = OpLoad %6 %8
+ %30 = OpExtInst %6 %1 Exp %29
+ %31 = OpCompositeConstruct %15 %24 %26 %28 %30
+ OpBranch %14
+ %14 = OpLabel
+-%45 = OpPhi %15 %31 %13 %42 %32
++%45 = OpPhi %15 %42 %32 %31 %13
+ OpStore %44 %45
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, ReorderedIfBlocksNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %8 %44
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %8 Location 0
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %18 RelaxedPrecision
+               OpDecorate %19 RelaxedPrecision
+               OpDecorate %20 RelaxedPrecision
+               OpDecorate %23 RelaxedPrecision
+               OpDecorate %24 RelaxedPrecision
+               OpDecorate %25 RelaxedPrecision
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %27 RelaxedPrecision
+               OpDecorate %28 RelaxedPrecision
+               OpDecorate %29 RelaxedPrecision
+               OpDecorate %30 RelaxedPrecision
+               OpDecorate %31 RelaxedPrecision
+               OpDecorate %33 RelaxedPrecision
+               OpDecorate %34 RelaxedPrecision
+               OpDecorate %35 RelaxedPrecision
+               OpDecorate %36 RelaxedPrecision
+               OpDecorate %37 RelaxedPrecision
+               OpDecorate %39 RelaxedPrecision
+               OpDecorate %40 RelaxedPrecision
+               OpDecorate %41 RelaxedPrecision
+               OpDecorate %42 RelaxedPrecision
+               OpDecorate %44 RelaxedPrecision
+               OpDecorate %44 Location 0
+               OpDecorate %45 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Input %6
+          %8 = OpVariable %7 Input
+         %10 = OpConstant %6 0
+         %11 = OpTypeBool
+         %15 = OpTypeVector %6 4
+         %16 = OpTypePointer Function %15
+         %21 = OpConstant %6 -0.5
+         %22 = OpConstant %6 -0.300000012
+         %38 = OpConstant %6 0.5
+         %43 = OpTypePointer Output %15
+         %44 = OpVariable %43 Output
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %9 = OpLoad %6 %8
+         %12 = OpFOrdLessThanEqual %11 %9 %10
+               OpSelectionMerge %14 None
+               OpBranchConditional %12 %13 %32
+         %13 = OpLabel
+         %18 = OpLoad %6 %8
+         %19 = OpExtInst %6 %1 Log %18
+         %20 = OpLoad %6 %8
+         %23 = OpExtInst %6 %1 FClamp %20 %21 %22
+         %24 = OpFMul %6 %19 %23
+         %25 = OpLoad %6 %8
+         %26 = OpExtInst %6 %1 Sin %25
+         %27 = OpLoad %6 %8
+         %28 = OpExtInst %6 %1 Cos %27
+         %29 = OpLoad %6 %8
+         %30 = OpExtInst %6 %1 Exp %29
+         %31 = OpCompositeConstruct %15 %24 %26 %28 %30
+               OpBranch %14
+         %32 = OpLabel
+         %33 = OpLoad %6 %8
+         %34 = OpExtInst %6 %1 Sqrt %33
+         %35 = OpLoad %6 %8
+         %36 = OpExtInst %6 %1 FSign %35
+         %37 = OpLoad %6 %8
+         %39 = OpExtInst %6 %1 FMax %37 %38
+         %40 = OpLoad %6 %8
+         %41 = OpExtInst %6 %1 Floor %40
+         %42 = OpCompositeConstruct %15 %34 %36 %39 %41
+               OpBranch %14
+         %14 = OpLabel
+         %45 = OpPhi %15 %31 %13 %42 %32
+               OpStore %44 %45
+               OpReturn
+               OpFunctionEnd
+)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %8 %44
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %8 Location 0
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %18 RelaxedPrecision
+               OpDecorate %19 RelaxedPrecision
+               OpDecorate %20 RelaxedPrecision
+               OpDecorate %21 RelaxedPrecision
+               OpDecorate %22 RelaxedPrecision
+               OpDecorate %24 RelaxedPrecision
+               OpDecorate %25 RelaxedPrecision
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %27 RelaxedPrecision
+               OpDecorate %29 RelaxedPrecision
+               OpDecorate %30 RelaxedPrecision
+               OpDecorate %31 RelaxedPrecision
+               OpDecorate %34 RelaxedPrecision
+               OpDecorate %35 RelaxedPrecision
+               OpDecorate %36 RelaxedPrecision
+               OpDecorate %37 RelaxedPrecision
+               OpDecorate %38 RelaxedPrecision
+               OpDecorate %39 RelaxedPrecision
+               OpDecorate %40 RelaxedPrecision
+               OpDecorate %41 RelaxedPrecision
+               OpDecorate %42 RelaxedPrecision
+               OpDecorate %44 RelaxedPrecision
+               OpDecorate %44 Location 0
+               OpDecorate %45 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Input %6
+          %8 = OpVariable %7 Input
+         %10 = OpConstant %6 0
+         %11 = OpTypeBool
+         %15 = OpTypeVector %6 4
+         %16 = OpTypePointer Function %15
+         %23 = OpConstant %6 0.5
+         %32 = OpConstant %6 -0.5
+         %33 = OpConstant %6 -0.300000012
+         %43 = OpTypePointer Output %15
+         %44 = OpVariable %43 Output
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %9 = OpLoad %6 %8
+         %12 = OpFOrdLessThanEqual %11 %9 %10
+               OpSelectionMerge %14 None
+               OpBranchConditional %12 %28 %13
+         %13 = OpLabel
+         %18 = OpLoad %6 %8
+         %19 = OpExtInst %6 %1 Sqrt %18
+         %20 = OpLoad %6 %8
+         %21 = OpExtInst %6 %1 FSign %20
+         %22 = OpLoad %6 %8
+         %24 = OpExtInst %6 %1 FMax %22 %23
+         %25 = OpLoad %6 %8
+         %26 = OpExtInst %6 %1 Floor %25
+         %27 = OpCompositeConstruct %15 %19 %21 %24 %26
+               OpBranch %14
+         %28 = OpLabel
+         %29 = OpLoad %6 %8
+         %30 = OpExtInst %6 %1 Log %29
+         %31 = OpLoad %6 %8
+         %34 = OpExtInst %6 %1 FClamp %31 %32 %33
+         %35 = OpFMul %6 %30 %34
+         %36 = OpLoad %6 %8
+         %37 = OpExtInst %6 %1 Sin %36
+         %38 = OpLoad %6 %8
+         %39 = OpExtInst %6 %1 Cos %38
+         %40 = OpLoad %6 %8
+         %41 = OpExtInst %6 %1 Exp %40
+         %42 = OpCompositeConstruct %15 %35 %37 %39 %41
+               OpBranch %14
+         %14 = OpLabel
+         %45 = OpPhi %15 %27 %13 %42 %28
+               OpStore %44 %45
+               OpReturn
+               OpFunctionEnd
+
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 46
++; Bound: 47
+ ; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %8 %44
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpDecorate %8 RelaxedPrecision
+ OpDecorate %8 Location 0
+ OpDecorate %9 RelaxedPrecision
+ OpDecorate %18 RelaxedPrecision
+ OpDecorate %19 RelaxedPrecision
+ OpDecorate %20 RelaxedPrecision
+ OpDecorate %23 RelaxedPrecision
+ OpDecorate %24 RelaxedPrecision
+ OpDecorate %25 RelaxedPrecision
+ OpDecorate %26 RelaxedPrecision
+ OpDecorate %27 RelaxedPrecision
+ OpDecorate %28 RelaxedPrecision
+ OpDecorate %29 RelaxedPrecision
+ OpDecorate %30 RelaxedPrecision
+ OpDecorate %31 RelaxedPrecision
+ OpDecorate %33 RelaxedPrecision
+ OpDecorate %34 RelaxedPrecision
+ OpDecorate %35 RelaxedPrecision
+ OpDecorate %36 RelaxedPrecision
+ OpDecorate %37 RelaxedPrecision
+ OpDecorate %39 RelaxedPrecision
+ OpDecorate %40 RelaxedPrecision
+ OpDecorate %41 RelaxedPrecision
+ OpDecorate %42 RelaxedPrecision
+ OpDecorate %44 RelaxedPrecision
+ OpDecorate %44 Location 0
+ OpDecorate %45 RelaxedPrecision
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypePointer Input %6
+ %8 = OpVariable %7 Input
+ %10 = OpConstant %6 0
+ %11 = OpTypeBool
+ %15 = OpTypeVector %6 4
+ %16 = OpTypePointer Function %15
+ %21 = OpConstant %6 -0.5
+ %22 = OpConstant %6 -0.300000012
+ %38 = OpConstant %6 0.5
+ %43 = OpTypePointer Output %15
+ %44 = OpVariable %43 Output
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %9 = OpLoad %6 %8
+ %12 = OpFOrdLessThanEqual %11 %9 %10
+ OpSelectionMerge %14 None
+ OpBranchConditional %12 %13 %32
+ %32 = OpLabel
+ %33 = OpLoad %6 %8
+ %34 = OpExtInst %6 %1 Sqrt %33
+ %35 = OpLoad %6 %8
+ %36 = OpExtInst %6 %1 FSign %35
+ %37 = OpLoad %6 %8
+ %39 = OpExtInst %6 %1 FMax %37 %38
+ %40 = OpLoad %6 %8
+ %41 = OpExtInst %6 %1 Floor %40
+ %42 = OpCompositeConstruct %15 %34 %36 %39 %41
+ OpBranch %14
+ %13 = OpLabel
+ %18 = OpLoad %6 %8
+ %19 = OpExtInst %6 %1 Log %18
+ %20 = OpLoad %6 %8
+ %23 = OpExtInst %6 %1 FClamp %20 %21 %22
+ %24 = OpFMul %6 %19 %23
+ %25 = OpLoad %6 %8
+ %26 = OpExtInst %6 %1 Sin %25
+ %27 = OpLoad %6 %8
+ %28 = OpExtInst %6 %1 Cos %27
+ %29 = OpLoad %6 %8
+ %30 = OpExtInst %6 %1 Exp %29
+ %31 = OpCompositeConstruct %15 %24 %26 %28 %30
+ OpBranch %14
+ %14 = OpLabel
+-%45 = OpPhi %15 %31 %13 %42 %32
++%45 = OpPhi %15 %42 %32 %31 %13
+ OpStore %44 %45
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/reordered_if_blocks_dst.spvasm b/test/diff/diff_files/reordered_if_blocks_dst.spvasm
new file mode 100644
index 0000000..cd1d6d5
--- /dev/null
+++ b/test/diff/diff_files/reordered_if_blocks_dst.spvasm
@@ -0,0 +1,87 @@
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %8 %44
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "v"
+               OpName %44 "color"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %8 Location 0
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %18 RelaxedPrecision
+               OpDecorate %19 RelaxedPrecision
+               OpDecorate %20 RelaxedPrecision
+               OpDecorate %21 RelaxedPrecision
+               OpDecorate %22 RelaxedPrecision
+               OpDecorate %24 RelaxedPrecision
+               OpDecorate %25 RelaxedPrecision
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %27 RelaxedPrecision
+               OpDecorate %29 RelaxedPrecision
+               OpDecorate %30 RelaxedPrecision
+               OpDecorate %31 RelaxedPrecision
+               OpDecorate %34 RelaxedPrecision
+               OpDecorate %35 RelaxedPrecision
+               OpDecorate %36 RelaxedPrecision
+               OpDecorate %37 RelaxedPrecision
+               OpDecorate %38 RelaxedPrecision
+               OpDecorate %39 RelaxedPrecision
+               OpDecorate %40 RelaxedPrecision
+               OpDecorate %41 RelaxedPrecision
+               OpDecorate %42 RelaxedPrecision
+               OpDecorate %44 RelaxedPrecision
+               OpDecorate %44 Location 0
+               OpDecorate %45 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Input %6
+          %8 = OpVariable %7 Input
+         %10 = OpConstant %6 0
+         %11 = OpTypeBool
+         %15 = OpTypeVector %6 4
+         %16 = OpTypePointer Function %15
+         %23 = OpConstant %6 0.5
+         %32 = OpConstant %6 -0.5
+         %33 = OpConstant %6 -0.300000012
+         %43 = OpTypePointer Output %15
+         %44 = OpVariable %43 Output
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %9 = OpLoad %6 %8
+         %12 = OpFOrdLessThanEqual %11 %9 %10
+               OpSelectionMerge %14 None
+               OpBranchConditional %12 %28 %13
+         %13 = OpLabel
+         %18 = OpLoad %6 %8
+         %19 = OpExtInst %6 %1 Sqrt %18
+         %20 = OpLoad %6 %8
+         %21 = OpExtInst %6 %1 FSign %20
+         %22 = OpLoad %6 %8
+         %24 = OpExtInst %6 %1 FMax %22 %23
+         %25 = OpLoad %6 %8
+         %26 = OpExtInst %6 %1 Floor %25
+         %27 = OpCompositeConstruct %15 %19 %21 %24 %26
+               OpBranch %14
+         %28 = OpLabel
+         %29 = OpLoad %6 %8
+         %30 = OpExtInst %6 %1 Log %29
+         %31 = OpLoad %6 %8
+         %34 = OpExtInst %6 %1 FClamp %31 %32 %33
+         %35 = OpFMul %6 %30 %34
+         %36 = OpLoad %6 %8
+         %37 = OpExtInst %6 %1 Sin %36
+         %38 = OpLoad %6 %8
+         %39 = OpExtInst %6 %1 Cos %38
+         %40 = OpLoad %6 %8
+         %41 = OpExtInst %6 %1 Exp %40
+         %42 = OpCompositeConstruct %15 %35 %37 %39 %41
+               OpBranch %14
+         %14 = OpLabel
+         %45 = OpPhi %15 %27 %13 %42 %28
+               OpStore %44 %45
+               OpReturn
+               OpFunctionEnd
+
diff --git a/test/diff/diff_files/reordered_if_blocks_src.spvasm b/test/diff/diff_files/reordered_if_blocks_src.spvasm
new file mode 100644
index 0000000..209cb45
--- /dev/null
+++ b/test/diff/diff_files/reordered_if_blocks_src.spvasm
@@ -0,0 +1,87 @@
+;; Test where src and dst have the true and false blocks of an if reordered.
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %8 %44
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "v"
+               OpName %44 "color"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %8 Location 0
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %18 RelaxedPrecision
+               OpDecorate %19 RelaxedPrecision
+               OpDecorate %20 RelaxedPrecision
+               OpDecorate %23 RelaxedPrecision
+               OpDecorate %24 RelaxedPrecision
+               OpDecorate %25 RelaxedPrecision
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %27 RelaxedPrecision
+               OpDecorate %28 RelaxedPrecision
+               OpDecorate %29 RelaxedPrecision
+               OpDecorate %30 RelaxedPrecision
+               OpDecorate %31 RelaxedPrecision
+               OpDecorate %33 RelaxedPrecision
+               OpDecorate %34 RelaxedPrecision
+               OpDecorate %35 RelaxedPrecision
+               OpDecorate %36 RelaxedPrecision
+               OpDecorate %37 RelaxedPrecision
+               OpDecorate %39 RelaxedPrecision
+               OpDecorate %40 RelaxedPrecision
+               OpDecorate %41 RelaxedPrecision
+               OpDecorate %42 RelaxedPrecision
+               OpDecorate %44 RelaxedPrecision
+               OpDecorate %44 Location 0
+               OpDecorate %45 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Input %6
+          %8 = OpVariable %7 Input
+         %10 = OpConstant %6 0
+         %11 = OpTypeBool
+         %15 = OpTypeVector %6 4
+         %16 = OpTypePointer Function %15
+         %21 = OpConstant %6 -0.5
+         %22 = OpConstant %6 -0.300000012
+         %38 = OpConstant %6 0.5
+         %43 = OpTypePointer Output %15
+         %44 = OpVariable %43 Output
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %9 = OpLoad %6 %8
+         %12 = OpFOrdLessThanEqual %11 %9 %10
+               OpSelectionMerge %14 None
+               OpBranchConditional %12 %13 %32
+         %13 = OpLabel
+         %18 = OpLoad %6 %8
+         %19 = OpExtInst %6 %1 Log %18
+         %20 = OpLoad %6 %8
+         %23 = OpExtInst %6 %1 FClamp %20 %21 %22
+         %24 = OpFMul %6 %19 %23
+         %25 = OpLoad %6 %8
+         %26 = OpExtInst %6 %1 Sin %25
+         %27 = OpLoad %6 %8
+         %28 = OpExtInst %6 %1 Cos %27
+         %29 = OpLoad %6 %8
+         %30 = OpExtInst %6 %1 Exp %29
+         %31 = OpCompositeConstruct %15 %24 %26 %28 %30
+               OpBranch %14
+         %32 = OpLabel
+         %33 = OpLoad %6 %8
+         %34 = OpExtInst %6 %1 Sqrt %33
+         %35 = OpLoad %6 %8
+         %36 = OpExtInst %6 %1 FSign %35
+         %37 = OpLoad %6 %8
+         %39 = OpExtInst %6 %1 FMax %37 %38
+         %40 = OpLoad %6 %8
+         %41 = OpExtInst %6 %1 Floor %40
+         %42 = OpCompositeConstruct %15 %34 %36 %39 %41
+               OpBranch %14
+         %14 = OpLabel
+         %45 = OpPhi %15 %31 %13 %42 %32
+               OpStore %44 %45
+               OpReturn
+               OpFunctionEnd
diff --git a/test/diff/diff_files/reordered_switch_blocks_autogen.cpp b/test/diff/diff_files/reordered_switch_blocks_autogen.cpp
new file mode 100644
index 0000000..c0ba48d
--- /dev/null
+++ b/test/diff/diff_files/reordered_switch_blocks_autogen.cpp
@@ -0,0 +1,582 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Test where src and dst have cases of a switch in different order.
+constexpr char kSrc[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %7 "BufferIn"
+               OpMemberName %7 0 "i"
+               OpName %9 ""
+               OpName %23 "BufferOut"
+               OpMemberName %23 0 "o"
+               OpName %25 ""
+               OpMemberDecorate %7 0 Offset 0
+               OpDecorate %7 Block
+               OpDecorate %9 DescriptorSet 0
+               OpDecorate %9 Binding 0
+               OpMemberDecorate %23 0 Offset 0
+               OpDecorate %23 BufferBlock
+               OpDecorate %25 DescriptorSet 0
+               OpDecorate %25 Binding 1
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 0
+          %7 = OpTypeStruct %6
+          %8 = OpTypePointer Uniform %7
+          %9 = OpVariable %8 Uniform
+         %10 = OpTypeInt 32 1
+         %11 = OpConstant %10 0
+         %12 = OpTypePointer Uniform %6
+         %23 = OpTypeStruct %6
+         %24 = OpTypePointer Uniform %23
+         %25 = OpVariable %24 Uniform
+         %28 = OpConstant %10 1
+         %34 = OpConstant %6 2
+         %52 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %13 = OpAccessChain %12 %9 %11
+         %14 = OpLoad %6 %13
+               OpSelectionMerge %22 None
+               OpSwitch %14 %21 0 %15 1 %16 2 %17 3 %18 4 %19 5 %20
+         %21 = OpLabel
+         %54 = OpAccessChain %12 %25 %11
+         %55 = OpLoad %6 %54
+         %56 = OpIAdd %6 %55 %34
+         %57 = OpAccessChain %12 %25 %11
+               OpStore %57 %56
+               OpBranch %22
+         %15 = OpLabel
+         %26 = OpAccessChain %12 %25 %11
+         %27 = OpLoad %6 %26
+         %29 = OpIAdd %6 %27 %28
+               OpStore %26 %29
+               OpBranch %22
+         %16 = OpLabel
+         %31 = OpAccessChain %12 %25 %11
+         %32 = OpLoad %6 %31
+         %33 = OpISub %6 %32 %28
+               OpStore %31 %33
+               OpBranch %17
+         %17 = OpLabel
+         %35 = OpAccessChain %12 %25 %11
+         %36 = OpLoad %6 %35
+         %37 = OpIMul %6 %36 %34
+         %38 = OpAccessChain %12 %25 %11
+               OpStore %38 %37
+               OpBranch %22
+         %18 = OpLabel
+         %40 = OpAccessChain %12 %25 %11
+         %41 = OpLoad %6 %40
+         %42 = OpUDiv %6 %41 %34
+         %43 = OpAccessChain %12 %25 %11
+               OpStore %43 %42
+               OpBranch %22
+         %19 = OpLabel
+         %45 = OpAccessChain %12 %25 %11
+         %46 = OpLoad %6 %45
+         %47 = OpAccessChain %12 %25 %11
+         %48 = OpLoad %6 %47
+         %49 = OpIMul %6 %46 %48
+         %50 = OpAccessChain %12 %25 %11
+               OpStore %50 %49
+               OpBranch %22
+         %20 = OpLabel
+         %53 = OpAccessChain %12 %25 %11
+               OpStore %53 %52
+               OpBranch %21
+         %22 = OpLabel
+               OpReturn
+               OpFunctionEnd)";
+constexpr char kDst[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %7 "BufferIn"
+               OpMemberName %7 0 "i"
+               OpName %9 ""
+               OpName %23 "BufferOut"
+               OpMemberName %23 0 "o"
+               OpName %25 ""
+               OpMemberDecorate %7 0 Offset 0
+               OpDecorate %7 Block
+               OpDecorate %9 DescriptorSet 0
+               OpDecorate %9 Binding 0
+               OpMemberDecorate %23 0 Offset 0
+               OpDecorate %23 BufferBlock
+               OpDecorate %25 DescriptorSet 0
+               OpDecorate %25 Binding 1
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 0
+          %7 = OpTypeStruct %6
+          %8 = OpTypePointer Uniform %7
+          %9 = OpVariable %8 Uniform
+         %10 = OpTypeInt 32 1
+         %11 = OpConstant %10 0
+         %12 = OpTypePointer Uniform %6
+         %23 = OpTypeStruct %6
+         %24 = OpTypePointer Uniform %23
+         %25 = OpVariable %24 Uniform
+         %28 = OpConstant %10 1
+         %34 = OpConstant %6 2
+         %52 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %13 = OpAccessChain %12 %9 %11
+         %14 = OpLoad %6 %13
+               OpSelectionMerge %22 None
+               OpSwitch %14 %21 0 %15 1 %16 2 %17 3 %18 4 %19 5 %20
+         %17 = OpLabel
+         %35 = OpAccessChain %12 %25 %11
+         %36 = OpLoad %6 %35
+         %37 = OpIMul %6 %36 %34
+         %38 = OpAccessChain %12 %25 %11
+               OpStore %38 %37
+               OpBranch %22
+         %18 = OpLabel
+         %40 = OpAccessChain %12 %25 %11
+         %41 = OpLoad %6 %40
+         %42 = OpUDiv %6 %41 %34
+         %43 = OpAccessChain %12 %25 %11
+               OpStore %43 %42
+               OpBranch %22
+         %21 = OpLabel
+         %54 = OpAccessChain %12 %25 %11
+         %55 = OpLoad %6 %54
+         %56 = OpIAdd %6 %55 %34
+         %57 = OpAccessChain %12 %25 %11
+               OpStore %57 %56
+               OpBranch %22
+         %20 = OpLabel
+         %53 = OpAccessChain %12 %25 %11
+               OpStore %53 %52
+               OpBranch %21
+         %15 = OpLabel
+         %26 = OpAccessChain %12 %25 %11
+         %27 = OpLoad %6 %26
+         %29 = OpIAdd %6 %27 %28
+               OpStore %26 %29
+               OpBranch %22
+         %19 = OpLabel
+         %45 = OpAccessChain %12 %25 %11
+         %46 = OpLoad %6 %45
+         %47 = OpAccessChain %12 %25 %11
+         %48 = OpLoad %6 %47
+         %49 = OpIMul %6 %46 %48
+         %50 = OpAccessChain %12 %25 %11
+               OpStore %50 %49
+               OpBranch %22
+         %16 = OpLabel
+         %31 = OpAccessChain %12 %25 %11
+         %32 = OpLoad %6 %31
+         %33 = OpISub %6 %32 %28
+               OpStore %31 %33
+               OpBranch %17
+         %22 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+TEST(DiffTest, ReorderedSwitchBlocks) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 58
++; Bound: 62
+ ; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %4 "main"
+ OpExecutionMode %4 LocalSize 1 1 1
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %7 "BufferIn"
+ OpMemberName %7 0 "i"
+ OpName %9 ""
+ OpName %23 "BufferOut"
+ OpMemberName %23 0 "o"
+ OpName %25 ""
+ OpMemberDecorate %7 0 Offset 0
+ OpDecorate %7 Block
+ OpDecorate %9 DescriptorSet 0
+ OpDecorate %9 Binding 0
+ OpMemberDecorate %23 0 Offset 0
+ OpDecorate %23 BufferBlock
+ OpDecorate %25 DescriptorSet 0
+ OpDecorate %25 Binding 1
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 0
+ %7 = OpTypeStruct %6
+ %8 = OpTypePointer Uniform %7
+ %9 = OpVariable %8 Uniform
+ %10 = OpTypeInt 32 1
+ %11 = OpConstant %10 0
+ %12 = OpTypePointer Uniform %6
+ %23 = OpTypeStruct %6
+ %24 = OpTypePointer Uniform %23
+ %25 = OpVariable %24 Uniform
+ %28 = OpConstant %10 1
+ %34 = OpConstant %6 2
+ %52 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %13 = OpAccessChain %12 %9 %11
+ %14 = OpLoad %6 %13
+ OpSelectionMerge %22 None
+ OpSwitch %14 %21 0 %15 1 %16 2 %17 3 %18 4 %19 5 %20
+ %20 = OpLabel
+ %53 = OpAccessChain %12 %25 %11
+ OpStore %53 %52
+ OpBranch %21
+ %19 = OpLabel
+ %45 = OpAccessChain %12 %25 %11
+ %46 = OpLoad %6 %45
+ %47 = OpAccessChain %12 %25 %11
+ %48 = OpLoad %6 %47
+ %49 = OpIMul %6 %46 %48
+ %50 = OpAccessChain %12 %25 %11
+ OpStore %50 %49
+ OpBranch %22
+ %18 = OpLabel
+ %40 = OpAccessChain %12 %25 %11
+ %41 = OpLoad %6 %40
+ %42 = OpUDiv %6 %41 %34
+ %43 = OpAccessChain %12 %25 %11
+ OpStore %43 %42
+ OpBranch %22
+ %16 = OpLabel
+ %31 = OpAccessChain %12 %25 %11
+ %32 = OpLoad %6 %31
+ %33 = OpISub %6 %32 %28
+ OpStore %31 %33
+ OpBranch %17
+ %17 = OpLabel
+ %35 = OpAccessChain %12 %25 %11
+ %36 = OpLoad %6 %35
+ %37 = OpIMul %6 %36 %34
+ %38 = OpAccessChain %12 %25 %11
+ OpStore %38 %37
+ OpBranch %22
+ %15 = OpLabel
+ %26 = OpAccessChain %12 %25 %11
+ %27 = OpLoad %6 %26
+ %29 = OpIAdd %6 %27 %28
+ OpStore %26 %29
+ OpBranch %22
+ %21 = OpLabel
+ %54 = OpAccessChain %12 %25 %11
+ %55 = OpLoad %6 %54
+ %56 = OpIAdd %6 %55 %34
+ %57 = OpAccessChain %12 %25 %11
+ OpStore %57 %56
+ OpBranch %22
+ %22 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, ReorderedSwitchBlocksNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpMemberDecorate %7 0 Offset 0
+               OpDecorate %7 Block
+               OpDecorate %9 DescriptorSet 0
+               OpDecorate %9 Binding 0
+               OpMemberDecorate %23 0 Offset 0
+               OpDecorate %23 BufferBlock
+               OpDecorate %25 DescriptorSet 0
+               OpDecorate %25 Binding 1
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 0
+          %7 = OpTypeStruct %6
+          %8 = OpTypePointer Uniform %7
+          %9 = OpVariable %8 Uniform
+         %10 = OpTypeInt 32 1
+         %11 = OpConstant %10 0
+         %12 = OpTypePointer Uniform %6
+         %23 = OpTypeStruct %6
+         %24 = OpTypePointer Uniform %23
+         %25 = OpVariable %24 Uniform
+         %28 = OpConstant %10 1
+         %34 = OpConstant %6 2
+         %52 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %13 = OpAccessChain %12 %9 %11
+         %14 = OpLoad %6 %13
+               OpSelectionMerge %22 None
+               OpSwitch %14 %21 0 %15 1 %16 2 %17 3 %18 4 %19 5 %20
+         %21 = OpLabel
+         %54 = OpAccessChain %12 %25 %11
+         %55 = OpLoad %6 %54
+         %56 = OpIAdd %6 %55 %34
+         %57 = OpAccessChain %12 %25 %11
+               OpStore %57 %56
+               OpBranch %22
+         %15 = OpLabel
+         %26 = OpAccessChain %12 %25 %11
+         %27 = OpLoad %6 %26
+         %29 = OpIAdd %6 %27 %28
+               OpStore %26 %29
+               OpBranch %22
+         %16 = OpLabel
+         %31 = OpAccessChain %12 %25 %11
+         %32 = OpLoad %6 %31
+         %33 = OpISub %6 %32 %28
+               OpStore %31 %33
+               OpBranch %17
+         %17 = OpLabel
+         %35 = OpAccessChain %12 %25 %11
+         %36 = OpLoad %6 %35
+         %37 = OpIMul %6 %36 %34
+         %38 = OpAccessChain %12 %25 %11
+               OpStore %38 %37
+               OpBranch %22
+         %18 = OpLabel
+         %40 = OpAccessChain %12 %25 %11
+         %41 = OpLoad %6 %40
+         %42 = OpUDiv %6 %41 %34
+         %43 = OpAccessChain %12 %25 %11
+               OpStore %43 %42
+               OpBranch %22
+         %19 = OpLabel
+         %45 = OpAccessChain %12 %25 %11
+         %46 = OpLoad %6 %45
+         %47 = OpAccessChain %12 %25 %11
+         %48 = OpLoad %6 %47
+         %49 = OpIMul %6 %46 %48
+         %50 = OpAccessChain %12 %25 %11
+               OpStore %50 %49
+               OpBranch %22
+         %20 = OpLabel
+         %53 = OpAccessChain %12 %25 %11
+               OpStore %53 %52
+               OpBranch %21
+         %22 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpMemberDecorate %7 0 Offset 0
+               OpDecorate %7 Block
+               OpDecorate %9 DescriptorSet 0
+               OpDecorate %9 Binding 0
+               OpMemberDecorate %23 0 Offset 0
+               OpDecorate %23 BufferBlock
+               OpDecorate %25 DescriptorSet 0
+               OpDecorate %25 Binding 1
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 0
+          %7 = OpTypeStruct %6
+          %8 = OpTypePointer Uniform %7
+          %9 = OpVariable %8 Uniform
+         %10 = OpTypeInt 32 1
+         %11 = OpConstant %10 0
+         %12 = OpTypePointer Uniform %6
+         %23 = OpTypeStruct %6
+         %24 = OpTypePointer Uniform %23
+         %25 = OpVariable %24 Uniform
+         %28 = OpConstant %10 1
+         %34 = OpConstant %6 2
+         %52 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %13 = OpAccessChain %12 %9 %11
+         %14 = OpLoad %6 %13
+               OpSelectionMerge %22 None
+               OpSwitch %14 %21 0 %15 1 %16 2 %17 3 %18 4 %19 5 %20
+         %17 = OpLabel
+         %35 = OpAccessChain %12 %25 %11
+         %36 = OpLoad %6 %35
+         %37 = OpIMul %6 %36 %34
+         %38 = OpAccessChain %12 %25 %11
+               OpStore %38 %37
+               OpBranch %22
+         %18 = OpLabel
+         %40 = OpAccessChain %12 %25 %11
+         %41 = OpLoad %6 %40
+         %42 = OpUDiv %6 %41 %34
+         %43 = OpAccessChain %12 %25 %11
+               OpStore %43 %42
+               OpBranch %22
+         %21 = OpLabel
+         %54 = OpAccessChain %12 %25 %11
+         %55 = OpLoad %6 %54
+         %56 = OpIAdd %6 %55 %34
+         %57 = OpAccessChain %12 %25 %11
+               OpStore %57 %56
+               OpBranch %22
+         %20 = OpLabel
+         %53 = OpAccessChain %12 %25 %11
+               OpStore %53 %52
+               OpBranch %21
+         %15 = OpLabel
+         %26 = OpAccessChain %12 %25 %11
+         %27 = OpLoad %6 %26
+         %29 = OpIAdd %6 %27 %28
+               OpStore %26 %29
+               OpBranch %22
+         %19 = OpLabel
+         %45 = OpAccessChain %12 %25 %11
+         %46 = OpLoad %6 %45
+         %47 = OpAccessChain %12 %25 %11
+         %48 = OpLoad %6 %47
+         %49 = OpIMul %6 %46 %48
+         %50 = OpAccessChain %12 %25 %11
+               OpStore %50 %49
+               OpBranch %22
+         %16 = OpLabel
+         %31 = OpAccessChain %12 %25 %11
+         %32 = OpLoad %6 %31
+         %33 = OpISub %6 %32 %28
+               OpStore %31 %33
+               OpBranch %17
+         %22 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 58
++; Bound: 62
+ ; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %4 "main"
+ OpExecutionMode %4 LocalSize 1 1 1
+ OpSource ESSL 310
+ OpMemberDecorate %7 0 Offset 0
+ OpDecorate %7 Block
+ OpDecorate %9 DescriptorSet 0
+ OpDecorate %9 Binding 0
+ OpMemberDecorate %23 0 Offset 0
+ OpDecorate %23 BufferBlock
+ OpDecorate %25 DescriptorSet 0
+ OpDecorate %25 Binding 1
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 0
+ %7 = OpTypeStruct %6
+ %8 = OpTypePointer Uniform %7
+ %9 = OpVariable %8 Uniform
+ %10 = OpTypeInt 32 1
+ %11 = OpConstant %10 0
+ %12 = OpTypePointer Uniform %6
+ %23 = OpTypeStruct %6
+ %24 = OpTypePointer Uniform %23
+ %25 = OpVariable %24 Uniform
+ %28 = OpConstant %10 1
+ %34 = OpConstant %6 2
+ %52 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %13 = OpAccessChain %12 %9 %11
+ %14 = OpLoad %6 %13
+ OpSelectionMerge %22 None
+ OpSwitch %14 %21 0 %15 1 %16 2 %17 3 %18 4 %19 5 %20
+ %20 = OpLabel
+ %53 = OpAccessChain %12 %25 %11
+ OpStore %53 %52
+ OpBranch %21
+ %19 = OpLabel
+ %45 = OpAccessChain %12 %25 %11
+ %46 = OpLoad %6 %45
+ %47 = OpAccessChain %12 %25 %11
+ %48 = OpLoad %6 %47
+ %49 = OpIMul %6 %46 %48
+ %50 = OpAccessChain %12 %25 %11
+ OpStore %50 %49
+ OpBranch %22
+ %18 = OpLabel
+ %40 = OpAccessChain %12 %25 %11
+ %41 = OpLoad %6 %40
+ %42 = OpUDiv %6 %41 %34
+ %43 = OpAccessChain %12 %25 %11
+ OpStore %43 %42
+ OpBranch %22
+ %16 = OpLabel
+ %31 = OpAccessChain %12 %25 %11
+ %32 = OpLoad %6 %31
+ %33 = OpISub %6 %32 %28
+ OpStore %31 %33
+ OpBranch %17
+ %17 = OpLabel
+ %35 = OpAccessChain %12 %25 %11
+ %36 = OpLoad %6 %35
+ %37 = OpIMul %6 %36 %34
+ %38 = OpAccessChain %12 %25 %11
+ OpStore %38 %37
+ OpBranch %22
+ %15 = OpLabel
+ %26 = OpAccessChain %12 %25 %11
+ %27 = OpLoad %6 %26
+ %29 = OpIAdd %6 %27 %28
+ OpStore %26 %29
+ OpBranch %22
+ %21 = OpLabel
+ %54 = OpAccessChain %12 %25 %11
+ %55 = OpLoad %6 %54
+ %56 = OpIAdd %6 %55 %34
+ %57 = OpAccessChain %12 %25 %11
+ OpStore %57 %56
+ OpBranch %22
+ %22 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/reordered_switch_blocks_dst.spvasm b/test/diff/diff_files/reordered_switch_blocks_dst.spvasm
new file mode 100644
index 0000000..8eabb4e
--- /dev/null
+++ b/test/diff/diff_files/reordered_switch_blocks_dst.spvasm
@@ -0,0 +1,91 @@
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %7 "BufferIn"
+               OpMemberName %7 0 "i"
+               OpName %9 ""
+               OpName %23 "BufferOut"
+               OpMemberName %23 0 "o"
+               OpName %25 ""
+               OpMemberDecorate %7 0 Offset 0
+               OpDecorate %7 Block
+               OpDecorate %9 DescriptorSet 0
+               OpDecorate %9 Binding 0
+               OpMemberDecorate %23 0 Offset 0
+               OpDecorate %23 BufferBlock
+               OpDecorate %25 DescriptorSet 0
+               OpDecorate %25 Binding 1
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 0
+          %7 = OpTypeStruct %6
+          %8 = OpTypePointer Uniform %7
+          %9 = OpVariable %8 Uniform
+         %10 = OpTypeInt 32 1
+         %11 = OpConstant %10 0
+         %12 = OpTypePointer Uniform %6
+         %23 = OpTypeStruct %6
+         %24 = OpTypePointer Uniform %23
+         %25 = OpVariable %24 Uniform
+         %28 = OpConstant %10 1
+         %34 = OpConstant %6 2
+         %52 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %13 = OpAccessChain %12 %9 %11
+         %14 = OpLoad %6 %13
+               OpSelectionMerge %22 None
+               OpSwitch %14 %21 0 %15 1 %16 2 %17 3 %18 4 %19 5 %20
+         %17 = OpLabel
+         %35 = OpAccessChain %12 %25 %11
+         %36 = OpLoad %6 %35
+         %37 = OpIMul %6 %36 %34
+         %38 = OpAccessChain %12 %25 %11
+               OpStore %38 %37
+               OpBranch %22
+         %18 = OpLabel
+         %40 = OpAccessChain %12 %25 %11
+         %41 = OpLoad %6 %40
+         %42 = OpUDiv %6 %41 %34
+         %43 = OpAccessChain %12 %25 %11
+               OpStore %43 %42
+               OpBranch %22
+         %21 = OpLabel
+         %54 = OpAccessChain %12 %25 %11
+         %55 = OpLoad %6 %54
+         %56 = OpIAdd %6 %55 %34
+         %57 = OpAccessChain %12 %25 %11
+               OpStore %57 %56
+               OpBranch %22
+         %20 = OpLabel
+         %53 = OpAccessChain %12 %25 %11
+               OpStore %53 %52
+               OpBranch %21
+         %15 = OpLabel
+         %26 = OpAccessChain %12 %25 %11
+         %27 = OpLoad %6 %26
+         %29 = OpIAdd %6 %27 %28
+               OpStore %26 %29
+               OpBranch %22
+         %19 = OpLabel
+         %45 = OpAccessChain %12 %25 %11
+         %46 = OpLoad %6 %45
+         %47 = OpAccessChain %12 %25 %11
+         %48 = OpLoad %6 %47
+         %49 = OpIMul %6 %46 %48
+         %50 = OpAccessChain %12 %25 %11
+               OpStore %50 %49
+               OpBranch %22
+         %16 = OpLabel
+         %31 = OpAccessChain %12 %25 %11
+         %32 = OpLoad %6 %31
+         %33 = OpISub %6 %32 %28
+               OpStore %31 %33
+               OpBranch %17
+         %22 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/diff/diff_files/reordered_switch_blocks_src.spvasm b/test/diff/diff_files/reordered_switch_blocks_src.spvasm
new file mode 100644
index 0000000..e377228
--- /dev/null
+++ b/test/diff/diff_files/reordered_switch_blocks_src.spvasm
@@ -0,0 +1,92 @@
+;; Test where src and dst have cases of a switch in different order.
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %7 "BufferIn"
+               OpMemberName %7 0 "i"
+               OpName %9 ""
+               OpName %23 "BufferOut"
+               OpMemberName %23 0 "o"
+               OpName %25 ""
+               OpMemberDecorate %7 0 Offset 0
+               OpDecorate %7 Block
+               OpDecorate %9 DescriptorSet 0
+               OpDecorate %9 Binding 0
+               OpMemberDecorate %23 0 Offset 0
+               OpDecorate %23 BufferBlock
+               OpDecorate %25 DescriptorSet 0
+               OpDecorate %25 Binding 1
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 0
+          %7 = OpTypeStruct %6
+          %8 = OpTypePointer Uniform %7
+          %9 = OpVariable %8 Uniform
+         %10 = OpTypeInt 32 1
+         %11 = OpConstant %10 0
+         %12 = OpTypePointer Uniform %6
+         %23 = OpTypeStruct %6
+         %24 = OpTypePointer Uniform %23
+         %25 = OpVariable %24 Uniform
+         %28 = OpConstant %10 1
+         %34 = OpConstant %6 2
+         %52 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %13 = OpAccessChain %12 %9 %11
+         %14 = OpLoad %6 %13
+               OpSelectionMerge %22 None
+               OpSwitch %14 %21 0 %15 1 %16 2 %17 3 %18 4 %19 5 %20
+         %21 = OpLabel
+         %54 = OpAccessChain %12 %25 %11
+         %55 = OpLoad %6 %54
+         %56 = OpIAdd %6 %55 %34
+         %57 = OpAccessChain %12 %25 %11
+               OpStore %57 %56
+               OpBranch %22
+         %15 = OpLabel
+         %26 = OpAccessChain %12 %25 %11
+         %27 = OpLoad %6 %26
+         %29 = OpIAdd %6 %27 %28
+               OpStore %26 %29
+               OpBranch %22
+         %16 = OpLabel
+         %31 = OpAccessChain %12 %25 %11
+         %32 = OpLoad %6 %31
+         %33 = OpISub %6 %32 %28
+               OpStore %31 %33
+               OpBranch %17
+         %17 = OpLabel
+         %35 = OpAccessChain %12 %25 %11
+         %36 = OpLoad %6 %35
+         %37 = OpIMul %6 %36 %34
+         %38 = OpAccessChain %12 %25 %11
+               OpStore %38 %37
+               OpBranch %22
+         %18 = OpLabel
+         %40 = OpAccessChain %12 %25 %11
+         %41 = OpLoad %6 %40
+         %42 = OpUDiv %6 %41 %34
+         %43 = OpAccessChain %12 %25 %11
+               OpStore %43 %42
+               OpBranch %22
+         %19 = OpLabel
+         %45 = OpAccessChain %12 %25 %11
+         %46 = OpLoad %6 %45
+         %47 = OpAccessChain %12 %25 %11
+         %48 = OpLoad %6 %47
+         %49 = OpIMul %6 %46 %48
+         %50 = OpAccessChain %12 %25 %11
+               OpStore %50 %49
+               OpBranch %22
+         %20 = OpLabel
+         %53 = OpAccessChain %12 %25 %11
+               OpStore %53 %52
+               OpBranch %21
+         %22 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/diff/diff_files/small_functions_small_diffs_autogen.cpp b/test/diff/diff_files/small_functions_small_diffs_autogen.cpp
new file mode 100644
index 0000000..c1a9100
--- /dev/null
+++ b/test/diff/diff_files/small_functions_small_diffs_autogen.cpp
@@ -0,0 +1,747 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Test where src and dst have many small functions with small differences.
+constexpr char kSrc[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "f1("
+               OpName %8 "f2("
+               OpName %10 "f3("
+               OpName %12 "f4("
+               OpName %14 "f5("
+               OpName %17 "BufferOut"
+               OpMemberName %17 0 "o"
+               OpName %19 ""
+               OpName %22 "BufferIn"
+               OpMemberName %22 0 "i"
+               OpName %24 ""
+               OpMemberDecorate %17 0 Offset 0
+               OpDecorate %17 BufferBlock
+               OpDecorate %19 DescriptorSet 0
+               OpDecorate %19 Binding 1
+               OpMemberDecorate %22 0 Offset 0
+               OpDecorate %22 Block
+               OpDecorate %24 DescriptorSet 0
+               OpDecorate %24 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %16 = OpTypeInt 32 0
+         %17 = OpTypeStruct %16
+         %18 = OpTypePointer Uniform %17
+         %19 = OpVariable %18 Uniform
+         %20 = OpTypeInt 32 1
+         %21 = OpConstant %20 0
+         %22 = OpTypeStruct %16
+         %23 = OpTypePointer Uniform %22
+         %24 = OpVariable %23 Uniform
+         %25 = OpTypePointer Uniform %16
+         %31 = OpConstant %20 1
+         %36 = OpConstant %16 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %45 = OpFunctionCall %2 %6
+         %46 = OpFunctionCall %2 %8
+         %47 = OpFunctionCall %2 %10
+         %48 = OpFunctionCall %2 %12
+         %49 = OpFunctionCall %2 %14
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %26 = OpAccessChain %25 %24 %21
+         %27 = OpLoad %16 %26
+         %28 = OpAccessChain %25 %19 %21
+               OpStore %28 %27
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+         %29 = OpAccessChain %25 %19 %21
+         %30 = OpLoad %16 %29
+         %32 = OpIAdd %16 %30 %31
+               OpStore %29 %32
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %3
+         %11 = OpLabel
+         %33 = OpAccessChain %25 %19 %21
+         %34 = OpLoad %16 %33
+         %35 = OpISub %16 %34 %31
+               OpStore %33 %35
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %2 None %3
+         %13 = OpLabel
+         %37 = OpAccessChain %25 %19 %21
+         %38 = OpLoad %16 %37
+         %39 = OpIMul %16 %38 %36
+         %40 = OpAccessChain %25 %19 %21
+               OpStore %40 %39
+               OpReturn
+               OpFunctionEnd
+         %14 = OpFunction %2 None %3
+         %15 = OpLabel
+         %41 = OpAccessChain %25 %19 %21
+         %42 = OpLoad %16 %41
+         %43 = OpUDiv %16 %42 %36
+         %44 = OpAccessChain %25 %19 %21
+               OpStore %44 %43
+               OpReturn
+               OpFunctionEnd
+)";
+constexpr char kDst[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "f1("
+               OpName %8 "f2("
+               OpName %10 "f3("
+               OpName %12 "f4("
+               OpName %14 "f5("
+               OpName %17 "BufferOut"
+               OpMemberName %17 0 "o"
+               OpName %19 ""
+               OpName %22 "BufferIn"
+               OpMemberName %22 0 "i"
+               OpName %24 ""
+               OpMemberDecorate %17 0 Offset 0
+               OpDecorate %17 BufferBlock
+               OpDecorate %19 DescriptorSet 0
+               OpDecorate %19 Binding 1
+               OpMemberDecorate %22 0 Offset 0
+               OpDecorate %22 Block
+               OpDecorate %24 DescriptorSet 0
+               OpDecorate %24 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %16 = OpTypeInt 32 0
+         %17 = OpTypeStruct %16
+         %18 = OpTypePointer Uniform %17
+         %19 = OpVariable %18 Uniform
+         %20 = OpTypeInt 32 1
+         %21 = OpConstant %20 0
+         %22 = OpTypeStruct %16
+         %23 = OpTypePointer Uniform %22
+         %24 = OpVariable %23 Uniform
+         %25 = OpTypePointer Uniform %16
+         %31 = OpConstant %20 1
+         %36 = OpConstant %16 2
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %26 = OpAccessChain %25 %24 %21
+         %27 = OpLoad %16 %26
+         %28 = OpAccessChain %25 %19 %21
+               OpStore %28 %27
+               OpReturn
+               OpFunctionEnd
+         %14 = OpFunction %2 None %3
+         %15 = OpLabel
+         %41 = OpAccessChain %25 %19 %21
+         %42 = OpLoad %16 %41
+         %43 = OpIAdd %16 %42 %36
+         %44 = OpAccessChain %25 %19 %21
+               OpStore %44 %43
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+         %29 = OpAccessChain %25 %19 %21
+         %30 = OpLoad %16 %29
+         %32 = OpISub %16 %30 %31
+               OpStore %29 %32
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %3
+         %11 = OpLabel
+         %33 = OpAccessChain %25 %19 %21
+         %34 = OpLoad %16 %33
+         %35 = OpIAdd %16 %34 %31
+               OpStore %33 %35
+               OpReturn
+               OpFunctionEnd
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %45 = OpFunctionCall %2 %6
+         %46 = OpFunctionCall %2 %8
+         %47 = OpFunctionCall %2 %10
+         %48 = OpFunctionCall %2 %12
+         %49 = OpFunctionCall %2 %14
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %2 None %3
+         %13 = OpLabel
+         %37 = OpAccessChain %25 %19 %21
+         %38 = OpLoad %16 %37
+         %39 = OpISub %16 %38 %36
+         %40 = OpAccessChain %25 %19 %21
+               OpStore %40 %39
+               OpReturn
+               OpFunctionEnd
+
+)";
+
+TEST(DiffTest, SmallFunctionsSmallDiffs) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 50
++; Bound: 54
+ ; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %4 "main"
+ OpExecutionMode %4 LocalSize 1 1 1
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %6 "f1("
+ OpName %8 "f2("
+ OpName %10 "f3("
+ OpName %12 "f4("
+ OpName %14 "f5("
+ OpName %17 "BufferOut"
+ OpMemberName %17 0 "o"
+ OpName %19 ""
+ OpName %22 "BufferIn"
+ OpMemberName %22 0 "i"
+ OpName %24 ""
+ OpMemberDecorate %17 0 Offset 0
+ OpDecorate %17 BufferBlock
+ OpDecorate %19 DescriptorSet 0
+ OpDecorate %19 Binding 1
+ OpMemberDecorate %22 0 Offset 0
+ OpDecorate %22 Block
+ OpDecorate %24 DescriptorSet 0
+ OpDecorate %24 Binding 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %16 = OpTypeInt 32 0
+ %17 = OpTypeStruct %16
+ %18 = OpTypePointer Uniform %17
+ %19 = OpVariable %18 Uniform
+ %20 = OpTypeInt 32 1
+ %21 = OpConstant %20 0
+ %22 = OpTypeStruct %16
+ %23 = OpTypePointer Uniform %22
+ %24 = OpVariable %23 Uniform
+ %25 = OpTypePointer Uniform %16
+ %31 = OpConstant %20 1
+ %36 = OpConstant %16 2
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %45 = OpFunctionCall %2 %6
+ %46 = OpFunctionCall %2 %8
+ %47 = OpFunctionCall %2 %10
+ %48 = OpFunctionCall %2 %12
+ %49 = OpFunctionCall %2 %14
+ OpReturn
+ OpFunctionEnd
+ %6 = OpFunction %2 None %3
+ %7 = OpLabel
+ %26 = OpAccessChain %25 %24 %21
+ %27 = OpLoad %16 %26
+ %28 = OpAccessChain %25 %19 %21
+ OpStore %28 %27
+ OpReturn
+ OpFunctionEnd
+ %8 = OpFunction %2 None %3
+ %9 = OpLabel
+ %29 = OpAccessChain %25 %19 %21
+ %30 = OpLoad %16 %29
+-%32 = OpIAdd %16 %30 %31
++%50 = OpISub %16 %30 %31
+-OpStore %29 %32
++OpStore %29 %50
+ OpReturn
+ OpFunctionEnd
+ %10 = OpFunction %2 None %3
+ %11 = OpLabel
+ %33 = OpAccessChain %25 %19 %21
+ %34 = OpLoad %16 %33
+-%35 = OpISub %16 %34 %31
++%51 = OpIAdd %16 %34 %31
+-OpStore %33 %35
++OpStore %33 %51
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %2 None %3
+ %13 = OpLabel
+ %37 = OpAccessChain %25 %19 %21
+ %38 = OpLoad %16 %37
+-%39 = OpIMul %16 %38 %36
++%52 = OpISub %16 %38 %36
+ %40 = OpAccessChain %25 %19 %21
+-OpStore %40 %39
++OpStore %40 %52
+ OpReturn
+ OpFunctionEnd
+ %14 = OpFunction %2 None %3
+ %15 = OpLabel
+ %41 = OpAccessChain %25 %19 %21
+ %42 = OpLoad %16 %41
+-%43 = OpUDiv %16 %42 %36
++%53 = OpIAdd %16 %42 %36
+ %44 = OpAccessChain %25 %19 %21
+-OpStore %44 %43
++OpStore %44 %53
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, SmallFunctionsSmallDiffsNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpMemberDecorate %17 0 Offset 0
+               OpDecorate %17 BufferBlock
+               OpDecorate %19 DescriptorSet 0
+               OpDecorate %19 Binding 1
+               OpMemberDecorate %22 0 Offset 0
+               OpDecorate %22 Block
+               OpDecorate %24 DescriptorSet 0
+               OpDecorate %24 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %16 = OpTypeInt 32 0
+         %17 = OpTypeStruct %16
+         %18 = OpTypePointer Uniform %17
+         %19 = OpVariable %18 Uniform
+         %20 = OpTypeInt 32 1
+         %21 = OpConstant %20 0
+         %22 = OpTypeStruct %16
+         %23 = OpTypePointer Uniform %22
+         %24 = OpVariable %23 Uniform
+         %25 = OpTypePointer Uniform %16
+         %31 = OpConstant %20 1
+         %36 = OpConstant %16 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %45 = OpFunctionCall %2 %6
+         %46 = OpFunctionCall %2 %8
+         %47 = OpFunctionCall %2 %10
+         %48 = OpFunctionCall %2 %12
+         %49 = OpFunctionCall %2 %14
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %26 = OpAccessChain %25 %24 %21
+         %27 = OpLoad %16 %26
+         %28 = OpAccessChain %25 %19 %21
+               OpStore %28 %27
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+         %29 = OpAccessChain %25 %19 %21
+         %30 = OpLoad %16 %29
+         %32 = OpIAdd %16 %30 %31
+               OpStore %29 %32
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %3
+         %11 = OpLabel
+         %33 = OpAccessChain %25 %19 %21
+         %34 = OpLoad %16 %33
+         %35 = OpISub %16 %34 %31
+               OpStore %33 %35
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %2 None %3
+         %13 = OpLabel
+         %37 = OpAccessChain %25 %19 %21
+         %38 = OpLoad %16 %37
+         %39 = OpIMul %16 %38 %36
+         %40 = OpAccessChain %25 %19 %21
+               OpStore %40 %39
+               OpReturn
+               OpFunctionEnd
+         %14 = OpFunction %2 None %3
+         %15 = OpLabel
+         %41 = OpAccessChain %25 %19 %21
+         %42 = OpLoad %16 %41
+         %43 = OpUDiv %16 %42 %36
+         %44 = OpAccessChain %25 %19 %21
+               OpStore %44 %43
+               OpReturn
+               OpFunctionEnd
+
+)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpMemberDecorate %17 0 Offset 0
+               OpDecorate %17 BufferBlock
+               OpDecorate %19 DescriptorSet 0
+               OpDecorate %19 Binding 1
+               OpMemberDecorate %22 0 Offset 0
+               OpDecorate %22 Block
+               OpDecorate %24 DescriptorSet 0
+               OpDecorate %24 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %16 = OpTypeInt 32 0
+         %17 = OpTypeStruct %16
+         %18 = OpTypePointer Uniform %17
+         %19 = OpVariable %18 Uniform
+         %20 = OpTypeInt 32 1
+         %21 = OpConstant %20 0
+         %22 = OpTypeStruct %16
+         %23 = OpTypePointer Uniform %22
+         %24 = OpVariable %23 Uniform
+         %25 = OpTypePointer Uniform %16
+         %31 = OpConstant %20 1
+         %36 = OpConstant %16 2
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %26 = OpAccessChain %25 %24 %21
+         %27 = OpLoad %16 %26
+         %28 = OpAccessChain %25 %19 %21
+               OpStore %28 %27
+               OpReturn
+               OpFunctionEnd
+         %14 = OpFunction %2 None %3
+         %15 = OpLabel
+         %41 = OpAccessChain %25 %19 %21
+         %42 = OpLoad %16 %41
+         %43 = OpIAdd %16 %42 %36
+         %44 = OpAccessChain %25 %19 %21
+               OpStore %44 %43
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+         %29 = OpAccessChain %25 %19 %21
+         %30 = OpLoad %16 %29
+         %32 = OpISub %16 %30 %31
+               OpStore %29 %32
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %3
+         %11 = OpLabel
+         %33 = OpAccessChain %25 %19 %21
+         %34 = OpLoad %16 %33
+         %35 = OpIAdd %16 %34 %31
+               OpStore %33 %35
+               OpReturn
+               OpFunctionEnd
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %45 = OpFunctionCall %2 %6
+         %46 = OpFunctionCall %2 %8
+         %47 = OpFunctionCall %2 %10
+         %48 = OpFunctionCall %2 %12
+         %49 = OpFunctionCall %2 %14
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %2 None %3
+         %13 = OpLabel
+         %37 = OpAccessChain %25 %19 %21
+         %38 = OpLoad %16 %37
+         %39 = OpISub %16 %38 %36
+         %40 = OpAccessChain %25 %19 %21
+               OpStore %40 %39
+               OpReturn
+               OpFunctionEnd
+
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 50
++; Bound: 52
+ ; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %4 "main"
+ OpExecutionMode %4 LocalSize 1 1 1
+ OpSource ESSL 310
+ OpMemberDecorate %17 0 Offset 0
+ OpDecorate %17 BufferBlock
+ OpDecorate %19 DescriptorSet 0
+ OpDecorate %19 Binding 1
+ OpMemberDecorate %22 0 Offset 0
+ OpDecorate %22 Block
+ OpDecorate %24 DescriptorSet 0
+ OpDecorate %24 Binding 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %16 = OpTypeInt 32 0
+ %17 = OpTypeStruct %16
+ %18 = OpTypePointer Uniform %17
+ %19 = OpVariable %18 Uniform
+ %20 = OpTypeInt 32 1
+ %21 = OpConstant %20 0
+ %22 = OpTypeStruct %16
+ %23 = OpTypePointer Uniform %22
+ %24 = OpVariable %23 Uniform
+ %25 = OpTypePointer Uniform %16
+ %31 = OpConstant %20 1
+ %36 = OpConstant %16 2
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %45 = OpFunctionCall %2 %6
+-%46 = OpFunctionCall %2 %8
++%46 = OpFunctionCall %2 %10
+-%47 = OpFunctionCall %2 %10
++%47 = OpFunctionCall %2 %8
+ %48 = OpFunctionCall %2 %12
+ %49 = OpFunctionCall %2 %14
+ OpReturn
+ OpFunctionEnd
+ %6 = OpFunction %2 None %3
+ %7 = OpLabel
+ %26 = OpAccessChain %25 %24 %21
+ %27 = OpLoad %16 %26
+ %28 = OpAccessChain %25 %19 %21
+ OpStore %28 %27
+ OpReturn
+ OpFunctionEnd
+ %8 = OpFunction %2 None %3
+ %9 = OpLabel
+ %29 = OpAccessChain %25 %19 %21
+ %30 = OpLoad %16 %29
+ %32 = OpIAdd %16 %30 %31
+ OpStore %29 %32
+ OpReturn
+ OpFunctionEnd
+ %10 = OpFunction %2 None %3
+ %11 = OpLabel
+ %33 = OpAccessChain %25 %19 %21
+ %34 = OpLoad %16 %33
+ %35 = OpISub %16 %34 %31
+ OpStore %33 %35
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %2 None %3
+ %13 = OpLabel
+ %37 = OpAccessChain %25 %19 %21
+ %38 = OpLoad %16 %37
+-%39 = OpIMul %16 %38 %36
++%50 = OpISub %16 %38 %36
+ %40 = OpAccessChain %25 %19 %21
+-OpStore %40 %39
++OpStore %40 %50
+ OpReturn
+ OpFunctionEnd
+ %14 = OpFunction %2 None %3
+ %15 = OpLabel
+ %41 = OpAccessChain %25 %19 %21
+ %42 = OpLoad %16 %41
+-%43 = OpUDiv %16 %42 %36
++%51 = OpIAdd %16 %42 %36
+ %44 = OpAccessChain %25 %19 %21
+-OpStore %44 %43
++OpStore %44 %51
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+TEST(DiffTest, SmallFunctionsSmallDiffsDumpIds) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 50
++; Bound: 54
+ ; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %4 "main"
+ OpExecutionMode %4 LocalSize 1 1 1
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %6 "f1("
+ OpName %8 "f2("
+ OpName %10 "f3("
+ OpName %12 "f4("
+ OpName %14 "f5("
+ OpName %17 "BufferOut"
+ OpMemberName %17 0 "o"
+ OpName %19 ""
+ OpName %22 "BufferIn"
+ OpMemberName %22 0 "i"
+ OpName %24 ""
+ OpMemberDecorate %17 0 Offset 0
+ OpDecorate %17 BufferBlock
+ OpDecorate %19 DescriptorSet 0
+ OpDecorate %19 Binding 1
+ OpMemberDecorate %22 0 Offset 0
+ OpDecorate %22 Block
+ OpDecorate %24 DescriptorSet 0
+ OpDecorate %24 Binding 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %16 = OpTypeInt 32 0
+ %17 = OpTypeStruct %16
+ %18 = OpTypePointer Uniform %17
+ %19 = OpVariable %18 Uniform
+ %20 = OpTypeInt 32 1
+ %21 = OpConstant %20 0
+ %22 = OpTypeStruct %16
+ %23 = OpTypePointer Uniform %22
+ %24 = OpVariable %23 Uniform
+ %25 = OpTypePointer Uniform %16
+ %31 = OpConstant %20 1
+ %36 = OpConstant %16 2
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %45 = OpFunctionCall %2 %6
+ %46 = OpFunctionCall %2 %8
+ %47 = OpFunctionCall %2 %10
+ %48 = OpFunctionCall %2 %12
+ %49 = OpFunctionCall %2 %14
+ OpReturn
+ OpFunctionEnd
+ %6 = OpFunction %2 None %3
+ %7 = OpLabel
+ %26 = OpAccessChain %25 %24 %21
+ %27 = OpLoad %16 %26
+ %28 = OpAccessChain %25 %19 %21
+ OpStore %28 %27
+ OpReturn
+ OpFunctionEnd
+ %8 = OpFunction %2 None %3
+ %9 = OpLabel
+ %29 = OpAccessChain %25 %19 %21
+ %30 = OpLoad %16 %29
+-%32 = OpIAdd %16 %30 %31
++%50 = OpISub %16 %30 %31
+-OpStore %29 %32
++OpStore %29 %50
+ OpReturn
+ OpFunctionEnd
+ %10 = OpFunction %2 None %3
+ %11 = OpLabel
+ %33 = OpAccessChain %25 %19 %21
+ %34 = OpLoad %16 %33
+-%35 = OpISub %16 %34 %31
++%51 = OpIAdd %16 %34 %31
+-OpStore %33 %35
++OpStore %33 %51
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %2 None %3
+ %13 = OpLabel
+ %37 = OpAccessChain %25 %19 %21
+ %38 = OpLoad %16 %37
+-%39 = OpIMul %16 %38 %36
++%52 = OpISub %16 %38 %36
+ %40 = OpAccessChain %25 %19 %21
+-OpStore %40 %39
++OpStore %40 %52
+ OpReturn
+ OpFunctionEnd
+ %14 = OpFunction %2 None %3
+ %15 = OpLabel
+ %41 = OpAccessChain %25 %19 %21
+ %42 = OpLoad %16 %41
+-%43 = OpUDiv %16 %42 %36
++%53 = OpIAdd %16 %42 %36
+ %44 = OpAccessChain %25 %19 %21
+-OpStore %44 %43
++OpStore %44 %53
+ OpReturn
+ OpFunctionEnd
+ Src ->  Dst
+   1 ->    1 [ExtInstImport]
+   2 ->    2 [TypeVoid]
+   3 ->    3 [TypeFunction]
+   4 ->    4 [Function]
+   5 ->    5 [Label]
+   6 ->    6 [Function]
+   7 ->    7 [Label]
+   8 ->    8 [Function]
+   9 ->    9 [Label]
+  10 ->   10 [Function]
+  11 ->   11 [Label]
+  12 ->   12 [Function]
+  13 ->   13 [Label]
+  14 ->   14 [Function]
+  15 ->   15 [Label]
+  16 ->   16 [TypeInt]
+  17 ->   17 [TypeStruct]
+  18 ->   18 [TypePointer]
+  19 ->   19 [Variable]
+  20 ->   20 [TypeInt]
+  21 ->   21 [Constant]
+  22 ->   22 [TypeStruct]
+  23 ->   23 [TypePointer]
+  24 ->   24 [Variable]
+  25 ->   25 [TypePointer]
+  26 ->   26 [AccessChain]
+  27 ->   27 [Load]
+  28 ->   28 [AccessChain]
+  29 ->   29 [AccessChain]
+  30 ->   30 [Load]
+  31 ->   31 [Constant]
+  32 ->   50 [IAdd]
+  33 ->   33 [AccessChain]
+  34 ->   34 [Load]
+  35 ->   51 [ISub]
+  36 ->   36 [Constant]
+  37 ->   37 [AccessChain]
+  38 ->   38 [Load]
+  39 ->   52 [IMul]
+  40 ->   40 [AccessChain]
+  41 ->   41 [AccessChain]
+  42 ->   42 [Load]
+  43 ->   53 [UDiv]
+  44 ->   44 [AccessChain]
+  45 ->   45 [FunctionCall]
+  46 ->   46 [FunctionCall]
+  47 ->   47 [FunctionCall]
+  48 ->   48 [FunctionCall]
+  49 ->   49 [FunctionCall]
+)";
+  Options options;
+  options.dump_id_map = true;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/small_functions_small_diffs_dst.spvasm b/test/diff/diff_files/small_functions_small_diffs_dst.spvasm
new file mode 100644
index 0000000..fabf569
--- /dev/null
+++ b/test/diff/diff_files/small_functions_small_diffs_dst.spvasm
@@ -0,0 +1,92 @@
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "f1("
+               OpName %8 "f2("
+               OpName %10 "f3("
+               OpName %12 "f4("
+               OpName %14 "f5("
+               OpName %17 "BufferOut"
+               OpMemberName %17 0 "o"
+               OpName %19 ""
+               OpName %22 "BufferIn"
+               OpMemberName %22 0 "i"
+               OpName %24 ""
+               OpMemberDecorate %17 0 Offset 0
+               OpDecorate %17 BufferBlock
+               OpDecorate %19 DescriptorSet 0
+               OpDecorate %19 Binding 1
+               OpMemberDecorate %22 0 Offset 0
+               OpDecorate %22 Block
+               OpDecorate %24 DescriptorSet 0
+               OpDecorate %24 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %16 = OpTypeInt 32 0
+         %17 = OpTypeStruct %16
+         %18 = OpTypePointer Uniform %17
+         %19 = OpVariable %18 Uniform
+         %20 = OpTypeInt 32 1
+         %21 = OpConstant %20 0
+         %22 = OpTypeStruct %16
+         %23 = OpTypePointer Uniform %22
+         %24 = OpVariable %23 Uniform
+         %25 = OpTypePointer Uniform %16
+         %31 = OpConstant %20 1
+         %36 = OpConstant %16 2
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %26 = OpAccessChain %25 %24 %21
+         %27 = OpLoad %16 %26
+         %28 = OpAccessChain %25 %19 %21
+               OpStore %28 %27
+               OpReturn
+               OpFunctionEnd
+         %14 = OpFunction %2 None %3
+         %15 = OpLabel
+         %41 = OpAccessChain %25 %19 %21
+         %42 = OpLoad %16 %41
+         %43 = OpIAdd %16 %42 %36
+         %44 = OpAccessChain %25 %19 %21
+               OpStore %44 %43
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+         %29 = OpAccessChain %25 %19 %21
+         %30 = OpLoad %16 %29
+         %32 = OpISub %16 %30 %31
+               OpStore %29 %32
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %3
+         %11 = OpLabel
+         %33 = OpAccessChain %25 %19 %21
+         %34 = OpLoad %16 %33
+         %35 = OpIAdd %16 %34 %31
+               OpStore %33 %35
+               OpReturn
+               OpFunctionEnd
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %45 = OpFunctionCall %2 %6
+         %46 = OpFunctionCall %2 %8
+         %47 = OpFunctionCall %2 %10
+         %48 = OpFunctionCall %2 %12
+         %49 = OpFunctionCall %2 %14
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %2 None %3
+         %13 = OpLabel
+         %37 = OpAccessChain %25 %19 %21
+         %38 = OpLoad %16 %37
+         %39 = OpISub %16 %38 %36
+         %40 = OpAccessChain %25 %19 %21
+               OpStore %40 %39
+               OpReturn
+               OpFunctionEnd
+
diff --git a/test/diff/diff_files/small_functions_small_diffs_src.spvasm b/test/diff/diff_files/small_functions_small_diffs_src.spvasm
new file mode 100644
index 0000000..895285b
--- /dev/null
+++ b/test/diff/diff_files/small_functions_small_diffs_src.spvasm
@@ -0,0 +1,93 @@
+;; Test where src and dst have many small functions with small differences.
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "f1("
+               OpName %8 "f2("
+               OpName %10 "f3("
+               OpName %12 "f4("
+               OpName %14 "f5("
+               OpName %17 "BufferOut"
+               OpMemberName %17 0 "o"
+               OpName %19 ""
+               OpName %22 "BufferIn"
+               OpMemberName %22 0 "i"
+               OpName %24 ""
+               OpMemberDecorate %17 0 Offset 0
+               OpDecorate %17 BufferBlock
+               OpDecorate %19 DescriptorSet 0
+               OpDecorate %19 Binding 1
+               OpMemberDecorate %22 0 Offset 0
+               OpDecorate %22 Block
+               OpDecorate %24 DescriptorSet 0
+               OpDecorate %24 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %16 = OpTypeInt 32 0
+         %17 = OpTypeStruct %16
+         %18 = OpTypePointer Uniform %17
+         %19 = OpVariable %18 Uniform
+         %20 = OpTypeInt 32 1
+         %21 = OpConstant %20 0
+         %22 = OpTypeStruct %16
+         %23 = OpTypePointer Uniform %22
+         %24 = OpVariable %23 Uniform
+         %25 = OpTypePointer Uniform %16
+         %31 = OpConstant %20 1
+         %36 = OpConstant %16 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %45 = OpFunctionCall %2 %6
+         %46 = OpFunctionCall %2 %8
+         %47 = OpFunctionCall %2 %10
+         %48 = OpFunctionCall %2 %12
+         %49 = OpFunctionCall %2 %14
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %26 = OpAccessChain %25 %24 %21
+         %27 = OpLoad %16 %26
+         %28 = OpAccessChain %25 %19 %21
+               OpStore %28 %27
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %2 None %3
+          %9 = OpLabel
+         %29 = OpAccessChain %25 %19 %21
+         %30 = OpLoad %16 %29
+         %32 = OpIAdd %16 %30 %31
+               OpStore %29 %32
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %3
+         %11 = OpLabel
+         %33 = OpAccessChain %25 %19 %21
+         %34 = OpLoad %16 %33
+         %35 = OpISub %16 %34 %31
+               OpStore %33 %35
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %2 None %3
+         %13 = OpLabel
+         %37 = OpAccessChain %25 %19 %21
+         %38 = OpLoad %16 %37
+         %39 = OpIMul %16 %38 %36
+         %40 = OpAccessChain %25 %19 %21
+               OpStore %40 %39
+               OpReturn
+               OpFunctionEnd
+         %14 = OpFunction %2 None %3
+         %15 = OpLabel
+         %41 = OpAccessChain %25 %19 %21
+         %42 = OpLoad %16 %41
+         %43 = OpUDiv %16 %42 %36
+         %44 = OpAccessChain %25 %19 %21
+               OpStore %44 %43
+               OpReturn
+               OpFunctionEnd
+
diff --git a/test/diff/diff_files/spec_constant_array_size_autogen.cpp b/test/diff/diff_files/spec_constant_array_size_autogen.cpp
new file mode 100644
index 0000000..1962d27
--- /dev/null
+++ b/test/diff/diff_files/spec_constant_array_size_autogen.cpp
@@ -0,0 +1,310 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Tests that identical specialization constants are not matched with constants
+// when used as array size.
+constexpr char kSrc[] = R"(; SPIR-V
+; Version: 1.0
+; Generator: Google ANGLE Shader Compiler; 0
+; Bound: 27
+; Schema: 0
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %22 "main" %4 %19
+OpSource GLSL 450
+OpName %4 "_ua_position"
+OpName %17 "gl_PerVertex"
+OpMemberName %17 0 "gl_Position"
+OpMemberName %17 1 "gl_PointSize"
+OpMemberName %17 2 "gl_ClipDistance"
+OpMemberName %17 3 "gl_CullDistance"
+OpName %19 ""
+OpName %22 "main"
+OpDecorate %4 Location 0
+OpMemberDecorate %17 1 RelaxedPrecision
+OpMemberDecorate %17 0 BuiltIn Position
+OpMemberDecorate %17 1 BuiltIn PointSize
+OpMemberDecorate %17 2 BuiltIn ClipDistance
+OpMemberDecorate %17 3 BuiltIn CullDistance
+OpDecorate %17 Block
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%5 = OpTypeInt 32 0
+%8 = OpTypeVector %5 4
+%15 = OpConstant %5 8
+%16 = OpTypeArray %1 %15
+%17 = OpTypeStruct %2 %1 %16 %16
+%20 = OpTypeVoid
+%25 = OpConstant %5 0
+%3 = OpTypePointer Input %2
+%13 = OpTypePointer Output %2
+%18 = OpTypePointer Output %17
+%21 = OpTypeFunction %20
+%4 = OpVariable %3 Input
+%19 = OpVariable %18 Output
+%22 = OpFunction %20 None %21
+%23 = OpLabel
+%24 = OpLoad %2 %4
+%26 = OpAccessChain %13 %19 %25
+OpStore %26 %24
+OpReturn
+OpFunctionEnd)";
+constexpr char kDst[] = R"(; SPIR-V
+; Version: 1.0
+; Generator: Google ANGLE Shader Compiler; 0
+; Bound: 27
+; Schema: 0
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %22 "main" %4 %19
+OpSource GLSL 450
+OpName %4 "_ua_position"
+OpName %17 "gl_PerVertex"
+OpMemberName %17 0 "gl_Position"
+OpMemberName %17 1 "gl_PointSize"
+OpMemberName %17 2 "gl_ClipDistance"
+OpMemberName %17 3 "gl_CullDistance"
+OpName %19 ""
+OpName %22 "main"
+OpDecorate %4 Location 0
+OpDecorate %15 SpecId 4
+OpMemberDecorate %17 1 RelaxedPrecision
+OpMemberDecorate %17 0 BuiltIn Position
+OpMemberDecorate %17 1 BuiltIn PointSize
+OpMemberDecorate %17 2 BuiltIn ClipDistance
+OpMemberDecorate %17 3 BuiltIn CullDistance
+OpDecorate %17 Block
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%5 = OpTypeInt 32 0
+%8 = OpTypeVector %5 4
+%15 = OpSpecConstant %5 8
+%16 = OpTypeArray %1 %15
+%17 = OpTypeStruct %2 %1 %16 %16
+%20 = OpTypeVoid
+%25 = OpConstant %5 0
+%3 = OpTypePointer Input %2
+%13 = OpTypePointer Output %2
+%18 = OpTypePointer Output %17
+%21 = OpTypeFunction %20
+%4 = OpVariable %3 Input
+%19 = OpVariable %18 Output
+%22 = OpFunction %20 None %21
+%23 = OpLabel
+%24 = OpLoad %2 %4
+%26 = OpAccessChain %13 %19 %25
+OpStore %26 %24
+OpReturn
+OpFunctionEnd
+)";
+
+TEST(DiffTest, SpecConstantArraySize) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 27
++; Bound: 36
+ ; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %22 "main" %4 %19
+ OpSource GLSL 450
+ OpName %4 "_ua_position"
+ OpName %17 "gl_PerVertex"
+ OpMemberName %17 0 "gl_Position"
+ OpMemberName %17 1 "gl_PointSize"
+ OpMemberName %17 2 "gl_ClipDistance"
+ OpMemberName %17 3 "gl_CullDistance"
+ OpName %19 ""
+ OpName %22 "main"
+ OpDecorate %4 Location 0
++OpDecorate %34 SpecId 4
+ OpMemberDecorate %17 1 RelaxedPrecision
+ OpMemberDecorate %17 0 BuiltIn Position
+ OpMemberDecorate %17 1 BuiltIn PointSize
+ OpMemberDecorate %17 2 BuiltIn ClipDistance
+ OpMemberDecorate %17 3 BuiltIn CullDistance
+ OpDecorate %17 Block
+ %1 = OpTypeFloat 32
+ %2 = OpTypeVector %1 4
+ %5 = OpTypeInt 32 0
+ %8 = OpTypeVector %5 4
+-%15 = OpConstant %5 8
+-%16 = OpTypeArray %1 %15
++%34 = OpSpecConstant %5 8
++%35 = OpTypeArray %1 %34
+-%17 = OpTypeStruct %2 %1 %16 %16
++%17 = OpTypeStruct %2 %1 %35 %35
+ %20 = OpTypeVoid
+ %25 = OpConstant %5 0
+ %3 = OpTypePointer Input %2
+ %13 = OpTypePointer Output %2
+ %18 = OpTypePointer Output %17
+ %21 = OpTypeFunction %20
+ %4 = OpVariable %3 Input
+ %19 = OpVariable %18 Output
+ %22 = OpFunction %20 None %21
+ %23 = OpLabel
+ %24 = OpLoad %2 %4
+ %26 = OpAccessChain %13 %19 %25
+ OpStore %26 %24
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, SpecConstantArraySizeNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(; SPIR-V
+; Version: 1.0
+; Generator: Google ANGLE Shader Compiler; 0
+; Bound: 27
+; Schema: 0
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %22 "main" %4 %19
+OpSource GLSL 450
+OpDecorate %4 Location 0
+OpMemberDecorate %17 1 RelaxedPrecision
+OpMemberDecorate %17 0 BuiltIn Position
+OpMemberDecorate %17 1 BuiltIn PointSize
+OpMemberDecorate %17 2 BuiltIn ClipDistance
+OpMemberDecorate %17 3 BuiltIn CullDistance
+OpDecorate %17 Block
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%5 = OpTypeInt 32 0
+%8 = OpTypeVector %5 4
+%15 = OpConstant %5 8
+%16 = OpTypeArray %1 %15
+%17 = OpTypeStruct %2 %1 %16 %16
+%20 = OpTypeVoid
+%25 = OpConstant %5 0
+%3 = OpTypePointer Input %2
+%13 = OpTypePointer Output %2
+%18 = OpTypePointer Output %17
+%21 = OpTypeFunction %20
+%4 = OpVariable %3 Input
+%19 = OpVariable %18 Output
+%22 = OpFunction %20 None %21
+%23 = OpLabel
+%24 = OpLoad %2 %4
+%26 = OpAccessChain %13 %19 %25
+OpStore %26 %24
+OpReturn
+OpFunctionEnd
+)";
+  constexpr char kDstNoDebug[] = R"(; SPIR-V
+; Version: 1.0
+; Generator: Google ANGLE Shader Compiler; 0
+; Bound: 27
+; Schema: 0
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %22 "main" %4 %19
+OpSource GLSL 450
+OpDecorate %4 Location 0
+OpDecorate %15 SpecId 4
+OpMemberDecorate %17 1 RelaxedPrecision
+OpMemberDecorate %17 0 BuiltIn Position
+OpMemberDecorate %17 1 BuiltIn PointSize
+OpMemberDecorate %17 2 BuiltIn ClipDistance
+OpMemberDecorate %17 3 BuiltIn CullDistance
+OpDecorate %17 Block
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%5 = OpTypeInt 32 0
+%8 = OpTypeVector %5 4
+%15 = OpSpecConstant %5 8
+%16 = OpTypeArray %1 %15
+%17 = OpTypeStruct %2 %1 %16 %16
+%20 = OpTypeVoid
+%25 = OpConstant %5 0
+%3 = OpTypePointer Input %2
+%13 = OpTypePointer Output %2
+%18 = OpTypePointer Output %17
+%21 = OpTypeFunction %20
+%4 = OpVariable %3 Input
+%19 = OpVariable %18 Output
+%22 = OpFunction %20 None %21
+%23 = OpLabel
+%24 = OpLoad %2 %4
+%26 = OpAccessChain %13 %19 %25
+OpStore %26 %24
+OpReturn
+OpFunctionEnd
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 27
++; Bound: 36
+ ; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %22 "main" %4 %19
+ OpSource GLSL 450
+ OpDecorate %4 Location 0
++OpDecorate %34 SpecId 4
+ OpMemberDecorate %17 1 RelaxedPrecision
+ OpMemberDecorate %17 0 BuiltIn Position
+ OpMemberDecorate %17 1 BuiltIn PointSize
+ OpMemberDecorate %17 2 BuiltIn ClipDistance
+ OpMemberDecorate %17 3 BuiltIn CullDistance
+ OpDecorate %17 Block
+ %1 = OpTypeFloat 32
+ %2 = OpTypeVector %1 4
+ %5 = OpTypeInt 32 0
+ %8 = OpTypeVector %5 4
+-%15 = OpConstant %5 8
+-%16 = OpTypeArray %1 %15
++%34 = OpSpecConstant %5 8
++%35 = OpTypeArray %1 %34
+-%17 = OpTypeStruct %2 %1 %16 %16
++%17 = OpTypeStruct %2 %1 %35 %35
+ %20 = OpTypeVoid
+ %25 = OpConstant %5 0
+ %3 = OpTypePointer Input %2
+ %13 = OpTypePointer Output %2
+ %18 = OpTypePointer Output %17
+ %21 = OpTypeFunction %20
+ %4 = OpVariable %3 Input
+ %19 = OpVariable %18 Output
+ %22 = OpFunction %20 None %21
+ %23 = OpLabel
+ %24 = OpLoad %2 %4
+ %26 = OpAccessChain %13 %19 %25
+ OpStore %26 %24
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/spec_constant_array_size_dst.spvasm b/test/diff/diff_files/spec_constant_array_size_dst.spvasm
new file mode 100644
index 0000000..7cedf08
--- /dev/null
+++ b/test/diff/diff_files/spec_constant_array_size_dst.spvasm
@@ -0,0 +1,47 @@
+; SPIR-V
+; Version: 1.0
+; Generator: Google ANGLE Shader Compiler; 0
+; Bound: 27
+; Schema: 0
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %22 "main" %4 %19
+OpSource GLSL 450
+OpName %4 "_ua_position"
+OpName %17 "gl_PerVertex"
+OpMemberName %17 0 "gl_Position"
+OpMemberName %17 1 "gl_PointSize"
+OpMemberName %17 2 "gl_ClipDistance"
+OpMemberName %17 3 "gl_CullDistance"
+OpName %19 ""
+OpName %22 "main"
+OpDecorate %4 Location 0
+OpDecorate %15 SpecId 4
+OpMemberDecorate %17 1 RelaxedPrecision
+OpMemberDecorate %17 0 BuiltIn Position
+OpMemberDecorate %17 1 BuiltIn PointSize
+OpMemberDecorate %17 2 BuiltIn ClipDistance
+OpMemberDecorate %17 3 BuiltIn CullDistance
+OpDecorate %17 Block
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%5 = OpTypeInt 32 0
+%8 = OpTypeVector %5 4
+%15 = OpSpecConstant %5 8
+%16 = OpTypeArray %1 %15
+%17 = OpTypeStruct %2 %1 %16 %16
+%20 = OpTypeVoid
+%25 = OpConstant %5 0
+%3 = OpTypePointer Input %2
+%13 = OpTypePointer Output %2
+%18 = OpTypePointer Output %17
+%21 = OpTypeFunction %20
+%4 = OpVariable %3 Input
+%19 = OpVariable %18 Output
+%22 = OpFunction %20 None %21
+%23 = OpLabel
+%24 = OpLoad %2 %4
+%26 = OpAccessChain %13 %19 %25
+OpStore %26 %24
+OpReturn
+OpFunctionEnd
diff --git a/test/diff/diff_files/spec_constant_array_size_src.spvasm b/test/diff/diff_files/spec_constant_array_size_src.spvasm
new file mode 100644
index 0000000..e17e04a
--- /dev/null
+++ b/test/diff/diff_files/spec_constant_array_size_src.spvasm
@@ -0,0 +1,48 @@
+;; Tests that identical specialization constants are not matched with constants
+;; when used as array size.
+; SPIR-V
+; Version: 1.0
+; Generator: Google ANGLE Shader Compiler; 0
+; Bound: 27
+; Schema: 0
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %22 "main" %4 %19
+OpSource GLSL 450
+OpName %4 "_ua_position"
+OpName %17 "gl_PerVertex"
+OpMemberName %17 0 "gl_Position"
+OpMemberName %17 1 "gl_PointSize"
+OpMemberName %17 2 "gl_ClipDistance"
+OpMemberName %17 3 "gl_CullDistance"
+OpName %19 ""
+OpName %22 "main"
+OpDecorate %4 Location 0
+OpMemberDecorate %17 1 RelaxedPrecision
+OpMemberDecorate %17 0 BuiltIn Position
+OpMemberDecorate %17 1 BuiltIn PointSize
+OpMemberDecorate %17 2 BuiltIn ClipDistance
+OpMemberDecorate %17 3 BuiltIn CullDistance
+OpDecorate %17 Block
+%1 = OpTypeFloat 32
+%2 = OpTypeVector %1 4
+%5 = OpTypeInt 32 0
+%8 = OpTypeVector %5 4
+%15 = OpConstant %5 8
+%16 = OpTypeArray %1 %15
+%17 = OpTypeStruct %2 %1 %16 %16
+%20 = OpTypeVoid
+%25 = OpConstant %5 0
+%3 = OpTypePointer Input %2
+%13 = OpTypePointer Output %2
+%18 = OpTypePointer Output %17
+%21 = OpTypeFunction %20
+%4 = OpVariable %3 Input
+%19 = OpVariable %18 Output
+%22 = OpFunction %20 None %21
+%23 = OpLabel
+%24 = OpLoad %2 %4
+%26 = OpAccessChain %13 %19 %25
+OpStore %26 %24
+OpReturn
+OpFunctionEnd
diff --git a/test/diff/diff_files/spec_constant_composite_autogen.cpp b/test/diff/diff_files/spec_constant_composite_autogen.cpp
new file mode 100644
index 0000000..e4b52cb
--- /dev/null
+++ b/test/diff/diff_files/spec_constant_composite_autogen.cpp
@@ -0,0 +1,186 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Tests OpSpecConstantComposite matching.
+constexpr char kSrc[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpSource GLSL 450
+               OpName %main "main"
+               OpDecorate %7 SpecId 3
+               OpDecorate %8 SpecId 4
+               OpDecorate %gl_WorkGroupSize BuiltIn WorkgroupSize
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+          %7 = OpSpecConstant %uint 1
+          %8 = OpSpecConstant %uint 1
+     %uint_1 = OpConstant %uint 1
+     %v3uint = OpTypeVector %uint 3
+%gl_WorkGroupSize = OpSpecConstantComposite %v3uint %7 %8 %uint_1
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd)";
+constexpr char kDst[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpSource GLSL 450
+               OpName %main "main"
+               OpDecorate %7 SpecId 3
+               OpDecorate %8 SpecId 4
+               OpDecorate %gl_WorkGroupSize BuiltIn WorkgroupSize
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+          %7 = OpSpecConstant %uint 2048
+          %8 = OpSpecConstant %uint 1
+     %uint_1 = OpConstant %uint 1
+     %v3uint = OpTypeVector %uint 3
+%gl_WorkGroupSize = OpSpecConstantComposite %v3uint %7 %8 %uint_1
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+TEST(DiffTest, SpecConstantComposite) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+ ; Bound: 12
+ ; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %2 "main"
+ OpExecutionMode %2 LocalSize 1 1 1
+ OpSource GLSL 450
+ OpName %2 "main"
+ OpDecorate %7 SpecId 3
+ OpDecorate %8 SpecId 4
+ OpDecorate %4 BuiltIn WorkgroupSize
+ %6 = OpTypeVoid
+ %3 = OpTypeFunction %6
+ %9 = OpTypeInt 32 0
+-%7 = OpSpecConstant %9 1
++%7 = OpSpecConstant %9 2048
+ %8 = OpSpecConstant %9 1
+ %10 = OpConstant %9 1
+ %11 = OpTypeVector %9 3
+ %4 = OpSpecConstantComposite %11 %7 %8 %10
+ %2 = OpFunction %6 None %3
+ %5 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, SpecConstantCompositeNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpSource GLSL 450
+               OpDecorate %7 SpecId 3
+               OpDecorate %8 SpecId 4
+               OpDecorate %gl_WorkGroupSize BuiltIn WorkgroupSize
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+          %7 = OpSpecConstant %uint 1
+          %8 = OpSpecConstant %uint 1
+     %uint_1 = OpConstant %uint 1
+     %v3uint = OpTypeVector %uint 3
+%gl_WorkGroupSize = OpSpecConstantComposite %v3uint %7 %8 %uint_1
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpSource GLSL 450
+               OpDecorate %7 SpecId 3
+               OpDecorate %8 SpecId 4
+               OpDecorate %gl_WorkGroupSize BuiltIn WorkgroupSize
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+          %7 = OpSpecConstant %uint 2048
+          %8 = OpSpecConstant %uint 1
+     %uint_1 = OpConstant %uint 1
+     %v3uint = OpTypeVector %uint 3
+%gl_WorkGroupSize = OpSpecConstantComposite %v3uint %7 %8 %uint_1
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+ ; Bound: 12
+ ; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %2 "main"
+ OpExecutionMode %2 LocalSize 1 1 1
+ OpSource GLSL 450
+ OpDecorate %7 SpecId 3
+ OpDecorate %8 SpecId 4
+ OpDecorate %4 BuiltIn WorkgroupSize
+ %6 = OpTypeVoid
+ %3 = OpTypeFunction %6
+ %9 = OpTypeInt 32 0
+-%7 = OpSpecConstant %9 1
++%7 = OpSpecConstant %9 2048
+ %8 = OpSpecConstant %9 1
+ %10 = OpConstant %9 1
+ %11 = OpTypeVector %9 3
+ %4 = OpSpecConstantComposite %11 %7 %8 %10
+ %2 = OpFunction %6 None %3
+ %5 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/spec_constant_composite_dst.spvasm b/test/diff/diff_files/spec_constant_composite_dst.spvasm
new file mode 100644
index 0000000..3ab8d4d
--- /dev/null
+++ b/test/diff/diff_files/spec_constant_composite_dst.spvasm
@@ -0,0 +1,22 @@
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpSource GLSL 450
+               OpName %main "main"
+               OpDecorate %7 SpecId 3
+               OpDecorate %8 SpecId 4
+               OpDecorate %gl_WorkGroupSize BuiltIn WorkgroupSize
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+          %7 = OpSpecConstant %uint 2048
+          %8 = OpSpecConstant %uint 1
+     %uint_1 = OpConstant %uint 1
+     %v3uint = OpTypeVector %uint 3
+%gl_WorkGroupSize = OpSpecConstantComposite %v3uint %7 %8 %uint_1
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/diff/diff_files/spec_constant_composite_src.spvasm b/test/diff/diff_files/spec_constant_composite_src.spvasm
new file mode 100644
index 0000000..ee48ef9
--- /dev/null
+++ b/test/diff/diff_files/spec_constant_composite_src.spvasm
@@ -0,0 +1,23 @@
+;; Tests OpSpecConstantComposite matching.
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpSource GLSL 450
+               OpName %main "main"
+               OpDecorate %7 SpecId 3
+               OpDecorate %8 SpecId 4
+               OpDecorate %gl_WorkGroupSize BuiltIn WorkgroupSize
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+          %7 = OpSpecConstant %uint 1
+          %8 = OpSpecConstant %uint 1
+     %uint_1 = OpConstant %uint 1
+     %v3uint = OpTypeVector %uint 3
+%gl_WorkGroupSize = OpSpecConstantComposite %v3uint %7 %8 %uint_1
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/diff/diff_files/spec_constant_op_autogen.cpp b/test/diff/diff_files/spec_constant_op_autogen.cpp
new file mode 100644
index 0000000..e88e15b
--- /dev/null
+++ b/test/diff/diff_files/spec_constant_op_autogen.cpp
@@ -0,0 +1,162 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Tests OpSpecConstantOp matching.
+constexpr char kSrc[] = R"(               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpExecutionMode %1 LocalSize 1 1 1
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpTypeInt 32 0
+          %5 = OpTypeVector %4 3
+          %6 = OpConstant %4 1
+          %7 = OpSpecConstantComposite %5 %6 %6 %6
+          %8 = OpSpecConstantOp %4 CompositeExtract %7 2
+          %9 = OpSpecConstantOp %4 CompositeExtract %7 1
+         %10 = OpSpecConstantOp %4 CompositeExtract %7 0
+          %1 = OpFunction %2 None %3
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd)";
+constexpr char kDst[] = R"(               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpExecutionMode %1 LocalSize 1 1 1
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpTypeInt 32 0
+          %5 = OpTypeVector %4 3
+          %6 = OpConstant %4 1
+          %7 = OpSpecConstantComposite %5 %6 %6 %6
+          %8 = OpSpecConstantOp %4 CompositeExtract %7 2
+          %9 = OpSpecConstantOp %4 CompositeExtract %7 3
+         %10 = OpSpecConstantOp %4 IMul %8 %8
+          %1 = OpFunction %2 None %3
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+TEST(DiffTest, SpecConstantOp) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 12
++; Bound: 14
+ ; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %1 "main"
+ OpExecutionMode %1 LocalSize 1 1 1
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %4 = OpTypeInt 32 0
+ %5 = OpTypeVector %4 3
+ %6 = OpConstant %4 1
+ %7 = OpSpecConstantComposite %5 %6 %6 %6
+ %8 = OpSpecConstantOp %4 CompositeExtract %7 2
+-%9 = OpSpecConstantOp %4 CompositeExtract %7 1
+-%10 = OpSpecConstantOp %4 CompositeExtract %7 0
++%12 = OpSpecConstantOp %4 CompositeExtract %7 3
++%13 = OpSpecConstantOp %4 IMul %8 %8
+ %1 = OpFunction %2 None %3
+ %11 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, SpecConstantOpNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpExecutionMode %1 LocalSize 1 1 1
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpTypeInt 32 0
+          %5 = OpTypeVector %4 3
+          %6 = OpConstant %4 1
+          %7 = OpSpecConstantComposite %5 %6 %6 %6
+          %8 = OpSpecConstantOp %4 CompositeExtract %7 2
+          %9 = OpSpecConstantOp %4 CompositeExtract %7 1
+         %10 = OpSpecConstantOp %4 CompositeExtract %7 0
+          %1 = OpFunction %2 None %3
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpExecutionMode %1 LocalSize 1 1 1
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpTypeInt 32 0
+          %5 = OpTypeVector %4 3
+          %6 = OpConstant %4 1
+          %7 = OpSpecConstantComposite %5 %6 %6 %6
+          %8 = OpSpecConstantOp %4 CompositeExtract %7 2
+          %9 = OpSpecConstantOp %4 CompositeExtract %7 3
+         %10 = OpSpecConstantOp %4 IMul %8 %8
+          %1 = OpFunction %2 None %3
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 12
++; Bound: 14
+ ; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %1 "main"
+ OpExecutionMode %1 LocalSize 1 1 1
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %4 = OpTypeInt 32 0
+ %5 = OpTypeVector %4 3
+ %6 = OpConstant %4 1
+ %7 = OpSpecConstantComposite %5 %6 %6 %6
+ %8 = OpSpecConstantOp %4 CompositeExtract %7 2
+-%9 = OpSpecConstantOp %4 CompositeExtract %7 1
+-%10 = OpSpecConstantOp %4 CompositeExtract %7 0
++%12 = OpSpecConstantOp %4 CompositeExtract %7 3
++%13 = OpSpecConstantOp %4 IMul %8 %8
+ %1 = OpFunction %2 None %3
+ %11 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/spec_constant_op_dst.spvasm b/test/diff/diff_files/spec_constant_op_dst.spvasm
new file mode 100644
index 0000000..b5b10e5
--- /dev/null
+++ b/test/diff/diff_files/spec_constant_op_dst.spvasm
@@ -0,0 +1,17 @@
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpExecutionMode %1 LocalSize 1 1 1
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpTypeInt 32 0
+          %5 = OpTypeVector %4 3
+          %6 = OpConstant %4 1
+          %7 = OpSpecConstantComposite %5 %6 %6 %6
+          %8 = OpSpecConstantOp %4 CompositeExtract %7 2
+          %9 = OpSpecConstantOp %4 CompositeExtract %7 3
+         %10 = OpSpecConstantOp %4 IMul %8 %8
+          %1 = OpFunction %2 None %3
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/diff/diff_files/spec_constant_op_src.spvasm b/test/diff/diff_files/spec_constant_op_src.spvasm
new file mode 100644
index 0000000..09306f8
--- /dev/null
+++ b/test/diff/diff_files/spec_constant_op_src.spvasm
@@ -0,0 +1,18 @@
+;; Tests OpSpecConstantOp matching.
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %1 "main"
+               OpExecutionMode %1 LocalSize 1 1 1
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpTypeInt 32 0
+          %5 = OpTypeVector %4 3
+          %6 = OpConstant %4 1
+          %7 = OpSpecConstantComposite %5 %6 %6 %6
+          %8 = OpSpecConstantOp %4 CompositeExtract %7 2
+          %9 = OpSpecConstantOp %4 CompositeExtract %7 1
+         %10 = OpSpecConstantOp %4 CompositeExtract %7 0
+          %1 = OpFunction %2 None %3
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
\ No newline at end of file
diff --git a/test/diff/diff_files/spec_constant_specid_autogen.cpp b/test/diff/diff_files/spec_constant_specid_autogen.cpp
new file mode 100644
index 0000000..240dc59
--- /dev/null
+++ b/test/diff/diff_files/spec_constant_specid_autogen.cpp
@@ -0,0 +1,141 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Tests OpSpecConstantComposite matching.
+constexpr char kSrc[] = R"(               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpDecorate %sc SpecId 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+     %v3uint = OpTypeVector %uint 3
+         %sc = OpSpecConstant %uint 10
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd)";
+constexpr char kDst[] = R"(               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+     %v3uint = OpTypeVector %uint 3
+         %ss = OpSpecConstant %uint 10
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+TEST(DiffTest, SpecConstantSpecid) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 8
++; Bound: 9
+ ; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %1 "main"
+ OpExecutionMode %1 LocalSize 1 1 1
+-OpDecorate %2 SpecId 0
+ %4 = OpTypeVoid
+ %3 = OpTypeFunction %4
+ %6 = OpTypeInt 32 0
+ %7 = OpTypeVector %6 3
+-%2 = OpSpecConstant %6 10
++%8 = OpSpecConstant %6 10
+ %1 = OpFunction %4 None %3
+ %5 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, SpecConstantSpecidNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpDecorate %sc SpecId 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+     %v3uint = OpTypeVector %uint 3
+         %sc = OpSpecConstant %uint 10
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+     %v3uint = OpTypeVector %uint 3
+         %ss = OpSpecConstant %uint 10
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 8
++; Bound: 9
+ ; Schema: 0
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %1 "main"
+ OpExecutionMode %1 LocalSize 1 1 1
+-OpDecorate %2 SpecId 0
+ %4 = OpTypeVoid
+ %3 = OpTypeFunction %4
+ %6 = OpTypeInt 32 0
+ %7 = OpTypeVector %6 3
+-%2 = OpSpecConstant %6 10
++%8 = OpSpecConstant %6 10
+ %1 = OpFunction %4 None %3
+ %5 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/spec_constant_specid_dst.spvasm b/test/diff/diff_files/spec_constant_specid_dst.spvasm
new file mode 100644
index 0000000..0e840ed
--- /dev/null
+++ b/test/diff/diff_files/spec_constant_specid_dst.spvasm
@@ -0,0 +1,13 @@
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+     %v3uint = OpTypeVector %uint 3
+         %ss = OpSpecConstant %uint 10
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/diff/diff_files/spec_constant_specid_src.spvasm b/test/diff/diff_files/spec_constant_specid_src.spvasm
new file mode 100644
index 0000000..7fdfeba
--- /dev/null
+++ b/test/diff/diff_files/spec_constant_specid_src.spvasm
@@ -0,0 +1,15 @@
+;; Tests OpSpecConstantComposite matching.
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpDecorate %sc SpecId 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+     %v3uint = OpTypeVector %uint 3
+         %sc = OpSpecConstant %uint 10
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/diff/diff_files/unrelated_shaders_autogen.cpp b/test/diff/diff_files/unrelated_shaders_autogen.cpp
new file mode 100644
index 0000000..e1a58cc
--- /dev/null
+++ b/test/diff/diff_files/unrelated_shaders_autogen.cpp
@@ -0,0 +1,230 @@
+// GENERATED FILE - DO NOT EDIT.
+// Generated by generate_tests.py
+//
+// Copyright (c) 2022 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 "../diff_test_utils.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+// Tests diff of unrelated shaders (with different execution models).
+constexpr char kSrc[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %4 "main" %8 %10
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "v"
+               OpName %10 "a"
+               OpDecorate %8 Location 0
+               OpDecorate %10 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Output %6
+          %8 = OpVariable %7 Output
+          %9 = OpTypePointer Input %6
+         %10 = OpVariable %9 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpLoad %6 %10
+               OpStore %8 %11
+               OpReturn
+               OpFunctionEnd
+)";
+constexpr char kDst[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %9 %11
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %9 "color"
+               OpName %11 "v"
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %9 Location 0
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %11 Location 0
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %13 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+          %8 = OpTypePointer Output %7
+          %9 = OpVariable %8 Output
+         %10 = OpTypePointer Input %6
+         %11 = OpVariable %10 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpLoad %6 %11
+         %13 = OpCompositeConstruct %7 %12 %12 %12 %12
+               OpStore %9 %13
+               OpReturn
+               OpFunctionEnd
+
+)";
+
+TEST(DiffTest, UnrelatedShaders) {
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 12
++; Bound: 16
+ ; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+-OpEntryPoint Vertex %4 "main" %8 %10
++OpEntryPoint Fragment %4 "main" %14 %8
++OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
++OpName %14 "color"
+ OpName %8 "v"
+-OpName %10 "a"
++OpDecorate %14 RelaxedPrecision
++OpDecorate %14 Location 0
++OpDecorate %8 RelaxedPrecision
+ OpDecorate %8 Location 0
+-OpDecorate %10 Location 0
++OpDecorate %11 RelaxedPrecision
++OpDecorate %15 RelaxedPrecision
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+-%7 = OpTypePointer Output %6
++%12 = OpTypeVector %6 4
++%13 = OpTypePointer Output %12
++%14 = OpVariable %13 Output
+-%8 = OpVariable %7 Output
++%8 = OpVariable %9 Input
+ %9 = OpTypePointer Input %6
+-%10 = OpVariable %9 Input
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+-%11 = OpLoad %6 %10
++%11 = OpLoad %6 %8
+-OpStore %8 %11
++%15 = OpCompositeConstruct %12 %11 %11 %11 %11
++OpStore %14 %15
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrc, kDst, kDiff, options);
+}
+
+TEST(DiffTest, UnrelatedShadersNoDebug) {
+  constexpr char kSrcNoDebug[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %4 "main" %8 %10
+               OpSource ESSL 310
+               OpDecorate %8 Location 0
+               OpDecorate %10 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Output %6
+          %8 = OpVariable %7 Output
+          %9 = OpTypePointer Input %6
+         %10 = OpVariable %9 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpLoad %6 %10
+               OpStore %8 %11
+               OpReturn
+               OpFunctionEnd
+
+)";
+  constexpr char kDstNoDebug[] = R"(               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %9 %11
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %9 Location 0
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %11 Location 0
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %13 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+          %8 = OpTypePointer Output %7
+          %9 = OpVariable %8 Output
+         %10 = OpTypePointer Input %6
+         %11 = OpVariable %10 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpLoad %6 %11
+         %13 = OpCompositeConstruct %7 %12 %12 %12 %12
+               OpStore %9 %13
+               OpReturn
+               OpFunctionEnd
+
+)";
+  constexpr char kDiff[] = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+-; Bound: 12
++; Bound: 15
+ ; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+-OpEntryPoint Vertex %4 "main" %8 %10
++OpEntryPoint Fragment %4 "main" %8 %10
++OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
++OpDecorate %8 RelaxedPrecision
+ OpDecorate %8 Location 0
++OpDecorate %10 RelaxedPrecision
+ OpDecorate %10 Location 0
++OpDecorate %11 RelaxedPrecision
++OpDecorate %14 RelaxedPrecision
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+-%7 = OpTypePointer Output %6
++%12 = OpTypeVector %6 4
++%13 = OpTypePointer Output %12
+-%8 = OpVariable %7 Output
++%8 = OpVariable %13 Output
+ %9 = OpTypePointer Input %6
+ %10 = OpVariable %9 Input
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %11 = OpLoad %6 %10
++%14 = OpCompositeConstruct %12 %11 %11 %11 %11
+-OpStore %8 %11
++OpStore %8 %14
+ OpReturn
+ OpFunctionEnd
+)";
+  Options options;
+  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_files/unrelated_shaders_dst.spvasm b/test/diff/diff_files/unrelated_shaders_dst.spvasm
new file mode 100644
index 0000000..719715b
--- /dev/null
+++ b/test/diff/diff_files/unrelated_shaders_dst.spvasm
@@ -0,0 +1,31 @@
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %9 %11
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %9 "color"
+               OpName %11 "v"
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %9 Location 0
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %11 Location 0
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %13 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+          %8 = OpTypePointer Output %7
+          %9 = OpVariable %8 Output
+         %10 = OpTypePointer Input %6
+         %11 = OpVariable %10 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpLoad %6 %11
+         %13 = OpCompositeConstruct %7 %12 %12 %12 %12
+               OpStore %9 %13
+               OpReturn
+               OpFunctionEnd
+
diff --git a/test/diff/diff_files/unrelated_shaders_src.spvasm b/test/diff/diff_files/unrelated_shaders_src.spvasm
new file mode 100644
index 0000000..e77b2d2
--- /dev/null
+++ b/test/diff/diff_files/unrelated_shaders_src.spvasm
@@ -0,0 +1,25 @@
+;; Tests diff of unrelated shaders (with different execution models).
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %4 "main" %8 %10
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "v"
+               OpName %10 "a"
+               OpDecorate %8 Location 0
+               OpDecorate %10 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Output %6
+          %8 = OpVariable %7 Output
+          %9 = OpTypePointer Input %6
+         %10 = OpVariable %9 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpLoad %6 %10
+               OpStore %8 %11
+               OpReturn
+               OpFunctionEnd
+
diff --git a/test/diff/diff_test.cpp b/test/diff/diff_test.cpp
new file mode 100644
index 0000000..5b11d0e
--- /dev/null
+++ b/test/diff/diff_test.cpp
@@ -0,0 +1,228 @@
+// Copyright (c) 2022 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/diff/diff.h"
+
+#include "diff_test_utils.h"
+
+#include "source/opt/build_module.h"
+#include "source/opt/ir_context.h"
+#include "source/spirv_constant.h"
+#include "spirv-tools/libspirv.hpp"
+#include "tools/io.h"
+#include "tools/util/cli_consumer.h"
+
+#include <fstream>
+#include <string>
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+constexpr auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_6;
+
+std::unique_ptr<spvtools::opt::IRContext> Assemble(const std::string& spirv) {
+  spvtools::SpirvTools t(kDefaultEnvironment);
+  t.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
+  std::vector<uint32_t> binary;
+  if (!t.Assemble(spirv, &binary,
+                  spvtools::SpirvTools::kDefaultAssembleOption |
+                      SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS))
+    return nullptr;
+  return spvtools::BuildModule(kDefaultEnvironment,
+                               spvtools::utils::CLIMessageConsumer,
+                               binary.data(), binary.size());
+}
+
+TEST(DiffIndentTest, Diff) {
+  const std::string src = R"(OpCapability Shader
+    %ext_inst = OpExtInstImport "GLSL.std.450"
+    OpMemoryModel Logical GLSL450
+    OpEntryPoint Fragment %main "main"
+    OpExecutionMode %main OriginUpperLeft
+    %void = OpTypeVoid
+    %func = OpTypeFunction %void
+
+    %main = OpFunction %void None %func
+    %main_entry = OpLabel
+    OpReturn
+    OpFunctionEnd;)";
+
+  const std::string dst = R"(OpCapability Shader
+    OpMemoryModel Logical GLSL450
+    OpEntryPoint Fragment %main "main"
+    OpExecutionMode %main OriginUpperLeft
+    %void = OpTypeVoid
+    %func = OpTypeFunction %void
+
+    %main = OpFunction %void None %func
+    %main_entry = OpLabel
+    OpReturn
+    OpFunctionEnd;)";
+
+  const std::string diff = R"( ; SPIR-V
+ ; Version: 1.6
+ ; Generator: Khronos SPIR-V Tools Assembler; 0
+ ; Bound: 6
+ ; Schema: 0
+                OpCapability Shader
+-          %1 = OpExtInstImport "GLSL.std.450"
+                OpMemoryModel Logical GLSL450
+                OpEntryPoint Fragment %2 "main"
+                OpExecutionMode %2 OriginUpperLeft
+           %3 = OpTypeVoid
+           %4 = OpTypeFunction %3
+           %2 = OpFunction %3 None %4
+           %5 = OpLabel
+                OpReturn
+                OpFunctionEnd
+)";
+
+  Options options;
+  options.indent = true;
+  DoStringDiffTest(src, dst, diff, options);
+}
+
+TEST(DiffNoHeaderTest, Diff) {
+  const std::string src = R"(OpCapability Shader
+    %ext_inst = OpExtInstImport "GLSL.std.450"
+    OpMemoryModel Logical GLSL450
+    OpEntryPoint Fragment %main "main"
+    OpExecutionMode %main OriginUpperLeft
+    %void = OpTypeVoid
+    %func = OpTypeFunction %void
+
+    %main = OpFunction %void None %func
+    %main_entry = OpLabel
+    OpReturn
+    OpFunctionEnd;)";
+
+  const std::string dst = R"(OpCapability Shader
+    OpMemoryModel Logical GLSL450
+    OpEntryPoint Fragment %main "main"
+    OpExecutionMode %main OriginUpperLeft
+    %void = OpTypeVoid
+    %func = OpTypeFunction %void
+
+    %main = OpFunction %void None %func
+    %main_entry = OpLabel
+    OpReturn
+    OpFunctionEnd;)";
+
+  const std::string diff = R"( OpCapability Shader
+-%1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %2 = OpFunction %3 None %4
+ %5 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+  Options options;
+  options.no_header = true;
+  DoStringDiffTest(src, dst, diff, options);
+}
+
+TEST(DiffHeaderTest, Diff) {
+  const std::string src_spirv = R"(OpCapability Shader
+    %ext_inst = OpExtInstImport "GLSL.std.450"
+    OpMemoryModel Logical GLSL450
+    OpEntryPoint Fragment %main "main"
+    OpExecutionMode %main OriginUpperLeft
+    %void = OpTypeVoid
+    %func = OpTypeFunction %void
+
+    %main = OpFunction %void None %func
+    %main_entry = OpLabel
+    OpReturn
+    OpFunctionEnd;)";
+
+  const std::string dst_spirv = R"(OpCapability Shader
+    OpMemoryModel Logical GLSL450
+    OpEntryPoint Fragment %main "main"
+    OpExecutionMode %main OriginUpperLeft
+    %void = OpTypeVoid
+    %func = OpTypeFunction %void
+
+    %main = OpFunction %void None %func
+    %main_entry = OpLabel
+    OpReturn
+    OpFunctionEnd;)";
+
+  const std::string diff = R"( ; SPIR-V
+-; Version: 1.3
++; Version: 1.2
+-; Generator: Khronos SPIR-V Tools Assembler; 3
++; Generator: Khronos Glslang Reference Front End; 10
+ ; Bound: 6
+ ; Schema: 0
+ OpCapability Shader
+-%1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %2 = OpFunction %3 None %4
+ %5 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+  // Load the src and dst modules
+  std::unique_ptr<spvtools::opt::IRContext> src = Assemble(src_spirv);
+  ASSERT_TRUE(src);
+
+  std::unique_ptr<spvtools::opt::IRContext> dst = Assemble(dst_spirv);
+  ASSERT_TRUE(dst);
+
+  // Differentiate them in the header.
+  const spvtools::opt::ModuleHeader src_header = {
+      SpvMagicNumber,
+      SPV_SPIRV_VERSION_WORD(1, 3),
+      SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_ASSEMBLER, 3),
+      src->module()->IdBound(),
+      src->module()->schema(),
+  };
+  const spvtools::opt::ModuleHeader dst_header = {
+      SpvMagicNumber,
+      SPV_SPIRV_VERSION_WORD(1, 2),
+      SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_GLSLANG, 10),
+      dst->module()->IdBound(),
+      dst->module()->schema(),
+  };
+
+  src->module()->SetHeader(src_header);
+  dst->module()->SetHeader(dst_header);
+
+  // Take the diff
+  Options options;
+  std::ostringstream diff_result;
+  spv_result_t result =
+      spvtools::diff::Diff(src.get(), dst.get(), diff_result, options);
+  ASSERT_EQ(result, SPV_SUCCESS);
+
+  // Expect they match
+  EXPECT_EQ(diff_result.str(), diff);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_test_utils.cpp b/test/diff/diff_test_utils.cpp
new file mode 100644
index 0000000..14bb821
--- /dev/null
+++ b/test/diff/diff_test_utils.cpp
@@ -0,0 +1,58 @@
+// Copyright (c) 2022 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 "diff_test_utils.h"
+
+#include "source/opt/build_module.h"
+#include "source/opt/ir_context.h"
+
+#include "spirv-tools/libspirv.hpp"
+#include "tools/io.h"
+#include "tools/util/cli_consumer.h"
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+
+static constexpr auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_6;
+
+void DoStringDiffTest(const std::string& src_spirv,
+                      const std::string& dst_spirv,
+                      const std::string& expected_diff, Options options) {
+  // Load the src and dst modules
+  std::unique_ptr<spvtools::opt::IRContext> src = spvtools::BuildModule(
+      kDefaultEnvironment, spvtools::utils::CLIMessageConsumer, src_spirv,
+      spvtools::SpirvTools::kDefaultAssembleOption |
+          SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ASSERT_TRUE(src);
+
+  std::unique_ptr<spvtools::opt::IRContext> dst = spvtools::BuildModule(
+      kDefaultEnvironment, spvtools::utils::CLIMessageConsumer, dst_spirv,
+      spvtools::SpirvTools::kDefaultAssembleOption |
+          SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ASSERT_TRUE(dst);
+
+  // Take the diff
+  std::ostringstream diff_result;
+  spv_result_t result =
+      spvtools::diff::Diff(src.get(), dst.get(), diff_result, options);
+  ASSERT_EQ(result, SPV_SUCCESS);
+
+  // Expect they match
+  EXPECT_EQ(diff_result.str(), expected_diff);
+}
+
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/diff/diff_test_utils.h b/test/diff/diff_test_utils.h
new file mode 100644
index 0000000..938236e
--- /dev/null
+++ b/test/diff/diff_test_utils.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2022 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 TEST_DIFF_DIFF_TEST_UTILS_H_
+#define TEST_DIFF_DIFF_TEST_UTILS_H_
+
+#include "source/diff/diff.h"
+
+namespace spvtools {
+namespace diff {
+
+void DoStringDiffTest(const std::string& src_spirv,
+                      const std::string& dst_spirv,
+                      const std::string& expected_diff, Options options);
+
+}  // namespace diff
+}  // namespace spvtools
+
+#endif  // TEST_DIFF_DIFF_TEST_UTILS_H_
diff --git a/test/diff/lcs_test.cpp b/test/diff/lcs_test.cpp
new file mode 100644
index 0000000..3e097b3
--- /dev/null
+++ b/test/diff/lcs_test.cpp
@@ -0,0 +1,329 @@
+// Copyright (c) 2022 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/diff/lcs.h"
+
+#include <string>
+
+#include "gtest/gtest.h"
+
+namespace spvtools {
+namespace diff {
+namespace {
+
+using Sequence = std::vector<int>;
+using LCS = LongestCommonSubsequence<Sequence>;
+
+void VerifyMatch(const Sequence& src, const Sequence& dst,
+                 size_t expected_match_count) {
+  DiffMatch src_match, dst_match;
+
+  LCS lcs(src, dst);
+  size_t match_count =
+      lcs.Get<int>([](int s, int d) { return s == d; }, &src_match, &dst_match);
+
+  EXPECT_EQ(match_count, expected_match_count);
+
+  size_t src_cur = 0;
+  size_t dst_cur = 0;
+  size_t matches_seen = 0;
+
+  while (src_cur < src.size() && dst_cur < dst.size()) {
+    if (src_match[src_cur] && dst_match[dst_cur]) {
+      EXPECT_EQ(src[src_cur], dst[dst_cur])
+          << "Src: " << src_cur << " Dst: " << dst_cur;
+      ++src_cur;
+      ++dst_cur;
+      ++matches_seen;
+      continue;
+    }
+    if (!src_match[src_cur]) {
+      ++src_cur;
+    }
+    if (!dst_match[dst_cur]) {
+      ++dst_cur;
+    }
+  }
+
+  EXPECT_EQ(matches_seen, expected_match_count);
+}
+
+TEST(LCSTest, EmptySequences) {
+  Sequence src, dst;
+
+  DiffMatch src_match, dst_match;
+
+  LCS lcs(src, dst);
+  size_t match_count =
+      lcs.Get<int>([](int s, int d) { return s == d; }, &src_match, &dst_match);
+
+  EXPECT_EQ(match_count, 0u);
+  EXPECT_TRUE(src_match.empty());
+  EXPECT_TRUE(dst_match.empty());
+}
+
+TEST(LCSTest, EmptySrc) {
+  Sequence src, dst = {1, 2, 3};
+
+  DiffMatch src_match, dst_match;
+
+  LCS lcs(src, dst);
+  size_t match_count =
+      lcs.Get<int>([](int s, int d) { return s == d; }, &src_match, &dst_match);
+
+  EXPECT_EQ(match_count, 0u);
+  EXPECT_TRUE(src_match.empty());
+  EXPECT_EQ(dst_match, DiffMatch(3, false));
+}
+
+TEST(LCSTest, EmptyDst) {
+  Sequence src = {1, 2, 3}, dst;
+
+  DiffMatch src_match, dst_match;
+
+  LCS lcs(src, dst);
+  size_t match_count =
+      lcs.Get<int>([](int s, int d) { return s == d; }, &src_match, &dst_match);
+
+  EXPECT_EQ(match_count, 0u);
+  EXPECT_EQ(src_match, DiffMatch(3, false));
+  EXPECT_TRUE(dst_match.empty());
+}
+
+TEST(LCSTest, Identical) {
+  Sequence src = {1, 2, 3, 4, 5, 6}, dst = {1, 2, 3, 4, 5, 6};
+
+  DiffMatch src_match, dst_match;
+
+  LCS lcs(src, dst);
+  size_t match_count =
+      lcs.Get<int>([](int s, int d) { return s == d; }, &src_match, &dst_match);
+
+  EXPECT_EQ(match_count, 6u);
+  EXPECT_EQ(src_match, DiffMatch(6, true));
+  EXPECT_EQ(dst_match, DiffMatch(6, true));
+}
+
+TEST(LCSTest, SrcPrefix) {
+  Sequence src = {1, 2, 3, 4}, dst = {1, 2, 3, 4, 5, 6};
+
+  DiffMatch src_match, dst_match;
+
+  LCS lcs(src, dst);
+  size_t match_count =
+      lcs.Get<int>([](int s, int d) { return s == d; }, &src_match, &dst_match);
+
+  const DiffMatch src_expect = {true, true, true, true};
+  const DiffMatch dst_expect = {true, true, true, true, false, false};
+
+  EXPECT_EQ(match_count, 4u);
+  EXPECT_EQ(src_match, src_expect);
+  EXPECT_EQ(dst_match, dst_expect);
+}
+
+TEST(LCSTest, DstPrefix) {
+  Sequence src = {1, 2, 3, 4, 5, 6}, dst = {1, 2, 3, 4, 5};
+
+  DiffMatch src_match, dst_match;
+
+  LCS lcs(src, dst);
+  size_t match_count =
+      lcs.Get<int>([](int s, int d) { return s == d; }, &src_match, &dst_match);
+
+  const DiffMatch src_expect = {true, true, true, true, true, false};
+  const DiffMatch dst_expect = {true, true, true, true, true};
+
+  EXPECT_EQ(match_count, 5u);
+  EXPECT_EQ(src_match, src_expect);
+  EXPECT_EQ(dst_match, dst_expect);
+}
+
+TEST(LCSTest, SrcSuffix) {
+  Sequence src = {3, 4, 5, 6}, dst = {1, 2, 3, 4, 5, 6};
+
+  DiffMatch src_match, dst_match;
+
+  LCS lcs(src, dst);
+  size_t match_count =
+      lcs.Get<int>([](int s, int d) { return s == d; }, &src_match, &dst_match);
+
+  const DiffMatch src_expect = {true, true, true, true};
+  const DiffMatch dst_expect = {false, false, true, true, true, true};
+
+  EXPECT_EQ(match_count, 4u);
+  EXPECT_EQ(src_match, src_expect);
+  EXPECT_EQ(dst_match, dst_expect);
+}
+
+TEST(LCSTest, DstSuffix) {
+  Sequence src = {1, 2, 3, 4, 5, 6}, dst = {5, 6};
+
+  DiffMatch src_match, dst_match;
+
+  LCS lcs(src, dst);
+  size_t match_count =
+      lcs.Get<int>([](int s, int d) { return s == d; }, &src_match, &dst_match);
+
+  const DiffMatch src_expect = {false, false, false, false, true, true};
+  const DiffMatch dst_expect = {true, true};
+
+  EXPECT_EQ(match_count, 2u);
+  EXPECT_EQ(src_match, src_expect);
+  EXPECT_EQ(dst_match, dst_expect);
+}
+
+TEST(LCSTest, None) {
+  Sequence src = {1, 3, 5, 7, 9}, dst = {2, 4, 6, 8, 10, 12};
+
+  DiffMatch src_match, dst_match;
+
+  LCS lcs(src, dst);
+  size_t match_count =
+      lcs.Get<int>([](int s, int d) { return s == d; }, &src_match, &dst_match);
+
+  EXPECT_EQ(match_count, 0u);
+  EXPECT_EQ(src_match, DiffMatch(5, false));
+  EXPECT_EQ(dst_match, DiffMatch(6, false));
+}
+
+TEST(LCSTest, NonContiguous) {
+  Sequence src = {1, 2, 3, 4, 5, 6, 10}, dst = {2, 4, 5, 8, 9, 10, 12};
+
+  DiffMatch src_match, dst_match;
+
+  LCS lcs(src, dst);
+  size_t match_count =
+      lcs.Get<int>([](int s, int d) { return s == d; }, &src_match, &dst_match);
+
+  const DiffMatch src_expect = {false, true, false, true, true, false, true};
+  const DiffMatch dst_expect = {true, true, true, false, false, true, false};
+
+  EXPECT_EQ(match_count, 4u);
+  EXPECT_EQ(src_match, src_expect);
+  EXPECT_EQ(dst_match, dst_expect);
+}
+
+TEST(LCSTest, WithDuplicates) {
+  Sequence src = {1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4},
+           dst = {1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4};
+  VerifyMatch(src, dst, 6);
+}
+
+TEST(LCSTest, Large) {
+  const std::string src_str =
+      "GUJwrJSlkKJXxCVIAxlVgnUyOrdyRyFtlZwWMmFhYGfkFTNnhiBmClgHyrcXMVwfrRxNUfQk"
+      "qaoGvCbPZHAzXsaZpXHPfJxOMCUtRDmIQpfiXKbHQbhTfPqhxBDWvmTQAqwsWTLajZYtMUnf"
+      "hNNCfkuAXkZsaebwEbIZOxTDZsqSMUfCMoGeKJGVSNFgLTiBMbdvchHGfFRkHKcYCDjBfIcj"
+      "todPnvDjzQYWBcvIfVvyBzHikrwpDORaGEZLhmyztIFCLJOqeLhOzERYmVqzlsoUzruTXTXq"
+      "DLTxQRakOCMRrgRzCDTXfwwfDcKMBVnxRZemjcwcEsOVxwtwdBCWJycsDcZKlvrCvZaenKlv"
+      "vyByDQeLdxAyBnPkIMQlMQwqjUfRLybeoaOanlbFkpTPPZdHelQrIvucTHzMpWWQTbuANwvN"
+      "OVhCGGoIcGNDpfIsaBexlMMdHsxMGerTngmjpdPeQQJHfvKZkdYqAzrtDohqtDsaFMxQViVQ"
+      "YszDVgyoSHZdOXAvXkJidojLvGZOzhRajVPhWDwKuGqdaympELxHsrXAJYufdCPwJdGJfWqq"
+      "yvTWpcrFHOIuCEmNLnSCDsxQGRVDwyCykBJazhApfCnrOadnafvqfVuFqEXMSrYbHTfTnzbz"
+      "MhISyOtMUITaurCXvanCbuOXBhHyCjOhVbxnMvhlPmZBMQgEHCghtAJVMXGPNRtszVZlPxVl"
+      "QIPTBnPUPejlyZGPqeICyNngdQGkvKbIoWlTLBtVhMdBeUMozNlKQTIPYBeImVcMdLafuxUf"
+      "TIXysmcTrUTcspOSKBxhdhLwiRnREGFWJTfUKsgGOAQeYojXdrqsGjMJfiKalyoiqrgLnlij"
+      "CtOapoxDGVOOBalNYGzCtBlxbvaAzxipGnJpOEbmXcpeoIsAdxspKBzBDgoPVxnuRBUwmTSr"
+      "CRpWhxikgUYQVCwalLUIeBPRyhhsECGCXJmGDZSCIUaBwROkigzdeVPOXhgCGBEprWtNdYfL"
+      "tOUYJHQXxiIJgSGmWntezJFpNQoTPbRRYAGhtYvAechvBcYWocLkYFxsDAuszvQNLXdhmAHw"
+      "DErcjbtCdQllnKcDADVNWVezljjLrAuyGHetINMgAvJZwOEYakihYVUbZGCsHEufluLNyNHy"
+      "gqtSTSFFjBHiIqQejTPWybLdpWNwZrWvIWnlzUcGNQPEYHVPCbteWknjAnWrdTBeCbHUDBoK"
+      "aHvDStmpNRGIjvlumiZTbdZNAzUeSFnFChCsSExwXeEfDJfjyOoSBofHzJqJErvHLNyUJTjX"
+      "qmtgKPpMKohUPBMhtCteQFcNEpWrUVGbibMOpvBwdiWYXNissArpSasVJFgDzrqTyGkerTMX"
+      "gcrzFUGFZRhNdekaJeKYPogsofJaRsUQmIRyYdkrxKeMgLPpwOfSKJOqzXDoeHljTzhOwEVy"
+      "krOEnACFrWhufajsMitjOWdLOHHchQDddGPzxknEgdwmZepKDvRZGCuPqzeQkjOPqUBKpKLJ"
+      "eKieSsRXkaqxSPGajfvPKmwFWdLByEcLgvrmteazgFjmMGrLYqRRxzUOfOCokenqHVYstBHf"
+      "AwsWsqPTvqsRJUfGGTaYiylZMGbQqTzINhFHvdlRQvvYKBcuAHdBeKlHSxVrSsEKbcAvnIcf"
+      "xzdVDdwQPHMCHeZZRpGHWvKzgTGzSTbYTeOPyKvvYWmQToTpsjAtKUJUjcEHWhmdBLDTBMHJ"
+      "ivBXcLGtCsumNNVFyGbVviGmqHTdyBlkneibXBesKJGOUzOtIwXCPJggqBekSzNQYkALlItk"
+      "cbEhbdXAIKVHYpInLwxXalKZrkrpxtfuagqMGmRJnJbFQaEoYMoqPsxZpocddPXXPyvxVkaF"
+      "qdKISejWDhBImnEEOPDcyWTubbfVfwUztciaFJcsPLhgYVfhqlOfoNjKbmTFptFttYuyBrUI"
+      "zzmZypOqrjQHTGFwlHStpIwxPtMvtsEDpsmWIgwzYgwmdpbMOnfElZMYpVIcvzSWejeJcdUB"
+      "QUoBRUmGQVVWvEDseuozrDjgdXFScPwwsgaUPwSzScfBNrkpmEFDSZLKfNjMqvOmUtocUkbo"
+      "VGFEKgGLbNruwLgXHTloWDrnqymPVAtzjWPutonIsMDPeeCmTjYWAFXcyTAlBeiJTIRkZxiM"
+      "kLjMnAflSNJzmZkatXkYiPEMYSmzHbLKEizHbEjQOxBDzpRHiFjhedqiyMiUMvThjaRFmwll"
+      "aMGgwKBIKepwyoEdnuhtzJzboiNEAFKiqiWxxmkRFRoTiFWXLPAWLuzSCrajgkQhDxAQDqyM"
+      "VwZlhZicQLEDYYisEalesDWZAYzcvENuHUwRutIsGgsdoYwOZiURhcgdbTGWBNqhrFjvTQCj"
+      "VlTPNlRdRLaaqzUBBwbdtyXFkCBUYYMbmRrkFxfxbCqkgZNGyHPKLkOPnezfVTRmRQgCgHbx"
+      "wcZlInVOwmFePnSIbThMJosimzkhfuiqYEpwHQiemqsSDNNdbNhBLzbsPZBJZujSHJGtYKGb"
+      "HaAYGJZxBumsKUrATwPuqXFLfwNyImLQbchBKiJAYRZhkcrKCHXBEGYyBhBGvSqvabcRUrfq"
+      "AbPiMzjHAehGYjDEmxAnYLyoSFdeWVrfJUCuYZPluhXEBuyUpKaRXDKXeiCvGidpvATwMbcz"
+      "DZpzxrhTZYyrFORFQWTbPLCBjMKMhlRMFEiarDgGPttjmkrQVlujztMSkxXffXFNqLWOLThI"
+      "KBoyMHoFTEPCdUAZjLTifAdjjUehyDLEGKlRTFoLpjalziRSUjZfRYbNzhiHgTHowMMkKTwE"
+      "ZgnqiirMtnNpaBJqhcIVrWXPpcPWZfRpsPstHleFJDZYAsxYhOREVbFtebXTZRAIjGgWeoiN"
+      "qPLCCAVadqmUrjOcqIbdCTpcDRWuDVbHrZOQRPhqbyvOWwxAWJphjLiDgoAybcjzgfVktPlj"
+      "kNBCjelpuQfnYsiTgPpCNKYtOrxGaLEEtAuLdGdDsONHNhSn";
+  const std::string dst_str =
+      "KzitfifORCbGhfNEbnbObUdFLLaAsLOpMkOeKupjCoatzqfHBkNJfSgqSMYouswfNMnoQngK"
+      "jWwyPKmEnoZWyPBUdQRmKUNudUclueKXKQefUdXWUyyqtumzsFKznrLVLwfvPZpLChNYrrHK"
+      "AtpfOuVHiUKyeRCrktJAhkyFKmPWrASEMvBLNOzuGlvinZjvZUUXazNEkyMPiOLdqXvCIroC"
+      "MeWsvjHShlLhDwLZrVlpYBnDJmILcsNFDSoaLWOKNNkNGBgNBvVjPCJXAuKfsrKZhYcdEpxK"
+      "UihiRkYvMiLyOUvaqBMklLDwEhvQBfCXHSRoqsLsSCzLZQhIYMhBapvHaPbDoRrHoJXZsNXc"
+      "rxZYCrOMIzYcVPwDCFiHBFnPNTTeAeKEMGeVUeCaAeuWZmngyPWlQBcgWumSUIfbhjVYdnpV"
+      "hRSJXrIoFZubBXfNOMhilAkVPixrhILZKgDoFTvytPFPfBLMnbhSOBmLWCbJsLQxrCrMAlOw"
+      "RmfSQyGhrjhzYVqFSBHeoQBagFwyxIjcHFZngntpVHbSwqhwHeMnWSsISPljTxSNXfCxLebW"
+      "GhMdlphtJbdvhEcjNpwPCFqhdquxCyOxkjsDUPNgjpDcpIMhMwMclNhfESTrroJaoyeGQclV"
+      "gonnhuQRmXcBwcsWeLqjNngZOlyMyfeQBwnwMVJEvGqknDyzSApniRTPgJpFoDkJJhXQFuFB"
+      "VqhuEPMRGCeTDOSEFmXeIHOnDxaJacvnmORwVpmrRhGjDpUCkuODNPdZMdupYExDEDnDLdNF"
+      "iObKBaVWpGVMKdgNLgsNxcpypBPPKKoaajeSGPZQJWSOKrkLjiFexYVmUGxJnbTNsCXXLfZp"
+      "jfxQAEVYvqKehBzMsVHVGWmTshWFAoCNDkNppzzjHBZWckrzSTANICioCJSpLwPwQvtXVxst"
+      "nTRBAboPFREEUFazibpFesCsjzUOnECwoPCOFiwGORlIZVLpUkJyhYXCENmzTBLVigOFuCWO"
+      "IiXBYmiMtsxnUdoqSTTGyEFFrQsNAjcDdOKDtHwlANWoUVwiJCMCQFILdGqzEePuSXFbOEOz"
+      "dLlEnTJbKRSTfAFToOZNtDXTfFgvQiefAKbSUWUXFcpCjRYCBNXCCcLMjjuUDXErpiNsRuIx"
+      "mgHsrObTEXcnmjdqxTGhTjTeYizNnkrJRhNQIqDXmZMwArBccnixpcuiGOOexjgkpcEyGAnz"
+      "UbgiBfflTUyJfZeFFLrZVueFkSRosebnnwAnakIrywTGByhQKWvmNQJsWQezqLhHQzXnEpeD"
+      "rFRTSQSpVxPzSeEzfWYzfpcenxsUyzOMLxhNEhfcuprDtqubsXehuqKqZlLQeSclvoGjuKJK"
+      "XoWrazsgjXXnkWHdqFESZdMGDYldyYdbpSZcgBPgEKLWZHfBirNPLUadmajYkiEzmGuWGELB"
+      "WLiSrMdaGSbptKmgYVqMGcQaaATStiZYteGAPxSEBHuAzzjlRHYsrdDkaGNXmzRGoalJMiCC"
+      "GMtWSDMhgvRSEgKnywbRgnqWXFlwrhXbbvcgLGtWSuKQBiqIlWkfPMozOTWgVoLHavDJGRYI"
+      "YerrmZnTMtuuxmZALWakfzUbksTwoetqkOiRPGqGZepcVXHoZyOaaaijjZWQLlIhYwiQNbfc"
+      "KCwhhFaMQBoaCnOecJEdKzdsMPFEYQuJNPYiiNtsYxaWBRuWjlLqGokHMNtyTQfSJKbgGdol"
+      "fWlOZdupouQMfUWXIYHzyJHefMDnqxxasDxtgArvDqtwjDBaVEMACPkLFpiDOoKCHqkWVizh"
+      "lKqbOHpsPKkhjRQRNGYRYEfxtBjYvlCvHBNUwVuIwDJYMqHxEFtwdLqYWvjdOfQmNiviDfUq"
+      "pbucbNwjNQfMYgwUuPnQWIPOlqHcbjtuDXvTzLtkdBQanJbrmLSyFqSapZCSPMDOrxWVYzyO"
+      "lwDTTJFmKxoyfPunadkHcrcSQaQsAbrQtbhqwSTXGTPURYTCbNozjAVwbmcyVxIbZudBZWYm"
+      "rnSDyelGCRRWYtrUxvOVWlTLHHdYuAmVMGnGbHscbjmjmAzmYLaCxNNwhmMYdExKvySxuYpE"
+      "rVGwfqMngBCHnZodotNaNJZiNRFWubuPDfiywXPiyVWoQMeOlSuWmpilLTIFOvfpjmJTgrWa"
+      "dgoxYeyPyOaglOvZVGdFOBSeqEcGXBwjoeUAXqkpvOxEpSXhmklKZydTvRVYVvfQdRNNDkCT"
+      "dLNfcZCFQbZORdcDOhwotoyccrSbWvlqYMoiAYeEpDzZTvkamapzZMmCpEutZFCcHBWGIIkr"
+      "urwDNHrobaErPpclyEegLJDtkfUWSNWZosWSbBGAHIvJsFNUlJXbnkSVycLkOVQVcNcUtiBy"
+      "djLDIFsycbPBEWaMvCbntNtJlOeCttvXypGnHAQFnFSiXFWWqonWuVIKmVPpKXuJtFguXCWC"
+      "rNExYYvxLGEmuZJLJDjHgjlQyOzeieCpizJxkrdqKCgomyEkvsyVYSsLeyLvOZQrrgEJgRFK"
+      "CjYtoOfluNrLdRMTRkQXmAiMRFwloYECpXCReAMxOkNiwCtutsrqWoMHsrogRqPoUCueonvW"
+      "MTwmkAkajfGJkhnQidwpwIMEttQkzIMOPvvyWZHpqkMHWlNTeSKibfRfwDyxveKENZhtlPwP"
+      "dfAjwegjRcavtFnkkTNVYdCdCrgdUvzsIcqmUjwGmVvuuQvjVrWWIDBmAzQtiZPYvCOEWjce"
+      "rWzeqVKeiYTJBOedmQCVidOgUIEjfRnbGvUbctYxfRybJkdmeAkLZQMRMGPOnsPbFswXAoCK"
+      "IxWGwohoPpEJxslbqHFKSwknxTmrDCITRZWEDkGQeucPxHBdYkduwbYhKnoxCKhgjBFiFawC"
+      "QtgTDldTQmlOsBiGLquMjuecAbrUJJvNtXbFNGjWxaZPimSRXUJWgRbydpsczOqSFIeEtuKA"
+      "ZpRhmLtPdVNKdSDQZeeImUFmUwXApRTUNHItyvFyJtNtn";
+
+  Sequence src;
+  Sequence dst;
+
+  src.reserve(src_str.length());
+  dst.reserve(dst_str.length());
+
+  for (char c : src_str) {
+    src.push_back(c);
+  }
+  for (char c : dst_str) {
+    dst.push_back(c);
+  }
+
+  VerifyMatch(src, dst, 723);
+}
+
+}  // namespace
+}  // namespace diff
+}  // namespace spvtools
diff --git a/test/ext_inst.cldebug100_test.cpp b/test/ext_inst.cldebug100_test.cpp
index 4f1e106..0bbdd3a 100644
--- a/test/ext_inst.cldebug100_test.cpp
+++ b/test/ext_inst.cldebug100_test.cpp
@@ -581,7 +581,7 @@
     OpenCLDebugInfo100DebugSource, ExtInstCLDebugInfo100RoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
         // TODO(dneto): Should this be a list of sourc texts,
-        // to accomodate length limits?
+        // to accommodate length limits?
         CASE_I(Source),
         CASE_II(Source),
     })));
diff --git a/test/ext_inst.opencl_test.cpp b/test/ext_inst.opencl_test.cpp
index 7547d92..d80a9bd 100644
--- a/test/ext_inst.opencl_test.cpp
+++ b/test/ext_inst.opencl_test.cpp
@@ -233,7 +233,7 @@
         CASE3(UMad_hi, u_mad_hi), // enum value 204
     })));
 
-// OpenCL.std: 2.3 Common instrucitons
+// OpenCL.std: 2.3 Common instructions
 INSTANTIATE_TEST_SUITE_P(
     OpenCLCommon, ExtInstOpenCLStdRoundTripTest,
     ::testing::ValuesIn(std::vector<InstructionCase>({
diff --git a/test/fuzz/fuzz_test_util.cpp b/test/fuzz/fuzz_test_util.cpp
index bf0a4ff..93c9c58 100644
--- a/test/fuzz/fuzz_test_util.cpp
+++ b/test/fuzz/fuzz_test_util.cpp
@@ -149,7 +149,7 @@
   json_options.add_whitespace = true;
   auto json_generation_status = google::protobuf::util::MessageToJsonString(
       transformations, &json_string, json_options);
-  if (json_generation_status == google::protobuf::util::Status::OK) {
+  if (json_generation_status.ok()) {
     std::ofstream transformations_json_file(filename);
     transformations_json_file << json_string;
     transformations_json_file.close();
diff --git a/test/fuzz/fuzzerutil_test.cpp b/test/fuzz/fuzzerutil_test.cpp
index 0ad3e74..1286d38 100644
--- a/test/fuzz/fuzzerutil_test.cpp
+++ b/test/fuzz/fuzzerutil_test.cpp
@@ -70,7 +70,7 @@
   ASSERT_TRUE(fuzzerutil::MaybeFindBlock(ir_context, block_id2) != nullptr);
   // Block with id 13 cannot be found.
   ASSERT_FALSE(fuzzerutil::MaybeFindBlock(ir_context, block_id3) != nullptr);
-  // Block with id 8 exisits but don't not of type OpLabel.
+  // Block with id 8 exists but don't not of type OpLabel.
   ASSERT_FALSE(fuzzerutil::MaybeFindBlock(ir_context, block_id4) != nullptr);
 }
 
@@ -965,7 +965,7 @@
   ASSERT_EQ(
       91, fuzzerutil::MaybeGetPointerType(ir_context, 90, input_storage_class));
 
-  // A pointer with id=91 and pointee type 90 exisits, but the type should be
+  // A pointer with id=91 and pointee type 90 exists, but the type should be
   // input.
   ASSERT_EQ(0, fuzzerutil::MaybeGetPointerType(ir_context, 90,
                                                function_storage_class));
diff --git a/test/fuzz/transformation_add_parameter_test.cpp b/test/fuzz/transformation_add_parameter_test.cpp
index 2ae60c9..7f069b5 100644
--- a/test/fuzz/transformation_add_parameter_test.cpp
+++ b/test/fuzz/transformation_add_parameter_test.cpp
@@ -367,7 +367,7 @@
       transformation_bad_3.IsApplicable(context.get(), transformation_context));
 
   // Function with id 14 does not have any callers.
-  // Bad: Id 18 is not a vaild type.
+  // Bad: Id 18 is not a valid type.
   TransformationAddParameter transformation_bad_4(14, 50, 18, {{}}, 51);
   ASSERT_FALSE(
       transformation_bad_4.IsApplicable(context.get(), transformation_context));
diff --git a/test/fuzz/transformation_add_type_int_test.cpp b/test/fuzz/transformation_add_type_int_test.cpp
index ed8e00a..4cbfed0 100644
--- a/test/fuzz/transformation_add_type_int_test.cpp
+++ b/test/fuzz/transformation_add_type_int_test.cpp
@@ -87,7 +87,7 @@
       transformation.IsApplicable(context.get(), transformation_context));
 
   // By default SPIR-V does not support 16-bit integers.
-  // Below we add such capability, so the test should now be succesful.
+  // Below we add such capability, so the test should now be successful.
   context.get()->get_feature_mgr()->AddCapability(SpvCapabilityInt16);
   ASSERT_TRUE(TransformationAddTypeInt(7, 16, true)
                   .IsApplicable(context.get(), transformation_context));
diff --git a/test/fuzz/transformation_adjust_branch_weights_test.cpp b/test/fuzz/transformation_adjust_branch_weights_test.cpp
index 1bf2c59..5984a3e 100644
--- a/test/fuzz/transformation_adjust_branch_weights_test.cpp
+++ b/test/fuzz/transformation_adjust_branch_weights_test.cpp
@@ -106,7 +106,7 @@
                                                kConsoleMessageConsumer));
   TransformationContext transformation_context(
       MakeUnique<FactManager>(context.get()), validator_options);
-  // Tests OpBranchConditional instruction with weigths.
+  // Tests OpBranchConditional instruction with weights.
   auto instruction_descriptor =
       MakeInstructionDescriptor(33, SpvOpBranchConditional, 0);
   auto transformation =
diff --git a/test/fuzzers/BUILD.gn b/test/fuzzers/BUILD.gn
index 2aef4e8..fc458a4 100644
--- a/test/fuzzers/BUILD.gn
+++ b/test/fuzzers/BUILD.gn
@@ -87,18 +87,21 @@
 spvtools_fuzzer("spvtools_opt_performance_fuzzer_src") {
   sources = [
     "spvtools_opt_performance_fuzzer.cpp",
+    "spvtools_opt_fuzzer_common.cpp",
   ]
 }
 
 spvtools_fuzzer("spvtools_opt_legalization_fuzzer_src") {
   sources = [
     "spvtools_opt_legalization_fuzzer.cpp",
+    "spvtools_opt_fuzzer_common.cpp",
   ]
 }
 
 spvtools_fuzzer("spvtools_opt_size_fuzzer_src") {
   sources = [
     "spvtools_opt_size_fuzzer.cpp",
+    "spvtools_opt_fuzzer_common.cpp",
   ]
 }
 
diff --git a/test/fuzzers/CMakeLists.txt b/test/fuzzers/CMakeLists.txt
index 50c4589..e1fe516 100644
--- a/test/fuzzers/CMakeLists.txt
+++ b/test/fuzzers/CMakeLists.txt
@@ -46,9 +46,9 @@
   add_spvtools_libfuzzer_target(TARGET spvtools_as_fuzzer SRCS spvtools_as_fuzzer.cpp random_generator.cpp LIBS ${SPIRV_TOOLS_FULL_VISIBILITY})
   add_spvtools_libfuzzer_target(TARGET spvtools_binary_parser_fuzzer SRCS spvtools_binary_parser_fuzzer.cpp random_generator.cpp LIBS ${SPIRV_TOOLS_FULL_VISIBILITY})
   add_spvtools_libfuzzer_target(TARGET spvtools_dis_fuzzer SRCS spvtools_dis_fuzzer.cpp random_generator.cpp LIBS ${SPIRV_TOOLS_FULL_VISIBILITY})
-  add_spvtools_libfuzzer_target(TARGET spvtools_opt_legalization_fuzzer SRCS spvtools_opt_legalization_fuzzer.cpp random_generator.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS_FULL_VISIBILITY})
-  add_spvtools_libfuzzer_target(TARGET spvtools_opt_performance_fuzzer SRCS spvtools_opt_performance_fuzzer.cpp random_generator.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS_FULL_VISIBILITY})
-  add_spvtools_libfuzzer_target(TARGET spvtools_opt_size_fuzzer SRCS spvtools_opt_size_fuzzer.cpp random_generator.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS_FULL_VISIBILITY})
+  add_spvtools_libfuzzer_target(TARGET spvtools_opt_legalization_fuzzer SRCS spvtools_opt_legalization_fuzzer.cpp spvtools_opt_fuzzer_common.cpp random_generator.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS_FULL_VISIBILITY})
+  add_spvtools_libfuzzer_target(TARGET spvtools_opt_performance_fuzzer SRCS spvtools_opt_performance_fuzzer.cpp spvtools_opt_fuzzer_common.cpp random_generator.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS_FULL_VISIBILITY})
+  add_spvtools_libfuzzer_target(TARGET spvtools_opt_size_fuzzer SRCS spvtools_opt_size_fuzzer.cpp spvtools_opt_fuzzer_common.cpp random_generator.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS_FULL_VISIBILITY})
   add_spvtools_libfuzzer_target(TARGET spvtools_val_fuzzer SRCS spvtools_val_fuzzer.cpp random_generator.cpp LIBS ${SPIRV_TOOLS_FULL_VISIBILITY})
   if (${SPIRV_BUILD_FUZZER})
     add_spvtools_libfuzzer_target(TARGET spvtools_fuzz_fuzzer SRCS spvtools_fuzz_fuzzer.cpp random_generator.cpp LIBS SPIRV-Tools-fuzz ${SPIRV_TOOLS_FULL_VISIBILITY})
diff --git a/test/fuzzers/spvtools_as_fuzzer.cpp b/test/fuzzers/spvtools_as_fuzzer.cpp
index ba3f5b9..8ead1cf 100644
--- a/test/fuzzers/spvtools_as_fuzzer.cpp
+++ b/test/fuzzers/spvtools_as_fuzzer.cpp
@@ -32,22 +32,13 @@
     return 0;
   }
 
-  std::vector<uint32_t> input;
-  input.resize(size >> 2);
-  size_t count = 0;
-  for (size_t i = 0; (i + 3) < size; i += 4) {
-    input[count++] = data[i] | (data[i + 1] << 8) | (data[i + 2] << 16) |
-                     (data[i + 3]) << 24;
-  }
-
-  std::vector<char> input_str;
-  size_t char_count = input.size() * sizeof(uint32_t) / sizeof(char);
-  input_str.resize(char_count);
-  memcpy(input_str.data(), input.data(), input.size() * sizeof(uint32_t));
+  std::vector<char> contents;
+  contents.resize(size);
+  memcpy(contents.data(), data, size);
 
   spv_binary binary = nullptr;
   spv_diagnostic diagnostic = nullptr;
-  spvTextToBinaryWithOptions(context, input_str.data(), input_str.size(),
+  spvTextToBinaryWithOptions(context, contents.data(), contents.size(),
                              SPV_TEXT_TO_BINARY_OPTION_NONE, &binary,
                              &diagnostic);
   if (diagnostic) {
@@ -61,7 +52,7 @@
     binary = nullptr;
   }
 
-  spvTextToBinaryWithOptions(context, input_str.data(), input_str.size(),
+  spvTextToBinaryWithOptions(context, contents.data(), contents.size(),
                              SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS,
                              &binary, &diagnostic);
   if (diagnostic) {
diff --git a/test/fuzzers/spvtools_opt_fuzzer_common.cpp b/test/fuzzers/spvtools_opt_fuzzer_common.cpp
new file mode 100644
index 0000000..4978509
--- /dev/null
+++ b/test/fuzzers/spvtools_opt_fuzzer_common.cpp
@@ -0,0 +1,84 @@
+// Copyright (c) 2021 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/fuzzers/spvtools_opt_fuzzer_common.h"
+
+#include "source/opt/build_module.h"
+#include "test/fuzzers/random_generator.h"
+
+namespace spvtools {
+namespace fuzzers {
+
+int OptFuzzerTestOneInput(
+    const uint8_t* data, size_t size,
+    const std::function<void(spvtools::Optimizer&)>& register_passes) {
+  if (size < 1) {
+    return 0;
+  }
+
+  spvtools::fuzzers::RandomGenerator random_gen(data, size);
+  auto target_env = random_gen.GetTargetEnv();
+  spvtools::Optimizer optimizer(target_env);
+  optimizer.SetMessageConsumer([](spv_message_level_t, const char*,
+                                  const spv_position_t&, const char*) {});
+
+  std::vector<uint32_t> input;
+  input.resize(size >> 2);
+
+  size_t count = 0;
+  for (size_t i = 0; (i + 3) < size; i += 4) {
+    input[count++] = data[i] | (data[i + 1] << 8) | (data[i + 2] << 16) |
+                     (data[i + 3]) << 24;
+  }
+
+  // The largest possible id bound is used when running the optimizer, to avoid
+  // the problem of id overflows.
+  const size_t kFinalIdLimit = UINT32_MAX;
+
+  // The input is scanned to check that it does not already use an id too close
+  // to this limit. This still gives the optimizer a large set of ids to
+  // consume. It is thus very unlikely that id overflow will occur during
+  // fuzzing. If it does, then the initial id limit should be decreased.
+  const size_t kInitialIdLimit = kFinalIdLimit - 1000000U;
+
+  // Build the module and scan it to check that all used ids are below the
+  // initial limit.
+  auto ir_context =
+      spvtools::BuildModule(target_env, nullptr, input.data(), input.size());
+  if (ir_context == nullptr) {
+    // It was not possible to build a valid module; that's OK - skip this input.
+    return 0;
+  }
+  if (ir_context->module()->id_bound() >= kInitialIdLimit) {
+    // The input already has a very large id bound. The input is thus abandoned,
+    // to avoid the possibility of ending up hitting the id bound limit.
+    return 0;
+  }
+
+  // Set the optimizer and its validator up with the largest possible id bound
+  // limit.
+  spvtools::ValidatorOptions validator_options;
+  spvtools::OptimizerOptions optimizer_options;
+  optimizer_options.set_max_id_bound(kFinalIdLimit);
+  validator_options.SetUniversalLimit(spv_validator_limit_max_id_bound,
+                                      kFinalIdLimit);
+  optimizer_options.set_validator_options(validator_options);
+  register_passes(optimizer);
+  optimizer.Run(input.data(), input.size(), &input, optimizer_options);
+
+  return 0;
+}
+
+}  // namespace fuzzers
+}  // namespace spvtools
diff --git a/test/fuzzers/spvtools_opt_fuzzer_common.h b/test/fuzzers/spvtools_opt_fuzzer_common.h
new file mode 100644
index 0000000..b8d4281
--- /dev/null
+++ b/test/fuzzers/spvtools_opt_fuzzer_common.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2021 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 TEST_FUZZERS_SPVTOOLS_OPT_FUZZER_COMMON_H_
+#define TEST_FUZZERS_SPVTOOLS_OPT_FUZZER_COMMON_H_
+
+#include <cinttypes>
+#include <cstddef>
+#include <functional>
+
+#include "spirv-tools/optimizer.hpp"
+
+namespace spvtools {
+namespace fuzzers {
+
+// Helper function capturing the common logic for the various optimizer fuzzers.
+int OptFuzzerTestOneInput(
+    const uint8_t* data, size_t size,
+    const std::function<void(spvtools::Optimizer&)>& register_passes);
+
+}  // namespace fuzzers
+}  // namespace spvtools
+
+#endif  // TEST_FUZZERS_SPVTOOLS_OPT_FUZZER_COMMON_H_
diff --git a/test/fuzzers/spvtools_opt_legalization_fuzzer.cpp b/test/fuzzers/spvtools_opt_legalization_fuzzer.cpp
index 6f4d7e8..fac4d23 100644
--- a/test/fuzzers/spvtools_opt_legalization_fuzzer.cpp
+++ b/test/fuzzers/spvtools_opt_legalization_fuzzer.cpp
@@ -12,33 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <cstdint>
-#include <vector>
+#include <cinttypes>
+#include <cstddef>
+#include <functional>
 
 #include "spirv-tools/optimizer.hpp"
-#include "test/fuzzers/random_generator.h"
+#include "test/fuzzers/spvtools_opt_fuzzer_common.h"
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  if (size < 1) {
-    return 0;
-  }
-
-  spvtools::fuzzers::RandomGenerator random_gen(data, size);
-  spvtools::Optimizer optimizer(random_gen.GetTargetEnv());
-  optimizer.SetMessageConsumer([](spv_message_level_t, const char*,
-                                  const spv_position_t&, const char*) {});
-
-  std::vector<uint32_t> input;
-  input.resize(size >> 2);
-
-  size_t count = 0;
-  for (size_t i = 0; (i + 3) < size; i += 4) {
-    input[count++] = data[i] | (data[i + 1] << 8) | (data[i + 2] << 16) |
-                     (data[i + 3]) << 24;
-  }
-
-  optimizer.RegisterLegalizationPasses();
-  optimizer.Run(input.data(), input.size(), &input);
-
-  return 0;
+  return spvtools::fuzzers::OptFuzzerTestOneInput(
+      data, size, [](spvtools::Optimizer& optimizer) -> void {
+        optimizer.RegisterLegalizationPasses();
+      });
 }
diff --git a/test/fuzzers/spvtools_opt_performance_fuzzer.cpp b/test/fuzzers/spvtools_opt_performance_fuzzer.cpp
index 9c47d7d..e6038b9 100644
--- a/test/fuzzers/spvtools_opt_performance_fuzzer.cpp
+++ b/test/fuzzers/spvtools_opt_performance_fuzzer.cpp
@@ -12,33 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <cstdint>
-#include <vector>
+#include <cinttypes>
+#include <cstddef>
+#include <functional>
 
 #include "spirv-tools/optimizer.hpp"
-#include "test/fuzzers/random_generator.h"
+#include "test/fuzzers/spvtools_opt_fuzzer_common.h"
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  if (size < 1) {
-    return 0;
-  }
-
-  spvtools::fuzzers::RandomGenerator random_gen(data, size);
-  spvtools::Optimizer optimizer(random_gen.GetTargetEnv());
-  optimizer.SetMessageConsumer([](spv_message_level_t, const char*,
-                                  const spv_position_t&, const char*) {});
-
-  std::vector<uint32_t> input;
-  input.resize(size >> 2);
-
-  size_t count = 0;
-  for (size_t i = 0; (i + 3) < size; i += 4) {
-    input[count++] = data[i] | (data[i + 1] << 8) | (data[i + 2] << 16) |
-                     (data[i + 3]) << 24;
-  }
-
-  optimizer.RegisterPerformancePasses();
-  optimizer.Run(input.data(), input.size(), &input);
-
-  return 0;
+  return spvtools::fuzzers::OptFuzzerTestOneInput(
+      data, size, [](spvtools::Optimizer& optimizer) -> void {
+        optimizer.RegisterPerformancePasses();
+      });
 }
diff --git a/test/fuzzers/spvtools_opt_size_fuzzer.cpp b/test/fuzzers/spvtools_opt_size_fuzzer.cpp
index 10fac42..65492b1 100644
--- a/test/fuzzers/spvtools_opt_size_fuzzer.cpp
+++ b/test/fuzzers/spvtools_opt_size_fuzzer.cpp
@@ -12,33 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <cstdint>
-#include <vector>
+#include <cinttypes>
+#include <cstddef>
+#include <functional>
 
 #include "spirv-tools/optimizer.hpp"
-#include "test/fuzzers/random_generator.h"
+#include "test/fuzzers/spvtools_opt_fuzzer_common.h"
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  if (size < 1) {
-    return 0;
-  }
-
-  spvtools::fuzzers::RandomGenerator random_gen(data, size);
-  spvtools::Optimizer optimizer(random_gen.GetTargetEnv());
-  optimizer.SetMessageConsumer([](spv_message_level_t, const char*,
-                                  const spv_position_t&, const char*) {});
-
-  std::vector<uint32_t> input;
-  input.resize(size >> 2);
-
-  size_t count = 0;
-  for (size_t i = 0; (i + 3) < size; i += 4) {
-    input[count++] = data[i] | (data[i + 1] << 8) | (data[i + 2] << 16) |
-                     (data[i + 3]) << 24;
-  }
-
-  optimizer.RegisterSizePasses();
-  optimizer.Run(input.data(), input.size(), &input);
-
-  return 0;
+  return spvtools::fuzzers::OptFuzzerTestOneInput(
+      data, size, [](spvtools::Optimizer& optimizer) -> void {
+        optimizer.RegisterSizePasses();
+      });
 }
diff --git a/test/hex_float_test.cpp b/test/hex_float_test.cpp
index ffdb8bd..25d3c70 100644
--- a/test/hex_float_test.cpp
+++ b/test/hex_float_test.cpp
@@ -1343,7 +1343,7 @@
 template <typename T>
 std::ostream& operator<<(std::ostream& os, const StreamParseCase<T>& fspc) {
   os << "StreamParseCase(" << fspc.literal
-     << ", expect_succes:" << int(fspc.expect_success) << ","
+     << ", expect_success:" << int(fspc.expect_success) << ","
      << fspc.expected_suffix << "," << fspc.expected_value << ")";
   return os;
 }
@@ -1395,6 +1395,47 @@
         {"0x1.0p1+", true, "+", 2.0f},
         {"0x1.0p1-", true, "-", 2.0f}}));
 
+INSTANTIATE_TEST_SUITE_P(
+    HexFloatPositiveExponentOverflow, FloatStreamParseTest,
+    ::testing::ValuesIn(std::vector<StreamParseCase<float>>{
+        // Positive exponents
+        {"0x1.0p1", true, "", 2.0f},       // fine, a normal number
+        {"0x1.0p15", true, "", 32768.0f},  // fine, a normal number
+        {"0x1.0p127", true, "", float(ldexp(1.0f, 127))},   // good large number
+        {"0x0.8p128", true, "", float(ldexp(1.0f, 127))},   // good large number
+        {"0x0.1p131", true, "", float(ldexp(1.0f, 127))},   // good large number
+        {"0x0.01p135", true, "", float(ldexp(1.0f, 127))},  // good large number
+        {"0x1.0p128", true, "", float(ldexp(1.0f, 128))},   // infinity
+        {"0x1.0p4294967295", true, "", float(ldexp(1.0f, 128))},  // infinity
+        {"0x1.0p5000000000", true, "", float(ldexp(1.0f, 128))},  // infinity
+        {"0x0.0p5000000000", true, "", 0.0f},  // zero mantissa, zero result
+    }));
+
+INSTANTIATE_TEST_SUITE_P(
+    HexFloatNegativeExponentOverflow, FloatStreamParseTest,
+    ::testing::ValuesIn(std::vector<StreamParseCase<float>>{
+        // Positive results, digits before '.'
+        {"0x1.0p-126", true, "",
+         float(ldexp(1.0f, -126))},  // fine, a small normal number
+        {"0x1.0p-127", true, "", float(ldexp(1.0f, -127))},  // denorm number
+        {"0x1.0p-149", true, "",
+         float(ldexp(1.0f, -149))},  // smallest positive denormal
+        {"0x0.8p-148", true, "",
+         float(ldexp(1.0f, -149))},  // smallest positive denormal
+        {"0x0.1p-145", true, "",
+         float(ldexp(1.0f, -149))},  // smallest positive denormal
+        {"0x0.01p-141", true, "",
+         float(ldexp(1.0f, -149))},  // smallest positive denormal
+
+        // underflow rounds down to zero
+        {"0x1.0p-150", true, "", 0.0f},
+        {"0x1.0p-4294967296", true, "",
+         0.0f},  // avoid exponent overflow in parser
+        {"0x1.0p-5000000000", true, "",
+         0.0f},  // avoid exponent overflow in parser
+        {"0x0.0p-5000000000", true, "", 0.0f},  // zero mantissa, zero result
+    }));
+
 // TODO(awoloszyn): Add fp16 tests and HexFloatTraits.
 }  // namespace
 }  // namespace utils
diff --git a/test/link/binary_version_test.cpp b/test/link/binary_version_test.cpp
index 80aab0f..a56030f 100644
--- a/test/link/binary_version_test.cpp
+++ b/test/link/binary_version_test.cpp
@@ -20,40 +20,57 @@
 namespace spvtools {
 namespace {
 
+using ::testing::HasSubstr;
 using BinaryVersion = spvtest::LinkerTest;
 
-TEST_F(BinaryVersion, LinkerChoosesMaxSpirvVersion) {
+spvtest::Binary CreateBinary(uint32_t version) {
+  return {
+      // clang-format off
+      // Header
+      SpvMagicNumber,
+      version,
+      SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS, 0),
+      1u,  // NOTE: Bound
+      0u,  // NOTE: Schema; reserved
+
+      // OpCapability Shader
+      SpvOpCapability | 2u << SpvWordCountShift,
+      SpvCapabilityShader,
+
+      // OpMemoryModel Logical Simple
+      SpvOpMemoryModel | 3u << SpvWordCountShift,
+      SpvAddressingModelLogical,
+      SpvMemoryModelSimple
+      // clang-format on
+  };
+}
+
+TEST_F(BinaryVersion, Match) {
   // clang-format off
   spvtest::Binaries binaries = {
-      {
-          SpvMagicNumber,
-          0x00010300u,
-          SPV_GENERATOR_CODEPLAY,
-          1u,  // NOTE: Bound
-          0u   // NOTE: Schema; reserved
-      },
-      {
-          SpvMagicNumber,
-          0x00010500u,
-          SPV_GENERATOR_CODEPLAY,
-          1u,  // NOTE: Bound
-          0u   // NOTE: Schema; reserved
-      },
-      {
-          SpvMagicNumber,
-          0x00010100u,
-          SPV_GENERATOR_CODEPLAY,
-          1u,  // NOTE: Bound
-          0u   // NOTE: Schema; reserved
-      }
+      CreateBinary(SPV_SPIRV_VERSION_WORD(1, 3)),
+      CreateBinary(SPV_SPIRV_VERSION_WORD(1, 3)),
   };
   // clang-format on
   spvtest::Binary linked_binary;
-
-  ASSERT_EQ(SPV_SUCCESS, Link(binaries, &linked_binary));
+  ASSERT_EQ(SPV_SUCCESS, Link(binaries, &linked_binary)) << GetErrorMessage();
   EXPECT_THAT(GetErrorMessage(), std::string());
+  EXPECT_EQ(SPV_SPIRV_VERSION_WORD(1, 3), linked_binary[1]);
+}
 
-  EXPECT_EQ(0x00010500u, linked_binary[1]);
+TEST_F(BinaryVersion, Mismatch) {
+  // clang-format off
+  spvtest::Binaries binaries = {
+      CreateBinary(SPV_SPIRV_VERSION_WORD(1, 3)),
+      CreateBinary(SPV_SPIRV_VERSION_WORD(1, 5)),
+  };
+  // clang-format on
+  spvtest::Binary linked_binary;
+  ASSERT_EQ(SPV_ERROR_INTERNAL, Link(binaries, &linked_binary))
+      << GetErrorMessage();
+  EXPECT_THAT(GetErrorMessage(),
+              HasSubstr("Conflicting SPIR-V versions: 1.3 (input modules 1 "
+                        "through 1) vs 1.5 (input module 2)."));
 }
 
 }  // namespace
diff --git a/test/link/entry_points_test.cpp b/test/link/entry_points_test.cpp
index bac8e02..edf9f42 100644
--- a/test/link/entry_points_test.cpp
+++ b/test/link/entry_points_test.cpp
@@ -26,6 +26,8 @@
 
 TEST_F(EntryPoints, SameModelDifferentName) {
   const std::string body1 = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
 OpEntryPoint GLCompute %3 "foo"
 %1 = OpTypeVoid
 %2 = OpTypeFunction %1
@@ -33,6 +35,8 @@
 OpFunctionEnd
 )";
   const std::string body2 = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
 OpEntryPoint GLCompute %3 "bar"
 %1 = OpTypeVoid
 %2 = OpTypeFunction %1
@@ -41,12 +45,15 @@
 )";
 
   spvtest::Binary linked_binary;
-  EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary));
+  ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
+      << GetErrorMessage();
   EXPECT_THAT(GetErrorMessage(), std::string());
 }
 
 TEST_F(EntryPoints, DifferentModelSameName) {
   const std::string body1 = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
 OpEntryPoint GLCompute %3 "foo"
 %1 = OpTypeVoid
 %2 = OpTypeFunction %1
@@ -54,6 +61,8 @@
 OpFunctionEnd
 )";
   const std::string body2 = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
 OpEntryPoint Vertex %3 "foo"
 %1 = OpTypeVoid
 %2 = OpTypeFunction %1
@@ -62,12 +71,15 @@
 )";
 
   spvtest::Binary linked_binary;
-  EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary));
+  ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
+      << GetErrorMessage();
   EXPECT_THAT(GetErrorMessage(), std::string());
 }
 
 TEST_F(EntryPoints, SameModelAndName) {
   const std::string body1 = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
 OpEntryPoint GLCompute %3 "foo"
 %1 = OpTypeVoid
 %2 = OpTypeFunction %1
@@ -75,6 +87,8 @@
 OpFunctionEnd
 )";
   const std::string body2 = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
 OpEntryPoint GLCompute %3 "foo"
 %1 = OpTypeVoid
 %2 = OpTypeFunction %1
@@ -90,5 +104,48 @@
                         "GLCompute, was already defined."));
 }
 
+TEST_F(EntryPoints, LinkedVariables) {
+  const std::string body1 = R"(
+               OpCapability Addresses
+OpCapability Linkage
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
+OpDecorate %7 LinkageAttributes "foo" Export
+%1 = OpTypeInt 32 0
+%2 = OpTypeVector %1 3
+%3 = OpTypePointer Input %2
+%4 = OpVariable %3 Input
+%5 = OpTypeVoid
+%6 = OpTypeFunction %5
+%7 = OpFunction %5 None %6
+%8 = OpLabel
+%9 = OpLoad %2 %4 Aligned 32
+OpReturn
+OpFunctionEnd
+)";
+  const std::string body2 = R"(
+OpCapability Linkage
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
+OpEntryPoint Kernel %4 "bar"
+OpDecorate %3 LinkageAttributes "foo" Import
+%1 = OpTypeVoid
+%2 = OpTypeFunction %1
+%3 = OpFunction %1 None %2
+OpFunctionEnd
+%4 = OpFunction %1 None %2
+%5 = OpLabel
+%6 = OpFunctionCall %1 %3
+OpReturn
+OpFunctionEnd
+)";
+
+  spvtest::Binary linked_binary;
+  EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary));
+  EXPECT_THAT(GetErrorMessage(), std::string());
+  EXPECT_TRUE(Validate(linked_binary));
+  EXPECT_THAT(GetErrorMessage(), std::string());
+}
+
 }  // namespace
 }  // namespace spvtools
diff --git a/test/link/global_values_amount_test.cpp b/test/link/global_values_amount_test.cpp
index 2c4ee1f..3158b7e 100644
--- a/test/link/global_values_amount_test.cpp
+++ b/test/link/global_values_amount_test.cpp
@@ -22,85 +22,56 @@
 
 using ::testing::HasSubstr;
 
+const uint32_t binary_count = 2u;
+
 class EntryPointsAmountTest : public spvtest::LinkerTest {
  public:
-  EntryPointsAmountTest() { binaries.reserve(0xFFFF); }
+  EntryPointsAmountTest() { binaries.reserve(binary_count + 1u); }
 
   void SetUp() override {
-    binaries.push_back({SpvMagicNumber,
-                        SpvVersion,
-                        SPV_GENERATOR_CODEPLAY,
-                        10u,  // NOTE: Bound
-                        0u,   // NOTE: Schema; reserved
+    const uint32_t global_variable_count_per_binary =
+        (SPV_LIMIT_GLOBAL_VARIABLES_MAX - 1u) / binary_count;
 
-                        3u << SpvWordCountShift | SpvOpTypeFloat,
-                        1u,   // NOTE: Result ID
-                        32u,  // NOTE: Width
+    spvtest::Binary common_binary = {
+        // clang-format off
+        SpvMagicNumber,
+        SpvVersion,
+        SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS, 0),
+        3u + global_variable_count_per_binary,  // NOTE: Bound
+        0u,                                     // NOTE: Schema; reserved
 
-                        4u << SpvWordCountShift | SpvOpTypePointer,
-                        2u,  // NOTE: Result ID
-                        SpvStorageClassInput,
-                        1u,  // NOTE: Type ID
+        SpvOpCapability | 2u << SpvWordCountShift,
+        SpvCapabilityShader,
 
-                        2u << SpvWordCountShift | SpvOpTypeVoid,
-                        3u,  // NOTE: Result ID
+        SpvOpMemoryModel | 3u << SpvWordCountShift,
+        SpvAddressingModelLogical,
+        SpvMemoryModelSimple,
 
-                        3u << SpvWordCountShift | SpvOpTypeFunction,
-                        4u,  // NOTE: Result ID
-                        3u,  // NOTE: Return type
+        SpvOpTypeFloat | 3u << SpvWordCountShift,
+        1u,   // NOTE: Result ID
+        32u,  // NOTE: Width
 
-                        5u << SpvWordCountShift | SpvOpFunction,
-                        3u,  // NOTE: Result type
-                        5u,  // NOTE: Result ID
-                        SpvFunctionControlMaskNone,
-                        4u,  // NOTE: Function type
+        SpvOpTypePointer | 4u << SpvWordCountShift,
+        2u,  // NOTE: Result ID
+        SpvStorageClassInput,
+        1u  // NOTE: Type ID
+        // clang-format on
+    };
 
-                        2u << SpvWordCountShift | SpvOpLabel,
-                        6u,  // NOTE: Result ID
+    binaries.push_back({});
+    spvtest::Binary& binary = binaries.back();
+    binary.reserve(common_binary.size() + global_variable_count_per_binary * 4);
+    binary.insert(binary.end(), common_binary.cbegin(), common_binary.cend());
 
-                        4u << SpvWordCountShift | SpvOpVariable,
-                        2u,  // NOTE: Type ID
-                        7u,  // NOTE: Result ID
-                        SpvStorageClassFunction,
+    for (uint32_t i = 0u; i < global_variable_count_per_binary; ++i) {
+      binary.push_back(SpvOpVariable | 4u << SpvWordCountShift);
+      binary.push_back(2u);      // NOTE: Type ID
+      binary.push_back(3u + i);  // NOTE: Result ID
+      binary.push_back(SpvStorageClassInput);
+    }
 
-                        4u << SpvWordCountShift | SpvOpVariable,
-                        2u,  // NOTE: Type ID
-                        8u,  // NOTE: Result ID
-                        SpvStorageClassFunction,
-
-                        4u << SpvWordCountShift | SpvOpVariable,
-                        2u,  // NOTE: Type ID
-                        9u,  // NOTE: Result ID
-                        SpvStorageClassFunction,
-
-                        1u << SpvWordCountShift | SpvOpReturn,
-
-                        1u << SpvWordCountShift | SpvOpFunctionEnd});
-    for (size_t i = 0u; i < 2u; ++i) {
-      spvtest::Binary binary = {
-          SpvMagicNumber,
-          SpvVersion,
-          SPV_GENERATOR_CODEPLAY,
-          103u,  // NOTE: Bound
-          0u,    // NOTE: Schema; reserved
-
-          3u << SpvWordCountShift | SpvOpTypeFloat,
-          1u,   // NOTE: Result ID
-          32u,  // NOTE: Width
-
-          4u << SpvWordCountShift | SpvOpTypePointer,
-          2u,  // NOTE: Result ID
-          SpvStorageClassInput,
-          1u  // NOTE: Type ID
-      };
-
-      for (uint32_t j = 0u; j < 0xFFFFu / 2u; ++j) {
-        binary.push_back(4u << SpvWordCountShift | SpvOpVariable);
-        binary.push_back(2u);      // NOTE: Type ID
-        binary.push_back(j + 3u);  // NOTE: Result ID
-        binary.push_back(SpvStorageClassInput);
-      }
-      binaries.push_back(binary);
+    for (uint32_t i = 0u; i < binary_count - 1u; ++i) {
+      binaries.push_back(binaries.back());
     }
   }
   void TearDown() override { binaries.clear(); }
@@ -111,42 +82,53 @@
 TEST_F(EntryPointsAmountTest, UnderLimit) {
   spvtest::Binary linked_binary;
 
-  EXPECT_EQ(SPV_SUCCESS, Link(binaries, &linked_binary));
+  ASSERT_EQ(SPV_SUCCESS, Link(binaries, &linked_binary)) << GetErrorMessage();
   EXPECT_THAT(GetErrorMessage(), std::string());
 }
 
 TEST_F(EntryPointsAmountTest, OverLimit) {
-  binaries.push_back({SpvMagicNumber,
-                      SpvVersion,
-                      SPV_GENERATOR_CODEPLAY,
-                      5u,  // NOTE: Bound
-                      0u,  // NOTE: Schema; reserved
+  binaries.push_back({
+      // clang-format off
+      SpvMagicNumber,
+      SpvVersion,
+      SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS, 0),
+      5u,  // NOTE: Bound
+      0u,  // NOTE: Schema; reserved
 
-                      3u << SpvWordCountShift | SpvOpTypeFloat,
-                      1u,   // NOTE: Result ID
-                      32u,  // NOTE: Width
+      SpvOpCapability | 2u << SpvWordCountShift,
+      SpvCapabilityShader,
 
-                      4u << SpvWordCountShift | SpvOpTypePointer,
-                      2u,  // NOTE: Result ID
-                      SpvStorageClassInput,
-                      1u,  // NOTE: Type ID
+      SpvOpMemoryModel | 3u << SpvWordCountShift,
+      SpvAddressingModelLogical,
+      SpvMemoryModelSimple,
 
-                      4u << SpvWordCountShift | SpvOpVariable,
-                      2u,  // NOTE: Type ID
-                      3u,  // NOTE: Result ID
-                      SpvStorageClassInput,
+      SpvOpTypeFloat | 3u << SpvWordCountShift,
+      1u,   // NOTE: Result ID
+      32u,  // NOTE: Width
 
-                      4u << SpvWordCountShift | SpvOpVariable,
-                      2u,  // NOTE: Type ID
-                      4u,  // NOTE: Result ID
-                      SpvStorageClassInput});
+      SpvOpTypePointer | 4u << SpvWordCountShift,
+      2u,  // NOTE: Result ID
+      SpvStorageClassInput,
+      1u,  // NOTE: Type ID
+
+      SpvOpVariable | 4u << SpvWordCountShift,
+      2u,  // NOTE: Type ID
+      3u,  // NOTE: Result ID
+      SpvStorageClassInput,
+
+      SpvOpVariable | 4u << SpvWordCountShift,
+      2u,  // NOTE: Type ID
+      4u,  // NOTE: Result ID
+      SpvStorageClassInput
+      // clang-format on
+  });
 
   spvtest::Binary linked_binary;
-
-  EXPECT_EQ(SPV_ERROR_INTERNAL, Link(binaries, &linked_binary));
-  EXPECT_THAT(GetErrorMessage(),
-              HasSubstr("The limit of global values, 65535, was exceeded; "
-                        "65536 global values were found."));
+  ASSERT_EQ(SPV_SUCCESS, Link(binaries, &linked_binary)) << GetErrorMessage();
+  EXPECT_THAT(
+      GetErrorMessage(),
+      HasSubstr("The minimum limit of global values, 65535, was exceeded; "
+                "65536 global values were found."));
 }
 
 }  // namespace
diff --git a/test/link/ids_limit_test.cpp b/test/link/ids_limit_test.cpp
index 6d7815a..846fbef 100644
--- a/test/link/ids_limit_test.cpp
+++ b/test/link/ids_limit_test.cpp
@@ -21,51 +21,114 @@
 namespace {
 
 using ::testing::HasSubstr;
-using IdsLimit = spvtest::LinkerTest;
 
-TEST_F(IdsLimit, UnderLimit) {
-  spvtest::Binaries binaries = {
-      {
-          SpvMagicNumber, SpvVersion, SPV_GENERATOR_CODEPLAY,
-          0x2FFFFFu,  // NOTE: Bound
-          0u,         // NOTE: Schema; reserved
-      },
-      {
-          SpvMagicNumber, SpvVersion, SPV_GENERATOR_CODEPLAY,
-          0x100000u,  // NOTE: Bound
-          0u,         // NOTE: Schema; reserved
-      }};
-  spvtest::Binary linked_binary;
+class IdsLimit : public spvtest::LinkerTest {
+ public:
+  IdsLimit() { binaries.reserve(2u); }
 
-  ASSERT_EQ(SPV_SUCCESS, Link(binaries, &linked_binary));
-  EXPECT_THAT(GetErrorMessage(), std::string());
-  EXPECT_EQ(0x3FFFFEu, linked_binary[3]);
+  void SetUp() override {
+    const uint32_t id_bound = SPV_LIMIT_RESULT_ID_BOUND - 1u;
+    const uint32_t constant_count =
+        id_bound -
+        2u;  // One ID is used for TypeBool, and (constant_count + 1) < id_bound
+
+    // This is needed, as otherwise the ID bound will get reset to 1 while
+    // running the RemoveDuplicates pass.
+    spvtest::Binary common_binary = {
+        // clang-format off
+        SpvMagicNumber,
+        SpvVersion,
+        SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS, 0),
+        id_bound,  // NOTE: Bound
+        0u,        // NOTE: Schema; reserved
+
+        SpvOpCapability | 2u << SpvWordCountShift,
+        SpvCapabilityShader,
+
+        SpvOpMemoryModel | 3u << SpvWordCountShift,
+        SpvAddressingModelLogical,
+        SpvMemoryModelSimple,
+
+        SpvOpTypeBool | 2u << SpvWordCountShift,
+        1u    // NOTE: Result ID
+        // clang-format on
+    };
+
+    binaries.push_back({});
+    spvtest::Binary& binary = binaries.back();
+    binary.reserve(common_binary.size() + constant_count * 3u);
+    binary.insert(binary.end(), common_binary.cbegin(), common_binary.cend());
+
+    for (uint32_t i = 0u; i < constant_count; ++i) {
+      binary.push_back(SpvOpConstantTrue | 3u << SpvWordCountShift);
+      binary.push_back(1u);      // NOTE: Type ID
+      binary.push_back(2u + i);  // NOTE: Result ID
+    }
+  }
+  void TearDown() override { binaries.clear(); }
+
+  spvtest::Binaries binaries;
+};
+
+spvtest::Binary CreateBinary(uint32_t id_bound) {
+  return {
+      // clang-format off
+      // Header
+      SpvMagicNumber,
+      SpvVersion,
+      SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS, 0),
+      id_bound,  // NOTE: Bound
+      0u,        // NOTE: Schema; reserved
+
+      // OpCapability Shader
+      SpvOpCapability | 2u << SpvWordCountShift,
+      SpvCapabilityShader,
+
+      // OpMemoryModel Logical Simple
+      SpvOpMemoryModel | 3u << SpvWordCountShift,
+      SpvAddressingModelLogical,
+      SpvMemoryModelSimple
+      // clang-format on
+  };
 }
 
-TEST_F(IdsLimit, OverLimit) {
-  spvtest::Binaries binaries = {
-      {
-          SpvMagicNumber, SpvVersion, SPV_GENERATOR_CODEPLAY,
-          0x2FFFFFu,  // NOTE: Bound
-          0u,         // NOTE: Schema; reserved
-      },
-      {
-          SpvMagicNumber, SpvVersion, SPV_GENERATOR_CODEPLAY,
-          0x100000u,  // NOTE: Bound
-          0u,         // NOTE: Schema; reserved
-      },
-      {
-          SpvMagicNumber, SpvVersion, SPV_GENERATOR_CODEPLAY,
-          3u,  // NOTE: Bound
-          0u,  // NOTE: Schema; reserved
-      }};
+TEST_F(IdsLimit, DISABLED_UnderLimit) {
+  spvtest::Binary linked_binary;
+  ASSERT_EQ(SPV_SUCCESS, Link(binaries, &linked_binary)) << GetErrorMessage();
+  EXPECT_THAT(GetErrorMessage(), std::string());
+  EXPECT_EQ(0x3FFFFFu, linked_binary[3]);
+}
+
+TEST_F(IdsLimit, DISABLED_OverLimit) {
+  spvtest::Binary& binary = binaries.back();
+
+  const uint32_t id_bound = binary[3];
+  binary[3] = id_bound + 1u;
+
+  binary.push_back(SpvOpConstantFalse | 3u << SpvWordCountShift);
+  binary.push_back(1u);        // NOTE: Type ID
+  binary.push_back(id_bound);  // NOTE: Result ID
+
+  spvtest::Binary linked_binary;
+  ASSERT_EQ(SPV_SUCCESS, Link(binaries, &linked_binary)) << GetErrorMessage();
+  EXPECT_THAT(
+      GetErrorMessage(),
+      HasSubstr("The minimum limit of IDs, 4194303, was exceeded: 4194304 is "
+                "the current ID bound."));
+  EXPECT_EQ(0x400000u, linked_binary[3]);
+}
+
+TEST_F(IdsLimit, DISABLED_Overflow) {
+  spvtest::Binaries binaries = {CreateBinary(0xFFFFFFFFu),
+                                CreateBinary(0x00000002u)};
 
   spvtest::Binary linked_binary;
 
-  EXPECT_EQ(SPV_ERROR_INVALID_ID, Link(binaries, &linked_binary));
-  EXPECT_THAT(GetErrorMessage(),
-              HasSubstr("The limit of IDs, 4194303, was exceeded: 4194304 is "
-                        "the current ID bound."));
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, Link(binaries, &linked_binary));
+  EXPECT_THAT(
+      GetErrorMessage(),
+      HasSubstr("Too many IDs (4294967296): combining all modules would "
+                "overflow the 32-bit word of the SPIR-V header."));
 }
 
 }  // namespace
diff --git a/test/link/linker_fixture.h b/test/link/linker_fixture.h
index 7bb1223..d005288 100644
--- a/test/link/linker_fixture.h
+++ b/test/link/linker_fixture.h
@@ -208,6 +208,10 @@
   // Returns the accumulated error messages for the test.
   std::string GetErrorMessage() const { return error_message_; }
 
+  bool Validate(const spvtest::Binary& binary) {
+    return tools_.Validate(binary);
+  }
+
  private:
   spvtools::Context context_;
   spvtools::SpirvTools
diff --git a/test/link/matching_imports_to_exports_test.cpp b/test/link/matching_imports_to_exports_test.cpp
index e76c69f..6b02fc4 100644
--- a/test/link/matching_imports_to_exports_test.cpp
+++ b/test/link/matching_imports_to_exports_test.cpp
@@ -26,6 +26,9 @@
 TEST_F(MatchingImportsToExports, Default) {
   const std::string body1 = R"(
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpDecorate %1 LinkageAttributes "foo" Import
 %2 = OpTypeFloat 32
 %1 = OpVariable %2 Uniform
@@ -33,6 +36,9 @@
 )";
   const std::string body2 = R"(
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpDecorate %1 LinkageAttributes "foo" Export
 %2 = OpTypeFloat 32
 %3 = OpConstant %2 42
@@ -40,11 +46,14 @@
 )";
 
   spvtest::Binary linked_binary;
-  EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
+  ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
       << GetErrorMessage();
 
   const std::string expected_res =
-      R"(OpModuleProcessed "Linked by SPIR-V Tools Linker"
+      R"(OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
+OpModuleProcessed "Linked by SPIR-V Tools Linker"
 %1 = OpTypeFloat 32
 %2 = OpVariable %1 Input
 %3 = OpConstant %1 42
@@ -52,7 +61,7 @@
 )";
   std::string res_body;
   SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
-  EXPECT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
+  ASSERT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
       << GetErrorMessage();
   EXPECT_EQ(expected_res, res_body);
 }
@@ -60,23 +69,29 @@
 TEST_F(MatchingImportsToExports, NotALibraryExtraExports) {
   const std::string body = R"(
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpDecorate %1 LinkageAttributes "foo" Export
 %2 = OpTypeFloat 32
 %1 = OpVariable %2 Uniform
 )";
 
   spvtest::Binary linked_binary;
-  EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body}, &linked_binary))
+  ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body}, &linked_binary))
       << GetErrorMessage();
 
   const std::string expected_res =
-      R"(OpModuleProcessed "Linked by SPIR-V Tools Linker"
+      R"(OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
+OpModuleProcessed "Linked by SPIR-V Tools Linker"
 %1 = OpTypeFloat 32
 %2 = OpVariable %1 Uniform
 )";
   std::string res_body;
   SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
-  EXPECT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
+  ASSERT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
       << GetErrorMessage();
   EXPECT_EQ(expected_res, res_body);
 }
@@ -84,6 +99,9 @@
 TEST_F(MatchingImportsToExports, LibraryExtraExports) {
   const std::string body = R"(
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpDecorate %1 LinkageAttributes "foo" Export
 %2 = OpTypeFloat 32
 %1 = OpVariable %2 Uniform
@@ -92,10 +110,13 @@
   spvtest::Binary linked_binary;
   LinkerOptions options;
   options.SetCreateLibrary(true);
-  EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body}, &linked_binary, options))
+  ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body}, &linked_binary, options))
       << GetErrorMessage();
 
   const std::string expected_res = R"(OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpModuleProcessed "Linked by SPIR-V Tools Linker"
 OpDecorate %1 LinkageAttributes "foo" Export
 %2 = OpTypeFloat 32
@@ -103,7 +124,7 @@
 )";
   std::string res_body;
   SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
-  EXPECT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
+  ASSERT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
       << GetErrorMessage();
   EXPECT_EQ(expected_res, res_body);
 }
@@ -111,11 +132,18 @@
 TEST_F(MatchingImportsToExports, UnresolvedImports) {
   const std::string body1 = R"(
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpDecorate %1 LinkageAttributes "foo" Import
 %2 = OpTypeFloat 32
 %1 = OpVariable %2 Uniform
 )";
-  const std::string body2 = R"()";
+  const std::string body2 = R"(
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
+)";
 
   spvtest::Binary linked_binary;
   EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
@@ -127,6 +155,9 @@
 TEST_F(MatchingImportsToExports, TypeMismatch) {
   const std::string body1 = R"(
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpDecorate %1 LinkageAttributes "foo" Import
 %2 = OpTypeFloat 32
 %1 = OpVariable %2 Uniform
@@ -134,6 +165,9 @@
 )";
   const std::string body2 = R"(
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpDecorate %1 LinkageAttributes "foo" Export
 %2 = OpTypeInt 32 0
 %3 = OpConstant %2 42
@@ -153,6 +187,9 @@
 TEST_F(MatchingImportsToExports, MultipleDefinitions) {
   const std::string body1 = R"(
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpDecorate %1 LinkageAttributes "foo" Import
 %2 = OpTypeFloat 32
 %1 = OpVariable %2 Uniform
@@ -160,6 +197,9 @@
 )";
   const std::string body2 = R"(
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpDecorate %1 LinkageAttributes "foo" Export
 %2 = OpTypeFloat 32
 %3 = OpConstant %2 42
@@ -167,6 +207,9 @@
 )";
   const std::string body3 = R"(
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpDecorate %1 LinkageAttributes "foo" Export
 %2 = OpTypeFloat 32
 %3 = OpConstant %2 -1
@@ -185,6 +228,9 @@
 TEST_F(MatchingImportsToExports, SameNameDifferentTypes) {
   const std::string body1 = R"(
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpDecorate %1 LinkageAttributes "foo" Import
 %2 = OpTypeFloat 32
 %1 = OpVariable %2 Uniform
@@ -192,6 +238,9 @@
 )";
   const std::string body2 = R"(
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpDecorate %1 LinkageAttributes "foo" Export
 %2 = OpTypeInt 32 0
 %3 = OpConstant %2 42
@@ -199,6 +248,9 @@
 )";
   const std::string body3 = R"(
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpDecorate %1 LinkageAttributes "foo" Export
 %2 = OpTypeFloat 32
 %3 = OpConstant %2 12
@@ -217,6 +269,9 @@
 TEST_F(MatchingImportsToExports, DecorationMismatch) {
   const std::string body1 = R"(
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpDecorate %1 LinkageAttributes "foo" Import
 OpDecorate %2 Constant
 %2 = OpTypeFloat 32
@@ -225,6 +280,9 @@
 )";
   const std::string body2 = R"(
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpDecorate %1 LinkageAttributes "foo" Export
 %2 = OpTypeFloat 32
 %3 = OpConstant %2 42
@@ -244,8 +302,10 @@
 TEST_F(MatchingImportsToExports,
        FuncParamAttrDifferButStillMatchExportToImport) {
   const std::string body1 = R"(
-OpCapability Kernel
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpDecorate %1 LinkageAttributes "foo" Import
 OpDecorate %2 FuncParamAttr Zext
 %3 = OpTypeVoid
@@ -256,8 +316,10 @@
 OpFunctionEnd
 )";
   const std::string body2 = R"(
-OpCapability Kernel
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpDecorate %1 LinkageAttributes "foo" Export
 OpDecorate %2 FuncParamAttr Sext
 %3 = OpTypeVoid
@@ -271,10 +333,12 @@
 )";
 
   spvtest::Binary linked_binary;
-  EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
+  ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
       << GetErrorMessage();
 
-  const std::string expected_res = R"(OpCapability Kernel
+  const std::string expected_res = R"(OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpModuleProcessed "Linked by SPIR-V Tools Linker"
 OpDecorate %1 FuncParamAttr Sext
 %2 = OpTypeVoid
@@ -288,7 +352,7 @@
 )";
   std::string res_body;
   SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
-  EXPECT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
+  ASSERT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
       << GetErrorMessage();
   EXPECT_EQ(expected_res, res_body);
 }
@@ -296,6 +360,9 @@
 TEST_F(MatchingImportsToExports, FunctionCtrl) {
   const std::string body1 = R"(
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpDecorate %1 LinkageAttributes "foo" Import
 %2 = OpTypeVoid
 %3 = OpTypeFunction %2
@@ -306,6 +373,9 @@
 )";
   const std::string body2 = R"(
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpDecorate %1 LinkageAttributes "foo" Export
 %2 = OpTypeVoid
 %3 = OpTypeFunction %2
@@ -316,11 +386,14 @@
 )";
 
   spvtest::Binary linked_binary;
-  EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
+  ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
       << GetErrorMessage();
 
   const std::string expected_res =
-      R"(OpModuleProcessed "Linked by SPIR-V Tools Linker"
+      R"(OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
+OpModuleProcessed "Linked by SPIR-V Tools Linker"
 %1 = OpTypeVoid
 %2 = OpTypeFunction %1
 %3 = OpTypeFloat 32
@@ -332,15 +405,17 @@
 )";
   std::string res_body;
   SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
-  EXPECT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
+  ASSERT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
       << GetErrorMessage();
   EXPECT_EQ(expected_res, res_body);
 }
 
 TEST_F(MatchingImportsToExports, UseExportedFuncParamAttr) {
   const std::string body1 = R"(
-OpCapability Kernel
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpDecorate %1 LinkageAttributes "foo" Import
 OpDecorate %2 FuncParamAttr Zext
 %2 = OpDecorationGroup
@@ -356,8 +431,10 @@
 OpFunctionEnd
 )";
   const std::string body2 = R"(
-OpCapability Kernel
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpDecorate %1 LinkageAttributes "foo" Export
 OpDecorate %2 FuncParamAttr Sext
 %3 = OpTypeVoid
@@ -371,10 +448,12 @@
 )";
 
   spvtest::Binary linked_binary;
-  EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
+  ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
       << GetErrorMessage();
 
-  const std::string expected_res = R"(OpCapability Kernel
+  const std::string expected_res = R"(OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpModuleProcessed "Linked by SPIR-V Tools Linker"
 OpDecorate %1 FuncParamAttr Zext
 %1 = OpDecorationGroup
@@ -394,15 +473,17 @@
 )";
   std::string res_body;
   SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
-  EXPECT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
+  ASSERT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
       << GetErrorMessage();
   EXPECT_EQ(expected_res, res_body);
 }
 
 TEST_F(MatchingImportsToExports, NamesAndDecorations) {
   const std::string body1 = R"(
-OpCapability Kernel
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpName %1 "foo"
 OpName %3 "param"
 OpDecorate %1 LinkageAttributes "foo" Import
@@ -422,8 +503,10 @@
 OpFunctionEnd
 )";
   const std::string body2 = R"(
-OpCapability Kernel
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpName %1 "foo"
 OpName %2 "param"
 OpDecorate %1 LinkageAttributes "foo" Export
@@ -440,10 +523,12 @@
 )";
 
   spvtest::Binary linked_binary;
-  EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
+  ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary))
       << GetErrorMessage();
 
-  const std::string expected_res = R"(OpCapability Kernel
+  const std::string expected_res = R"(OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpName %1 "foo"
 OpName %2 "param"
 OpModuleProcessed "Linked by SPIR-V Tools Linker"
@@ -467,7 +552,7 @@
 )";
   std::string res_body;
   SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
-  EXPECT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
+  ASSERT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body))
       << GetErrorMessage();
   EXPECT_EQ(expected_res, res_body);
 }
diff --git a/test/link/memory_model_test.cpp b/test/link/memory_model_test.cpp
index 2add504..280a776 100644
--- a/test/link/memory_model_test.cpp
+++ b/test/link/memory_model_test.cpp
@@ -50,9 +50,9 @@
   spvtest::Binary linked_binary;
   EXPECT_EQ(SPV_ERROR_INTERNAL,
             AssembleAndLink({body1, body2}, &linked_binary));
-  EXPECT_THAT(
-      GetErrorMessage(),
-      HasSubstr("Conflicting addressing models: Logical vs Physical32."));
+  EXPECT_THAT(GetErrorMessage(),
+              HasSubstr("Conflicting addressing models: Logical (input modules "
+                        "1 through 1) vs Physical32 (input module 2)."));
 }
 
 TEST_F(MemoryModel, MemoryMismatch) {
@@ -67,7 +67,38 @@
   EXPECT_EQ(SPV_ERROR_INTERNAL,
             AssembleAndLink({body1, body2}, &linked_binary));
   EXPECT_THAT(GetErrorMessage(),
-              HasSubstr("Conflicting memory models: Simple vs GLSL450."));
+              HasSubstr("Conflicting memory models: Simple (input modules 1 "
+                        "through 1) vs GLSL450 (input module 2)."));
+}
+
+TEST_F(MemoryModel, FirstLackMemoryModel) {
+  const std::string body1 = R"(
+)";
+  const std::string body2 = R"(
+OpMemoryModel Logical GLSL450
+)";
+
+  spvtest::Binary linked_binary;
+  EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
+            AssembleAndLink({body1, body2}, &linked_binary));
+  EXPECT_THAT(
+      GetErrorMessage(),
+      HasSubstr("Input module 1 is lacking an OpMemoryModel instruction."));
+}
+
+TEST_F(MemoryModel, SecondLackMemoryModel) {
+  const std::string body1 = R"(
+OpMemoryModel Logical GLSL450
+)";
+  const std::string body2 = R"(
+)";
+
+  spvtest::Binary linked_binary;
+  EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
+            AssembleAndLink({body1, body2}, &linked_binary));
+  EXPECT_THAT(
+      GetErrorMessage(),
+      HasSubstr("Input module 2 is lacking an OpMemoryModel instruction."));
 }
 
 }  // namespace
diff --git a/test/link/partial_linkage_test.cpp b/test/link/partial_linkage_test.cpp
index c43b06e..bf4b508 100644
--- a/test/link/partial_linkage_test.cpp
+++ b/test/link/partial_linkage_test.cpp
@@ -26,6 +26,9 @@
 TEST_F(PartialLinkage, Allowed) {
   const std::string body1 = R"(
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpDecorate %1 LinkageAttributes "foo" Import
 OpDecorate %2 LinkageAttributes "bar" Import
 %3 = OpTypeFloat 32
@@ -34,6 +37,9 @@
 )";
   const std::string body2 = R"(
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpDecorate %1 LinkageAttributes "bar" Export
 %2 = OpTypeFloat 32
 %3 = OpConstant %2 3.1415
@@ -44,9 +50,13 @@
   LinkerOptions linker_options;
   linker_options.SetAllowPartialLinkage(true);
   ASSERT_EQ(SPV_SUCCESS,
-            AssembleAndLink({body1, body2}, &linked_binary, linker_options));
+            AssembleAndLink({body1, body2}, &linked_binary, linker_options))
+      << GetErrorMessage();
 
   const std::string expected_res = R"(OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpModuleProcessed "Linked by SPIR-V Tools Linker"
 OpDecorate %1 LinkageAttributes "foo" Import
 %2 = OpTypeFloat 32
@@ -64,6 +74,9 @@
 TEST_F(PartialLinkage, Disallowed) {
   const std::string body1 = R"(
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpDecorate %1 LinkageAttributes "foo" Import
 OpDecorate %2 LinkageAttributes "bar" Import
 %3 = OpTypeFloat 32
@@ -72,6 +85,9 @@
 )";
   const std::string body2 = R"(
 OpCapability Linkage
+OpCapability Addresses
+OpCapability Kernel
+OpMemoryModel Physical64 OpenCL
 OpDecorate %1 LinkageAttributes "bar" Export
 %2 = OpTypeFloat 32
 %3 = OpConstant %2 3.1415
diff --git a/test/link/type_match_test.cpp b/test/link/type_match_test.cpp
index dae70c1..efc5cf7 100644
--- a/test/link/type_match_test.cpp
+++ b/test/link/type_match_test.cpp
@@ -66,6 +66,7 @@
         "OpCapability Kernel\n"                                 \
         "OpCapability Shader\n"                                 \
         "OpCapability Addresses\n"                              \
+        "OpMemoryModel Physical64 OpenCL\n"                     \
         "OpDecorate %var LinkageAttributes \"foo\" "            \
         "{Import,Export}\n"                                     \
         "; CHECK: [[baseint:%\\w+]] = OpTypeInt 32 1\n"         \
diff --git a/test/link/unique_ids_test.cpp b/test/link/unique_ids_test.cpp
index 55c70ea..16a9b52 100644
--- a/test/link/unique_ids_test.cpp
+++ b/test/link/unique_ids_test.cpp
@@ -135,7 +135,8 @@
   LinkerOptions options;
   options.SetVerifyIds(true);
   spv_result_t res = AssembleAndLink(bodies, &linked_binary, options);
-  EXPECT_EQ(SPV_SUCCESS, res);
+  ASSERT_EQ(SPV_SUCCESS, res) << GetErrorMessage();
+  EXPECT_THAT(GetErrorMessage(), std::string());
 }
 
 }  // namespace
diff --git a/test/operand_capabilities_test.cpp b/test/operand_capabilities_test.cpp
index bc0ee05..6050346 100644
--- a/test/operand_capabilities_test.cpp
+++ b/test/operand_capabilities_test.cpp
@@ -97,6 +97,14 @@
     }                                                                \
   }
 
+#define CASE6(TYPE, VALUE, CAP1, CAP2, CAP3, CAP4, CAP5, CAP6)          \
+  {                                                                     \
+    SPV_OPERAND_TYPE_##TYPE, uint32_t(Spv##VALUE), CapabilitySet {      \
+      SpvCapability##CAP1, SpvCapability##CAP2, SpvCapability##CAP3,    \
+          SpvCapability##CAP4, SpvCapability##CAP5, SpvCapability##CAP6 \
+    }                                                                   \
+  }
+
 // See SPIR-V Section 3.3 Execution Model
 INSTANTIATE_TEST_SUITE_P(
     ExecutionModel, EnumCapabilityTest,
@@ -168,10 +176,10 @@
                   Geometry),
             CASE1(EXECUTION_MODE, ExecutionModeQuads, Tessellation),
             CASE1(EXECUTION_MODE, ExecutionModeIsolines, Tessellation),
-            CASE3(EXECUTION_MODE, ExecutionModeOutputVertices, Geometry,
-                  Tessellation, MeshShadingNV),
-            CASE2(EXECUTION_MODE, ExecutionModeOutputPoints, Geometry,
-                  MeshShadingNV),
+            CASE4(EXECUTION_MODE, ExecutionModeOutputVertices, Geometry,
+                  Tessellation, MeshShadingNV, MeshShadingEXT),
+            CASE3(EXECUTION_MODE, ExecutionModeOutputPoints, Geometry,
+                  MeshShadingNV, MeshShadingEXT),
             CASE1(EXECUTION_MODE, ExecutionModeOutputLineStrip, Geometry),
             CASE1(EXECUTION_MODE, ExecutionModeOutputTriangleStrip, Geometry),
             CASE1(EXECUTION_MODE, ExecutionModeVecTypeHint, Kernel),
@@ -486,11 +494,11 @@
             CASE1(BUILT_IN, BuiltInCullDistance, CullDistance),  // Bug 1407, 15234
             CASE1(BUILT_IN, BuiltInVertexId, Shader),
             CASE1(BUILT_IN, BuiltInInstanceId, Shader),
-            CASE5(BUILT_IN, BuiltInPrimitiveId, Geometry, Tessellation,
-                  RayTracingNV, RayTracingKHR, MeshShadingNV),
+            CASE6(BUILT_IN, BuiltInPrimitiveId, Geometry, Tessellation,
+                  RayTracingNV, RayTracingKHR, MeshShadingNV, MeshShadingEXT),
             CASE2(BUILT_IN, BuiltInInvocationId, Geometry, Tessellation),
-            CASE3(BUILT_IN, BuiltInLayer, Geometry, ShaderViewportIndexLayerEXT, MeshShadingNV),
-            CASE3(BUILT_IN, BuiltInViewportIndex, MultiViewport, ShaderViewportIndexLayerEXT, MeshShadingNV),  // Bug 15234
+            CASE4(BUILT_IN, BuiltInLayer, Geometry, ShaderViewportIndexLayerEXT, MeshShadingNV, MeshShadingEXT),
+            CASE4(BUILT_IN, BuiltInViewportIndex, MultiViewport, ShaderViewportIndexLayerEXT, MeshShadingNV, MeshShadingEXT),  // Bug 15234
             CASE1(BUILT_IN, BuiltInTessLevelOuter, Tessellation),
             CASE1(BUILT_IN, BuiltInTessLevelInner, Tessellation),
             CASE1(BUILT_IN, BuiltInTessCoord, Tessellation),
@@ -533,11 +541,11 @@
         Values(SPV_ENV_UNIVERSAL_1_5),
         ValuesIn(std::vector<EnumCapabilityCase>{
             // SPIR-V 1.5 adds new capabilities to enable these two builtins.
-            CASE4(BUILT_IN, BuiltInLayer, Geometry, ShaderLayer,
-                  ShaderViewportIndexLayerEXT, MeshShadingNV),
-            CASE4(BUILT_IN, BuiltInViewportIndex, MultiViewport,
+            CASE5(BUILT_IN, BuiltInLayer, Geometry, ShaderLayer,
+                  ShaderViewportIndexLayerEXT, MeshShadingNV, MeshShadingEXT),
+            CASE5(BUILT_IN, BuiltInViewportIndex, MultiViewport,
                   ShaderViewportIndex, ShaderViewportIndexLayerEXT,
-                  MeshShadingNV),
+                  MeshShadingNV, MeshShadingEXT),
         })));
 
 // See SPIR-V Section 3.22 Selection Control
diff --git a/test/operand_pattern_test.cpp b/test/operand_pattern_test.cpp
index 1caf008..a98a9d7 100644
--- a/test/operand_pattern_test.cpp
+++ b/test/operand_pattern_test.cpp
@@ -91,9 +91,14 @@
         {SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS, 0, {PREFIX0}, {PREFIX0}},
         // Unknown bits means no change.  Use all bits that aren't in the
         // grammar.
-        // The last mask enum is 0x20
+        // The used mask bits are:
+        //          1 through...
+        //       0x20 SpvMemoryAccessNonPrivatePointerMask
+        // also
+        //    0x10000 SpvMemoryAccessAliasScopeINTELMaskShift
+        //    0x20000 SpvMemoryAccessNoAliasINTELMaskMask
         {SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS,
-         0xffffffc0,
+         0xffffffc0 ^ (0x10000) ^ (0x20000),
          {PREFIX1},
          {PREFIX1}},
         // Volatile has no operands.
@@ -111,6 +116,7 @@
          SpvMemoryAccessVolatileMask | SpvMemoryAccessAlignedMask,
          {PREFIX1},
          {PREFIX1, SPV_OPERAND_TYPE_LITERAL_INTEGER}},
+        // Newer masks are not tested
     }));
 #undef PREFIX0
 #undef PREFIX1
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
index bc44e8d..15966c1 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -42,8 +42,10 @@
        desc_sroa_test.cpp
        eliminate_dead_const_test.cpp
        eliminate_dead_functions_test.cpp
+       eliminate_dead_input_components_test.cpp
        eliminate_dead_member_test.cpp
        feature_manager_test.cpp
+       fix_func_call_arguments_test.cpp
        fix_storage_class_test.cpp
        flatten_decoration_test.cpp
        fold_spec_const_op_composite_test.cpp
@@ -60,6 +62,7 @@
        inst_debug_printf_test.cpp
        instruction_list_test.cpp
        instruction_test.cpp
+       interface_var_sroa_test.cpp
        interp_fixup_test.cpp
        ir_builder.cpp
        ir_context_test.cpp
@@ -82,7 +85,8 @@
        propagator_test.cpp
        reduce_load_size_test.cpp
        redundancy_elimination_test.cpp
-	   remove_unused_interface_variables_test.cpp
+       remove_dontinline_test.cpp
+       remove_unused_interface_variables_test.cpp
        register_liveness.cpp
        relax_float_ops_test.cpp
        replace_desc_array_access_using_var_index_test.cpp
@@ -91,9 +95,10 @@
        scalar_replacement_test.cpp
        set_spec_const_default_value_test.cpp
        simplification_test.cpp
+       spread_volatile_semantics_test.cpp
        strength_reduction_test.cpp
        strip_debug_info_test.cpp
-       strip_reflect_info_test.cpp
+       strip_nonsemantic_info_test.cpp
        struct_cfg_analysis_test.cpp
        type_manager_test.cpp
        types_test.cpp
diff --git a/test/opt/aggressive_dead_code_elim_test.cpp b/test/opt/aggressive_dead_code_elim_test.cpp
index f228c8c..e51098e 100644
--- a/test/opt/aggressive_dead_code_elim_test.cpp
+++ b/test/opt/aggressive_dead_code_elim_test.cpp
@@ -38,55 +38,45 @@
   //      vec4 dv = sqrt(Dead);
   //      gl_FragColor = v;
   //  }
-
-  const std::string predefs1 =
-      R"(OpCapability Shader
+  const std::string spirv = R"(
+OpCapability Shader
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
+; CHECK: OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor
 OpEntryPoint Fragment %main "main" %BaseColor %Dead %gl_FragColor
 OpExecutionMode %main OriginUpperLeft
 OpSource GLSL 140
-)";
-
-  const std::string names_before =
-      R"(OpName %main "main"
+OpName %main "main"
 OpName %v "v"
 OpName %BaseColor "BaseColor"
+; CHECK-NOT: OpName %dv "dv"
 OpName %dv "dv"
+; CHECK-NOT: OpName %Dead "Dead"
 OpName %Dead "Dead"
 OpName %gl_FragColor "gl_FragColor"
-)";
-
-  const std::string names_after =
-      R"(OpName %main "main"
-OpName %v "v"
-OpName %BaseColor "BaseColor"
-OpName %Dead "Dead"
-OpName %gl_FragColor "gl_FragColor"
-)";
-
-  const std::string predefs2 =
-      R"(%void = OpTypeVoid
+%void = OpTypeVoid
 %9 = 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
+; CHECK-NOT: %Dead = OpVariable
 %Dead = OpVariable %_ptr_Input_v4float Input
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %gl_FragColor = OpVariable %_ptr_Output_v4float Output
-)";
-
-  const std::string func_before =
-      R"(%main = OpFunction %void None %9
+%main = OpFunction %void None %9
 %15 = OpLabel
 %v = OpVariable %_ptr_Function_v4float Function
+; CHECK-NOT: %dv = OpVariable
 %dv = OpVariable %_ptr_Function_v4float Function
 %16 = OpLoad %v4float %BaseColor
 OpStore %v %16
+; CHECK-NOT: OpLoad %v4float %Dead
 %17 = OpLoad %v4float %Dead
+; CHECK-NOT: OpExtInst %v4float %1 Sqrt
 %18 = OpExtInst %v4float %1 Sqrt %17
+; CHECK-NOT: OpStore %dv
 OpStore %dv %18
 %19 = OpLoad %v4float %v
 OpStore %gl_FragColor %19
@@ -94,21 +84,7 @@
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%main = OpFunction %void None %9
-%15 = OpLabel
-%v = OpVariable %_ptr_Function_v4float Function
-%16 = OpLoad %v4float %BaseColor
-OpStore %v %16
-%19 = OpLoad %v4float %v
-OpStore %gl_FragColor %19
-OpReturn
-OpFunctionEnd
-)";
-
-  SinglePassRunAndCheck<AggressiveDCEPass>(
-      predefs1 + names_before + predefs2 + func_before,
-      predefs1 + names_after + predefs2 + func_after, true, true);
+  SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
 }
 
 TEST_F(AggressiveDCETest, NoEliminateFrexp) {
@@ -242,35 +218,23 @@
   //     gl_FragColor = v;
   // }
 
-  const std::string predefs1 =
-      R"(OpCapability Shader
+  const std::string spirv =
+      R"(
+OpCapability Shader
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %main "main" %BaseColor %Dead %gl_FragColor
 OpExecutionMode %main OriginUpperLeft
 OpSource GLSL 140
-)";
-
-  const std::string names_before =
-      R"(OpName %main "main"
+OpName %main "main"
 OpName %v "v"
 OpName %BaseColor "BaseColor"
 OpName %dv "dv"
 OpName %Dead "Dead"
 OpName %gl_FragColor "gl_FragColor"
+; CHECK-NOT: OpDecorate
 OpDecorate %8 RelaxedPrecision
-)";
-
-  const std::string names_after =
-      R"(OpName %main "main"
-OpName %v "v"
-OpName %BaseColor "BaseColor"
-OpName %Dead "Dead"
-OpName %gl_FragColor "gl_FragColor"
-)";
-
-  const std::string predefs2_before =
-      R"(%void = OpTypeVoid
+%void = OpTypeVoid
 %10 = OpTypeFunction %void
 %float = OpTypeFloat 32
 %v4float = OpTypeVector %float 4
@@ -281,29 +245,14 @@
 %float_0_5 = OpConstant %float 0.5
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %gl_FragColor = OpVariable %_ptr_Output_v4float Output
-)";
-
-  const std::string predefs2_after =
-      R"(%void = OpTypeVoid
-%10 = 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
-%Dead = OpVariable %_ptr_Input_v4float Input
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%gl_FragColor = OpVariable %_ptr_Output_v4float Output
-)";
-
-  const std::string func_before =
-      R"(%main = OpFunction %void None %10
+%main = OpFunction %void None %10
 %17 = OpLabel
 %v = OpVariable %_ptr_Function_v4float Function
 %dv = OpVariable %_ptr_Function_v4float Function
 %18 = OpLoad %v4float %BaseColor
 OpStore %v %18
 %19 = OpLoad %v4float %Dead
+; CHECK-NOT: OpVectorTimesScalar
 %8 = OpVectorTimesScalar %v4float %19 %float_0_5
 OpStore %dv %8
 %20 = OpLoad %v4float %v
@@ -312,21 +261,7 @@
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%main = OpFunction %void None %10
-%17 = OpLabel
-%v = OpVariable %_ptr_Function_v4float Function
-%18 = OpLoad %v4float %BaseColor
-OpStore %v %18
-%20 = OpLoad %v4float %v
-OpStore %gl_FragColor %20
-OpReturn
-OpFunctionEnd
-)";
-
-  SinglePassRunAndCheck<AggressiveDCEPass>(
-      predefs1 + names_before + predefs2_before + func_before,
-      predefs1 + names_after + predefs2_after + func_after, true, true);
+  SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
 }
 
 TEST_F(AggressiveDCETest, Simple) {
@@ -342,53 +277,44 @@
   //      gl_FragColor = v;
   //  }
 
-  const std::string predefs1 =
-      R"(OpCapability Shader
+  const std::string spirv =
+      R"(
+OpCapability Shader
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
+; CHECK: OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor
 OpEntryPoint Fragment %main "main" %BaseColor %Dead %gl_FragColor
 OpExecutionMode %main OriginUpperLeft
 OpSource GLSL 140
-)";
-
-  const std::string names_before =
-      R"(OpName %main "main"
+OpName %main "main"
 OpName %v "v"
 OpName %BaseColor "BaseColor"
+; CHECK-NOT: OpName %dv "dv"
 OpName %dv "dv"
+; CHECK-NOT: OpName %Dead "Dead"
 OpName %Dead "Dead"
 OpName %gl_FragColor "gl_FragColor"
-)";
-
-  const std::string names_after =
-      R"(OpName %main "main"
-OpName %v "v"
-OpName %BaseColor "BaseColor"
-OpName %Dead "Dead"
-OpName %gl_FragColor "gl_FragColor"
-)";
-
-  const std::string predefs2 =
-      R"(%void = OpTypeVoid
+%void = OpTypeVoid
 %9 = 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
+; CHECK-NOT: %Dead = OpVariable
 %Dead = OpVariable %_ptr_Input_v4float Input
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %gl_FragColor = OpVariable %_ptr_Output_v4float Output
-)";
-
-  const std::string func_before =
-      R"(%main = OpFunction %void None %9
+%main = OpFunction %void None %9
 %15 = OpLabel
 %v = OpVariable %_ptr_Function_v4float Function
+; CHECK-NOT: %dv = OpVariable
 %dv = OpVariable %_ptr_Function_v4float Function
 %16 = OpLoad %v4float %BaseColor
 OpStore %v %16
+; CHECK-NOT: OpLoad %v4float %Dead
 %17 = OpLoad %v4float %Dead
+; CHECK-NOT: OpStore %dv
 OpStore %dv %17
 %18 = OpLoad %v4float %v
 OpStore %gl_FragColor %18
@@ -396,21 +322,7 @@
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%main = OpFunction %void None %9
-%15 = OpLabel
-%v = OpVariable %_ptr_Function_v4float Function
-%16 = OpLoad %v4float %BaseColor
-OpStore %v %16
-%18 = OpLoad %v4float %v
-OpStore %gl_FragColor %18
-OpReturn
-OpFunctionEnd
-)";
-
-  SinglePassRunAndCheck<AggressiveDCEPass>(
-      predefs1 + names_before + predefs2 + func_before,
-      predefs1 + names_after + predefs2 + func_after, true, true);
+  SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
 }
 
 TEST_F(AggressiveDCETest, OptAllowListExtension) {
@@ -426,35 +338,22 @@
   //      gl_FragColor = v;
   //  }
 
-  const std::string predefs1 =
+  const std::string spirv =
       R"(OpCapability Shader
 OpExtension "SPV_AMD_gpu_shader_int16"
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
+; CHECK: OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor
 OpEntryPoint Fragment %main "main" %BaseColor %Dead %gl_FragColor
 OpExecutionMode %main OriginUpperLeft
 OpSource GLSL 140
-)";
-
-  const std::string names_before =
-      R"(OpName %main "main"
+OpName %main "main"
 OpName %v "v"
 OpName %BaseColor "BaseColor"
 OpName %dv "dv"
 OpName %Dead "Dead"
 OpName %gl_FragColor "gl_FragColor"
-)";
-
-  const std::string names_after =
-      R"(OpName %main "main"
-OpName %v "v"
-OpName %BaseColor "BaseColor"
-OpName %Dead "Dead"
-OpName %gl_FragColor "gl_FragColor"
-)";
-
-  const std::string predefs2 =
-      R"(%void = OpTypeVoid
+%void = OpTypeVoid
 %9 = OpTypeFunction %void
 %float = OpTypeFloat 32
 %v4float = OpTypeVector %float 4
@@ -464,10 +363,7 @@
 %Dead = OpVariable %_ptr_Input_v4float Input
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %gl_FragColor = OpVariable %_ptr_Output_v4float Output
-)";
-
-  const std::string func_before =
-      R"(%main = OpFunction %void None %9
+%main = OpFunction %void None %9
 %15 = OpLabel
 %v = OpVariable %_ptr_Function_v4float Function
 %dv = OpVariable %_ptr_Function_v4float Function
@@ -481,21 +377,7 @@
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%main = OpFunction %void None %9
-%15 = OpLabel
-%v = OpVariable %_ptr_Function_v4float Function
-%16 = OpLoad %v4float %BaseColor
-OpStore %v %16
-%18 = OpLoad %v4float %v
-OpStore %gl_FragColor %18
-OpReturn
-OpFunctionEnd
-)";
-
-  SinglePassRunAndCheck<AggressiveDCEPass>(
-      predefs1 + names_before + predefs2 + func_before,
-      predefs1 + names_after + predefs2 + func_after, true, true);
+  SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
 }
 
 TEST_F(AggressiveDCETest, NoOptDenyListExtension) {
@@ -571,7 +453,7 @@
   //     gl_FragColor = vec4(0.0);
   // }
 
-  const std::string defs_before =
+  const std::string text =
       R"( OpCapability Shader
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
@@ -600,54 +482,25 @@
 %gl_FragColor = OpVariable %_ptr_Output_v4float Output
 %float_0 = OpConstant %float 0
 %20 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
-)";
-
-  const std::string defs_after =
-      R"(OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main" %i1 %i2 %gl_FragColor
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 140
-OpName %main "main"
-OpName %nothing_vf4_ "nothing(vf4;"
-OpName %v "v"
-OpName %v1 "v1"
-OpName %i1 "i1"
-OpName %i2 "i2"
-OpName %param "param"
-OpName %gl_FragColor "gl_FragColor"
-%void = OpTypeVoid
-%12 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%v4float = OpTypeVector %float 4
-%_ptr_Function_v4float = OpTypePointer Function %v4float
-%16 = OpTypeFunction %void %_ptr_Function_v4float
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%i1 = OpVariable %_ptr_Input_v4float Input
-%i2 = OpVariable %_ptr_Input_v4float Input
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%gl_FragColor = OpVariable %_ptr_Output_v4float Output
-%float_0 = OpConstant %float 0
-%20 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
-)";
-
-  const std::string func_before =
-      R"(%main = OpFunction %void None %12
+%main = OpFunction %void None %12
 %21 = OpLabel
 %v1 = OpVariable %_ptr_Function_v4float Function
 %v2 = OpVariable %_ptr_Function_v4float Function
 %param = OpVariable %_ptr_Function_v4float Function
 %22 = OpLoad %v4float %i1
 OpStore %v1 %22
+; CHECK-NOT: OpLoad %v4float %i2
 %23 = OpLoad %v4float %i2
+; CHECK-NOT: OpStore %v2
 OpStore %v2 %23
 %24 = OpLoad %v4float %v1
 OpStore %param %24
+; CHECK: OpFunctionCall %void %nothing_vf4_
 %25 = OpFunctionCall %void %nothing_vf4_ %param
 OpStore %gl_FragColor %20
 OpReturn
 OpFunctionEnd
+; CHECK: %nothing_vf4_ = OpFunction
 %nothing_vf4_ = OpFunction %void None %16
 %v = OpFunctionParameter %_ptr_Function_v4float
 %26 = OpLabel
@@ -655,28 +508,7 @@
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%main = OpFunction %void None %12
-%21 = OpLabel
-%v1 = OpVariable %_ptr_Function_v4float Function
-%param = OpVariable %_ptr_Function_v4float Function
-%22 = OpLoad %v4float %i1
-OpStore %v1 %22
-%24 = OpLoad %v4float %v1
-OpStore %param %24
-%25 = OpFunctionCall %void %nothing_vf4_ %param
-OpStore %gl_FragColor %20
-OpReturn
-OpFunctionEnd
-%nothing_vf4_ = OpFunction %void None %16
-%v = OpFunctionParameter %_ptr_Function_v4float
-%26 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
-
-  SinglePassRunAndCheck<AggressiveDCEPass>(defs_before + func_before,
-                                           defs_after + func_after, true, true);
+  SinglePassRunAndMatch<AggressiveDCEPass>(text, true);
 }
 
 TEST_F(AggressiveDCETest, NoParamElim) {
@@ -998,7 +830,7 @@
   //     OutColor = v;
   // }
 
-  const std::string predefs_before =
+  const std::string spirv =
       R"(OpCapability Shader
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
@@ -1008,6 +840,7 @@
 OpName %main "main"
 OpName %v "v"
 OpName %BaseColor "BaseColor"
+; CHECK-NOT: OpName %dv "dv"
 OpName %dv "dv"
 OpName %Dead "Dead"
 OpName %OutColor "OutColor"
@@ -1019,49 +852,22 @@
 %float = OpTypeFloat 32
 %v4float = OpTypeVector %float 4
 %_ptr_Function_v4float = OpTypePointer Function %v4float
+; CHECK-NOT: OpTypePointer Private
 %_ptr_Private_v4float = OpTypePointer Private %v4float
 %_ptr_Input_v4float = OpTypePointer Input %v4float
 %BaseColor = OpVariable %_ptr_Input_v4float Input
 %Dead = OpVariable %_ptr_Input_v4float Input
 %_ptr_Output_v4float = OpTypePointer Output %v4float
+; CHECK-NOT: %dv = OpVariable
 %dv = OpVariable %_ptr_Private_v4float Private
 %OutColor = OpVariable %_ptr_Output_v4float Output
-)";
-
-  const std::string predefs_after =
-      R"(OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main" %BaseColor %Dead %OutColor
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 450
-OpName %main "main"
-OpName %v "v"
-OpName %BaseColor "BaseColor"
-OpName %Dead "Dead"
-OpName %OutColor "OutColor"
-OpDecorate %BaseColor Location 0
-OpDecorate %Dead Location 1
-OpDecorate %OutColor Location 0
-%void = OpTypeVoid
-%9 = 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
-%Dead = OpVariable %_ptr_Input_v4float Input
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%OutColor = OpVariable %_ptr_Output_v4float Output
-)";
-
-  const std::string main_before =
-      R"(%main = OpFunction %void None %9
+%main = OpFunction %void None %9
 %16 = OpLabel
 %v = OpVariable %_ptr_Function_v4float Function
 %17 = OpLoad %v4float %BaseColor
 OpStore %v %17
 %18 = OpLoad %v4float %Dead
+; CHECK-NOT: OpStore %dv
 OpStore %dv %18
 %19 = OpLoad %v4float %v
 %20 = OpFNegate %v4float %19
@@ -1070,21 +876,7 @@
 OpFunctionEnd
 )";
 
-  const std::string main_after =
-      R"(%main = OpFunction %void None %9
-%16 = OpLabel
-%v = OpVariable %_ptr_Function_v4float Function
-%17 = OpLoad %v4float %BaseColor
-OpStore %v %17
-%19 = OpLoad %v4float %v
-%20 = OpFNegate %v4float %19
-OpStore %OutColor %20
-OpReturn
-OpFunctionEnd
-)";
-
-  SinglePassRunAndCheck<AggressiveDCEPass>(
-      predefs_before + main_before, predefs_after + main_after, true, true);
+  SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
 }
 
 TEST_F(AggressiveDCETest, NoPrivateStoreElimIfLoad) {
@@ -1288,7 +1080,7 @@
   //     OutColor = v;
   // }
 
-  const std::string predefs_before =
+  const std::string spirv =
       R"(OpCapability Shader
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
@@ -1298,6 +1090,7 @@
 OpName %main "main"
 OpName %v "v"
 OpName %BaseColor "BaseColor"
+; CHECK-NOT: OpName %dv "dv"
 OpName %dv "dv"
 OpName %Dead "Dead"
 OpName %OutColor "OutColor"
@@ -1309,49 +1102,22 @@
 %float = OpTypeFloat 32
 %v4float = OpTypeVector %float 4
 %_ptr_Function_v4float = OpTypePointer Function %v4float
+; CHECK-NOT: OpTypePointer Workgroup
 %_ptr_Workgroup_v4float = OpTypePointer Workgroup %v4float
 %_ptr_Input_v4float = OpTypePointer Input %v4float
 %BaseColor = OpVariable %_ptr_Input_v4float Input
 %Dead = OpVariable %_ptr_Input_v4float Input
 %_ptr_Output_v4float = OpTypePointer Output %v4float
+; CHECK-NOT: %dv = OpVariable
 %dv = OpVariable %_ptr_Workgroup_v4float Workgroup
 %OutColor = OpVariable %_ptr_Output_v4float Output
-)";
-
-  const std::string predefs_after =
-      R"(OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main" %BaseColor %Dead %OutColor
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 450
-OpName %main "main"
-OpName %v "v"
-OpName %BaseColor "BaseColor"
-OpName %Dead "Dead"
-OpName %OutColor "OutColor"
-OpDecorate %BaseColor Location 0
-OpDecorate %Dead Location 1
-OpDecorate %OutColor Location 0
-%void = OpTypeVoid
-%9 = 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
-%Dead = OpVariable %_ptr_Input_v4float Input
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%OutColor = OpVariable %_ptr_Output_v4float Output
-)";
-
-  const std::string main_before =
-      R"(%main = OpFunction %void None %9
+%main = OpFunction %void None %9
 %16 = OpLabel
 %v = OpVariable %_ptr_Function_v4float Function
 %17 = OpLoad %v4float %BaseColor
 OpStore %v %17
 %18 = OpLoad %v4float %Dead
+; CHECK-NOT: OpStore %dv
 OpStore %dv %18
 %19 = OpLoad %v4float %v
 %20 = OpFNegate %v4float %19
@@ -1360,21 +1126,7 @@
 OpFunctionEnd
 )";
 
-  const std::string main_after =
-      R"(%main = OpFunction %void None %9
-%16 = OpLabel
-%v = OpVariable %_ptr_Function_v4float Function
-%17 = OpLoad %v4float %BaseColor
-OpStore %v %17
-%19 = OpLoad %v4float %v
-%20 = OpFNegate %v4float %19
-OpStore %OutColor %20
-OpReturn
-OpFunctionEnd
-)";
-
-  SinglePassRunAndCheck<AggressiveDCEPass>(
-      predefs_before + main_before, predefs_after + main_after, true, true);
+  SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
 }
 
 TEST_F(AggressiveDCETest, EliminateDeadIfThenElse) {
@@ -1393,7 +1145,7 @@
   //     OutColor = vec4(1.0,1.0,1.0,1.0);
   // }
 
-  const std::string predefs_before =
+  const std::string spirv =
       R"(OpCapability Shader
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
@@ -1424,34 +1176,11 @@
 %OutColor = OpVariable %_ptr_Output_v4float Output
 %float_1 = OpConstant %float 1
 %21 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
-)";
-
-  const std::string predefs_after =
-      R"(OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main" %BaseColor %OutColor
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 450
-OpName %main "main"
-OpName %BaseColor "BaseColor"
-OpName %OutColor "OutColor"
-OpDecorate %BaseColor Location 0
-OpDecorate %OutColor Location 0
-%void = OpTypeVoid
-%7 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%v4float = OpTypeVector %float 4
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%BaseColor = OpVariable %_ptr_Input_v4float Input
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%OutColor = OpVariable %_ptr_Output_v4float Output
-%float_1 = OpConstant %float 1
-%21 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
-)";
-
-  const std::string func_before =
-      R"(%main = OpFunction %void None %7
+; CHECK: = OpFunction %void
+; CHECK-NEXT: %22 = OpLabel
+; CHECK-NEXT: OpBranch %26
+; CHECK-NEXT: %26 = OpLabel
+%main = OpFunction %void None %7
 %22 = OpLabel
 %d = OpVariable %_ptr_Function_float Function
 %23 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0
@@ -1475,18 +1204,7 @@
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%main = OpFunction %void None %7
-%22 = OpLabel
-OpBranch %26
-%26 = OpLabel
-OpStore %OutColor %21
-OpReturn
-OpFunctionEnd
-)";
-
-  SinglePassRunAndCheck<AggressiveDCEPass>(
-      predefs_before + func_before, predefs_after + func_after, true, true);
+  SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
 }
 
 TEST_F(AggressiveDCETest, EliminateDeadIfThen) {
@@ -1503,7 +1221,7 @@
   //     OutColor = vec4(1.0,1.0,1.0,1.0);
   // }
 
-  const std::string predefs_before =
+  const std::string spirv =
       R"(OpCapability Shader
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
@@ -1533,34 +1251,11 @@
 %OutColor = OpVariable %_ptr_Output_v4float Output
 %float_1 = OpConstant %float 1
 %20 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
-)";
-
-  const std::string predefs_after =
-      R"(OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main" %BaseColor %OutColor
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 450
-OpName %main "main"
-OpName %BaseColor "BaseColor"
-OpName %OutColor "OutColor"
-OpDecorate %BaseColor Location 0
-OpDecorate %OutColor Location 0
-%void = OpTypeVoid
-%7 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%v4float = OpTypeVector %float 4
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%BaseColor = OpVariable %_ptr_Input_v4float Input
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%OutColor = OpVariable %_ptr_Output_v4float Output
-%float_1 = OpConstant %float 1
-%20 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
-)";
-
-  const std::string func_before =
-      R"(%main = OpFunction %void None %7
+; CHECK: = OpFunction
+; CHECK-NEXT: %21 = OpLabel
+; CHECK-NEXT: OpBranch [[target:%\w+]]
+; CHECK-NEXT: [[target]] = OpLabel
+%main = OpFunction %void None %7
 %21 = OpLabel
 %d = OpVariable %_ptr_Function_float Function
 %22 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0
@@ -1579,18 +1274,7 @@
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%main = OpFunction %void None %7
-%21 = OpLabel
-OpBranch %25
-%25 = OpLabel
-OpStore %OutColor %20
-OpReturn
-OpFunctionEnd
-)";
-
-  SinglePassRunAndCheck<AggressiveDCEPass>(
-      predefs_before + func_before, predefs_after + func_after, true, true);
+  SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
 }
 
 TEST_F(AggressiveDCETest, EliminateDeadSwitch) {
@@ -1609,7 +1293,7 @@
   //     }
   //     OutColor = vec4(1.0,1.0,1.0,1.0);
   // }
-  const std::string before =
+  const std::string spirv =
       R"(OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
@@ -1642,6 +1326,10 @@
    %OutColor = OpVariable %_ptr_Output_v4float Output
     %float_1 = OpConstant %float 1
          %27 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+; CHECK: = OpFunction
+; CHECK-NEXT: = OpLabel
+; CHECK-NEXT: OpBranch [[target:%\w+]]
+; CHECK-NEXT: [[target]] = OpLabel
        %main = OpFunction %void None %3
           %5 = OpLabel
           %d = OpVariable %_ptr_Function_float Function
@@ -1658,45 +1346,7 @@
                OpReturn
                OpFunctionEnd)";
 
-  const std::string after =
-      R"(OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main" %x %BaseColor %OutColor
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 450
-OpName %main "main"
-OpName %x "x"
-OpName %BaseColor "BaseColor"
-OpName %OutColor "OutColor"
-OpDecorate %x Flat
-OpDecorate %x Location 1
-OpDecorate %BaseColor Location 0
-OpDecorate %OutColor Location 0
-%void = OpTypeVoid
-%3 = OpTypeFunction %void
-%int = OpTypeInt 32 1
-%_ptr_Input_int = OpTypePointer Input %int
-%x = OpVariable %_ptr_Input_int Input
-%float = OpTypeFloat 32
-%v4float = OpTypeVector %float 4
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%BaseColor = OpVariable %_ptr_Input_v4float Input
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%OutColor = OpVariable %_ptr_Output_v4float Output
-%float_1 = OpConstant %float 1
-%27 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
-%main = OpFunction %void None %3
-%5 = OpLabel
-OpBranch %11
-%11 = OpLabel
-OpStore %OutColor %27
-OpReturn
-OpFunctionEnd
-)";
-
-  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<AggressiveDCEPass>(before, after, true, true);
+  SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
 }
 
 TEST_F(AggressiveDCETest, EliminateDeadIfThenElseNested) {
@@ -1721,7 +1371,7 @@
   //     OutColor = vec4(1.0,1.0,1.0,1.0);
   // }
 
-  const std::string predefs_before =
+  const std::string spirv =
       R"(OpCapability Shader
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
@@ -1754,34 +1404,14 @@
 %OutColor = OpVariable %_ptr_Output_v4float Output
 %float_1 = OpConstant %float 1
 %23 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
-)";
 
-  const std::string predefs_after =
-      R"(OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main" %BaseColor %OutColor
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 450
-OpName %main "main"
-OpName %BaseColor "BaseColor"
-OpName %OutColor "OutColor"
-OpDecorate %BaseColor Location 0
-OpDecorate %OutColor Location 0
-%void = OpTypeVoid
-%7 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%v4float = OpTypeVector %float 4
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%BaseColor = OpVariable %_ptr_Input_v4float Input
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%OutColor = OpVariable %_ptr_Output_v4float Output
-%float_1 = OpConstant %float 1
-%23 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
-)";
+; CHECK: = OpFunction
+; CHECK-NEXT: = OpLabel
+; CHECK-NEXT: OpBranch [[target:%\w+]]
+; CHECK-NEXT: [[target]] = OpLabel
+; CHECK-NOT: OpLabel
 
-  const std::string func_before =
-      R"(%main = OpFunction %void None %7
+%main = OpFunction %void None %7
 %24 = OpLabel
 %d = OpVariable %_ptr_Function_float Function
 %25 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0
@@ -1823,18 +1453,7 @@
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%main = OpFunction %void None %7
-%24 = OpLabel
-OpBranch %28
-%28 = OpLabel
-OpStore %OutColor %23
-OpReturn
-OpFunctionEnd
-)";
-
-  SinglePassRunAndCheck<AggressiveDCEPass>(
-      predefs_before + func_before, predefs_after + func_after, true, true);
+  SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
 }
 
 TEST_F(AggressiveDCETest, NoEliminateLiveIfThenElse) {
@@ -2578,7 +2197,7 @@
   //       d = BaseColor.z;
   // }
 
-  const std::string predefs_before =
+  const std::string spirv =
       R"(OpCapability Shader
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
@@ -2607,32 +2226,15 @@
 %uint_2 = OpConstant %uint 2
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %OutColor = OpVariable %_ptr_Output_v4float Output
-)";
 
-  const std::string predefs_after =
-      R"(OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main" %BaseColor %OutColor
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 450
-OpName %main "main"
-OpName %BaseColor "BaseColor"
-OpName %OutColor "OutColor"
-OpDecorate %BaseColor Location 0
-OpDecorate %OutColor Location 0
-%void = OpTypeVoid
-%7 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%v4float = OpTypeVector %float 4
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%BaseColor = OpVariable %_ptr_Input_v4float Input
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%OutColor = OpVariable %_ptr_Output_v4float Output
-)";
+; CHECK: = OpFunction
+; CHECK-NEXT: = OpLabel
+; CHECK-NEXT: OpBranch [[target:%\w+]]
+; CHECK-NEXT: [[target]] = OpLabel
+; CHECK-NEXT: OpReturn
+; CHECK-NEXT: OpFunctionEnd
 
-  const std::string func_before =
-      R"(%main = OpFunction %void None %7
+%main = OpFunction %void None %7
 %20 = OpLabel
 %d = OpVariable %_ptr_Function_float Function
 %21 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0
@@ -2655,17 +2257,7 @@
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%main = OpFunction %void None %7
-%20 = OpLabel
-OpBranch %24
-%24 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
-
-  SinglePassRunAndCheck<AggressiveDCEPass>(
-      predefs_before + func_before, predefs_after + func_after, true, true);
+  SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
 }
 
 TEST_F(AggressiveDCETest, EliminateUselessInnerLoop) {
@@ -3949,9 +3541,11 @@
 OpBranch %16
 %16 = OpLabel
 OpSelectionMerge %18 None
-OpSwitch %13 %18 0 %17 1 %15
+OpSwitch %13 %18 0 %17 1 %19
 %17 = OpLabel
 OpStore %3 %uint_1
+OpBranch %19
+%19 = OpLabel
 OpBranch %15
 %15 = OpLabel
 OpBranch %12
@@ -4731,7 +4325,7 @@
         },
 
         // Uint vector type spec constants. One vector has all component dead,
-        // another vector has one dead unsigend integer and one used unsigned
+        // another vector has one dead unsigned integer and one used unsigned
         // integer.
         {
             /* .used_consts = */
@@ -5521,10 +5115,9 @@
 OpCapability Shader
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %2 "min" %gl_GlobalInvocationID
+OpEntryPoint GLCompute %2 "min"
 OpExecutionMode %2 LocalSize 64 1 1
 OpSource HLSL 600
-OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
 OpDecorate %4 DescriptorSet 4
 OpDecorate %4 Binding 70
 %uint = OpTypeInt 32 0
@@ -5535,12 +5128,9 @@
 %10 = OpTypeFunction %void
 %uint_0 = OpConstant %uint 0
 %uint_1 = OpConstant %uint 1
-%v3uint = OpTypeVector %uint 3
-%_ptr_Input_v3uint = OpTypePointer Input %v3uint
 %_ptr_Image_uint = OpTypePointer Image %uint
 %4 = OpVariable %_ptr_UniformConstant_6 UniformConstant
 %16 = OpVariable %_ptr_Private_6 Private
-%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
 %2 = OpFunction %void None %10
 %17 = OpLabel
 %18 = OpLoad %6 %4
@@ -6393,8 +5983,8 @@
 
 TEST_F(AggressiveDCETest, DeadInputInterfaceV13) {
   const std::string spirv = R"(
-; CHECK: OpEntryPoint GLCompute %main "main" [[var:%\w+]]
-; CHECK: [[var]] = OpVariable
+; CHECK: OpEntryPoint GLCompute %main "main"
+; CHECK-NOT: OpVariable
 OpCapability Shader
 OpMemoryModel Logical GLSL450
 OpEntryPoint GLCompute %main "main" %dead
@@ -6417,8 +6007,8 @@
 
 TEST_F(AggressiveDCETest, DeadInputInterfaceV14) {
   const std::string spirv = R"(
-; CHECK: OpEntryPoint GLCompute %main "main" [[var:%\w+]]
-; CHECK: [[var]] = OpVariable
+; CHECK: OpEntryPoint GLCompute %main "main"
+; CHECK-NOT: OpVariable
 OpCapability Shader
 OpMemoryModel Logical GLSL450
 OpEntryPoint GLCompute %main "main" %dead
@@ -8038,6 +7628,155 @@
   SinglePassRunAndCheck<AggressiveDCEPass>(text, text, false);
 }
 
+TEST_F(AggressiveDCETest, FunctionBecomesUnreachableAfterDCE) {
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 320
+       %void = OpTypeVoid
+          %4 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+          %7 = OpTypeFunction %int
+     %int_n1 = OpConstant %int -1
+          %2 = OpFunction %void None %4
+          %9 = OpLabel
+               OpKill
+         %10 = OpLabel
+         %11 = OpFunctionCall %int %12
+               OpReturn
+               OpFunctionEnd
+; CHECK: {{%\w+}} = OpFunction %int DontInline|Pure
+         %12 = OpFunction %int DontInline|Pure %7
+; CHECK-NEXT: {{%\w+}} = OpLabel
+         %13 = OpLabel
+         %14 = OpVariable %_ptr_Function_int Function
+; CHECK-NEXT: OpBranch [[header:%\w+]]
+               OpBranch %15
+; CHECK-NEXT: [[header]] = OpLabel
+; CHECK-NEXT: OpBranch [[merge:%\w+]]
+         %15 = OpLabel
+               OpLoopMerge %16 %17 None
+               OpBranch %18
+         %18 = OpLabel
+         %19 = OpLoad %int %14
+               OpBranch %17
+         %17 = OpLabel
+               OpBranch %15
+; CHECK-NEXT: [[merge]] = OpLabel
+         %16 = OpLabel
+; CHECK-NEXT: OpReturnValue %int_n1
+               OpReturnValue %int_n1
+; CHECK-NEXT: OpFunctionEnd
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<AggressiveDCEPass>(text, true);
+}
+
+TEST_F(AggressiveDCETest, KeepTopLevelDebugInfo) {
+  // Don't eliminate DebugCompilationUnit, DebugSourceContinued, and
+  // DebugEntryPoint
+  const std::string text = R"(
+               OpCapability Shader
+               OpExtension "SPV_KHR_non_semantic_info"
+          %1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %MainPs "MainPs" %out_var_SV_Target0
+               OpExecutionMode %MainPs OriginUpperLeft
+          %4 = OpString "foo2.frag"
+          %5 = OpString "
+struct PS_OUTPUT
+{
+    float4 vColor : SV_Target0 ;
+} ;
+
+"
+          %6 = OpString "
+PS_OUTPUT MainPs ( )
+{
+    PS_OUTPUT ps_output ;
+    ps_output . vColor = float4( 1.0, 0.0, 0.0, 0.0 );
+    return ps_output ;
+}
+
+"
+          %7 = OpString "float"
+          %8 = OpString "vColor"
+          %9 = OpString "PS_OUTPUT"
+         %10 = OpString "MainPs"
+         %11 = OpString ""
+         %12 = OpString "ps_output"
+         %13 = OpString "97a939fb"
+         %14 = OpString " foo2.frag -E MainPs -T ps_6_1 -spirv -fspv-target-env=vulkan1.2 -fspv-debug=vulkan-with-source -fcgl -Fo foo2.frag.nopt.spv -Qembed_debug"
+               OpName %out_var_SV_Target0 "out.var.SV_Target0"
+               OpName %MainPs "MainPs"
+               OpDecorate %out_var_SV_Target0 Location 0
+      %float = OpTypeFloat 32
+    %float_1 = OpConstant %float 1
+    %float_0 = OpConstant %float 0
+    %v4float = OpTypeVector %float 4
+         %23 = OpConstantComposite %v4float %float_1 %float_0 %float_0 %float_0
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %uint = OpTypeInt 32 0
+    %uint_32 = OpConstant %uint 32
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+     %uint_1 = OpConstant %uint 1
+     %uint_4 = OpConstant %uint 4
+     %uint_5 = OpConstant %uint 5
+     %uint_3 = OpConstant %uint 3
+     %uint_0 = OpConstant %uint 0
+   %uint_128 = OpConstant %uint 128
+    %uint_12 = OpConstant %uint 12
+     %uint_2 = OpConstant %uint 2
+     %uint_8 = OpConstant %uint 8
+     %uint_7 = OpConstant %uint 7
+     %uint_9 = OpConstant %uint 9
+    %uint_15 = OpConstant %uint 15
+         %42 = OpTypeFunction %void
+    %uint_10 = OpConstant %uint 10
+    %uint_53 = OpConstant %uint 53
+%out_var_SV_Target0 = OpVariable %_ptr_Output_v4float Output
+         %49 = OpExtInst %void %1 DebugExpression
+         %50 = OpExtInst %void %1 DebugSource %4 %5
+         %51 = OpExtInst %void %1 DebugSourceContinued %6
+; CHECK:     %51 = OpExtInst %void %1 DebugSourceContinued %6
+         %52 = OpExtInst %void %1 DebugCompilationUnit %uint_1 %uint_4 %50 %uint_5
+; CHECK:     %52 = OpExtInst %void %1 DebugCompilationUnit %uint_1 %uint_4 %50 %uint_5
+         %53 = OpExtInst %void %1 DebugTypeBasic %7 %uint_32 %uint_3 %uint_0
+         %54 = OpExtInst %void %1 DebugTypeVector %53 %uint_4
+         %55 = OpExtInst %void %1 DebugTypeMember %8 %54 %50 %uint_4 %uint_12 %uint_0 %uint_128 %uint_3
+         %56 = OpExtInst %void %1 DebugTypeComposite %9 %uint_1 %50 %uint_2 %uint_8 %52 %9 %uint_128 %uint_3 %55
+         %57 = OpExtInst %void %1 DebugTypeFunction %uint_3 %56
+         %58 = OpExtInst %void %1 DebugFunction %10 %57 %50 %uint_7 %uint_1 %52 %11 %uint_3 %uint_8
+         %59 = OpExtInst %void %1 DebugLexicalBlock %50 %uint_8 %uint_1 %58
+         %60 = OpExtInst %void %1 DebugLocalVariable %12 %56 %50 %uint_9 %uint_15 %59 %uint_4
+         %61 = OpExtInst %void %1 DebugEntryPoint %58 %52 %13 %14
+; CHECK:     %61 = OpExtInst %void %1 DebugEntryPoint %58 %52 %13 %14
+     %MainPs = OpFunction %void None %42
+         %62 = OpLabel
+         %63 = OpExtInst %void %1 DebugFunctionDefinition %58 %MainPs
+        %112 = OpExtInst %void %1 DebugScope %59
+        %111 = OpExtInst %void %1 DebugLine %50 %uint_10 %uint_10 %uint_5 %uint_53
+        %110 = OpExtInst %void %1 DebugValue %60 %23 %49 %int_0
+        %113 = OpExtInst %void %1 DebugNoLine
+        %114 = OpExtInst %void %1 DebugNoScope
+               OpStore %out_var_SV_Target0 %23
+         %66 = OpExtInst %void %1 DebugLine %50 %uint_12 %uint_12 %uint_1 %uint_1
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_2);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<AggressiveDCEPass>(text, true);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/assembly_builder.h b/test/opt/assembly_builder.h
index 1673c09..b94e90f 100644
--- a/test/opt/assembly_builder.h
+++ b/test/opt/assembly_builder.h
@@ -70,7 +70,7 @@
   static const uint32_t SPEC_ID_BASE = 200;
 
  public:
-  // Initalize a minimal SPIR-V assembly code as the template. The minimal
+  // Initialize a minimal SPIR-V assembly code as the template. The minimal
   // module contains an empty main function and some predefined names for the
   // main function.
   AssemblyBuilder()
@@ -102,7 +102,7 @@
     });
   }
 
-  // Appends OpName instructions to this builder. Instrcution strings that do
+  // Appends OpName instructions to this builder. Instruction strings that do
   // not start with 'OpName ' will be skipped. Returns the references of this
   // assembly builder.
   AssemblyBuilder& AppendNames(const std::vector<std::string>& vec_asm_code) {
diff --git a/test/opt/block_merge_test.cpp b/test/opt/block_merge_test.cpp
index 6903c4e..9698fed 100644
--- a/test/opt/block_merge_test.cpp
+++ b/test/opt/block_merge_test.cpp
@@ -884,7 +884,7 @@
 ; CHECK-NEXT: [[header]] = OpLabel
 ; CHECK-NEXT: OpSelectionMerge [[merge:%\w+]]
 ; CHECK: [[merge]] = OpLabel
-; CHEKC: OpReturn
+; CHECK: OpReturn
 OpCapability Shader
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %func "func"
diff --git a/test/opt/ccp_test.cpp b/test/opt/ccp_test.cpp
index 212e051..f0f2436 100644
--- a/test/opt/ccp_test.cpp
+++ b/test/opt/ccp_test.cpp
@@ -582,6 +582,35 @@
   EXPECT_EQ(std::get<1>(res), Pass::Status::SuccessWithoutChange);
 }
 
+TEST_F(CCPTest, FoldConstantCompositeInstrucitonsWithSpecConst) {
+  const std::string spv_asm = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %1 "main"
+               OpExecutionMode %1 OriginUpperLeft
+       %void = OpTypeVoid
+          %4 = OpTypeFunction %void
+       %bool = OpTypeBool
+     %v3bool = OpTypeVector %bool 3
+  %_struct_8 = OpTypeStruct %v3bool
+       %true = OpConstantTrue %bool
+; CHECK: [[spec_const:%\w+]] = OpSpecConstantComposite %v3bool
+         %11 = OpSpecConstantComposite %v3bool %true %true %true
+         %12 = OpConstantComposite %_struct_8 %11
+; CHECK: OpFunction
+          %1 = OpFunction %void None %4
+         %29 = OpLabel
+         %31 = OpCompositeExtract %v3bool %12 0
+; CHECK: OpCompositeExtract %bool [[spec_const]] 0
+         %32 = OpCompositeExtract %bool %31 0
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  auto result = SinglePassRunAndMatch<CCPPass>(spv_asm, true);
+  EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange);
+}
+
 TEST_F(CCPTest, UpdateSubsequentPhisToVarying) {
   const std::string text = R"(
 OpCapability Shader
@@ -1234,6 +1263,86 @@
 
   SinglePassRunAndCheck<CCPPass>(text, text, false);
 }
+
+// Test from https://github.com/KhronosGroup/SPIRV-Tools/issues/4462.
+// The test was causing a lateral movement in the constant lattice, which was
+// not being detected as varying by CCP. In this test, FClamp is evaluated
+// twice.  On the first evaluation, if computes FClamp(0.5, 0.5, -1) which
+// returns -1.  On the second evaluation, it computes FClamp(0.5, 0.5, VARYING)
+// which returns 0.5.
+//
+// Both fold() computations are correct given the semantics of FClamp() but
+// this causes a lateral transition in the constant lattice which was not being
+// considered VARYING by CCP.
+TEST_F(CCPTest, LateralLatticeTransition) {
+  const std::string text = R"(OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %gl_FragCoord %outColor
+               OpExecutionMode %main OriginUpperLeft
+               OpSource ESSL 310
+               OpName %main "main"
+               OpName %gl_FragCoord "gl_FragCoord"
+               OpName %outColor "outColor"
+               OpDecorate %gl_FragCoord BuiltIn FragCoord
+               OpDecorate %outColor Location 0
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+  %float_0_5 = OpConstant %float 0.5
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+%_ptr_Input_float = OpTypePointer Input %float
+    %float_0 = OpConstant %float 0
+       %bool = OpTypeBool
+   %float_n1 = OpConstant %float -1
+    %float_1 = OpConstant %float 1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+   %outColor = OpVariable %_ptr_Output_v4float Output
+
+; This constant is created during the first evaluation of the CompositeConstruct
+; CHECK: [[new_constant:%\d+]] = OpConstantComposite %v4float %float_n1 %float_0_5 %float_0 %float_1
+
+       %main = OpFunction %void None %6
+         %19 = OpLabel
+         %20 = OpAccessChain %_ptr_Input_float %gl_FragCoord %uint_0
+         %21 = OpLoad %float %20
+         %22 = OpFOrdLessThan %bool %21 %float_0
+               OpSelectionMerge %23 None
+               OpBranchConditional %22 %24 %25
+         %24 = OpLabel
+               OpBranch %23
+         %25 = OpLabel
+               OpBranch %26
+         %26 = OpLabel
+               OpBranch %23
+         %23 = OpLabel
+         %27 = OpPhi %float %float_n1 %24 %float_0_5 %26
+         %28 = OpExtInst %float %1 FClamp %float_0_5 %float_0_5 %27
+
+         ; On first evaluation, the result from FClamp will return 0.5.
+         ; But on second evaluation, FClamp should return VARYING.  Check
+         ; that CCP is not keeping the first result.
+         ; CHECK-NOT: %29 = OpCompositeConstruct %v4float %float_0_5 %float_0_5 %float_0 %float_1
+         %29 = OpCompositeConstruct %v4float %28 %float_0_5 %float_0 %float_1
+
+         ; CHECK-NOT: OpCopyObject %v4float [[new_constant]]
+         %42 = OpCopyObject %v4float %29
+
+         ; CHECK-NOT: OpStore %outColor [[new_constant]]
+               OpStore %outColor %42
+
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndMatch<CCPPass>(text, true);
+  EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/cfg_test.cpp b/test/opt/cfg_test.cpp
index 2cfc9f3..7dfd2bc 100644
--- a/test/opt/cfg_test.cpp
+++ b/test/opt/cfg_test.cpp
@@ -200,6 +200,125 @@
                            ContainerEq(expected_result2)));
 }
 
+TEST_F(CFGTest, SplitLoopHeaderForSingleBlockLoop) {
+  const std::string test = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+       %void = OpTypeVoid
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+          %6 = OpTypeFunction %void
+          %2 = OpFunction %void None %6
+          %7 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+          %9 = OpPhi %uint %uint_0 %7 %9 %8
+               OpLoopMerge %10 %8 None
+               OpBranch %8
+         %10 = OpLabel
+               OpUnreachable
+               OpFunctionEnd
+)";
+
+  const std::string expected_result = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%6 = OpTypeFunction %void
+%2 = OpFunction %void None %6
+%7 = OpLabel
+OpBranch %8
+%8 = OpLabel
+OpBranch %11
+%11 = OpLabel
+%9 = OpPhi %uint %9 %11 %uint_0 %8
+OpLoopMerge %10 %11 None
+OpBranch %11
+%10 = OpLabel
+OpUnreachable
+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);
+
+  BasicBlock* loop_header = context->get_instr_block(8);
+  ASSERT_TRUE(loop_header->GetLoopMergeInst() != nullptr);
+
+  CFG* cfg = context->cfg();
+  cfg->SplitLoopHeader(loop_header);
+
+  std::vector<uint32_t> binary;
+  bool skip_nop = false;
+  context->module()->ToBinary(&binary, skip_nop);
+
+  std::string optimized_asm;
+  SpirvTools tools(SPV_ENV_UNIVERSAL_1_1);
+  EXPECT_TRUE(tools.Disassemble(binary, &optimized_asm,
+                                SpirvTools::kDefaultDisassembleOption))
+      << "Disassembling failed for shader\n"
+      << std::endl;
+
+  EXPECT_EQ(optimized_asm, expected_result);
+}
+
+TEST_F(CFGTest, ComputeStructedOrderForLoop) {
+  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
+OpBranch %12
+%12 = 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::list<BasicBlock*> order;
+  cfg->ComputeStructuredOrder(function, context->get_instr_block(9),
+                              context->get_instr_block(11), &order);
+
+  // Order should contain the loop header, the continue target, and the merge
+  // node.
+  std::list<BasicBlock*> expected_result = {context->get_instr_block(9),
+                                            context->get_instr_block(10),
+                                            context->get_instr_block(11)};
+  EXPECT_THAT(order, ContainerEq(expected_result));
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/code_sink_test.cpp b/test/opt/code_sink_test.cpp
index f1bd127..bf5029b 100644
--- a/test/opt/code_sink_test.cpp
+++ b/test/opt/code_sink_test.cpp
@@ -378,7 +378,7 @@
        %uint = OpTypeInt 32 0
      %uint_0 = OpConstant %uint 0
      %uint_4 = OpConstant %uint 4
-%mem_semantics = OpConstant %uint 0x42 ; Uniform memeory arquire
+%mem_semantics = OpConstant %uint 0x42 ; Uniform memory 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
@@ -419,7 +419,7 @@
        %uint = OpTypeInt 32 0
      %uint_0 = OpConstant %uint 0
      %uint_4 = OpConstant %uint 4
-%mem_semantics = OpConstant %uint 0x42 ; Uniform memeory arquire
+%mem_semantics = OpConstant %uint 0x42 ; Uniform memory 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
@@ -460,7 +460,7 @@
        %uint = OpTypeInt 32 0
      %uint_0 = OpConstant %uint 0
      %uint_4 = OpConstant %uint 4
-%mem_semantics = OpConstant %uint 0x42 ; Uniform memeory arquire
+%mem_semantics = OpConstant %uint 0x42 ; Uniform memory 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
diff --git a/test/opt/compact_ids_test.cpp b/test/opt/compact_ids_test.cpp
index ba31d84..7c232fe 100644
--- a/test/opt/compact_ids_test.cpp
+++ b/test/opt/compact_ids_test.cpp
@@ -310,6 +310,41 @@
   EXPECT_THAT(disassembly, ::testing::Eq(expected));
 }
 
+TEST(CompactIds, ResetIdBound) {
+  const std::string input(R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "main"
+OpExecutionMode %1 OriginUpperLeft
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%1 = OpFunction %void None %3
+%4 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+
+  spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_1);
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, input,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ASSERT_NE(context, nullptr);
+
+  CompactIdsPass compact_id_pass;
+  context->module()->SetIdBound(20000);
+  const auto status = compact_id_pass.Run(context.get());
+  EXPECT_EQ(status, Pass::Status::SuccessWithChange);
+  EXPECT_EQ(context->module()->id_bound(), 5);
+
+  // Test output just in case
+  std::vector<uint32_t> binary;
+  context->module()->ToBinary(&binary, false);
+  std::string disassembly;
+  tools.Disassemble(binary, &disassembly,
+                    SpirvTools::kDefaultDisassembleOption);
+
+  EXPECT_THAT(disassembly, ::testing::Eq(input));
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/copy_prop_array_test.cpp b/test/opt/copy_prop_array_test.cpp
index a4599f0..d6e376e 100644
--- a/test/opt/copy_prop_array_test.cpp
+++ b/test/opt/copy_prop_array_test.cpp
@@ -1839,6 +1839,110 @@
 
   SinglePassRunAndCheck<CopyPropagateArrays>(text, text, false);
 }
+
+// Since Spir-V 1.4, resources that are used by a shader must be on the
+// OpEntryPoint instruction with the inputs and outputs. This test ensures that
+// this does not stop the pass from working.
+TEST_F(CopyPropArrayPassTest, EntryPointUser) {
+  const std::string before = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main" %g_rwTexture3d
+OpExecutionMode %main LocalSize 256 1 1
+OpSource HLSL 660
+OpName %type_3d_image "type.3d.image"
+OpName %g_rwTexture3d "g_rwTexture3d"
+OpName %main "main"
+OpDecorate %g_rwTexture3d DescriptorSet 0
+OpDecorate %g_rwTexture3d Binding 0
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%uint_2 = OpConstant %uint 2
+%uint_3 = OpConstant %uint 3
+%v3uint = OpTypeVector %uint 3
+%10 = OpConstantComposite %v3uint %uint_1 %uint_2 %uint_3
+%type_3d_image = OpTypeImage %uint 3D 2 0 0 2 R32ui
+%_ptr_UniformConstant_type_3d_image = OpTypePointer UniformConstant %type_3d_image
+%void = OpTypeVoid
+%13 = OpTypeFunction %void
+%_ptr_Function_type_3d_image = OpTypePointer Function %type_3d_image
+%_ptr_Image_uint = OpTypePointer Image %uint
+%g_rwTexture3d = OpVariable %_ptr_UniformConstant_type_3d_image UniformConstant
+%main = OpFunction %void None %13
+%16 = OpLabel
+%17 = OpVariable %_ptr_Function_type_3d_image Function
+%18 = OpLoad %type_3d_image %g_rwTexture3d
+OpStore %17 %18
+; CHECK: %19 = OpImageTexelPointer %_ptr_Image_uint %g_rwTexture3d %10 %uint_0
+%19 = OpImageTexelPointer %_ptr_Image_uint %17 %10 %uint_0
+%20 = OpAtomicIAdd %uint %19 %uint_1 %uint_0 %uint_1
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
+  SinglePassRunAndMatch<CopyPropagateArrays>(before, false);
+}
+
+// As per SPIRV spec, struct cannot be indexed with non-constant indices
+// through OpAccessChain, only arrays.
+// The copy-propagate-array pass tries to remove superfluous copies when the
+// original array could be indexed instead of the copy.
+//
+// This test verifies we handle this case:
+//  struct SRC { int field1; ...; int fieldN }
+//  int tmp_arr[N] = { SRC.field1, ..., SRC.fieldN }
+//  return tmp_arr[index];
+//
+// In such case, we cannot optimize the access: this array was added to allow
+// dynamic indexing in the struct.
+TEST_F(CopyPropArrayPassTest, StructIndexCannotBecomeDynamic) {
+  const std::string text = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %1 "main"
+OpDecorate %2 DescriptorSet 0
+OpDecorate %2 Binding 0
+OpMemberDecorate %_struct_3 0 Offset 0
+OpDecorate %_struct_3 Block
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_struct_3 = OpTypeStruct %v4float
+%_ptr_Uniform__struct_3 = OpTypePointer Uniform %_struct_3
+%uint = OpTypeInt 32 0
+%void = OpTypeVoid
+%11 = OpTypeFunction %void
+%_ptr_Function_uint = OpTypePointer Function %uint
+%13 = OpTypeFunction %v4float %_ptr_Function_uint
+%uint_1 = OpConstant %uint 1
+%_arr_v4float_uint_1 = OpTypeArray %v4float %uint_1
+%_ptr_Function__arr_v4float_uint_1 = OpTypePointer Function %_arr_v4float_uint_1
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
+%2 = OpVariable %_ptr_Uniform__struct_3 Uniform
+%19 = OpUndef %v4float
+%1 = OpFunction %void None %11
+%20 = OpLabel
+OpReturn
+OpFunctionEnd
+%21 = OpFunction %v4float None %13
+%22 = OpFunctionParameter %_ptr_Function_uint
+%23 = OpLabel
+%24 = OpVariable %_ptr_Function__arr_v4float_uint_1 Function
+%25 = OpAccessChain %_ptr_Uniform_v4float %2 %int_0
+%26 = OpLoad %v4float %25
+%27 = OpCompositeConstruct %_arr_v4float_uint_1 %26
+OpStore %24 %27
+%28 = OpLoad %uint %22
+%29 = OpAccessChain %_ptr_Function_v4float %24 %28
+OpReturnValue %19
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<CopyPropagateArrays>(text, text, false);
+}
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/dead_branch_elim_test.cpp b/test/opt/dead_branch_elim_test.cpp
index 9c1ef2a..1095d3b 100644
--- a/test/opt/dead_branch_elim_test.cpp
+++ b/test/opt/dead_branch_elim_test.cpp
@@ -1613,7 +1613,7 @@
 %11 = OpLogicalOr %bool %true %false
 OpBranch %7
 %7 = OpLabel
-; This phi is in a block preceeding the merge %14!
+; This phi is in a block preceding the merge %14!
 %8 = OpPhi %bool %10 %5 %11 %6
 OpBranch %14
 %14 = OpLabel
@@ -2570,9 +2570,8 @@
 
 TEST_F(DeadBranchElimTest, SelectionMergeWithExitToLoop3) {
   // Checks that if a selection merge construct contains a conditional branch
-  // to the merge of a surrounding loop, the selection merge, and another block
-  // inside the selection merge, then we must keep the OpSelectionMerge
-  // instruction on that branch.
+  // to the selection merge, and another block inside the selection merge,
+  // then we must keep the OpSelectionMerge instruction on that branch.
   const std::string predefs = R"(
 OpCapability Shader
 %1 = OpExtInstImport "GLSL.std.450"
@@ -2586,6 +2585,7 @@
 %true = OpConstantTrue %bool
 %uint = OpTypeInt 32 0
 %undef_int = OpUndef %uint
+%undef_bool = OpUndef %bool
 )";
 
   const std::string body =
@@ -2596,7 +2596,7 @@
 ; CHECK-NEXT: OpBranch [[bb2:%\w+]]
 ; CHECK: [[bb2]] = OpLabel
 ; CHECK-NEXT: OpSelectionMerge [[sel_merge:%\w+]] None
-; CHECK-NEXT: OpSwitch {{%\w+}} [[sel_merge]] 0 [[loop_merge]] 1 [[bb3:%\w+]]
+; CHECK-NEXT: OpBranchConditional {{%\w+}} [[sel_merge]] [[bb3:%\w+]]
 ; CHECK: [[bb3]] = OpLabel
 ; CHECK-NEXT: OpBranch [[sel_merge]]
 ; CHECK: [[sel_merge]] = OpLabel
@@ -2613,7 +2613,8 @@
 OpSelectionMerge %sel_merge None
 OpBranchConditional %true %bb2 %bb4
 %bb2 = OpLabel
-OpSwitch %undef_int %sel_merge 0 %loop_merge 1 %bb3
+;OpSwitch %undef_int %sel_merge 0 %loop_merge 1 %bb3
+OpBranchConditional %undef_bool %sel_merge %bb3
 %bb3 = OpLabel
 OpBranch %sel_merge
 %bb4 = OpLabel
@@ -2632,9 +2633,8 @@
 
 TEST_F(DeadBranchElimTest, SelectionMergeWithExitToLoopContinue3) {
   // Checks that if a selection merge construct contains a conditional branch
-  // to the merge of a surrounding loop, the selection merge, and another block
-  // inside the selection merge, then we must keep the OpSelectionMerge
-  // instruction on that branch.
+  // the selection merge, and another block inside the selection merge, then we
+  // must keep the OpSelectionMerge instruction on that branch.
   const std::string predefs = R"(
 OpCapability Shader
 %1 = OpExtInstImport "GLSL.std.450"
@@ -2648,6 +2648,7 @@
 %true = OpConstantTrue %bool
 %uint = OpTypeInt 32 0
 %undef_int = OpUndef %uint
+%undef_bool = OpUndef %bool
 )";
 
   const std::string body =
@@ -2660,7 +2661,7 @@
 ; CHECK-NEXT: OpBranch [[bb2:%\w+]]
 ; CHECK: [[bb2]] = OpLabel
 ; CHECK-NEXT: OpSelectionMerge [[sel_merge:%\w+]] None
-; CHECK-NEXT: OpSwitch {{%\w+}} [[sel_merge]] 0 [[loop_continue]] 1 [[bb3:%\w+]]
+; CHECK-NEXT: OpBranchConditional {{%\w+}} [[sel_merge]] [[bb3:%\w+]]
 ; CHECK: [[bb3]] = OpLabel
 ; CHECK-NEXT: OpBranch [[sel_merge]]
 ; CHECK: [[sel_merge]] = OpLabel
@@ -2679,131 +2680,7 @@
 OpSelectionMerge %sel_merge None
 OpBranchConditional %true %bb2 %bb4
 %bb2 = OpLabel
-OpSwitch %undef_int %sel_merge 0 %cont 1 %bb3
-%bb3 = OpLabel
-OpBranch %sel_merge
-%bb4 = OpLabel
-OpBranch %sel_merge
-%sel_merge = OpLabel
-OpBranch %loop_merge
-%cont = OpLabel
-OpBranch %loop_header
-%loop_merge = OpLabel
-OpReturn
-OpFunctionEnd
-)";
-
-  SinglePassRunAndMatch<DeadBranchElimPass>(predefs + body, true);
-}
-
-TEST_F(DeadBranchElimTest, SelectionMergeWithExitToLoop4) {
-  // Same as |SelectionMergeWithExitToLoop|, except the branch in the selection
-  // construct is an |OpSwitch| instead of an |OpConditionalBranch|.  The
-  // OpSelectionMerge instruction is not needed in this case either.
-  const std::string predefs = R"(
-OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main"
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 140
-%void = OpTypeVoid
-%func_type = OpTypeFunction %void
-%bool = OpTypeBool
-%true = OpConstantTrue %bool
-%uint = OpTypeInt 32 0
-%undef_int = OpUndef %uint
-)";
-
-  const std::string body =
-      R"(
-; CHECK: OpLoopMerge [[loop_merge:%\w+]]
-; CHECK-NEXT: OpBranch [[bb1:%\w+]]
-; CHECK: [[bb1]] = OpLabel
-; CHECK-NEXT: OpBranch [[bb2:%\w+]]
-; CHECK: [[bb2]] = OpLabel
-; CHECK-NEXT: OpSelectionMerge
-; CHECK-NEXT: OpSwitch {{%\w+}} [[bb3:%\w+]] 0 [[loop_merge]] 1 [[bb3:%\w+]]
-; CHECK: [[bb3]] = OpLabel
-; CHECK-NEXT: OpBranch [[sel_merge:%\w+]]
-; CHECK: [[sel_merge]] = OpLabel
-; CHECK-NEXT: OpBranch [[loop_merge]]
-; CHECK: [[loop_merge]] = OpLabel
-; CHECK-NEXT: OpReturn
-%main = OpFunction %void None %func_type
-%entry_bb = OpLabel
-OpBranch %loop_header
-%loop_header = OpLabel
-OpLoopMerge %loop_merge %cont None
-OpBranch %bb1
-%bb1 = OpLabel
-OpSelectionMerge %sel_merge None
-OpBranchConditional %true %bb2 %bb4
-%bb2 = OpLabel
-OpSelectionMerge %bb3 None
-OpSwitch %undef_int %bb3 0 %loop_merge 1 %bb3
-%bb3 = OpLabel
-OpBranch %sel_merge
-%bb4 = OpLabel
-OpBranch %sel_merge
-%sel_merge = OpLabel
-OpBranch %loop_merge
-%cont = OpLabel
-OpBranch %loop_header
-%loop_merge = OpLabel
-OpReturn
-OpFunctionEnd
-)";
-
-  SinglePassRunAndMatch<DeadBranchElimPass>(predefs + body, true);
-}
-
-TEST_F(DeadBranchElimTest, SelectionMergeWithExitToLoopContinue4) {
-  // Same as |SelectionMergeWithExitToLoopContinue|, except the branch in the
-  // selection construct is an |OpSwitch| instead of an |OpConditionalBranch|.
-  // The OpSelectionMerge instruction is not needed in this case either.
-  const std::string predefs = R"(
-OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main"
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 140
-%void = OpTypeVoid
-%func_type = OpTypeFunction %void
-%bool = OpTypeBool
-%true = OpConstantTrue %bool
-%uint = OpTypeInt 32 0
-%undef_int = OpUndef %uint
-)";
-
-  const std::string body =
-      R"(
-; CHECK: OpLoopMerge [[loop_merge:%\w+]] [[loop_cont:%\w+]]
-; CHECK-NEXT: OpBranch [[bb1:%\w+]]
-; CHECK: [[bb1]] = OpLabel
-; CHECK-NEXT: OpBranch [[bb2:%\w+]]
-; CHECK: [[bb2]] = OpLabel
-; CHECK-NEXT: OpSelectionMerge
-; CHECK-NEXT: OpSwitch {{%\w+}} [[bb3:%\w+]] 0 [[loop_cont]] 1 [[bb3:%\w+]]
-; CHECK: [[bb3]] = OpLabel
-; CHECK-NEXT: OpBranch [[sel_merge:%\w+]]
-; CHECK: [[sel_merge]] = OpLabel
-; CHECK-NEXT: OpBranch [[loop_merge]]
-; CHECK: [[loop_merge]] = OpLabel
-; CHECK-NEXT: OpReturn
-%main = OpFunction %void None %func_type
-%entry_bb = OpLabel
-OpBranch %loop_header
-%loop_header = OpLabel
-OpLoopMerge %loop_merge %cont None
-OpBranch %bb1
-%bb1 = OpLabel
-OpSelectionMerge %sel_merge None
-OpBranchConditional %true %bb2 %bb4
-%bb2 = OpLabel
-OpSelectionMerge %bb3 None
-OpSwitch %undef_int %bb3 0 %cont 1 %bb3
+OpBranchConditional %undef_bool %sel_merge %bb3
 %bb3 = OpLabel
 OpBranch %sel_merge
 %bb4 = OpLabel
@@ -3036,9 +2913,11 @@
 ; 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: OpLoopMerge [[inner_merge:%\w+]] [[inner_cont:%\w+]] None
 ; CHECK: [[inner_cont]] = OpLabel
 ; CHECK-NEXT: OpBranch [[inner]]
+; CHECK: [[inner_merge]] = OpLabel
+; CHECK-NEXT: OpUnreachable
 ; CHECK: [[outer_cont]] = OpLabel
 ; CHECK-NEXT: OpBranch [[outer]]
 ; CHECK: [[outer_merge]] = OpLabel
@@ -3058,7 +2937,7 @@
 OpLoopMerge %outer_merge %outer_continue None
 OpBranch %inner_loop
 %inner_loop = OpLabel
-OpLoopMerge %outer_continue %inner_continue None
+OpLoopMerge %inner_merge %inner_continue None
 OpBranch %inner_body
 %inner_body = OpLabel
 OpSelectionMerge %inner_continue None
@@ -3066,7 +2945,9 @@
 %ret = OpLabel
 OpReturn
 %inner_continue = OpLabel
-OpBranchConditional %true %outer_continue %inner_loop
+OpBranchConditional %true %inner_merge %inner_loop
+%inner_merge = OpLabel
+OpBranch %outer_continue
 %outer_continue = OpLabel
 OpBranchConditional %true %outer_merge %outer_loop
 %outer_merge = OpLabel
diff --git a/test/opt/decoration_manager_test.cpp b/test/opt/decoration_manager_test.cpp
index fcfbff0..c9fabe7 100644
--- a/test/opt/decoration_manager_test.cpp
+++ b/test/opt/decoration_manager_test.cpp
@@ -118,7 +118,7 @@
 TEST_F(DecorationManagerTest,
        ComparingDecorationsWithDiffOpcodesDecorateDecorateId) {
   IRContext ir_context(SPV_ENV_UNIVERSAL_1_2, GetConsumer());
-  // This parameter can be interprated both as { SpvDecorationConstant }
+  // This parameter can be interpreted both as { SpvDecorationConstant }
   // and also as a list of IDs:  { 22 }
   const std::vector<uint32_t> param{SpvDecorationConstant};
   // OpDecorate %1 Constant
@@ -137,7 +137,7 @@
 TEST_F(DecorationManagerTest,
        ComparingDecorationsWithDiffOpcodesDecorateDecorateString) {
   IRContext ir_context(SPV_ENV_UNIVERSAL_1_2, GetConsumer());
-  // This parameter can be interprated both as { SpvDecorationConstant }
+  // This parameter can be interpreted both as { SpvDecorationConstant }
   // and also as a null-terminated string with a single character with value 22.
   const std::vector<uint32_t> param{SpvDecorationConstant};
   // OpDecorate %1 Constant
diff --git a/test/opt/def_use_test.cpp b/test/opt/def_use_test.cpp
index cfdad74..0210095 100644
--- a/test/opt/def_use_test.cpp
+++ b/test/opt/def_use_test.cpp
@@ -1656,7 +1656,7 @@
           "OpGroupDecorate %1 %2 %3",
         },
       },
-      // memeber decorate
+      // member decorate
       {
         // code
         "OpMemberDecorate %1 0 RelaxedPrecision "
@@ -1707,9 +1707,10 @@
   def->SetInOperands({{SPV_OPERAND_TYPE_ID, {25}}});
   context->UpdateDefUse(def);
 
-  auto users = def_use_mgr->id_to_users();
-  UserEntry entry = {def, use};
-  EXPECT_THAT(users, Contains(entry));
+  auto scanUser = [&](Instruction* user) { return user != use; };
+  bool userFound = !def_use_mgr->WhileEachUser(def, scanUser);
+
+  EXPECT_TRUE(userFound);
 }
 // clang-format on
 
diff --git a/test/opt/desc_sroa_test.cpp b/test/opt/desc_sroa_test.cpp
index dcb625d..91c950e 100644
--- a/test/opt/desc_sroa_test.cpp
+++ b/test/opt/desc_sroa_test.cpp
@@ -833,6 +833,93 @@
   SinglePassRunAndMatch<DescriptorScalarReplacement>(shader, true);
 }
 
+TEST_F(DescriptorScalarReplacementTest, DecorateStringForReflect) {
+  // Check that an OpDecorateString instruction is correctly cloned to new
+  // variable.
+
+  const std::string shader = R"(
+; CHECK: OpName %g_testTextures_0_ "g_testTextures[0]"
+; CHECK: OpDecorate %g_testTextures_0_ DescriptorSet 0
+; CHECK: OpDecorate %g_testTextures_0_ Binding 0
+; CHECK: OpDecorateString %g_testTextures_0_ UserTypeGOOGLE "texture2d"
+               OpCapability Shader
+               OpExtension "SPV_GOOGLE_hlsl_functionality1"
+               OpExtension "SPV_GOOGLE_user_type"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %gl_FragCoord %out_var_SV_Target
+               OpExecutionMode %main OriginUpperLeft
+               OpSource HLSL 600
+               OpName %type_2d_image "type.2d.image"
+               OpName %g_testTextures "g_testTextures"
+               OpName %out_var_SV_Target "out.var.SV_Target"
+               OpName %main "main"
+               OpName %param_var_vPixelPos "param.var.vPixelPos"
+               OpName %src_main "src.main"
+               OpName %vPixelPos "vPixelPos"
+               OpName %bb_entry "bb.entry"
+               OpDecorate %gl_FragCoord BuiltIn FragCoord
+               OpDecorateString %gl_FragCoord UserSemantic "SV_Position"
+               OpDecorateString %out_var_SV_Target UserSemantic "SV_Target"
+               OpDecorate %out_var_SV_Target Location 0
+               OpDecorate %g_testTextures DescriptorSet 0
+               OpDecorate %g_testTextures Binding 0
+               OpDecorateString %g_testTextures UserTypeGOOGLE "texture2d"
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+     %uint_2 = OpConstant %uint 2
+      %float = OpTypeFloat 32
+%type_2d_image = OpTypeImage %float 2D 2 0 0 1 Unknown
+%_arr_type_2d_image_uint_2 = OpTypeArray %type_2d_image %uint_2
+%_ptr_UniformConstant__arr_type_2d_image_uint_2 = OpTypePointer UniformConstant %_arr_type_2d_image_uint_2
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %18 = OpTypeFunction %void
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+         %25 = OpTypeFunction %v4float %_ptr_Function_v4float
+    %v2float = OpTypeVector %float 2
+     %v3uint = OpTypeVector %uint 3
+      %v3int = OpTypeVector %int 3
+      %v2int = OpTypeVector %int 2
+%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
+%g_testTextures = OpVariable %_ptr_UniformConstant__arr_type_2d_image_uint_2 UniformConstant
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
+       %main = OpFunction %void None %18
+         %19 = OpLabel
+%param_var_vPixelPos = OpVariable %_ptr_Function_v4float Function
+         %22 = OpLoad %v4float %gl_FragCoord
+               OpStore %param_var_vPixelPos %22
+         %23 = OpFunctionCall %v4float %src_main %param_var_vPixelPos
+               OpStore %out_var_SV_Target %23
+               OpReturn
+               OpFunctionEnd
+   %src_main = OpFunction %v4float None %25
+  %vPixelPos = OpFunctionParameter %_ptr_Function_v4float
+   %bb_entry = OpLabel
+         %28 = OpLoad %v4float %vPixelPos
+         %30 = OpVectorShuffle %v2float %28 %28 0 1
+         %31 = OpCompositeExtract %float %30 0
+         %32 = OpCompositeExtract %float %30 1
+         %33 = OpConvertFToU %uint %31
+         %34 = OpConvertFToU %uint %32
+         %36 = OpCompositeConstruct %v3uint %33 %34 %uint_0
+         %38 = OpBitcast %v3int %36
+         %40 = OpVectorShuffle %v2int %38 %38 0 1
+         %41 = OpCompositeExtract %int %38 2
+         %43 = OpAccessChain %_ptr_UniformConstant_type_2d_image %g_testTextures %int_0
+         %44 = OpLoad %type_2d_image %43
+         %45 = OpImageFetch %v4float %44 %40 Lod %41
+               OpReturnValue %45
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<DescriptorScalarReplacement>(shader, true);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/dominator_tree/generated.cpp b/test/opt/dominator_tree/generated.cpp
index 534f770..4fccef0 100644
--- a/test/opt/dominator_tree/generated.cpp
+++ b/test/opt/dominator_tree/generated.cpp
@@ -57,7 +57,7 @@
   }
 }
 
-// Check that x does not dominates y and vise versa
+// Check that x does not dominates y and vice versa
 void check_no_dominance(const DominatorAnalysisBase& dom_tree,
                         const Function* fn, uint32_t x, uint32_t y) {
   SCOPED_TRACE("Check no domination for Basic Block " + std::to_string(x) +
diff --git a/test/opt/eliminate_dead_const_test.cpp b/test/opt/eliminate_dead_const_test.cpp
index 59f06f9..87aab54 100644
--- a/test/opt/eliminate_dead_const_test.cpp
+++ b/test/opt/eliminate_dead_const_test.cpp
@@ -547,7 +547,7 @@
         },
 
         // Uint vector type spec constants. One vector has all component dead,
-        // another vector has one dead unsigend integer and one used unsigned
+        // another vector has one dead unsigned integer and one used unsigned
         // integer.
         {
             /* .used_consts = */
diff --git a/test/opt/eliminate_dead_input_components_test.cpp b/test/opt/eliminate_dead_input_components_test.cpp
new file mode 100644
index 0000000..822914a
--- /dev/null
+++ b/test/opt/eliminate_dead_input_components_test.cpp
@@ -0,0 +1,468 @@
+// Copyright (c) 2022 The Khronos Group Inc.
+// Copyright (c) 2022 LunarG 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.
+
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ElimDeadInputComponentsTest = PassTest<::testing::Test>;
+
+TEST_F(ElimDeadInputComponentsTest, ElimOneConstantIndex) {
+  // Should reduce to uv[2]
+  //
+  // #version 450
+  //
+  // layout(location = 0) in vec4 uv[8];
+  //
+  // out gl_PerVertex {
+  //     vec4 gl_Position;
+  // };
+  //
+  // void main()
+  // {
+  //     gl_Position = uv[1];
+  // }
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %_ %uv
+               OpSource GLSL 450
+               OpName %main "main"
+               OpName %gl_PerVertex "gl_PerVertex"
+               OpMemberName %gl_PerVertex 0 "gl_Position"
+               OpName %_ ""
+               OpName %uv "uv"
+               OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
+               OpDecorate %gl_PerVertex Block
+               OpDecorate %uv Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%gl_PerVertex = OpTypeStruct %v4float
+%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
+          %_ = OpVariable %_ptr_Output_gl_PerVertex Output
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %uint = OpTypeInt 32 0
+     %uint_8 = OpConstant %uint 8
+%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8
+%_ptr_Input__arr_v4float_uint_8 = OpTypePointer Input %_arr_v4float_uint_8
+           %uv = OpVariable %_ptr_Input__arr_v4float_uint_8 Input
+      %int_1 = OpConstant %int 1
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+;CHECK-NOT: %uv = OpVariable %_ptr_Input__arr_v4float_uint_8 Input
+;CHECK:     %uv = OpVariable %_ptr_Input__arr_v4float_uint_2 Input
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %20 = OpAccessChain %_ptr_Input_v4float %uv %int_1
+         %21 = OpLoad %v4float %20
+         %23 = OpAccessChain %_ptr_Output_v4float %_ %int_0
+               OpStore %23 %21
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_3);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<EliminateDeadInputComponentsPass>(text, true);
+}
+
+TEST_F(ElimDeadInputComponentsTest, ElimOneConstantIndexInBounds) {
+  // Same as ElimOneConstantIndex but with OpInBoundsAccessChain
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %_ %uv
+               OpSource GLSL 450
+               OpName %main "main"
+               OpName %gl_PerVertex "gl_PerVertex"
+               OpMemberName %gl_PerVertex 0 "gl_Position"
+               OpName %_ ""
+               OpName %uv "uv"
+               OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
+               OpDecorate %gl_PerVertex Block
+               OpDecorate %uv Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%gl_PerVertex = OpTypeStruct %v4float
+%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
+          %_ = OpVariable %_ptr_Output_gl_PerVertex Output
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %uint = OpTypeInt 32 0
+     %uint_8 = OpConstant %uint 8
+%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8
+%_ptr_Input__arr_v4float_uint_8 = OpTypePointer Input %_arr_v4float_uint_8
+           %uv = OpVariable %_ptr_Input__arr_v4float_uint_8 Input
+      %int_1 = OpConstant %int 1
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+;CHECK-NOT: %uv = OpVariable %_ptr_Input__arr_v4float_uint_8 Input
+;CHECK:     %uv = OpVariable %_ptr_Input__arr_v4float_uint_2 Input
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %20 = OpInBoundsAccessChain %_ptr_Input_v4float %uv %int_1
+         %21 = OpLoad %v4float %20
+         %23 = OpAccessChain %_ptr_Output_v4float %_ %int_0
+               OpStore %23 %21
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_3);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<EliminateDeadInputComponentsPass>(text, true);
+}
+
+TEST_F(ElimDeadInputComponentsTest, ElimTwoConstantIndices) {
+  // Should reduce to uv[4]
+  //
+  // #version 450
+  //
+  // layout(location = 0) in vec4 uv[8];
+  //
+  // out gl_PerVertex {
+  //     vec4 gl_Position;
+  // };
+  //
+  // void main()
+  // {
+  //     gl_Position = uv[1] + uv[3];
+  // }
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %_ %uv
+               OpSource GLSL 450
+               OpName %main "main"
+               OpName %gl_PerVertex "gl_PerVertex"
+               OpMemberName %gl_PerVertex 0 "gl_Position"
+               OpName %_ ""
+               OpName %uv "uv"
+               OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
+               OpDecorate %gl_PerVertex Block
+               OpDecorate %uv Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%gl_PerVertex = OpTypeStruct %v4float
+%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
+          %_ = OpVariable %_ptr_Output_gl_PerVertex Output
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %uint = OpTypeInt 32 0
+     %uint_8 = OpConstant %uint 8
+%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8
+%_ptr_Input__arr_v4float_uint_8 = OpTypePointer Input %_arr_v4float_uint_8
+         %uv = OpVariable %_ptr_Input__arr_v4float_uint_8 Input
+      %int_1 = OpConstant %int 1
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+      %int_3 = OpConstant %int 3
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+;CHECK-NOT: %uv = OpVariable %_ptr_Input__arr_v4float_uint_8 Input
+;CHECK:     %uv = OpVariable %_ptr_Input__arr_v4float_uint_4 Input
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %20 = OpAccessChain %_ptr_Input_v4float %uv %int_1
+         %21 = OpLoad %v4float %20
+         %23 = OpAccessChain %_ptr_Input_v4float %uv %int_3
+         %24 = OpLoad %v4float %23
+         %25 = OpFAdd %v4float %21 %24
+         %27 = OpAccessChain %_ptr_Output_v4float %_ %int_0
+               OpStore %27 %25
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_3);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<EliminateDeadInputComponentsPass>(text, true);
+}
+
+TEST_F(ElimDeadInputComponentsTest, NoElimMaxConstantIndex) {
+  // Should not reduce uv[8] because of max index of 7
+  //
+  // #version 450
+  //
+  // layout(location = 0) in vec4 uv[8];
+  //
+  // out gl_PerVertex {
+  //     vec4 gl_Position;
+  // };
+  //
+  // void main()
+  // {
+  //     gl_Position = uv[1] + uv[7];
+  // }
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %_ %uv
+               OpSource GLSL 450
+               OpName %main "main"
+               OpName %gl_PerVertex "gl_PerVertex"
+               OpMemberName %gl_PerVertex 0 "gl_Position"
+               OpName %_ ""
+               OpName %uv "uv"
+               OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
+               OpDecorate %gl_PerVertex Block
+               OpDecorate %uv Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%gl_PerVertex = OpTypeStruct %v4float
+%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
+          %_ = OpVariable %_ptr_Output_gl_PerVertex Output
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %uint = OpTypeInt 32 0
+     %uint_8 = OpConstant %uint 8
+%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8
+%_ptr_Input__arr_v4float_uint_8 = OpTypePointer Input %_arr_v4float_uint_8
+         %uv = OpVariable %_ptr_Input__arr_v4float_uint_8 Input
+      %int_1 = OpConstant %int 1
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+      %int_7 = OpConstant %int 7
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+;CHECK:     %uv = OpVariable %_ptr_Input__arr_v4float_uint_8 Input
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %20 = OpAccessChain %_ptr_Input_v4float %uv %int_1
+         %21 = OpLoad %v4float %20
+         %23 = OpAccessChain %_ptr_Input_v4float %uv %int_7
+         %24 = OpLoad %v4float %23
+         %25 = OpFAdd %v4float %21 %24
+         %27 = OpAccessChain %_ptr_Output_v4float %_ %int_0
+               OpStore %27 %25
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_3);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<EliminateDeadInputComponentsPass>(text, true);
+}
+
+TEST_F(ElimDeadInputComponentsTest, NoElimNonConstantIndex) {
+  // Should not reduce uv[8] because of non-constant index of ui
+  //
+  // #version 450
+  //
+  // layout(location = 0) in vec4 uv[8];
+  //
+  // out gl_PerVertex {
+  //     vec4 gl_Position;
+  // };
+  //
+  // uniform ubname {
+  //     int ui;
+  // } ubinst;
+  //
+  // void main()
+  // {
+  //     gl_Position = uv[1] + uv[ubinst.ui];
+  // }
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %_ %uv %ubinst
+               OpSource GLSL 450
+               OpName %main "main"
+               OpName %gl_PerVertex "gl_PerVertex"
+               OpMemberName %gl_PerVertex 0 "gl_Position"
+               OpName %_ ""
+               OpName %uv "uv"
+               OpName %ubname "ubname"
+               OpMemberName %ubname 0 "ui"
+               OpName %ubinst "ubinst"
+               OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
+               OpDecorate %gl_PerVertex Block
+               OpDecorate %uv Location 0
+               OpMemberDecorate %ubname 0 Offset 0
+               OpDecorate %ubname Block
+               OpDecorate %ubinst DescriptorSet 0
+               OpDecorate %ubinst Binding 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%gl_PerVertex = OpTypeStruct %v4float
+%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
+          %_ = OpVariable %_ptr_Output_gl_PerVertex Output
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %uint = OpTypeInt 32 0
+     %uint_8 = OpConstant %uint 8
+%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8
+%_ptr_Input__arr_v4float_uint_8 = OpTypePointer Input %_arr_v4float_uint_8
+         %uv = OpVariable %_ptr_Input__arr_v4float_uint_8 Input
+      %int_1 = OpConstant %int 1
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+     %ubname = OpTypeStruct %int
+%_ptr_Uniform_ubname = OpTypePointer Uniform %ubname
+     %ubinst = OpVariable %_ptr_Uniform_ubname Uniform
+%_ptr_Uniform_int = OpTypePointer Uniform %int
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+;CHECK:  %uv = OpVariable %_ptr_Input__arr_v4float_uint_8 Input
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %20 = OpAccessChain %_ptr_Input_v4float %uv %int_1
+         %21 = OpLoad %v4float %20
+         %26 = OpAccessChain %_ptr_Uniform_int %ubinst %int_0
+         %27 = OpLoad %int %26
+         %28 = OpAccessChain %_ptr_Input_v4float %uv %27
+         %29 = OpLoad %v4float %28
+         %30 = OpFAdd %v4float %21 %29
+         %32 = OpAccessChain %_ptr_Output_v4float %_ %int_0
+               OpStore %32 %30
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_3);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<EliminateDeadInputComponentsPass>(text, true);
+}
+
+TEST_F(ElimDeadInputComponentsTest, NoElimNonIndexedAccessChain) {
+  // Should not change due to non-indexed access chain
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %_ %uv
+               OpSource GLSL 450
+               OpName %main "main"
+               OpName %gl_PerVertex "gl_PerVertex"
+               OpMemberName %gl_PerVertex 0 "gl_Position"
+               OpName %_ ""
+               OpName %uv "uv"
+               OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
+               OpDecorate %gl_PerVertex Block
+               OpDecorate %uv Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%gl_PerVertex = OpTypeStruct %v4float
+%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
+          %_ = OpVariable %_ptr_Output_gl_PerVertex Output
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %uint = OpTypeInt 32 0
+     %uint_8 = OpConstant %uint 8
+%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8
+%_ptr_Input__arr_v4float_uint_8 = OpTypePointer Input %_arr_v4float_uint_8
+           %uv = OpVariable %_ptr_Input__arr_v4float_uint_8 Input
+      %int_1 = OpConstant %int 1
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+;CHECK:  %uv = OpVariable %_ptr_Input__arr_v4float_uint_8 Input
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %20 = OpAccessChain %_ptr_Input__arr_v4float_uint_8 %uv
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_3);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<EliminateDeadInputComponentsPass>(text, true);
+}
+
+TEST_F(ElimDeadInputComponentsTest, ElimStructMember) {
+  // Should eliminate uv
+  //
+  // #version 450
+  //
+  // in Vertex {
+  //   vec4 Cd;
+  //   vec2 uv;
+  // } iVert;
+  //
+  // out vec4 fragColor;
+  //
+  // void main()
+  // {
+  //   vec4 color = vec4(iVert.Cd);
+  //   fragColor = color;
+  // }
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %iVert %fragColor
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpName %main "main"
+               OpName %Vertex "Vertex"
+               OpMemberName %Vertex 0 "Cd"
+               OpMemberName %Vertex 1 "uv"
+               OpName %iVert "iVert"
+               OpName %fragColor "fragColor"
+               OpDecorate %Vertex Block
+               OpDecorate %iVert Location 0
+               OpDecorate %fragColor Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+    %v2float = OpTypeVector %float 2
+     %Vertex = OpTypeStruct %v4float %v2float
+; CHECK: %Vertex = OpTypeStruct %v4float %v2float
+; CHECK: [[sty:%\w+]] = OpTypeStruct %v4float
+%_ptr_Input_Vertex = OpTypePointer Input %Vertex
+; CHECK: [[pty:%\w+]] = OpTypePointer Input [[sty]]
+      %iVert = OpVariable %_ptr_Input_Vertex Input
+; CHECK: %iVert = OpVariable [[pty]] Input
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+  %fragColor = OpVariable %_ptr_Output_v4float Output
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %17 = OpAccessChain %_ptr_Input_v4float %iVert %int_0
+         %18 = OpLoad %v4float %17
+               OpStore %fragColor %18
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_3);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<EliminateDeadInputComponentsPass>(text, true);
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/test/opt/fix_func_call_arguments_test.cpp b/test/opt/fix_func_call_arguments_test.cpp
new file mode 100644
index 0000000..ecd13a8
--- /dev/null
+++ b/test/opt/fix_func_call_arguments_test.cpp
@@ -0,0 +1,152 @@
+// Copyright (c) 2022 Advanced Micro Devices, 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.
+
+#include "gmock/gmock.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using FixFuncCallArgumentsTest = PassTest<::testing::Test>;
+TEST_F(FixFuncCallArgumentsTest, Simple) {
+  const std::string text = R"(
+;
+; CHECK: [[v0:%\w+]] = OpVariable %_ptr_Function_float Function
+; CHECK: [[v1:%\w+]] = OpVariable %_ptr_Function_float Function
+; CHECK: [[v2:%\w+]] = OpVariable %_ptr_Function_T Function
+; CHECK: [[ac0:%\w+]] = OpAccessChain %_ptr_Function_float %t %int_0
+; CHECK: [[ac1:%\w+]] = OpAccessChain %_ptr_Uniform_float %r1 %int_0 %uint_0
+; CHECK: [[ld0:%\w+]] = OpLoad %float [[ac0]]
+; CHECK:                OpStore [[v1]] [[ld0]]
+; CHECK: [[ld1:%\w+]] = OpLoad %float [[ac1]]
+; CHECK:                OpStore [[v0]] [[ld1]]
+; CHECK: [[func:%\w+]] = OpFunctionCall %void %fn [[v1]] [[v0]]
+; CHECK: [[ld2:%\w+]] = OpLoad %float [[v0]]
+; CHECK: OpStore [[ac1]] [[ld2]]
+; CHECK: [[ld3:%\w+]] = OpLoad %float [[v1]]
+; CHECK: OpStore [[ac0]] [[ld3]]
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpSource HLSL 630
+OpName %type_RWStructuredBuffer_float "type.RWStructuredBuffer.float"
+OpName %r1 "r1"
+OpName %type_ACSBuffer_counter "type.ACSBuffer.counter"
+OpMemberName %type_ACSBuffer_counter 0 "counter"
+OpName %counter_var_r1 "counter.var.r1"
+OpName %main "main"
+OpName %bb_entry "bb.entry"
+OpName %T "T"
+OpMemberName %T 0 "t0"
+OpName %t "t"
+OpName %fn "fn"
+OpName %p0 "p0"
+OpName %p2 "p2"
+OpName %bb_entry_0 "bb.entry"
+OpDecorate %main LinkageAttributes "main" Export
+OpDecorate %r1 DescriptorSet 0
+OpDecorate %r1 Binding 0
+OpDecorate %counter_var_r1 DescriptorSet 0
+OpDecorate %counter_var_r1 Binding 1
+OpDecorate %_runtimearr_float ArrayStride 4
+OpMemberDecorate %type_RWStructuredBuffer_float 0 Offset 0
+OpDecorate %type_RWStructuredBuffer_float 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
+%int_1 = OpConstant %int 1
+%float = OpTypeFloat 32
+%_runtimearr_float = OpTypeRuntimeArray %float
+%type_RWStructuredBuffer_float = OpTypeStruct %_runtimearr_float
+%_ptr_Uniform_type_RWStructuredBuffer_float = OpTypePointer Uniform %type_RWStructuredBuffer_float
+%type_ACSBuffer_counter = OpTypeStruct %int
+%_ptr_Uniform_type_ACSBuffer_counter = OpTypePointer Uniform %type_ACSBuffer_counter
+%15 = OpTypeFunction %int
+%T = OpTypeStruct %float
+%_ptr_Function_T = OpTypePointer Function %T
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%void = OpTypeVoid
+%27 = OpTypeFunction %void %_ptr_Function_float %_ptr_Function_float
+%r1 = OpVariable %_ptr_Uniform_type_RWStructuredBuffer_float Uniform
+%counter_var_r1 = OpVariable %_ptr_Uniform_type_ACSBuffer_counter Uniform
+%main = OpFunction %int None %15
+%bb_entry = OpLabel
+%t = OpVariable %_ptr_Function_T Function
+%21 = OpAccessChain %_ptr_Function_float %t %int_0
+%23 = OpAccessChain %_ptr_Uniform_float %r1 %int_0 %uint_0
+%25 = OpFunctionCall %void %fn %21 %23
+OpReturnValue %int_1
+OpFunctionEnd
+%fn = OpFunction %void DontInline %27
+%p0 = OpFunctionParameter %_ptr_Function_float
+%p2 = OpFunctionParameter %_ptr_Function_float
+%bb_entry_0 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FixFuncCallArgumentsPass>(text, true);
+}
+
+TEST_F(FixFuncCallArgumentsTest, NotAccessChainInput) {
+  const std::string text = R"(
+;
+; CHECK: [[o:%\w+]] = OpCopyObject %_ptr_Function_float %t
+; CHECK: [[func:%\w+]] = OpFunctionCall %void %fn [[o]]
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpSource HLSL 630
+OpName %main "main"
+OpName %bb_entry "bb.entry"
+OpName %t "t"
+OpName %fn "fn"
+OpName %p0 "p0"
+OpName %bb_entry_0 "bb.entry"
+OpDecorate %main LinkageAttributes "main" Export
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%4 = OpTypeFunction %int
+%float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%void = OpTypeVoid
+%12 = OpTypeFunction %void %_ptr_Function_float
+%main = OpFunction %int None %4
+%bb_entry = OpLabel
+%t = OpVariable %_ptr_Function_float Function
+%t1 = OpCopyObject %_ptr_Function_float %t
+%10 = OpFunctionCall %void %fn %t1
+OpReturnValue %int_1
+OpFunctionEnd
+%fn = OpFunction %void DontInline %12
+%p0 = OpFunctionParameter %_ptr_Function_float
+%bb_entry_0 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<FixFuncCallArgumentsPass>(text, false);
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
\ No newline at end of file
diff --git a/test/opt/flatten_decoration_test.cpp b/test/opt/flatten_decoration_test.cpp
index d8d8867..63207fd 100644
--- a/test/opt/flatten_decoration_test.cpp
+++ b/test/opt/flatten_decoration_test.cpp
@@ -43,7 +43,7 @@
 )";
 }
 
-// Retuns types
+// Returns types
 std::string TypesAndFunctionsAssembly() {
   return
       R"(%void = OpTypeVoid
diff --git a/test/opt/fold_spec_const_op_composite_test.cpp b/test/opt/fold_spec_const_op_composite_test.cpp
index 7eddf7e..c98a44c 100644
--- a/test/opt/fold_spec_const_op_composite_test.cpp
+++ b/test/opt/fold_spec_const_op_composite_test.cpp
@@ -105,6 +105,209 @@
       builder.GetCode(), builder.GetCode(), /* skip_nop = */ true);
 }
 
+// Test where OpSpecConstantOp depends on another OpSpecConstantOp with
+// CompositeExtract
+TEST_F(FoldSpecConstantOpAndCompositePassBasicTest, StackedCompositeExtract) {
+  AssemblyBuilder builder;
+  builder.AppendTypesConstantsGlobals({
+      // clang-format off
+    "%uint = OpTypeInt 32 0",
+    "%v3uint = OpTypeVector %uint 3",
+    "%uint_2 = OpConstant %uint 2",
+    "%uint_3 = OpConstant %uint 3",
+    // Folding target:
+    "%composite_0 = OpSpecConstantComposite %v3uint %uint_2 %uint_3 %uint_2",
+    "%op_0 = OpSpecConstantOp %uint CompositeExtract %composite_0 0",
+    "%op_1 = OpSpecConstantOp %uint CompositeExtract %composite_0 1",
+    "%op_2 = OpSpecConstantOp %uint IMul %op_0 %op_1",
+    "%composite_1 = OpSpecConstantComposite %v3uint %op_0 %op_1 %op_2",
+    "%op_3 = OpSpecConstantOp %uint CompositeExtract %composite_1 0",
+    "%op_4 = OpSpecConstantOp %uint IMul %op_2 %op_3",
+      // clang-format on
+  });
+
+  std::vector<const char*> expected = {
+      // clang-format off
+        "OpCapability Shader",
+        "OpCapability Float64",
+    "%1 = OpExtInstImport \"GLSL.std.450\"",
+        "OpMemoryModel Logical GLSL450",
+        "OpEntryPoint Vertex %main \"main\"",
+        "OpName %void \"void\"",
+        "OpName %main_func_type \"main_func_type\"",
+        "OpName %main \"main\"",
+        "OpName %main_func_entry_block \"main_func_entry_block\"",
+        "OpName %uint \"uint\"",
+        "OpName %v3uint \"v3uint\"",
+        "OpName %uint_2 \"uint_2\"",
+        "OpName %uint_3 \"uint_3\"",
+        "OpName %composite_0 \"composite_0\"",
+        "OpName %op_0 \"op_0\"",
+        "OpName %op_1 \"op_1\"",
+        "OpName %op_2 \"op_2\"",
+        "OpName %composite_1 \"composite_1\"",
+        "OpName %op_3 \"op_3\"",
+        "OpName %op_4 \"op_4\"",
+    "%void = OpTypeVoid",
+"%main_func_type = OpTypeFunction %void",
+    "%uint = OpTypeInt 32 0",
+  "%v3uint = OpTypeVector %uint 3",
+  "%uint_2 = OpConstant %uint 2",
+  "%uint_3 = OpConstant %uint 3",
+"%composite_0 = OpConstantComposite %v3uint %uint_2 %uint_3 %uint_2",
+    "%op_0 = OpConstant %uint 2",
+    "%op_1 = OpConstant %uint 3",
+    "%op_2 = OpConstant %uint 6",
+"%composite_1 = OpConstantComposite %v3uint %op_0 %op_1 %op_2",
+"%op_3 = OpConstant %uint 2",
+ "%op_4 = OpConstant %uint 12",
+    "%main = OpFunction %void None %main_func_type",
+"%main_func_entry_block = OpLabel",
+            "OpReturn",
+            "OpFunctionEnd",
+      // clang-format on
+  };
+  SinglePassRunAndCheck<FoldSpecConstantOpAndCompositePass>(
+      builder.GetCode(), JoinAllInsts(expected), /* skip_nop = */ true);
+}
+
+// Test where OpSpecConstantOp depends on another OpSpecConstantOp with
+// VectorShuffle
+TEST_F(FoldSpecConstantOpAndCompositePassBasicTest, StackedVectorShuffle) {
+  AssemblyBuilder builder;
+  builder.AppendTypesConstantsGlobals({
+      // clang-format off
+    "%uint = OpTypeInt 32 0",
+    "%v3uint = OpTypeVector %uint 3",
+    "%uint_1 = OpConstant %uint 1",
+    "%uint_2 = OpConstant %uint 2",
+    "%uint_3 = OpConstant %uint 3",
+    "%uint_4 = OpConstant %uint 4",
+    "%uint_5 = OpConstant %uint 5",
+    "%uint_6 = OpConstant %uint 6",
+    // Folding target:
+    "%composite_0 = OpSpecConstantComposite %v3uint %uint_1 %uint_2 %uint_3",
+    "%composite_1 = OpSpecConstantComposite %v3uint %uint_4 %uint_5 %uint_6",
+    "%vecshuffle = OpSpecConstantOp %v3uint VectorShuffle %composite_0 %composite_1 0 5 3",
+    "%op = OpSpecConstantOp %uint CompositeExtract %vecshuffle 1",
+      // clang-format on
+  });
+
+  std::vector<const char*> expected = {
+      // clang-format off
+        "OpCapability Shader",
+        "OpCapability Float64",
+        "%1 = OpExtInstImport \"GLSL.std.450\"",
+        "OpMemoryModel Logical GLSL450",
+        "OpEntryPoint Vertex %main \"main\"",
+        "OpName %void \"void\"",
+        "OpName %main_func_type \"main_func_type\"",
+        "OpName %main \"main\"",
+        "OpName %main_func_entry_block \"main_func_entry_block\"",
+        "OpName %uint \"uint\"",
+        "OpName %v3uint \"v3uint\"",
+        "OpName %uint_1 \"uint_1\"",
+        "OpName %uint_2 \"uint_2\"",
+        "OpName %uint_3 \"uint_3\"",
+        "OpName %uint_4 \"uint_4\"",
+        "OpName %uint_5 \"uint_5\"",
+        "OpName %uint_6 \"uint_6\"",
+        "OpName %composite_0 \"composite_0\"",
+        "OpName %composite_1 \"composite_1\"",
+        "OpName %vecshuffle \"vecshuffle\"",
+        "OpName %op \"op\"",
+    "%void = OpTypeVoid",
+"%main_func_type = OpTypeFunction %void",
+    "%uint = OpTypeInt 32 0",
+  "%v3uint = OpTypeVector %uint 3",
+  "%uint_1 = OpConstant %uint 1",
+  "%uint_2 = OpConstant %uint 2",
+  "%uint_3 = OpConstant %uint 3",
+  "%uint_4 = OpConstant %uint 4",
+  "%uint_5 = OpConstant %uint 5",
+  "%uint_6 = OpConstant %uint 6",
+"%composite_0 = OpConstantComposite %v3uint %uint_1 %uint_2 %uint_3",
+"%composite_1 = OpConstantComposite %v3uint %uint_4 %uint_5 %uint_6",
+"%vecshuffle = OpConstantComposite %v3uint %uint_1 %uint_6 %uint_4",
+      "%op = OpConstant %uint 6",
+    "%main = OpFunction %void None %main_func_type",
+"%main_func_entry_block = OpLabel",
+        "OpReturn",
+        "OpFunctionEnd",
+      // clang-format on
+  };
+  SinglePassRunAndCheck<FoldSpecConstantOpAndCompositePass>(
+      builder.GetCode(), JoinAllInsts(expected), /* skip_nop = */ true);
+}
+
+// Test CompositeExtract with matrix
+TEST_F(FoldSpecConstantOpAndCompositePassBasicTest, CompositeExtractMaxtrix) {
+  AssemblyBuilder builder;
+  builder.AppendTypesConstantsGlobals({
+      // clang-format off
+    "%uint = OpTypeInt 32 0",
+    "%v3uint = OpTypeVector %uint 3",
+    "%mat3x3 = OpTypeMatrix %v3uint 3",
+    "%uint_1 = OpConstant %uint 1",
+    "%uint_2 = OpConstant %uint 2",
+    "%uint_3 = OpConstant %uint 3",
+    // Folding target:
+    "%a = OpSpecConstantComposite %v3uint %uint_1 %uint_1 %uint_1",
+    "%b = OpSpecConstantComposite %v3uint %uint_1 %uint_1 %uint_3",
+    "%c = OpSpecConstantComposite %v3uint %uint_1 %uint_2 %uint_1",
+    "%op = OpSpecConstantComposite %mat3x3 %a %b %c",
+    "%x = OpSpecConstantOp %uint CompositeExtract %op 2 1",
+    "%y = OpSpecConstantOp %uint CompositeExtract %op 1 2",
+      // clang-format on
+  });
+
+  std::vector<const char*> expected = {
+      // clang-format off
+        "OpCapability Shader",
+        "OpCapability Float64",
+   "%1 = OpExtInstImport \"GLSL.std.450\"",
+        "OpMemoryModel Logical GLSL450",
+        "OpEntryPoint Vertex %main \"main\"",
+        "OpName %void \"void\"",
+        "OpName %main_func_type \"main_func_type\"",
+        "OpName %main \"main\"",
+        "OpName %main_func_entry_block \"main_func_entry_block\"",
+        "OpName %uint \"uint\"",
+        "OpName %v3uint \"v3uint\"",
+        "OpName %mat3x3 \"mat3x3\"",
+        "OpName %uint_1 \"uint_1\"",
+        "OpName %uint_2 \"uint_2\"",
+        "OpName %uint_3 \"uint_3\"",
+        "OpName %a \"a\"",
+        "OpName %b \"b\"",
+        "OpName %c \"c\"",
+        "OpName %op \"op\"",
+        "OpName %x \"x\"",
+        "OpName %y \"y\"",
+    "%void = OpTypeVoid",
+"%main_func_type = OpTypeFunction %void",
+    "%uint = OpTypeInt 32 0",
+  "%v3uint = OpTypeVector %uint 3",
+  "%mat3x3 = OpTypeMatrix %v3uint 3",
+  "%uint_1 = OpConstant %uint 1",
+  "%uint_2 = OpConstant %uint 2",
+  "%uint_3 = OpConstant %uint 3",
+       "%a = OpConstantComposite %v3uint %uint_1 %uint_1 %uint_1",
+       "%b = OpConstantComposite %v3uint %uint_1 %uint_1 %uint_3",
+       "%c = OpConstantComposite %v3uint %uint_1 %uint_2 %uint_1",
+      "%op = OpConstantComposite %mat3x3 %a %b %c",
+       "%x = OpConstant %uint 2",
+       "%y = OpConstant %uint 3",
+    "%main = OpFunction %void None %main_func_type",
+"%main_func_entry_block = OpLabel",
+        "OpReturn",
+        "OpFunctionEnd",
+      // clang-format on
+  };
+  SinglePassRunAndCheck<FoldSpecConstantOpAndCompositePass>(
+      builder.GetCode(), JoinAllInsts(expected), /* skip_nop = */ true);
+}
+
 // All types and some common constants that are potentially required in
 // FoldSpecConstantOpAndCompositeTest.
 std::vector<std::string> CommonTypesAndConstants() {
@@ -199,7 +402,7 @@
 struct FoldSpecConstantOpAndCompositePassTestCase {
   // Original constants with unfolded spec constants.
   std::vector<std::string> original;
-  // Expected cosntants after folding.
+  // Expected constant after folding.
   std::vector<std::string> expected;
 };
 
diff --git a/test/opt/fold_test.cpp b/test/opt/fold_test.cpp
index 0487e78..b324803 100644
--- a/test/opt/fold_test.cpp
+++ b/test/opt/fold_test.cpp
@@ -92,8 +92,13 @@
     inst = def_use_mgr->GetDef(inst->GetSingleWordInOperand(0));
     EXPECT_EQ(inst->opcode(), SpvOpConstant);
     analysis::ConstantManager* const_mrg = context->get_constant_mgr();
-    const analysis::IntConstant* result =
-        const_mrg->GetConstantFromInst(inst)->AsIntConstant();
+    const analysis::Constant* constant = const_mrg->GetConstantFromInst(inst);
+    // We expect to see either integer types or 16-bit float types here.
+    EXPECT_TRUE((constant->AsIntConstant() != nullptr) ||
+                ((constant->AsFloatConstant() != nullptr) &&
+                 (constant->type()->AsFloat()->width() == 16)));
+    const analysis::ScalarConstant* result =
+        const_mrg->GetConstantFromInst(inst)->AsScalarConstant();
     EXPECT_NE(result, nullptr);
     if (result != nullptr) {
       EXPECT_EQ(result->GetU32BitValue(), tc.expected_result);
@@ -115,6 +120,7 @@
   static const std::string header = R"(OpCapability Shader
 OpCapability Float16
 OpCapability Float64
+OpCapability Int8
 OpCapability Int16
 OpCapability Int64
 %1 = OpExtInstImport "GLSL.std.450"
@@ -134,6 +140,9 @@
 %false = OpConstantFalse %bool
 %bool_null = OpConstantNull %bool
 %short = OpTypeInt 16 1
+%ushort = OpTypeInt 16 0
+%byte = OpTypeInt 8 1
+%ubyte = OpTypeInt 8 0
 %int = OpTypeInt 32 1
 %long = OpTypeInt 64 1
 %uint = OpTypeInt 32 0
@@ -147,6 +156,9 @@
 %v2double = OpTypeVector %double 2
 %v2half = OpTypeVector %half 2
 %v2bool = OpTypeVector %bool 2
+%m2x2int = OpTypeMatrix %v2int 2
+%mat4v4float = OpTypeMatrix %v4float 4
+%mat4v4double = OpTypeMatrix %v4double 4
 %struct_v2int_int_int = OpTypeStruct %v2int %int %int
 %_ptr_int = OpTypePointer Function %int
 %_ptr_uint = OpTypePointer Function %uint
@@ -166,6 +178,8 @@
 %short_0 = OpConstant %short 0
 %short_2 = OpConstant %short 2
 %short_3 = OpConstant %short 3
+%ubyte_1 = OpConstant %ubyte 1
+%byte_n1 = OpConstant %byte -1
 %100 = OpConstant %int 0 ; Need a def with an numerical id to define id maps.
 %103 = OpConstant %int 7 ; Need a def with an numerical id to define id maps.
 %int_0 = OpConstant %int 0
@@ -218,12 +232,15 @@
 %struct_v2int_int_int_null = OpConstantNull %struct_v2int_int_int
 %v2int_null = OpConstantNull %v2int
 %102 = OpConstantComposite %v2int %103 %103
+%v4int_undef = OpUndef %v4int
 %v4int_0_0_0_0 = OpConstantComposite %v4int %int_0 %int_0 %int_0 %int_0
+%m2x2int_undef = OpUndef %m2x2int
 %struct_undef_0_0 = OpConstantComposite %struct_v2int_int_int %v2int_undef %int_0 %int_0
 %float_n1 = OpConstant %float -1
 %104 = OpConstant %float 0 ; Need a def with an numerical id to define id maps.
 %float_null = OpConstantNull %float
 %float_0 = OpConstant %float 0
+%float_n0 = OpConstant %float -0.0
 %float_1 = OpConstant %float 1
 %float_2 = OpConstant %float 2
 %float_3 = OpConstant %float 3
@@ -249,6 +266,7 @@
 %105 = OpConstant %double 0 ; Need a def with an numerical id to define id maps.
 %double_null = OpConstantNull %double
 %double_0 = OpConstant %double 0
+%double_n0 = OpConstant %double -0.0
 %double_1 = OpConstant %double 1
 %double_2 = OpConstant %double 2
 %double_3 = OpConstant %double 3
@@ -271,13 +289,20 @@
 %v4float_0_0_0_1 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_1
 %v4float_0_1_0_0 = OpConstantComposite %v4float %float_0 %float_1 %float_null %float_0
 %v4float_1_1_1_1 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+%v4float_1_2_3_4 = OpConstantComposite %v4float %float_1 %float_2 %float_3 %float_4
+%v4float_null = OpConstantNull %v4float
+%mat4v4float_null = OpConstantComposite %mat4v4float %v4float_null %v4float_null %v4float_null %v4float_null
+%mat4v4float_1_2_3_4 = OpConstantComposite %mat4v4float %v4float_1_2_3_4 %v4float_1_2_3_4 %v4float_1_2_3_4 %v4float_1_2_3_4
 %107 = OpConstantComposite %v4double %double_0 %double_0 %double_0 %double_0
 %v4double_0_0_0_0 = OpConstantComposite %v4double %double_0 %double_0 %double_0 %double_0
 %v4double_0_0_0_1 = OpConstantComposite %v4double %double_0 %double_0 %double_0 %double_1
 %v4double_0_1_0_0 = OpConstantComposite %v4double %double_0 %double_1 %double_null %double_0
 %v4double_1_1_1_1 = OpConstantComposite %v4double %double_1 %double_1 %double_1 %double_1
+%v4double_1_2_3_4 = OpConstantComposite %v4double %double_1 %double_2 %double_3 %double_4
 %v4double_1_1_1_0p5 = OpConstantComposite %v4double %double_1 %double_1 %double_1 %double_0p5
 %v4double_null = OpConstantNull %v4double
+%mat4v4double_null = OpConstantComposite %mat4v4double %v4double_null %v4double_null %v4double_null %v4double_null
+%mat4v4double_1_2_3_4 = OpConstantComposite %mat4v4double %v4double_1_2_3_4 %v4double_1_2_3_4 %v4double_1_2_3_4 %v4double_1_2_3_4
 %v4float_n1_2_1_3 = OpConstantComposite %v4float %float_n1 %float_2 %float_1 %float_3
 %uint_0x3f800000 = OpConstant %uint 0x3f800000
 %uint_0xbf800000 = OpConstant %uint 0xbf800000
@@ -288,6 +313,8 @@
 %int_0xC05FD666 = OpConstant %int 0xC05FD666
 %int_0x66666666 = OpConstant %int 0x66666666
 %v4int_0x3FF00000_0x00000000_0xC05FD666_0x66666666 = OpConstantComposite %v4int %int_0x00000000 %int_0x3FF00000 %int_0x66666666 %int_0xC05FD666
+%ushort_0xBC00 = OpConstant %ushort 0xBC00
+%short_0xBC00 = OpConstant %short 0xBC00
 )";
 
   return header;
@@ -762,7 +789,95 @@
             "%2 = OpBitcast %uint %float_1\n" +
             "OpReturn\n" +
             "OpFunctionEnd",
-        2, static_cast<uint32_t>(0x3f800000))
+        2, static_cast<uint32_t>(0x3f800000)),
+    // Test case 49: Bit-cast ushort 0xBC00 to ushort
+    InstructionFoldingCase<uint32_t>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpBitcast %ushort %ushort_0xBC00\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 0xBC00),
+    // Test case 50: Bit-cast short 0xBC00 to ushort
+    InstructionFoldingCase<uint32_t>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpBitcast %ushort %short_0xBC00\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 0xFFFFBC00),
+    // Test case 51: Bit-cast half 1 to ushort
+    InstructionFoldingCase<uint32_t>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpBitcast %ushort %half_1\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 0x3C00),
+    // Test case 52: Bit-cast ushort 0xBC00 to short
+    InstructionFoldingCase<uint32_t>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpBitcast %short %ushort_0xBC00\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 0xBC00),
+    // Test case 53: Bit-cast short 0xBC00 to short
+    InstructionFoldingCase<uint32_t>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpBitcast %short %short_0xBC00\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 0xFFFFBC00),
+    // Test case 54: Bit-cast half 1 to short
+    InstructionFoldingCase<uint32_t>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpBitcast %short %half_1\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 0x3C00),
+    // Test case 55: Bit-cast ushort 0xBC00 to half
+    InstructionFoldingCase<uint32_t>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpBitcast %half %ushort_0xBC00\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 0xBC00),
+    // Test case 56: Bit-cast short 0xBC00 to half
+    InstructionFoldingCase<uint32_t>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpBitcast %half %short_0xBC00\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 0xFFFFBC00),
+    // Test case 57: Bit-cast half 1 to half
+    InstructionFoldingCase<uint32_t>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpBitcast %half %half_1\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 0x3C00),
+    // Test case 58: Bit-cast ubyte 1 to byte
+    InstructionFoldingCase<uint32_t>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpBitcast %byte %ubyte_1\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 1),
+    // Test case 59: Bit-cast byte -1 to ubyte
+    InstructionFoldingCase<uint32_t>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpBitcast %ubyte %byte_n1\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 0xFFFFFFFF)
 ));
 // clang-format on
 
@@ -907,7 +1022,61 @@
            "%2 = OpBitcast %v2double %v4int_0x3FF00000_0x00000000_0xC05FD666_0x66666666\n" +
            "OpReturn\n" +
            "OpFunctionEnd",
-       2, {1.0,-127.35})
+       2, {1.0,-127.35}),
+   // Test case 1: OpVectorTimesMatrix Non-Zero Zero {{0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}} {1.0, 2.0, 3.0, 4.0} {0.0, 0.0, 0.0, 0.0}
+   InstructionFoldingCase<std::vector<double>>(
+       Header() +
+       "%main = OpFunction %void None %void_func\n" +
+       "%main_lab = OpLabel\n" +
+       "%2 = OpVectorTimesMatrix %v4double %v4double_1_2_3_4 %mat4v4double_null\n" +
+       "OpReturn\n" +
+       "OpFunctionEnd",
+       2, {0.0,0.0,0.0,0.0}),
+   // Test case 2: OpVectorTimesMatrix Zero Non-Zero {{1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}} {0.0, 0.0, 0.0, 0.0} {0.0, 0.0, 0.0, 0.0}
+   InstructionFoldingCase<std::vector<double>>(
+       Header() +
+       "%main = OpFunction %void None %void_func\n" +
+       "%main_lab = OpLabel\n" +
+       "%2 = OpVectorTimesMatrix %v4double %v4double_null %mat4v4double_1_2_3_4\n" +
+       "OpReturn\n" +
+       "OpFunctionEnd",
+       2, {0.0,0.0,0.0,0.0}),
+   // Test case 3: OpVectorTimesMatrix Non-Zero Non-Zero {{1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}} {1.0, 2.0, 3.0, 4.0} {30.0, 30.0, 30.0, 30.0}
+   InstructionFoldingCase<std::vector<double>>(
+       Header() +
+       "%main = OpFunction %void None %void_func\n" +
+       "%main_lab = OpLabel\n" +
+       "%2 = OpVectorTimesMatrix %v4double %v4double_1_2_3_4 %mat4v4double_1_2_3_4\n" +
+       "OpReturn\n" +
+       "OpFunctionEnd",
+       2, {30.0,30.0,30.0,30.0}),
+   // Test case 4: OpMatrixTimesVector Zero Non-Zero {1.0, 2.0, 3.0, 4.0} {{0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}} {0.0, 0.0, 0.0, 0.0}
+   InstructionFoldingCase<std::vector<double>>(
+       Header() +
+       "%main = OpFunction %void None %void_func\n" +
+       "%main_lab = OpLabel\n" +
+       "%2 = OpMatrixTimesVector %v4double %mat4v4double_null %v4double_1_2_3_4\n" +
+       "OpReturn\n" +
+       "OpFunctionEnd",
+       2, {0.0,0.0,0.0,0.0}),
+   // Test case 5: OpMatrixTimesVector Non-Zero Zero {{1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}} {0.0, 0.0, 0.0, 0.0} {0.0, 0.0, 0.0, 0.0}
+   InstructionFoldingCase<std::vector<double>>(
+       Header() +
+       "%main = OpFunction %void None %void_func\n" +
+       "%main_lab = OpLabel\n" +
+       "%2 = OpMatrixTimesVector %v4double %mat4v4double_1_2_3_4 %v4double_null\n" +
+       "OpReturn\n" +
+       "OpFunctionEnd",
+       2, {0.0,0.0,0.0,0.0}),
+   // Test case 6: OpMatrixTimesVector Non-Zero Non-Zero {1.0, 2.0, 3.0, 4.0} {{1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}} {10.0, 20.0, 30.0, 40.0}
+   InstructionFoldingCase<std::vector<double>>(
+       Header() +
+       "%main = OpFunction %void None %void_func\n" +
+       "%main_lab = OpLabel\n" +
+       "%2 = OpMatrixTimesVector %v4double %mat4v4double_1_2_3_4 %v4double_1_2_3_4\n" +
+       "OpReturn\n" +
+       "OpFunctionEnd",
+       2, {10.0,20.0,30.0,40.0})
 ));
 
 using FloatVectorInstructionFoldingTest =
@@ -976,7 +1145,61 @@
            "%2 = OpBitcast %v2float %long_0xbf8000003f800000\n" +
            "OpReturn\n" +
            "OpFunctionEnd",
-       2, {1.0f,-1.0f})
+       2, {1.0f,-1.0f}),
+   // Test case 3: OpVectorTimesMatrix Non-Zero Zero {{0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}} {1.0, 2.0, 3.0, 4.0} {0.0, 0.0, 0.0, 0.0}
+   InstructionFoldingCase<std::vector<float>>(
+       Header() +
+       "%main = OpFunction %void None %void_func\n" +
+       "%main_lab = OpLabel\n" +
+       "%2 = OpVectorTimesMatrix %v4float %v4float_1_2_3_4 %mat4v4float_null\n" +
+       "OpReturn\n" +
+       "OpFunctionEnd",
+       2, {0.0f,0.0f,0.0f,0.0f}),
+   // Test case 4: OpVectorTimesMatrix Zero Non-Zero {{1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}} {0.0, 0.0, 0.0, 0.0} {0.0, 0.0, 0.0, 0.0}
+   InstructionFoldingCase<std::vector<float>>(
+       Header() +
+       "%main = OpFunction %void None %void_func\n" +
+       "%main_lab = OpLabel\n" +
+       "%2 = OpVectorTimesMatrix %v4float %v4float_null %mat4v4float_1_2_3_4\n" +
+       "OpReturn\n" +
+       "OpFunctionEnd",
+       2, {0.0f,0.0f,0.0f,0.0f}),
+   // Test case 5: OpVectorTimesMatrix Non-Zero Non-Zero {{1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}} {1.0, 2.0, 3.0, 4.0} {30.0, 30.0, 30.0, 30.0}
+   InstructionFoldingCase<std::vector<float>>(
+       Header() +
+       "%main = OpFunction %void None %void_func\n" +
+       "%main_lab = OpLabel\n" +
+       "%2 = OpVectorTimesMatrix %v4float %v4float_1_2_3_4 %mat4v4float_1_2_3_4\n" +
+       "OpReturn\n" +
+       "OpFunctionEnd",
+       2, {30.0f,30.0f,30.0f,30.0f}),
+   // Test case 6: OpMatrixTimesVector Zero Non-Zero {1.0, 2.0, 3.0, 4.0} {{0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}, {0.0, 0.0, 0.0, 0.0}} {0.0, 0.0, 0.0, 0.0}
+   InstructionFoldingCase<std::vector<float>>(
+       Header() +
+       "%main = OpFunction %void None %void_func\n" +
+       "%main_lab = OpLabel\n" +
+       "%2 = OpMatrixTimesVector %v4float %mat4v4float_null %v4float_1_2_3_4\n" +
+       "OpReturn\n" +
+       "OpFunctionEnd",
+       2, {0.0f,0.0f,0.0f,0.0f}),
+   // Test case 7: OpMatrixTimesVector Non-Zero Zero {{1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}} {0.0, 0.0, 0.0, 0.0} {0.0, 0.0, 0.0, 0.0}
+   InstructionFoldingCase<std::vector<float>>(
+       Header() +
+       "%main = OpFunction %void None %void_func\n" +
+       "%main_lab = OpLabel\n" +
+       "%2 = OpMatrixTimesVector %v4float %mat4v4float_1_2_3_4 %v4float_null\n" +
+       "OpReturn\n" +
+       "OpFunctionEnd",
+       2, {0.0f,0.0f,0.0f,0.0f}),
+   // Test case 8: OpMatrixTimesVector Non-Zero Non-Zero {1.0, 2.0, 3.0, 4.0} {{1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}, {1.0, 2.0, 3.0, 4.0}} {10.0, 20.0, 30.0, 40.0}
+   InstructionFoldingCase<std::vector<float>>(
+       Header() +
+       "%main = OpFunction %void None %void_func\n" +
+       "%main_lab = OpLabel\n" +
+       "%2 = OpMatrixTimesVector %v4float %mat4v4float_1_2_3_4 %v4float_1_2_3_4\n" +
+       "OpReturn\n" +
+       "OpFunctionEnd",
+       2, {10.0f,20.0f,30.0f,40.0f})
 ));
 // clang-format on
 using BooleanInstructionFoldingTest =
@@ -1987,7 +2210,39 @@
             "%2 = OpExtInst %float %1 Pow %float_2 %float_3\n" +
             "OpReturn\n" +
             "OpFunctionEnd",
-        2, 8.0)
+        2, 8.0),
+    // Test case 43: Fold 1.0 / -0.0.
+    InstructionFoldingCase<float>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpFDiv %float %float_1 %float_n0\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, -std::numeric_limits<float>::infinity()),
+    // Test case 44: Fold -1.0 / -0.0
+    InstructionFoldingCase<float>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpFDiv %float %float_n1 %float_n0\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, std::numeric_limits<float>::infinity()),
+    // Test case 45: Fold 0.0 / 0.0
+    InstructionFoldingCase<float>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpFDiv %float %float_0 %float_0\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, std::numeric_limits<float>::quiet_NaN()),
+    // Test case 46: Fold 0.0 / -0.0
+    InstructionFoldingCase<float>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpFDiv %float %float_0 %float_n0\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, std::numeric_limits<float>::quiet_NaN())
 ));
 // clang-format on
 
@@ -2019,7 +2274,11 @@
         const_mrg->GetConstantFromInst(inst)->AsFloatConstant();
     EXPECT_NE(result, nullptr);
     if (result != nullptr) {
-      EXPECT_EQ(result->GetDoubleValue(), tc.expected_result);
+      if (!std::isnan(tc.expected_result)) {
+        EXPECT_EQ(result->GetDoubleValue(), tc.expected_result);
+      } else {
+        EXPECT_TRUE(std::isnan(result->GetDoubleValue()));
+      }
     }
   }
 }
@@ -2222,7 +2481,39 @@
                 "%2 = OpExtInst %double %1 Pow %double_2 %double_3\n" +
                 "OpReturn\n" +
                 "OpFunctionEnd",
-            2, 8.0)
+            2, 8.0),
+        // Test case 23: Fold 1.0 / -0.0.
+        InstructionFoldingCase<double>(
+            Header() + "%main = OpFunction %void None %void_func\n" +
+                "%main_lab = OpLabel\n" +
+                "%2 = OpFDiv %double %double_1 %double_n0\n" +
+                "OpReturn\n" +
+                "OpFunctionEnd",
+            2, -std::numeric_limits<double>::infinity()),
+        // Test case 24: Fold -1.0 / -0.0
+        InstructionFoldingCase<double>(
+            Header() + "%main = OpFunction %void None %void_func\n" +
+                "%main_lab = OpLabel\n" +
+                "%2 = OpFDiv %double %double_n1 %double_n0\n" +
+                "OpReturn\n" +
+                "OpFunctionEnd",
+            2, std::numeric_limits<double>::infinity()),
+        // Test case 25: Fold 0.0 / 0.0
+        InstructionFoldingCase<double>(
+            Header() + "%main = OpFunction %void None %void_func\n" +
+                "%main_lab = OpLabel\n" +
+                "%2 = OpFDiv %double %double_0 %double_0\n" +
+                "OpReturn\n" +
+                "OpFunctionEnd",
+            2, std::numeric_limits<double>::quiet_NaN()),
+        // Test case 26: Fold 0.0 / -0.0
+        InstructionFoldingCase<double>(
+            Header() + "%main = OpFunction %void None %void_func\n" +
+                "%main_lab = OpLabel\n" +
+                "%2 = OpFDiv %double %double_0 %double_n0\n" +
+                "OpReturn\n" +
+                "OpFunctionEnd",
+            2, std::numeric_limits<double>::quiet_NaN())
 ));
 // clang-format on
 
@@ -6792,7 +7083,7 @@
       4, true)
 ));
 
-INSTANTIATE_TEST_SUITE_P(CompositeExtractMatchingTest, MatchingInstructionFoldingTest,
+INSTANTIATE_TEST_SUITE_P(CompositeExtractOrInsertMatchingTest, MatchingInstructionFoldingTest,
 ::testing::Values(
     // Test case 0: Extracting from result of consecutive shuffles of differing
     // size.
@@ -6932,7 +7223,145 @@
             "%4 = OpCompositeExtract %int %3 1\n" +
             "OpReturn\n" +
             "OpFunctionEnd",
-        4, true)
+        4, true),
+    // Test case 8: Inserting every element of a vector turns into a composite construct.
+    InstructionFoldingCase<bool>(
+        Header() +
+            "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+            "; CHECK-DAG: [[v4:%\\w+]] = OpTypeVector [[int]] 4\n" +
+            "; CHECK-DAG: [[int1:%\\w+]] = OpConstant [[int]] 1\n" +
+            "; CHECK-DAG: [[int2:%\\w+]] = OpConstant [[int]] 2\n" +
+            "; CHECK-DAG: [[int3:%\\w+]] = OpConstant [[int]] 3\n" +
+            "; CHECK: [[construct:%\\w+]] = OpCompositeConstruct [[v4]] %100 [[int1]] [[int2]] [[int3]]\n" +
+            "; CHECK: %5 = OpCopyObject [[v4]] [[construct]]\n" +
+            "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpCompositeInsert %v4int %100 %v4int_undef 0\n" +
+            "%3 = OpCompositeInsert %v4int %int_1 %2 1\n" +
+            "%4 = OpCompositeInsert %v4int %int_2 %3 2\n" +
+            "%5 = OpCompositeInsert %v4int %int_3 %4 3\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        5, true),
+    // Test case 9: Inserting every element of a vector turns into a composite construct in a different order.
+    InstructionFoldingCase<bool>(
+        Header() +
+            "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+            "; CHECK-DAG: [[v4:%\\w+]] = OpTypeVector [[int]] 4\n" +
+            "; CHECK-DAG: [[int1:%\\w+]] = OpConstant [[int]] 1\n" +
+            "; CHECK-DAG: [[int2:%\\w+]] = OpConstant [[int]] 2\n" +
+            "; CHECK-DAG: [[int3:%\\w+]] = OpConstant [[int]] 3\n" +
+            "; CHECK: [[construct:%\\w+]] = OpCompositeConstruct [[v4]] %100 [[int1]] [[int2]] [[int3]]\n" +
+            "; CHECK: %5 = OpCopyObject [[v4]] [[construct]]\n" +
+            "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpCompositeInsert %v4int %100 %v4int_undef 0\n" +
+            "%4 = OpCompositeInsert %v4int %int_2 %2 2\n" +
+            "%3 = OpCompositeInsert %v4int %int_1 %4 1\n" +
+            "%5 = OpCompositeInsert %v4int %int_3 %3 3\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        5, true),
+    // Test case 10: Check multiple inserts to the same position are handled correctly.
+    InstructionFoldingCase<bool>(
+        Header() +
+            "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+            "; CHECK-DAG: [[v4:%\\w+]] = OpTypeVector [[int]] 4\n" +
+            "; CHECK-DAG: [[int1:%\\w+]] = OpConstant [[int]] 1\n" +
+            "; CHECK-DAG: [[int2:%\\w+]] = OpConstant [[int]] 2\n" +
+            "; CHECK-DAG: [[int3:%\\w+]] = OpConstant [[int]] 3\n" +
+            "; CHECK: [[construct:%\\w+]] = OpCompositeConstruct [[v4]] %100 [[int1]] [[int2]] [[int3]]\n" +
+            "; CHECK: %6 = OpCopyObject [[v4]] [[construct]]\n" +
+            "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpCompositeInsert %v4int %100 %v4int_undef 0\n" +
+            "%3 = OpCompositeInsert %v4int %int_2 %2 2\n" +
+            "%4 = OpCompositeInsert %v4int %int_4 %3 1\n" +
+            "%5 = OpCompositeInsert %v4int %int_1 %4 1\n" +
+            "%6 = OpCompositeInsert %v4int %int_3 %5 3\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        6, true),
+    // Test case 11: The last indexes are 0 and 1, but they have different first indexes.  This should not be folded.
+    InstructionFoldingCase<bool>(
+        Header() +
+            "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpCompositeInsert %m2x2int %100 %m2x2int_undef 0 0\n" +
+            "%3 = OpCompositeInsert %m2x2int %int_1 %2 1 1\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        3, false),
+    // Test case 12: Don't fold when there is a partial insertion.
+    InstructionFoldingCase<bool>(
+        Header() +
+            "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpCompositeInsert %m2x2int %v2int_1_0 %m2x2int_undef 0\n" +
+            "%3 = OpCompositeInsert %m2x2int %int_4 %2 0 0\n" +
+            "%4 = OpCompositeInsert %m2x2int %v2int_2_3 %3 1\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        4, false),
+    // Test case 13: Insert into a column of a matrix
+    InstructionFoldingCase<bool>(
+        Header() +
+            "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+            "; CHECK-DAG: [[v2:%\\w+]] = OpTypeVector [[int]] 2\n" +
+            "; CHECK: [[m2x2:%\\w+]] = OpTypeMatrix [[v2]] 2\n" +
+            "; CHECK-DAG: [[m2x2_undef:%\\w+]] = OpUndef [[m2x2]]\n" +
+            "; CHECK-DAG: [[int1:%\\w+]] = OpConstant [[int]] 1\n" +
+// We keep this insert in the chain.  DeadInsertElimPass should remove it.
+            "; CHECK: [[insert:%\\w+]] = OpCompositeInsert [[m2x2]] %100 [[m2x2_undef]] 0 0\n" +
+            "; CHECK: [[construct:%\\w+]] = OpCompositeConstruct [[v2]] %100 [[int1]]\n" +
+            "; CHECK: %3 = OpCompositeInsert [[m2x2]] [[construct]] [[insert]] 0\n" +
+            "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpCompositeInsert %m2x2int %100 %m2x2int_undef 0 0\n" +
+            "%3 = OpCompositeInsert %m2x2int %int_1 %2 0 1\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        3, true),
+    // Test case 14: Insert all elements of the matrix.
+    InstructionFoldingCase<bool>(
+        Header() +
+            "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+            "; CHECK-DAG: [[v2:%\\w+]] = OpTypeVector [[int]] 2\n" +
+            "; CHECK: [[m2x2:%\\w+]] = OpTypeMatrix [[v2]] 2\n" +
+            "; CHECK-DAG: [[m2x2_undef:%\\w+]] = OpUndef [[m2x2]]\n" +
+            "; CHECK-DAG: [[int1:%\\w+]] = OpConstant [[int]] 1\n" +
+            "; CHECK-DAG: [[int2:%\\w+]] = OpConstant [[int]] 2\n" +
+            "; CHECK-DAG: [[int3:%\\w+]] = OpConstant [[int]] 3\n" +
+            "; CHECK: [[c0:%\\w+]] = OpCompositeConstruct [[v2]] %100 [[int1]]\n" +
+            "; CHECK: [[c1:%\\w+]] = OpCompositeConstruct [[v2]] [[int2]] [[int3]]\n" +
+            "; CHECK: [[matrix:%\\w+]] = OpCompositeConstruct [[m2x2]] [[c0]] [[c1]]\n" +
+            "; CHECK: %5 = OpCopyObject [[m2x2]] [[matrix]]\n" +
+            "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpCompositeConstruct %v2int %100 %int_1\n" +
+            "%3 = OpCompositeInsert %m2x2int %2 %m2x2int_undef 0\n" +
+            "%4 = OpCompositeInsert %m2x2int %int_2 %3 1 0\n" +
+            "%5 = OpCompositeInsert %m2x2int %int_3 %4 1 1\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        5, true),
+    // Test case 15: Replace construct with extract when reconstructing a member
+    // of another object.
+    InstructionFoldingCase<bool>(
+        Header() +
+            "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+            "; CHECK: [[v2:%\\w+]] = OpTypeVector [[int]] 2\n" +
+            "; CHECK: [[m2x2:%\\w+]] = OpTypeMatrix [[v2]] 2\n" +
+            "; CHECK: [[m2x2_undef:%\\w+]] = OpUndef [[m2x2]]\n" +
+            "; CHECK: %5 = OpCompositeExtract [[v2]] [[m2x2_undef]]\n" +
+            "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%3 = OpCompositeExtract %int %m2x2int_undef 1 0\n" +
+            "%4 = OpCompositeExtract %int %m2x2int_undef 1 1\n" +
+            "%5 = OpCompositeConstruct %v2int %3 %4\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        5, true)
 ));
 
 INSTANTIATE_TEST_SUITE_P(DotProductMatchingTest, MatchingInstructionFoldingTest,
@@ -7017,12 +7446,297 @@
         3, true)
 ));
 
+INSTANTIATE_TEST_SUITE_P(VectorShuffleMatchingTest, MatchingInstructionFoldingTest,
+::testing::Values(
+    // Test case 0: Using OpDot to extract last element.
+    InstructionFoldingCase<bool>(
+        Header() +
+            "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+            "; CHECK: [[v2int:%\\w+]] = OpTypeVector [[int]] 2{{[[:space:]]}}\n" +
+            "; CHECK: [[null:%\\w+]] = OpConstantNull [[v2int]]\n" +
+            "; CHECK: OpVectorShuffle\n" +
+            "; CHECK: %3 = OpVectorShuffle [[v2int]] [[null]] {{%\\w+}} 4294967295 2\n" +
+            "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%n = OpVariable %_ptr_int Function\n" +
+            "%load = OpLoad %int %n\n" +
+            "%2 = OpVectorShuffle %v2int %v2int_null %v2int_2_3 3 0xFFFFFFFF \n" +
+            "%3 = OpVectorShuffle %v2int %2 %v2int_2_3 1 2 \n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        3, true)
+ ));
+
+INSTANTIATE_TEST_SUITE_P(FmaGenerationMatchingTest, MatchingInstructionFoldingTest,
+::testing::Values(
+   // Test case 0: (x * y) + a = Fma(x, y, a)
+   InstructionFoldingCase<bool>(
+       Header() +
+           "; CHECK: [[ext:%\\w+]] = OpExtInstImport \"GLSL.std.450\"\n" +
+           "; CHECK: OpFunction\n" +
+           "; CHECK: [[x:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
+           "; CHECK: [[y:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
+           "; CHECK: [[a:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
+           "; CHECK: [[lx:%\\w+]] = OpLoad {{%\\w+}} [[x]]\n" +
+           "; CHECK: [[ly:%\\w+]] = OpLoad {{%\\w+}} [[y]]\n" +
+           "; CHECK: [[la:%\\w+]] = OpLoad {{%\\w+}} [[a]]\n" +
+           "; CHECK: [[fma:%\\w+]] = OpExtInst {{%\\w+}} [[ext]] Fma [[lx]] [[ly]] [[la]]\n" +
+           "; CHECK: OpStore {{%\\w+}} [[fma]]\n" +
+           "%main = OpFunction %void None %void_func\n" +
+           "%main_lab = OpLabel\n" +
+           "%x = OpVariable %_ptr_float Function\n" +
+           "%y = OpVariable %_ptr_float Function\n" +
+           "%a = OpVariable %_ptr_float Function\n" +
+           "%lx = OpLoad %float %x\n" +
+           "%ly = OpLoad %float %y\n" +
+           "%mul = OpFMul %float %lx %ly\n" +
+           "%la = OpLoad %float %a\n" +
+           "%3 = OpFAdd %float %mul %la\n" +
+           "OpStore %a %3\n" +
+           "OpReturn\n" +
+           "OpFunctionEnd",
+       3, true),
+    // Test case 1:  a + (x * y) = Fma(x, y, a)
+   InstructionFoldingCase<bool>(
+       Header() +
+           "; CHECK: [[ext:%\\w+]] = OpExtInstImport \"GLSL.std.450\"\n" +
+           "; CHECK: OpFunction\n" +
+           "; CHECK: [[x:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
+           "; CHECK: [[y:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
+           "; CHECK: [[a:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
+           "; CHECK: [[lx:%\\w+]] = OpLoad {{%\\w+}} [[x]]\n" +
+           "; CHECK: [[ly:%\\w+]] = OpLoad {{%\\w+}} [[y]]\n" +
+           "; CHECK: [[la:%\\w+]] = OpLoad {{%\\w+}} [[a]]\n" +
+           "; CHECK: [[fma:%\\w+]] = OpExtInst {{%\\w+}} [[ext]] Fma [[lx]] [[ly]] [[la]]\n" +
+           "; CHECK: OpStore {{%\\w+}} [[fma]]\n" +
+           "%main = OpFunction %void None %void_func\n" +
+           "%main_lab = OpLabel\n" +
+           "%x = OpVariable %_ptr_float Function\n" +
+           "%y = OpVariable %_ptr_float Function\n" +
+           "%a = OpVariable %_ptr_float Function\n" +
+           "%lx = OpLoad %float %x\n" +
+           "%ly = OpLoad %float %y\n" +
+           "%mul = OpFMul %float %lx %ly\n" +
+           "%la = OpLoad %float %a\n" +
+           "%3 = OpFAdd %float %la %mul\n" +
+           "OpStore %a %3\n" +
+           "OpReturn\n" +
+           "OpFunctionEnd",
+       3, true),
+   // Test case 2: (x * y) + a = Fma(x, y, a) with vectors
+   InstructionFoldingCase<bool>(
+       Header() +
+           "; CHECK: [[ext:%\\w+]] = OpExtInstImport \"GLSL.std.450\"\n" +
+           "; CHECK: OpFunction\n" +
+           "; CHECK: [[x:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
+           "; CHECK: [[y:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
+           "; CHECK: [[a:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
+           "; CHECK: [[lx:%\\w+]] = OpLoad {{%\\w+}} [[x]]\n" +
+           "; CHECK: [[ly:%\\w+]] = OpLoad {{%\\w+}} [[y]]\n" +
+           "; CHECK: [[la:%\\w+]] = OpLoad {{%\\w+}} [[a]]\n" +
+           "; CHECK: [[fma:%\\w+]] = OpExtInst {{%\\w+}} [[ext]] Fma [[lx]] [[ly]] [[la]]\n" +
+           "; CHECK: OpStore {{%\\w+}} [[fma]]\n" +
+           "%main = OpFunction %void None %void_func\n" +
+           "%main_lab = OpLabel\n" +
+           "%x = OpVariable %_ptr_v4float Function\n" +
+           "%y = OpVariable %_ptr_v4float Function\n" +
+           "%a = OpVariable %_ptr_v4float Function\n" +
+           "%lx = OpLoad %v4float %x\n" +
+           "%ly = OpLoad %v4float %y\n" +
+           "%mul = OpFMul %v4float %lx %ly\n" +
+           "%la = OpLoad %v4float %a\n" +
+           "%3 = OpFAdd %v4float %mul %la\n" +
+           "OpStore %a %3\n" +
+           "OpReturn\n" +
+           "OpFunctionEnd",
+       3, true),
+    // Test case 3:  a + (x * y) = Fma(x, y, a) with vectors
+   InstructionFoldingCase<bool>(
+       Header() +
+           "; CHECK: [[ext:%\\w+]] = OpExtInstImport \"GLSL.std.450\"\n" +
+           "; CHECK: OpFunction\n" +
+           "; CHECK: [[x:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
+           "; CHECK: [[y:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
+           "; CHECK: [[a:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
+           "; CHECK: [[lx:%\\w+]] = OpLoad {{%\\w+}} [[x]]\n" +
+           "; CHECK: [[ly:%\\w+]] = OpLoad {{%\\w+}} [[y]]\n" +
+           "; CHECK: [[la:%\\w+]] = OpLoad {{%\\w+}} [[a]]\n" +
+           "; CHECK: [[fma:%\\w+]] = OpExtInst {{%\\w+}} [[ext]] Fma [[lx]] [[ly]] [[la]]\n" +
+           "; CHECK: OpStore {{%\\w+}} [[fma]]\n" +
+           "%main = OpFunction %void None %void_func\n" +
+           "%main_lab = OpLabel\n" +
+           "%x = OpVariable %_ptr_float Function\n" +
+           "%y = OpVariable %_ptr_float Function\n" +
+           "%a = OpVariable %_ptr_float Function\n" +
+           "%lx = OpLoad %float %x\n" +
+           "%ly = OpLoad %float %y\n" +
+           "%mul = OpFMul %float %lx %ly\n" +
+           "%la = OpLoad %float %a\n" +
+           "%3 = OpFAdd %float %la %mul\n" +
+           "OpStore %a %3\n" +
+           "OpReturn\n" +
+           "OpFunctionEnd",
+       3, true),
+    // Test 4: that the OpExtInstImport instruction is generated if it is missing.
+   InstructionFoldingCase<bool>(
+           std::string() +
+           "; CHECK: [[ext:%\\w+]] = OpExtInstImport \"GLSL.std.450\"\n" +
+           "; CHECK: OpFunction\n" +
+           "; CHECK: [[x:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
+           "; CHECK: [[y:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
+           "; CHECK: [[a:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
+           "; CHECK: [[lx:%\\w+]] = OpLoad {{%\\w+}} [[x]]\n" +
+           "; CHECK: [[ly:%\\w+]] = OpLoad {{%\\w+}} [[y]]\n" +
+           "; CHECK: [[la:%\\w+]] = OpLoad {{%\\w+}} [[a]]\n" +
+           "; CHECK: [[fma:%\\w+]] = OpExtInst {{%\\w+}} [[ext]] Fma [[lx]] [[ly]] [[la]]\n" +
+           "; CHECK: OpStore {{%\\w+}} [[fma]]\n" +
+           "OpCapability Shader\n" +
+           "OpMemoryModel Logical GLSL450\n" +
+           "OpEntryPoint Fragment %main \"main\"\n" +
+           "OpExecutionMode %main OriginUpperLeft\n" +
+           "OpSource GLSL 140\n" +
+           "OpName %main \"main\"\n" +
+           "%void = OpTypeVoid\n" +
+           "%void_func = OpTypeFunction %void\n" +
+           "%bool = OpTypeBool\n" +
+           "%float = OpTypeFloat 32\n" +
+           "%_ptr_float = OpTypePointer Function %float\n" +
+           "%main = OpFunction %void None %void_func\n" +
+           "%main_lab = OpLabel\n" +
+           "%x = OpVariable %_ptr_float Function\n" +
+           "%y = OpVariable %_ptr_float Function\n" +
+           "%a = OpVariable %_ptr_float Function\n" +
+           "%lx = OpLoad %float %x\n" +
+           "%ly = OpLoad %float %y\n" +
+           "%mul = OpFMul %float %lx %ly\n" +
+           "%la = OpLoad %float %a\n" +
+           "%3 = OpFAdd %float %mul %la\n" +
+           "OpStore %a %3\n" +
+           "OpReturn\n" +
+           "OpFunctionEnd",
+       3, true),
+   // Test 5: Don't fold if the multiple is marked no contract.
+   InstructionFoldingCase<bool>(
+       std::string() +
+           "OpCapability Shader\n" +
+           "OpMemoryModel Logical GLSL450\n" +
+           "OpEntryPoint Fragment %main \"main\"\n" +
+           "OpExecutionMode %main OriginUpperLeft\n" +
+           "OpSource GLSL 140\n" +
+           "OpName %main \"main\"\n" +
+           "OpDecorate %mul NoContraction\n" +
+           "%void = OpTypeVoid\n" +
+           "%void_func = OpTypeFunction %void\n" +
+           "%bool = OpTypeBool\n" +
+           "%float = OpTypeFloat 32\n" +
+           "%_ptr_float = OpTypePointer Function %float\n" +
+           "%main = OpFunction %void None %void_func\n" +
+           "%main_lab = OpLabel\n" +
+           "%x = OpVariable %_ptr_float Function\n" +
+           "%y = OpVariable %_ptr_float Function\n" +
+           "%a = OpVariable %_ptr_float Function\n" +
+           "%lx = OpLoad %float %x\n" +
+           "%ly = OpLoad %float %y\n" +
+           "%mul = OpFMul %float %lx %ly\n" +
+           "%la = OpLoad %float %a\n" +
+           "%3 = OpFAdd %float %mul %la\n" +
+           "OpStore %a %3\n" +
+           "OpReturn\n" +
+           "OpFunctionEnd",
+       3, false),
+       // Test 6: Don't fold if the add is marked no contract.
+       InstructionFoldingCase<bool>(
+           std::string() +
+               "OpCapability Shader\n" +
+               "OpMemoryModel Logical GLSL450\n" +
+               "OpEntryPoint Fragment %main \"main\"\n" +
+               "OpExecutionMode %main OriginUpperLeft\n" +
+               "OpSource GLSL 140\n" +
+               "OpName %main \"main\"\n" +
+               "OpDecorate %3 NoContraction\n" +
+               "%void = OpTypeVoid\n" +
+               "%void_func = OpTypeFunction %void\n" +
+               "%bool = OpTypeBool\n" +
+               "%float = OpTypeFloat 32\n" +
+               "%_ptr_float = OpTypePointer Function %float\n" +
+               "%main = OpFunction %void None %void_func\n" +
+               "%main_lab = OpLabel\n" +
+               "%x = OpVariable %_ptr_float Function\n" +
+               "%y = OpVariable %_ptr_float Function\n" +
+               "%a = OpVariable %_ptr_float Function\n" +
+               "%lx = OpLoad %float %x\n" +
+               "%ly = OpLoad %float %y\n" +
+               "%mul = OpFMul %float %lx %ly\n" +
+               "%la = OpLoad %float %a\n" +
+               "%3 = OpFAdd %float %mul %la\n" +
+               "OpStore %a %3\n" +
+               "OpReturn\n" +
+               "OpFunctionEnd",
+           3, false),
+    // Test case 7: (x * y) - a = Fma(x, y, -a)
+    InstructionFoldingCase<bool>(
+       Header() +
+           "; CHECK: [[ext:%\\w+]] = OpExtInstImport \"GLSL.std.450\"\n" +
+           "; CHECK: OpFunction\n" +
+           "; CHECK: [[x:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
+           "; CHECK: [[y:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
+           "; CHECK: [[a:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
+           "; CHECK: [[lx:%\\w+]] = OpLoad {{%\\w+}} [[x]]\n" +
+           "; CHECK: [[ly:%\\w+]] = OpLoad {{%\\w+}} [[y]]\n" +
+           "; CHECK: [[la:%\\w+]] = OpLoad {{%\\w+}} [[a]]\n" +
+           "; CHECK: [[na:%\\w+]] = OpFNegate {{%\\w+}} [[la]]\n" +
+           "; CHECK: [[fma:%\\w+]] = OpExtInst {{%\\w+}} [[ext]] Fma [[lx]] [[ly]] [[na]]\n" +
+           "; CHECK: OpStore {{%\\w+}} [[fma]]\n" +
+           "%main = OpFunction %void None %void_func\n" +
+           "%main_lab = OpLabel\n" +
+           "%x = OpVariable %_ptr_float Function\n" +
+           "%y = OpVariable %_ptr_float Function\n" +
+           "%a = OpVariable %_ptr_float Function\n" +
+           "%lx = OpLoad %float %x\n" +
+           "%ly = OpLoad %float %y\n" +
+           "%mul = OpFMul %float %lx %ly\n" +
+           "%la = OpLoad %float %a\n" +
+           "%3 = OpFSub %float %mul %la\n" +
+           "OpStore %a %3\n" +
+           "OpReturn\n" +
+           "OpFunctionEnd",
+       3, true),
+   // Test case 8: a - (x * y) = Fma(-x, y, a)
+   InstructionFoldingCase<bool>(
+       Header() +
+           "; CHECK: [[ext:%\\w+]] = OpExtInstImport \"GLSL.std.450\"\n" +
+           "; CHECK: OpFunction\n" +
+           "; CHECK: [[x:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
+           "; CHECK: [[y:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
+           "; CHECK: [[a:%\\w+]] = OpVariable {{%\\w+}} Function\n" +
+           "; CHECK: [[lx:%\\w+]] = OpLoad {{%\\w+}} [[x]]\n" +
+           "; CHECK: [[ly:%\\w+]] = OpLoad {{%\\w+}} [[y]]\n" +
+           "; CHECK: [[la:%\\w+]] = OpLoad {{%\\w+}} [[a]]\n" +
+           "; CHECK: [[nx:%\\w+]] = OpFNegate {{%\\w+}} [[lx]]\n" +
+           "; CHECK: [[fma:%\\w+]] = OpExtInst {{%\\w+}} [[ext]] Fma [[nx]] [[ly]] [[la]]\n" +
+           "; CHECK: OpStore {{%\\w+}} [[fma]]\n" +
+           "%main = OpFunction %void None %void_func\n" +
+           "%main_lab = OpLabel\n" +
+           "%x = OpVariable %_ptr_float Function\n" +
+           "%y = OpVariable %_ptr_float Function\n" +
+           "%a = OpVariable %_ptr_float Function\n" +
+           "%lx = OpLoad %float %x\n" +
+           "%ly = OpLoad %float %y\n" +
+           "%mul = OpFMul %float %lx %ly\n" +
+           "%la = OpLoad %float %a\n" +
+           "%3 = OpFSub %float %la %mul\n" +
+           "OpStore %a %3\n" +
+           "OpReturn\n" +
+           "OpFunctionEnd",
+       3, true)
+));
+
 using MatchingInstructionWithNoResultFoldingTest =
 ::testing::TestWithParam<InstructionFoldingCase<bool>>;
 
 // Test folding instructions that do not have a result.  The instruction
 // that will be folded is the last instruction before the return.  If there
-// are multiple returns, there is not guarentee which one is used.
+// are multiple returns, there is not guarantee which one is used.
 TEST_P(MatchingInstructionWithNoResultFoldingTest, Case) {
   const auto& tc = GetParam();
 
@@ -7350,6 +8064,27 @@
             "%9 = OpVectorShuffle %v4double %7 %8 2 0 1 4294967295\n" +
             "OpReturn\n" +
             "OpFunctionEnd",
+        9, true),
+    // Test case 14: Shuffle with undef literal and change size of first input vector.
+    InstructionFoldingCase<bool>(
+        Header() +
+            "; CHECK: [[double:%\\w+]] = OpTypeFloat 64\n" +
+            "; CHECK: [[v4double:%\\w+]] = OpTypeVector [[double]] 2\n" +
+            "; CHECK: OpVectorShuffle\n" +
+            "; CHECK: OpVectorShuffle {{%\\w+}} %5 %7 0 1 4 4294967295\n" +
+            "; CHECK: OpReturn\n" +
+            "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpVariable %_ptr_v4double Function\n" +
+            "%3 = OpVariable %_ptr_v4double Function\n" +
+            "%4 = OpVariable %_ptr_v4double Function\n" +
+            "%5 = OpLoad %v4double %2\n" +
+            "%6 = OpLoad %v4double %3\n" +
+            "%7 = OpLoad %v4double %4\n" +
+            "%8 = OpVectorShuffle %v2double %5 %5 0 1\n" +
+            "%9 = OpVectorShuffle %v4double %8 %7 0 1 2 4294967295\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
         9, true)
 ));
 
diff --git a/test/opt/freeze_spec_const_test.cpp b/test/opt/freeze_spec_const_test.cpp
index e5999ce..ad0fc32 100644
--- a/test/opt/freeze_spec_const_test.cpp
+++ b/test/opt/freeze_spec_const_test.cpp
@@ -128,6 +128,51 @@
                                                      /* skip_nop = */ true);
 }
 
+TEST_F(FreezeSpecConstantValueRemoveDecorationTest,
+       RemoveDecorationForLocalSizeIdWithSpecId) {
+  std::vector<const char*> text = {
+      // clang-format off
+               "OpCapability Shader",
+          "%1 = OpExtInstImport \"GLSL.std.450\"",
+               "OpMemoryModel Logical GLSL450",
+               "OpEntryPoint GLCompute %2 \"main\"",
+               "OpExecutionModeId %2 LocalSizeId %uint_32 %uint_1 %uint_1_0",
+               "OpSource GLSL 450",
+               "OpDecorate %3 SpecId 18",
+               "OpDecorate %5 SpecId 19",
+       "%void = OpTypeVoid",
+          "%9 = OpTypeFunction %void",
+       "%uint = OpTypeInt 32 0",
+    "%uint_32 = OpSpecConstant %uint 32",
+     "%uint_1 = OpConstant %uint 1",
+   "%uint_1_0 = OpSpecConstant %uint 1",
+          "%2 = OpFunction %void None %9",
+         "%11 = OpLabel",
+               "OpReturn",
+               "OpFunctionEnd",
+      // clang-format on
+  };
+  std::string expected_disassembly = SelectiveJoin(text, [](const char* line) {
+    return std::string(line).find("SpecId") != std::string::npos;
+  });
+  std::vector<std::pair<const char*, const char*>> replacement_pairs = {
+      {"%uint_32 = OpSpecConstant %uint 32", "%uint_32 = OpConstant %uint 32"},
+      {"%uint_1_0 = OpSpecConstant %uint 1", "%uint_1_0 = OpConstant %uint 1"},
+  };
+  for (auto& p : replacement_pairs) {
+    EXPECT_TRUE(FindAndReplace(&expected_disassembly, p.first, p.second))
+        << "text:\n"
+        << expected_disassembly << "\n"
+        << "find_str:\n"
+        << p.first << "\n"
+        << "replace_str:\n"
+        << p.second << "\n";
+  }
+  SinglePassRunAndCheck<FreezeSpecConstantValuePass>(JoinAllInsts(text),
+                                                     expected_disassembly,
+                                                     /* skip_nop = */ true);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/function_test.cpp b/test/opt/function_test.cpp
index af25bac..34a0387 100644
--- a/test/opt/function_test.cpp
+++ b/test/opt/function_test.cpp
@@ -296,6 +296,65 @@
   EXPECT_EQ(1, non_semantic_ids.count(8));
 }
 
+TEST(FunctionTest, ReorderBlocksinStructuredOrder) {
+  // The spir-v has the basic block in a random order.  We want to reorder them
+  // in structured order.
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %100 "PSMain"
+               OpExecutionMode %PSMain OriginUpperLeft
+               OpSource HLSL 600
+        %int = OpTypeInt 32 1
+       %void = OpTypeVoid
+         %19 = OpTypeFunction %void
+       %bool = OpTypeBool
+%undef_bool = OpUndef %bool
+%undef_int = OpUndef %int
+        %100 = OpFunction %void None %19
+          %11 = OpLabel
+               OpSelectionMerge %10 None
+               OpSwitch %undef_int %3 0 %2 10 %1
+          %2 = OpLabel
+               OpReturn
+          %7 = OpLabel
+               OpBranch %8
+          %3 = OpLabel
+               OpBranch %4
+         %10 = OpLabel
+               OpReturn
+          %9 = OpLabel
+               OpBranch %10
+          %8 = OpLabel
+               OpBranch %4
+          %4 = OpLabel
+               OpLoopMerge %9 %8 None
+               OpBranchConditional %undef_bool %5 %9
+          %1 = OpLabel
+               OpReturn
+          %6 = OpLabel
+               OpBranch %7
+          %5 = OpLabel
+               OpSelectionMerge %7 None
+               OpBranchConditional %undef_bool %6 %7
+               OpFunctionEnd
+)";
+
+  std::unique_ptr<IRContext> ctx =
+      spvtools::BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                            SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ASSERT_TRUE(ctx);
+  auto* func = spvtest::GetFunction(ctx->module(), 100);
+  ASSERT_TRUE(func);
+  func->ReorderBasicBlocksInStructuredOrder();
+
+  auto first_block = func->begin();
+  auto bb = first_block;
+  for (++bb; bb != func->end(); ++bb) {
+    EXPECT_EQ(bb->id(), (bb - first_block));
+  }
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/graphics_robust_access_test.cpp b/test/opt/graphics_robust_access_test.cpp
index 3c23347..057b909 100644
--- a/test/opt/graphics_robust_access_test.cpp
+++ b/test/opt/graphics_robust_access_test.cpp
@@ -1242,7 +1242,7 @@
        ; CHECK-DAG: %int_9 = OpConstant %int 9
        ; CHECK-DAG: %[[intmax:\w+]] = OpConstant %int 2147483647
        ; CHECK: OpLabel
-       ; This access chain is manufatured only so we can compute the array length.
+       ; This access chain is manufactured only so we can compute the array length.
        ; Note that the %int_9 is already clamped
        ; CHECK: %[[ssbo_base:\w+]] = )" << ac
             << R"( %[[ssbo_p]] %var %int_9
diff --git a/test/opt/if_conversion_test.cpp b/test/opt/if_conversion_test.cpp
index dee15c3..dc7f831 100644
--- a/test/opt/if_conversion_test.cpp
+++ b/test/opt/if_conversion_test.cpp
@@ -328,6 +328,40 @@
   SinglePassRunAndCheck<IfConversion>(text, text, true, true);
 }
 
+TEST_F(IfConversionTest, DontFlatten) {
+  const std::string text = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %1 "func" %2
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%v2uint = OpTypeVector %uint 2
+%10 = OpConstantComposite %v2uint %uint_0 %uint_1
+%11 = OpConstantComposite %v2uint %uint_1 %uint_0
+%_ptr_Output_v2uint = OpTypePointer Output %v2uint
+%2 = OpVariable %_ptr_Output_v2uint Output
+%13 = OpTypeFunction %void
+%1 = OpFunction %void None %13
+%14 = OpLabel
+OpSelectionMerge %15 DontFlatten
+OpBranchConditional %true %16 %17
+%16 = OpLabel
+OpBranch %15
+%17 = OpLabel
+OpBranch %15
+%15 = OpLabel
+%18 = OpPhi %v2uint %10 %16 %11 %17
+OpStore %2 %18
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<IfConversion>(text, text, true, true);
+}
+
 TEST_F(IfConversionTest, LoopUntouched) {
   const std::string text = R"(OpCapability Shader
 OpMemoryModel Logical GLSL450
@@ -557,6 +591,33 @@
   SinglePassRunAndMatch<IfConversion>(text, true);
 }
 
+TEST_F(IfConversionTest, MultipleEdgesFromSameBlock) {
+  // If a block has two out going edges that go to the same block, then there
+  // can be an OpPhi instruction with fewer entries than the number of incoming
+  // edges.  This must be handled.
+  const std::string text = 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
+%true = OpConstantTrue %bool
+%true_0 = OpConstantTrue %bool
+%2 = OpFunction %void None %4
+%8 = OpLabel
+OpSelectionMerge %9 None
+OpBranchConditional %true_0 %9 %9
+%9 = OpLabel
+%10 = OpPhi %bool %true %8
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<IfConversion>(text, text, true, true);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/inline_test.cpp b/test/opt/inline_test.cpp
index d22f027..f3ff81c 100644
--- a/test/opt/inline_test.cpp
+++ b/test/opt/inline_test.cpp
@@ -1,5 +1,5 @@
-// Copyright (c) 2017 Valve Corporation
-// Copyright (c) 2017 LunarG Inc.
+// Copyright (c) 2017-2022 Valve Corporation
+// Copyright (c) 2017-2022 LunarG Inc.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -1533,9 +1533,11 @@
 %9 = OpLabel
 OpBranch %10
 %10 = OpLabel
-OpLoopMerge %12 %10 None
+OpLoopMerge %12 %15 None
 OpBranch %14
 %14 = OpLabel
+OpBranch %15
+%15 = OpLabel
 OpBranchConditional %true %10 %12
 %12 = OpLabel
 OpReturn
@@ -1694,7 +1696,7 @@
 OpBranch %13
 %13 = OpLabel
 %14 = OpCopyObject %bool %false
-OpLoopMerge %16 %13 None
+OpLoopMerge %16 %22 None
 OpBranch %17
 %17 = OpLabel
 %19 = OpCopyObject %bool %true
@@ -1702,6 +1704,8 @@
 OpBranchConditional %true %20 %20
 %20 = OpLabel
 %21 = OpPhi %bool %19 %17
+OpBranch %22
+%22 = OpLabel
 OpBranchConditional %true %13 %16
 %16 = OpLabel
 OpReturn
@@ -4238,7 +4242,7 @@
   // This shader causes CreateDebugInlinedAt to generate a constant.
   // Using the Constant manager would attempt to build the invalidated
   // DefUse manager during inlining which could cause an assert because
-  // the function is in an inconsistant state. This test verifies that
+  // the function is in an inconsistent state. This test verifies that
   // CreateDebugInlinedAt detects that the DefUse manager is disabled
   // and creates a duplicate constant safely without the Constant manager.
   //
@@ -4333,6 +4337,91 @@
   SinglePassRunAndMatch<InlineExhaustivePass>(text, true);
 }
 
+TEST_F(InlineTest, CreateDebugInlinedAtFromDebugLine) {
+  const std::string text = R"(OpCapability Shader
+; CHECK: OpExtInst %void %1 DebugInlinedAt %uint_6
+OpExtension "SPV_KHR_non_semantic_info"
+%1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%3 = OpString "debuginlinedat.frag"
+%8 = OpString "int"
+%15 = OpString "int function1() {
+	return 1;
+}
+
+void main() {
+	function1();
+}
+"
+%20 = OpString "function1"
+%21 = OpString ""
+%26 = OpString "main"
+OpName %main "main"
+OpName %src_main "src.main"
+OpName %bb_entry "bb.entry"
+OpName %function1 "function1"
+OpName %bb_entry_0 "bb.entry"
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%uint = OpTypeInt 32 0
+%uint_32 = OpConstant %uint 32
+%void = OpTypeVoid
+%uint_4 = OpConstant %uint 4
+%uint_0 = OpConstant %uint 0
+%uint_3 = OpConstant %uint 3
+%uint_1 = OpConstant %uint 1
+%uint_5 = OpConstant %uint 5
+%uint_17 = OpConstant %uint 17
+%uint_13 = OpConstant %uint 13
+%30 = OpTypeFunction %void
+%uint_7 = OpConstant %uint 7
+%uint_6 = OpConstant %uint 6
+%uint_2 = OpConstant %uint 2
+%uint_12 = OpConstant %uint 12
+%48 = OpTypeFunction %int
+%uint_9 = OpConstant %uint 9
+%10 = OpExtInst %void %1 DebugTypeBasic %8 %uint_32 %uint_4 %uint_0
+%13 = OpExtInst %void %1 DebugTypeFunction %uint_3 %10
+%16 = OpExtInst %void %1 DebugSource %3 %15
+%17 = OpExtInst %void %1 DebugCompilationUnit %uint_1 %uint_4 %16 %uint_5
+%22 = OpExtInst %void %1 DebugFunction %20 %13 %16 %uint_1 %uint_1 %17 %21 %uint_3 %uint_1
+%23 = OpExtInst %void %1 DebugLexicalBlock %16 %uint_1 %uint_17 %22
+%25 = OpExtInst %void %1 DebugTypeFunction %uint_3 %void
+%27 = OpExtInst %void %1 DebugFunction %26 %25 %16 %uint_5 %uint_1 %17 %21 %uint_3 %uint_5
+%28 = OpExtInst %void %1 DebugLexicalBlock %16 %uint_5 %uint_13 %27
+%main = OpFunction %void None %30
+%31 = OpLabel
+%32 = OpFunctionCall %void %src_main
+%34 = OpExtInst %void %1 DebugLine %16 %uint_7 %uint_7 %uint_1 %uint_1
+OpReturn
+OpFunctionEnd
+%src_main = OpFunction %void None %30
+%bb_entry = OpLabel
+%37 = OpExtInst %void %1 DebugScope %27
+%38 = OpExtInst %void %1 DebugFunctionDefinition %27 %src_main
+%39 = OpExtInst %void %1 DebugScope %28
+%40 = OpExtInst %void %1 DebugLine %16 %uint_6 %uint_6 %uint_2 %uint_12
+%44 = OpFunctionCall %int %function1
+%46 = OpExtInst %void %1 DebugScope %27
+%47 = OpExtInst %void %1 DebugLine %16 %uint_7 %uint_7 %uint_1 %uint_1
+OpReturn
+OpFunctionEnd
+%function1 = OpFunction %int None %48
+%bb_entry_0 = OpLabel
+%50 = OpExtInst %void %1 DebugScope %22
+%51 = OpExtInst %void %1 DebugFunctionDefinition %22 %function1
+%52 = OpExtInst %void %1 DebugScope %23
+%53 = OpExtInst %void %1 DebugLine %16 %uint_2 %uint_2 %uint_2 %uint_9
+OpReturnValue %int_1
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_2);
+  SinglePassRunAndMatch<InlineExhaustivePass>(text, true);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    Empty modules
diff --git a/test/opt/inst_bindless_check_test.cpp b/test/opt/inst_bindless_check_test.cpp
index 1a42329..90f40bc 100644
--- a/test/opt/inst_bindless_check_test.cpp
+++ b/test/opt/inst_bindless_check_test.cpp
@@ -1,5 +1,5 @@
-// Copyright (c) 2017 Valve Corporation
-// Copyright (c) 2017 LunarG Inc.
+// Copyright (c) 2017-2022 Valve Corporation
+// Copyright (c) 2017-2022 LunarG Inc.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -28,6 +28,297 @@
 
 using InstBindlessTest = PassTest<::testing::Test>;
 
+static const std::string kOutputDecorations = R"(
+; CHECK: OpDecorate [[output_buffer_type:%inst_bindless_OutputBuffer]] Block
+; CHECK: OpMemberDecorate [[output_buffer_type]] 0 Offset 0
+; CHECK: OpMemberDecorate [[output_buffer_type]] 1 Offset 4
+; CHECK: OpDecorate [[output_buffer_var:%\w+]] DescriptorSet 7
+; CHECK: OpDecorate [[output_buffer_var]] Binding 0
+)";
+
+static const std::string kOutputGlobals = R"(
+; CHECK: [[output_buffer_type]] = OpTypeStruct %uint %_runtimearr_uint
+; CHECK: [[output_ptr_type:%\w+]] = OpTypePointer StorageBuffer [[output_buffer_type]]
+; CHECK: [[output_buffer_var]] = OpVariable [[output_ptr_type]] StorageBuffer
+)";
+
+static const std::string kStreamWrite4Begin = R"(
+; CHECK: %inst_bindless_stream_write_4 = OpFunction %void None {{%\w+}}
+; CHECK: [[param_1:%\w+]] = OpFunctionParameter %uint
+; CHECK: [[param_2:%\w+]] = OpFunctionParameter %uint
+; CHECK: [[param_3:%\w+]] = OpFunctionParameter %uint
+; CHECK: [[param_4:%\w+]] = OpFunctionParameter %uint
+; CHECK: {{%\w+}} = OpLabel
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_0
+; CHECK: {{%\w+}} = OpAtomicIAdd %uint {{%\w+}} %uint_4 %uint_0 %uint_10
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_10
+; CHECK: {{%\w+}} = OpArrayLength %uint [[output_buffer_var]] 1
+; CHECK: {{%\w+}} = OpULessThanEqual %bool {{%\w+}} {{%\w+}}
+; CHECK: OpSelectionMerge {{%\w+}} None
+; CHECK: OpBranchConditional {{%\w+}} {{%\w+}} {{%\w+}}
+; CHECK: {{%\w+}} = OpLabel
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_0
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} %uint_10
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_1
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} %uint_23
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_2
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} [[param_1]]
+)";
+
+static const std::string kStreamWrite4End = R"(
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_7
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} [[param_2]]
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_8
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} [[param_3]]
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_9
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} [[param_4]]
+; CHECK: OpBranch {{%\w+}}
+; CHECK: {{%\w+}} = OpLabel
+; CHECK: OpReturn
+; CHECK: OpFunctionEnd
+)";
+
+// clang-format off
+static const std::string kStreamWrite4Frag = kStreamWrite4Begin + R"(
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_3
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} %uint_4
+; CHECK: {{%\w+}} = OpLoad %v4float %gl_FragCoord
+; CHECK: {{%\w+}} = OpBitcast %v4uint {{%\w+}}
+; CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 0
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_4
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} {{%\w+}}
+; CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 1
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_5
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} {{%\w+}}
+)" + kStreamWrite4End;
+
+static const std::string kStreamWrite4Tese = kStreamWrite4Begin + R"(
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_3
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} %uint_2
+; CHECK: {{%\w+}} = OpLoad %uint %gl_PrimitiveID
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_4
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} {{%\w+}}
+; CHECK: {{%\w+}} = OpLoad %v3float %gl_TessCoord
+; CHECK: {{%\w+}} = OpBitcast %v3uint {{%\w+}}
+; CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 0
+; CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 1
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_5
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} {{%\w+}}
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_6
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} {{%\w+}}
+)" + kStreamWrite4End;
+
+static const std::string kStreamWrite4Vert = kStreamWrite4Begin + R"(
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_3
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} %uint_0
+; CHECK: {{%\w+}} = OpLoad %uint %gl_VertexIndex
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_4
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} {{%\w+}}
+; CHECK: {{%\w+}} = OpLoad %uint %gl_InstanceIndex
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_5
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} {{%\w+}}
+)" + kStreamWrite4End;
+
+static const std::string kStreamWrite4Compute = kStreamWrite4Begin + R"(
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_3
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} %uint_5
+; CHECK: {{%\w+}} = OpLoad %v3uint %gl_GlobalInvocationID
+; CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 0
+; CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 1
+; CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 2
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_4
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} {{%\w+}}
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_5
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} {{%\w+}}
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_6
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} {{%\w+}}
+)" + kStreamWrite4End;
+
+static const std::string kStreamWrite4Ray = kStreamWrite4Begin + R"(
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_3
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} {{%\w+}}
+; CHECK: {{%\w+}} = OpLoad %v3uint {{%\w+}}
+; CHECK: {{%\w+}} = OpCompositeExtract %uint %90 0
+; CHECK: {{%\w+}} = OpCompositeExtract %uint %90 1
+; CHECK: {{%\w+}} = OpCompositeExtract %uint %90 2
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_4
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} {{%\w+}}
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_5
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} {{%\w+}}
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_6
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} {{%\w+}}
+)" + kStreamWrite4End;
+// clang-format on
+
+static const std::string kStreamWrite5Begin = R"(
+; CHECK: %inst_bindless_stream_write_5 = OpFunction %void None {{%\w+}}
+; CHECK: [[param_1:%\w+]] = OpFunctionParameter %uint
+; CHECK: [[param_2:%\w+]] = OpFunctionParameter %uint
+; CHECK: [[param_3:%\w+]] = OpFunctionParameter %uint
+; CHECK: [[param_4:%\w+]] = OpFunctionParameter %uint
+; CHECK: [[param_5:%\w+]] = OpFunctionParameter %uint
+; CHECK: {{%\w+}} = OpLabel
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_0
+; CHECK: {{%\w+}} = OpAtomicIAdd %uint {{%\w+}} %uint_4 %uint_0 %uint_11
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_11
+; CHECK: {{%\w+}} = OpArrayLength %uint [[output_buffer_var]] 1
+; CHECK: {{%\w+}} = OpULessThanEqual %bool {{%\w+}} {{%\w+}}
+; CHECK: OpSelectionMerge {{%\w+}} None
+; CHECK: OpBranchConditional {{%\w+}} {{%\w+}} {{%\w+}}
+; CHECK: {{%\w+}} = OpLabel
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_0
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} %uint_11
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_1
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} %uint_23
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_2
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} [[param_1]]
+)";
+
+static const std::string kStreamWrite5End = R"(
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_7
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} [[param_2]]
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_8
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} [[param_3]]
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_9
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} [[param_4]]
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_10
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} [[param_5]]
+; CHECK: OpBranch {{%\w+}}
+; CHECK: {{%\w+}} = OpLabel
+; CHECK: OpReturn
+; CHECK: OpFunctionEnd
+)";
+
+// clang-format off
+static const std::string kStreamWrite5Frag = kStreamWrite5Begin + R"(
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_3
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} %uint_4
+; CHECK: {{%\w+}} = OpLoad %v4float %gl_FragCoord
+; CHECK: {{%\w+}} = OpBitcast %v4uint {{%\w+}}
+; CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 0
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_4
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} {{%\w+}}
+; CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 1
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_5
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} {{%\w+}}
+)" + kStreamWrite4End;
+
+static const std::string kStreamWrite5Vert = kStreamWrite5Begin + R"(
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_3
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} %uint_0
+; CHECK: {{%\w+}} = OpLoad %uint %gl_VertexIndex
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_4
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} {{%\w+}}
+; CHECK: {{%\w+}} = OpLoad %uint %gl_InstanceIndex
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_5
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} {{%\w+}}
+)" + kStreamWrite5End;
+// clang-format on
+
+static const std::string kInputDecorations = R"(
+; CHECK: OpDecorate [[input_buffer_type:%inst_bindless_InputBuffer]] Block
+; CHECK: OpMemberDecorate [[input_buffer_type]] 0 Offset 0
+; CHECK: OpDecorate [[input_buffer_var:%\w+]] DescriptorSet 7
+; CHECK: OpDecorate [[input_buffer_var]] Binding 1
+)";
+
+static const std::string kInputGlobals = R"(
+; CHECK: [[input_buffer_type]] = OpTypeStruct %_runtimearr_uint
+; CHECK: [[input_ptr_type:%\w+]] = OpTypePointer StorageBuffer [[input_buffer_type]]
+; CHECK: [[input_buffer_var]] = OpVariable [[input_ptr_type]] StorageBuffer
+)";
+
+static const std::string kDirectRead2 = R"(
+; CHECK: %inst_bindless_direct_read_2 = OpFunction %uint None {{%\w+}}
+; CHECK: [[param_1:%\w+]] = OpFunctionParameter %uint
+; CHECK: [[param_2:%\w+]] = OpFunctionParameter %uint
+; CHECK: {{%\w+}} = OpLabel
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[input_buffer_var]] %uint_0 [[param_1]]
+; CHECK: {{%\w+}} = OpLoad %uint {{%\w+}}
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} [[param_2]]
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[input_buffer_var]] %uint_0 {{%\w+}}
+; CHECK: {{%\w+}} = OpLoad %uint {{%\w+}}
+; CHECK: OpReturnValue {{%\w+}}
+; CHECK: OpFunctionEnd
+)";
+
+static const std::string kDirectRead3 = R"(
+ ;CHECK: %inst_bindless_direct_read_3 = OpFunction %uint None {{%\w+}}
+; CHECK: [[param_1:%\w+]] = OpFunctionParameter %uint
+; CHECK: [[param_2:%\w+]] = OpFunctionParameter %uint
+; CHECK: [[param_3:%\w+]] = OpFunctionParameter %uint
+ ;CHECK: {{%\w+}} = OpLabel
+ ;CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[input_buffer_var]] %uint_0 [[param_1]]
+ ;CHECK: {{%\w+}} = OpLoad %uint {{%\w+}}
+ ;CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} [[param_2]]
+ ;CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[input_buffer_var]] %uint_0 {{%\w+}}
+ ;CHECK: {{%\w+}} = OpLoad %uint {{%\w+}}
+ ;CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} [[param_3]]
+ ;CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[input_buffer_var]] %uint_0 {{%\w+}}
+ ;CHECK: {{%\w+}} = OpLoad %uint {{%\w+}}
+ ;CHECK: OpReturnValue {{%\w+}}
+ ;CHECK: OpFunctionEnd
+)";
+
+static const std::string kDirectRead4 = R"(
+; CHECK: %inst_bindless_direct_read_4 = OpFunction %uint None {{%\w+}}
+; CHECK: [[param_1:%\w+]] = OpFunctionParameter %uint
+; CHECK: [[param_2:%\w+]] = OpFunctionParameter %uint
+; CHECK: [[param_3:%\w+]] = OpFunctionParameter %uint
+; CHECK: [[param_4:%\w+]] = OpFunctionParameter %uint
+; CHECK: {{%\w+}} = OpLabel
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[input_buffer_var]] %uint_0 [[param_1]]
+; CHECK: {{%\w+}} = OpLoad %uint {{%\w+}}
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} [[param_2]]
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[input_buffer_var]] %uint_0 {{%\w+}}
+; CHECK: {{%\w+}} = OpLoad %uint {{%\w+}}
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} [[param_3]]
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[input_buffer_var]] %uint_0 {{%\w+}}
+; CHECK: {{%\w+}} = OpLoad %uint {{%\w+}}
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} [[param_4]]
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[input_buffer_var]] %uint_0 {{%\w+}}
+; CHECK: {{%\w+}} = OpLoad %uint {{%\w+}}
+; CHECK: OpReturnValue {{%\w+}}
+; CHECK: OpFunctionEnd
+)";
+
 TEST_F(InstBindlessTest, NoInstrumentConstIndexInbounds) {
   // Texture2D g_tColor[128];
   //
@@ -215,27 +506,20 @@
   //   return ps_output;
   // }
 
-  const std::string entry_before =
-      R"(OpCapability Shader
+  const std::string entry = R"(
+OpCapability Shader
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+; CHECK: OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
 OpExecutionMode %MainPs OriginUpperLeft
 OpSource HLSL 500
 )";
 
-  const std::string entry_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
-)";
-
-  const std::string names_annots =
-      R"(OpName %MainPs "MainPs"
+  // clang-format off
+  const std::string names_annots = R"(
+OpName %MainPs "MainPs"
 OpName %g_tColor "g_tColor"
 OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
 OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
@@ -250,20 +534,13 @@
 OpDecorate %g_sAniso DescriptorSet 0
 OpDecorate %i_vTextureCoords Location 0
 OpDecorate %_entryPointOutput_vColor Location 0
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+)" + kOutputDecorations + R"(
+; CHECK: OpDecorate %gl_FragCoord BuiltIn FragCoord
 )";
 
-  const std::string new_annots =
-      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
-OpDecorate %57 Binding 0
-OpDecorate %gl_FragCoord BuiltIn FragCoord
-)";
-
-  const std::string consts_types_vars =
-      R"(%void = OpTypeVoid
+  const std::string consts_types_vars = R"(
+%void = OpTypeVoid
 %10 = OpTypeFunction %void
 %float = OpTypeFloat 32
 %v2float = OpTypeVector %float 2
@@ -289,36 +566,32 @@
 %i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+; CHECK: %uint_0 = OpConstant %uint 0
+; CHECK: %bool = OpTypeBool
+; CHECK: %48 = OpTypeFunction %void %uint %uint %uint %uint
+; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+)" + kOutputGlobals + R"(
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %uint_10 = OpConstant %uint 10
+; CHECK: %uint_4 = OpConstant %uint 4
+; CHECK: %uint_1 = OpConstant %uint 1
+; CHECK: %uint_23 = OpConstant %uint 23
+; CHECK: %uint_2 = OpConstant %uint 2
+; CHECK: %uint_3 = OpConstant %uint 3
+; CHECK: %_ptr_Input_v4float = OpTypePointer Input %v4float
+; CHECK: %gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+; CHECK: %v4uint = OpTypeVector %uint 4
+; CHECK: %uint_5 = OpConstant %uint 5
+; CHECK: %uint_7 = OpConstant %uint 7
+; CHECK: %uint_8 = OpConstant %uint 8
+; CHECK: %uint_9 = OpConstant %uint 9
+; CHECK: %uint_56 = OpConstant %uint 56
+; CHECK: %103 = OpConstantNull %v4float
 )";
+  // clang-format on
 
-  const std::string new_consts_types_vars =
-      R"(%uint_0 = OpConstant %uint 0
-%bool = OpTypeBool
-%48 = OpTypeFunction %void %uint %uint %uint %uint
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_55 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_55 = OpTypePointer StorageBuffer %_struct_55
-%57 = OpVariable %_ptr_StorageBuffer__struct_55 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_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_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%uint_9 = OpConstant %uint 9
-%uint_56 = OpConstant %uint 56
-%103 = OpConstantNull %v4float
-)";
-
-  const std::string func_pt1 =
-      R"(%MainPs = OpFunction %void None %10
+  const std::string main_func = R"(
+%MainPs = OpFunction %void None %10
 %29 = OpLabel
 %30 = OpLoad %v2float %i_vTextureCoords
 %31 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
@@ -327,93 +600,34 @@
 %34 = OpLoad %16 %33
 %35 = OpLoad %24 %g_sAniso
 %36 = OpSampledImage %26 %34 %35
-)";
-
-  const std::string func_pt2_before =
-      R"(%37 = OpImageSampleImplicitLod %v4float %36 %30
+%37 = OpImageSampleImplicitLod %v4float %36 %30
 OpStore %_entryPointOutput_vColor %37
+; CHECK-NOT: %37 = OpImageSampleImplicitLod %v4float %36 %30
+; CHECK-NOT: OpStore %_entryPointOutput_vColor %37
+; CHECK: %40 = OpULessThan %bool %32 %uint_128
+; CHECK: OpSelectionMerge %41 None
+; CHECK: OpBranchConditional %40 %42 %43
+; CHECK: %42 = OpLabel
+; CHECK: %44 = OpLoad %16 %33
+; CHECK: %45 = OpSampledImage %26 %44 %35
+; CHECK: %46 = OpImageSampleImplicitLod %v4float %45 %30
+; CHECK: OpBranch %41
+; CHECK: %43 = OpLabel
+; CHECK: %102 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_56 %uint_0 %32 %uint_128
+; CHECK: OpBranch %41
+; CHECK: %41 = OpLabel
+; CHECK: %104 = OpPhi %v4float %46 %42 %103 %43
+; CHECK: OpStore %_entryPointOutput_vColor %104
 OpReturn
 OpFunctionEnd
 )";
 
-  const std::string func_pt2_after =
-      R"(%40 = OpULessThan %bool %32 %uint_128
-OpSelectionMerge %41 None
-OpBranchConditional %40 %42 %43
-%42 = OpLabel
-%44 = OpLoad %16 %33
-%45 = OpSampledImage %26 %44 %35
-%46 = OpImageSampleImplicitLod %v4float %45 %30
-OpBranch %41
-%43 = OpLabel
-%102 = OpFunctionCall %void %47 %uint_56 %uint_0 %32 %uint_128
-OpBranch %41
-%41 = OpLabel
-%104 = OpPhi %v4float %46 %42 %103 %43
-OpStore %_entryPointOutput_vColor %104
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string output_func =
-      R"(%47 = OpFunction %void None %48
-%49 = OpFunctionParameter %uint
-%50 = OpFunctionParameter %uint
-%51 = OpFunctionParameter %uint
-%52 = OpFunctionParameter %uint
-%53 = OpLabel
-%59 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_0
-%62 = OpAtomicIAdd %uint %59 %uint_4 %uint_0 %uint_10
-%63 = OpIAdd %uint %62 %uint_10
-%64 = OpArrayLength %uint %57 1
-%65 = OpULessThanEqual %bool %63 %64
-OpSelectionMerge %66 None
-OpBranchConditional %65 %67 %66
-%67 = OpLabel
-%68 = OpIAdd %uint %62 %uint_0
-%70 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %68
-OpStore %70 %uint_10
-%72 = OpIAdd %uint %62 %uint_1
-%73 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %72
-OpStore %73 %uint_23
-%75 = OpIAdd %uint %62 %uint_2
-%76 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %75
-OpStore %76 %49
-%78 = OpIAdd %uint %62 %uint_3
-%79 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %78
-OpStore %79 %uint_4
-%82 = OpLoad %v4float %gl_FragCoord
-%84 = OpBitcast %v4uint %82
-%85 = OpCompositeExtract %uint %84 0
-%86 = OpIAdd %uint %62 %uint_4
-%87 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %86
-OpStore %87 %85
-%88 = OpCompositeExtract %uint %84 1
-%90 = OpIAdd %uint %62 %uint_5
-%91 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %90
-OpStore %91 %88
-%93 = OpIAdd %uint %62 %uint_7
-%94 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %93
-OpStore %94 %50
-%96 = OpIAdd %uint %62 %uint_8
-%97 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %96
-OpStore %97 %51
-%99 = OpIAdd %uint %62 %uint_9
-%100 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %99
-OpStore %100 %52
-OpBranch %66
-%66 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
+  const std::string output_func = kStreamWrite4Frag;
 
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBindlessCheckPass, uint32_t, uint32_t, bool, bool>(
-      entry_before + names_annots + consts_types_vars + func_pt1 +
-          func_pt2_before,
-      entry_after + names_annots + new_annots + consts_types_vars +
-          new_consts_types_vars + func_pt1 + func_pt2_after + output_func,
-      true, true, 7u, 23u, false, false, false, false, false);
+  SinglePassRunAndMatch<InstBindlessCheckPass, uint32_t, uint32_t, bool, bool>(
+      entry + names_annots + consts_types_vars + main_func + output_func, true,
+      7u, 23u, false, false, false, false, false);
 }
 
 TEST_F(InstBindlessTest, InstrumentMultipleInstructions) {
@@ -447,11 +661,14 @@
   //   return ps_output;
   // }
 
-  const std::string defs_before =
-      R"(OpCapability Shader
+  // clang-format off
+  const std::string defs = R"(
+OpCapability Shader
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+; CHECK: OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
 OpExecutionMode %MainPs OriginUpperLeft
 OpSource HLSL 500
 OpName %MainPs "MainPs"
@@ -470,6 +687,9 @@
 OpDecorate %g_sAniso DescriptorSet 0
 OpDecorate %i_vTextureCoords Location 0
 OpDecorate %_entryPointOutput_vColor Location 0
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+)" + kOutputDecorations + R"(
+; CHECK: OpDecorate %gl_FragCoord BuiltIn FragCoord
 %void = OpTypeVoid
 %10 = OpTypeFunction %void
 %float = OpTypeFloat 32
@@ -497,93 +717,32 @@
 %i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+; CHECK: %uint_0 = OpConstant %uint 0
+; CHECK: %bool = OpTypeBool
+; CHECK: %56 = OpTypeFunction %void %uint %uint %uint %uint
+; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+)" + kOutputGlobals + R"(
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %uint_10 = OpConstant %uint 10
+; CHECK: %uint_4 = OpConstant %uint 4
+; CHECK: %uint_1 = OpConstant %uint 1
+; CHECK: %uint_23 = OpConstant %uint 23
+; CHECK: %uint_2 = OpConstant %uint 2
+; CHECK: %uint_3 = OpConstant %uint 3
+; CHECK: %_ptr_Input_v4float = OpTypePointer Input %v4float
+; CHECK: %gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+; CHECK: %v4uint = OpTypeVector %uint 4
+; CHECK: %uint_5 = OpConstant %uint 5
+; CHECK: %uint_7 = OpConstant %uint 7
+; CHECK: %uint_8 = OpConstant %uint 8
+; CHECK: %uint_9 = OpConstant %uint 9
+; CHECK: %uint_58 = OpConstant %uint 58
+; CHECK: %111 = OpConstantNull %v4float
+; CHECK: %uint_64 = OpConstant %uint 64
 )";
+  // clang-format on
 
-  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
-OpMemberDecorate %PerViewConstantBuffer_t 1 Offset 4
-OpDecorate %PerViewConstantBuffer_t Block
-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
-OpDecorate %65 DescriptorSet 7
-OpDecorate %65 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
-%int_1 = OpConstant %int 1
-%17 = OpTypeImage %float 2D 0 0 0 1 Unknown
-%uint = OpTypeInt 32 0
-%uint_128 = OpConstant %uint 128
-%_arr_17_uint_128 = OpTypeArray %17 %uint_128
-%_ptr_UniformConstant__arr_17_uint_128 = OpTypePointer UniformConstant %_arr_17_uint_128
-%g_tColor = OpVariable %_ptr_UniformConstant__arr_17_uint_128 UniformConstant
-%PerViewConstantBuffer_t = OpTypeStruct %uint %uint
-%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
-%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
-%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
-%_ptr_UniformConstant_17 = OpTypePointer UniformConstant %17
-%25 = OpTypeSampler
-%_ptr_UniformConstant_25 = OpTypePointer UniformConstant %25
-%g_sAniso = OpVariable %_ptr_UniformConstant_25 UniformConstant
-%27 = OpTypeSampledImage %17
-%_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
-%56 = OpTypeFunction %void %uint %uint %uint %uint
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_63 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_63 = OpTypePointer StorageBuffer %_struct_63
-%65 = OpVariable %_ptr_StorageBuffer__struct_63 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_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_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%uint_9 = OpConstant %uint 9
-%uint_58 = OpConstant %uint 58
-%111 = OpConstantNull %v4float
-%uint_64 = OpConstant %uint 64
-)";
-
-  const std::string func_before =
+  const std::string main_func =
       R"(%MainPs = OpFunction %void None %10
 %30 = OpLabel
 %31 = OpLoad %v2float %i_vTextureCoords
@@ -594,6 +753,20 @@
 %36 = OpLoad %25 %g_sAniso
 %37 = OpSampledImage %27 %35 %36
 %38 = OpImageSampleImplicitLod %v4float %37 %31
+; CHECK-NOT: %38 = OpImageSampleImplicitLod %v4float %37 %31
+; CHECK: %48 = OpULessThan %bool %33 %uint_128
+; CHECK: OpSelectionMerge %49 None
+; CHECK: OpBranchConditional %48 %50 %51
+; CHECK: %50 = OpLabel
+; CHECK: %52 = OpLoad %17 %34
+; CHECK: %53 = OpSampledImage %27 %52 %36
+; CHECK: %54 = OpImageSampleImplicitLod %v4float %53 %31
+; CHECK: OpBranch %49
+; CHECK: %51 = OpLabel
+; CHECK: %110 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_58 %uint_0 %33 %uint_128
+; CHECK: OpBranch %49
+; CHECK: %49 = OpLabel
+; CHECK: %112 = OpPhi %v4float %54 %50 %111 %51
 %39 = OpAccessChain %_ptr_PushConstant_uint %_ %int_1
 %40 = OpLoad %uint %39
 %41 = OpAccessChain %_ptr_UniformConstant_17 %g_tColor %40
@@ -601,114 +774,33 @@
 %43 = OpSampledImage %27 %42 %36
 %44 = OpImageSampleImplicitLod %v4float %43 %31
 %45 = OpFAdd %v4float %38 %44
+; CHECK-NOT: %44 = OpImageSampleImplicitLod %v4float %43 %31
+; CHECK-NOT: %45 = OpFAdd %v4float %38 %44
+; CHECK: %113 = OpULessThan %bool %40 %uint_128
+; CHECK: OpSelectionMerge %114 None
+; CHECK: OpBranchConditional %113 %115 %116
+; CHECK: %115 = OpLabel
+; CHECK: %117 = OpLoad %17 %41
+; CHECK: %118 = OpSampledImage %27 %117 %36
+; CHECK: %119 = OpImageSampleImplicitLod %v4float %118 %31
+; CHECK: OpBranch %114
+; CHECK: %116 = OpLabel
+; CHECK: %121 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_64 %uint_0 %40 %uint_128
+; CHECK: OpBranch %114
+; CHECK: %114 = OpLabel
+; CHECK: %122 = OpPhi %v4float %119 %115 %111 %116
+; CHECK: %45 = OpFAdd %v4float %112 %122
 OpStore %_entryPointOutput_vColor %45
 OpReturn
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%MainPs = OpFunction %void None %10
-%30 = OpLabel
-%31 = OpLoad %v2float %i_vTextureCoords
-%32 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
-%33 = OpLoad %uint %32
-%34 = OpAccessChain %_ptr_UniformConstant_17 %g_tColor %33
-%35 = OpLoad %17 %34
-%36 = OpLoad %25 %g_sAniso
-%37 = OpSampledImage %27 %35 %36
-%48 = OpULessThan %bool %33 %uint_128
-OpSelectionMerge %49 None
-OpBranchConditional %48 %50 %51
-%50 = OpLabel
-%52 = OpLoad %17 %34
-%53 = OpSampledImage %27 %52 %36
-%54 = OpImageSampleImplicitLod %v4float %53 %31
-OpBranch %49
-%51 = OpLabel
-%110 = OpFunctionCall %void %55 %uint_58 %uint_0 %33 %uint_128
-OpBranch %49
-%49 = OpLabel
-%112 = OpPhi %v4float %54 %50 %111 %51
-%39 = OpAccessChain %_ptr_PushConstant_uint %_ %int_1
-%40 = OpLoad %uint %39
-%41 = OpAccessChain %_ptr_UniformConstant_17 %g_tColor %40
-%42 = OpLoad %17 %41
-%43 = OpSampledImage %27 %42 %36
-%113 = OpULessThan %bool %40 %uint_128
-OpSelectionMerge %114 None
-OpBranchConditional %113 %115 %116
-%115 = OpLabel
-%117 = OpLoad %17 %41
-%118 = OpSampledImage %27 %117 %36
-%119 = OpImageSampleImplicitLod %v4float %118 %31
-OpBranch %114
-%116 = OpLabel
-%121 = OpFunctionCall %void %55 %uint_64 %uint_0 %40 %uint_128
-OpBranch %114
-%114 = OpLabel
-%122 = OpPhi %v4float %119 %115 %111 %116
-%45 = OpFAdd %v4float %112 %122
-OpStore %_entryPointOutput_vColor %45
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string output_func =
-      R"(%55 = OpFunction %void None %56
-%57 = OpFunctionParameter %uint
-%58 = OpFunctionParameter %uint
-%59 = OpFunctionParameter %uint
-%60 = OpFunctionParameter %uint
-%61 = OpLabel
-%67 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_0
-%70 = OpAtomicIAdd %uint %67 %uint_4 %uint_0 %uint_10
-%71 = OpIAdd %uint %70 %uint_10
-%72 = OpArrayLength %uint %65 1
-%73 = OpULessThanEqual %bool %71 %72
-OpSelectionMerge %74 None
-OpBranchConditional %73 %75 %74
-%75 = OpLabel
-%76 = OpIAdd %uint %70 %uint_0
-%78 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %76
-OpStore %78 %uint_10
-%80 = OpIAdd %uint %70 %uint_1
-%81 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %80
-OpStore %81 %uint_23
-%83 = OpIAdd %uint %70 %uint_2
-%84 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %83
-OpStore %84 %57
-%86 = OpIAdd %uint %70 %uint_3
-%87 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %86
-OpStore %87 %uint_4
-%90 = OpLoad %v4float %gl_FragCoord
-%92 = OpBitcast %v4uint %90
-%93 = OpCompositeExtract %uint %92 0
-%94 = OpIAdd %uint %70 %uint_4
-%95 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %94
-OpStore %95 %93
-%96 = OpCompositeExtract %uint %92 1
-%98 = OpIAdd %uint %70 %uint_5
-%99 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %98
-OpStore %99 %96
-%101 = OpIAdd %uint %70 %uint_7
-%102 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %101
-OpStore %102 %58
-%104 = OpIAdd %uint %70 %uint_8
-%105 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %104
-OpStore %105 %59
-%107 = OpIAdd %uint %70 %uint_9
-%108 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %107
-OpStore %108 %60
-OpBranch %74
-%74 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
+  const std::string output_func = kStreamWrite4Frag;
 
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBindlessCheckPass>(
-      defs_before + func_before, defs_after + func_after + output_func, true,
-      true, 7u, 23u, false, false, false, false, false);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(defs + main_func + output_func,
+                                               true, 7u, 23u, false, false,
+                                               false, false, false);
 }
 
 TEST_F(InstBindlessTest, InstrumentOpImage) {
@@ -716,12 +808,15 @@
   // using OpImage. This test was created by editing the SPIR-V
   // from the Simple test.
 
-  const std::string defs_before =
-      R"(OpCapability Shader
+  // clang-format off
+  const std::string defs = R"(
+OpCapability Shader
 OpCapability StorageImageReadWithoutFormat
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+; CHECK: OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
 OpExecutionMode %MainPs OriginUpperLeft
 OpSource HLSL 500
 OpName %MainPs "MainPs"
@@ -737,6 +832,9 @@
 OpDecorate %PerViewConstantBuffer_t Block
 OpDecorate %i_vTextureCoords Location 0
 OpDecorate %_entryPointOutput_vColor Location 0
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+)" + kOutputDecorations + R"(
+; CHECK: OpDecorate %gl_FragCoord BuiltIn FragCoord
 %void = OpTypeVoid
 %3 = OpTypeFunction %void
 %float = OpTypeFloat 32
@@ -760,87 +858,32 @@
 %i_vTextureCoords = OpVariable %_ptr_Input_v2int Input
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+; CHECK: uint_0 = OpConstant %uint 0
+; CHECK: bool = OpTypeBool
+; CHECK: %86 = OpTypeFunction %void %uint %uint %uint %uint
+; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+)" + kOutputGlobals + R"(
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %uint_10 = OpConstant %uint 10
+; CHECK: %uint_4 = OpConstant %uint 4
+; CHECK: %uint_1 = OpConstant %uint 1
+; CHECK: %uint_23 = OpConstant %uint 23
+; CHECK: %uint_2 = OpConstant %uint 2
+; CHECK: %uint_3 = OpConstant %uint 3
+; CHECK: %_ptr_Input_v4float = OpTypePointer Input %v4float
+; CHECK: %gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+; CHECK: %v4uint = OpTypeVector %uint 4
+; CHECK: %uint_5 = OpConstant %uint 5
+; CHECK: %uint_7 = OpConstant %uint 7
+; CHECK: %uint_8 = OpConstant %uint 8
+; CHECK: %uint_9 = OpConstant %uint 9
+; CHECK: %uint_51 = OpConstant %uint 51
+; CHECK: %141 = OpConstantNull %v4float
 )";
+  // clang-format on
 
-  const std::string defs_after =
-      R"(OpCapability Shader
-OpCapability StorageImageReadWithoutFormat
-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 %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 %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
-OpDecorate %53 DescriptorSet 7
-OpDecorate %53 Binding 0
-OpDecorate %gl_FragCoord BuiltIn FragCoord
-%void = OpTypeVoid
-%9 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%v4float = OpTypeVector %float 4
-%int = OpTypeInt 32 1
-%v2int = OpTypeVector %int 2
-%int_0 = OpConstant %int 0
-%15 = OpTypeImage %float 2D 0 0 0 0 Unknown
-%uint = OpTypeInt 32 0
-%uint_128 = OpConstant %uint 128
-%18 = OpTypeSampledImage %15
-%_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
-%_ptr_Input_v2int = OpTypePointer Input %v2int
-%i_vTextureCoords = OpVariable %_ptr_Input_v2int Input
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
-%uint_0 = OpConstant %uint 0
-%bool = OpTypeBool
-%44 = OpTypeFunction %void %uint %uint %uint %uint
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_51 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_51 = OpTypePointer StorageBuffer %_struct_51
-%53 = OpVariable %_ptr_StorageBuffer__struct_51 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_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_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%uint_9 = OpConstant %uint 9
-%uint_51 = OpConstant %uint 51
-%99 = OpConstantNull %v4float
-)";
-
-  const std::string func_before =
-      R"(%MainPs = OpFunction %void None %3
+  const std::string main_func = R"(
+%MainPs = OpFunction %void None %3
 %5 = OpLabel
 %53 = OpLoad %v2int %i_vTextureCoords
 %63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
@@ -850,93 +893,32 @@
 %75 = OpImage %20 %66
 %71 = OpImageRead %v4float %75 %53
 OpStore %_entryPointOutput_vColor %71
+; CHECK-NOT: %71 = OpImageRead %v4float %75 %53
+; CHECK-NOT: OpStore %_entryPointOutput_vColor %71
+; CHECK: %78 = OpULessThan %bool %64 %uint_128
+; CHECK: OpSelectionMerge %79 None
+; CHECK: OpBranchConditional %78 %80 %81
+; CHECK: %80 = OpLabel
+; CHECK: %82 = OpLoad %39 %65
+; CHECK: %83 = OpImage %20 %82
+; CHECK: %84 = OpImageRead %v4float %83 %53
+; CHECK: OpBranch %79
+; CHECK: %81 = OpLabel
+; CHECK: %140 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_51 %uint_0 %64 %uint_128
+; CHECK: OpBranch %79
+; CHECK: %79 = OpLabel
+; CHECK: %142 = OpPhi %v4float %84 %80 %141 %81
+; CHECK: OpStore %_entryPointOutput_vColor %142
 OpReturn
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%MainPs = OpFunction %void None %9
-%26 = OpLabel
-%27 = OpLoad %v2int %i_vTextureCoords
-%28 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
-%29 = OpLoad %uint %28
-%30 = OpAccessChain %_ptr_UniformConstant_18 %g_tColor %29
-%31 = OpLoad %18 %30
-%32 = OpImage %15 %31
-%36 = OpULessThan %bool %29 %uint_128
-OpSelectionMerge %37 None
-OpBranchConditional %36 %38 %39
-%38 = OpLabel
-%40 = OpLoad %18 %30
-%41 = OpImage %15 %40
-%42 = OpImageRead %v4float %41 %27
-OpBranch %37
-%39 = OpLabel
-%98 = OpFunctionCall %void %43 %uint_51 %uint_0 %29 %uint_128
-OpBranch %37
-%37 = OpLabel
-%100 = OpPhi %v4float %42 %38 %99 %39
-OpStore %_entryPointOutput_vColor %100
-OpReturn
-OpFunctionEnd
-)";
+  const std::string output_func = kStreamWrite4Frag;
 
-  const std::string output_func =
-      R"(%43 = OpFunction %void None %44
-%45 = OpFunctionParameter %uint
-%46 = OpFunctionParameter %uint
-%47 = OpFunctionParameter %uint
-%48 = OpFunctionParameter %uint
-%49 = OpLabel
-%55 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_0
-%58 = OpAtomicIAdd %uint %55 %uint_4 %uint_0 %uint_10
-%59 = OpIAdd %uint %58 %uint_10
-%60 = OpArrayLength %uint %53 1
-%61 = OpULessThanEqual %bool %59 %60
-OpSelectionMerge %62 None
-OpBranchConditional %61 %63 %62
-%63 = OpLabel
-%64 = OpIAdd %uint %58 %uint_0
-%66 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %64
-OpStore %66 %uint_10
-%68 = OpIAdd %uint %58 %uint_1
-%69 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %68
-OpStore %69 %uint_23
-%71 = OpIAdd %uint %58 %uint_2
-%72 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %71
-OpStore %72 %45
-%74 = OpIAdd %uint %58 %uint_3
-%75 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %74
-OpStore %75 %uint_4
-%78 = OpLoad %v4float %gl_FragCoord
-%80 = OpBitcast %v4uint %78
-%81 = OpCompositeExtract %uint %80 0
-%82 = OpIAdd %uint %58 %uint_4
-%83 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %82
-OpStore %83 %81
-%84 = OpCompositeExtract %uint %80 1
-%86 = OpIAdd %uint %58 %uint_5
-%87 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %86
-OpStore %87 %84
-%89 = OpIAdd %uint %58 %uint_7
-%90 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %89
-OpStore %90 %46
-%92 = OpIAdd %uint %58 %uint_8
-%93 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %92
-OpStore %93 %47
-%95 = OpIAdd %uint %58 %uint_9
-%96 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %95
-OpStore %96 %48
-OpBranch %62
-%62 = 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, 7u, 23u, false, false, false, false, false);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(defs + main_func + output_func,
+                                               true, 7u, 23u, false, false,
+                                               false, false, false);
 }
 
 TEST_F(InstBindlessTest, InstrumentSampledImage) {
@@ -944,11 +926,14 @@
   // using sampled image. This test was created by editing the SPIR-V
   // from the Simple test.
 
-  const std::string defs_before =
-      R"(OpCapability Shader
+  // clang-format off
+  const std::string defs = R"(
+OpCapability Shader
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+; CHECK: OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
 OpExecutionMode %MainPs OriginUpperLeft
 OpSource HLSL 500
 OpName %MainPs "MainPs"
@@ -964,6 +949,8 @@
 OpDecorate %PerViewConstantBuffer_t Block
 OpDecorate %i_vTextureCoords Location 0
 OpDecorate %_entryPointOutput_vColor Location 0
+)" + kOutputDecorations + R"(
+; CHECK: OpDecorate %gl_FragCoord BuiltIn FragCoord
 %void = OpTypeVoid
 %3 = OpTypeFunction %void
 %float = OpTypeFloat 32
@@ -987,86 +974,32 @@
 %i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+; CHECK: uint_0 = OpConstant %uint 0
+; CHECK: bool = OpTypeBool
+; CHECK: %81 = OpTypeFunction %void %uint %uint %uint %uint
+; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+)" + kOutputGlobals + R"(
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %uint_10 = OpConstant %uint 10
+; CHECK: %uint_4 = OpConstant %uint 4
+; CHECK: %uint_1 = OpConstant %uint 1
+; CHECK: %uint_23 = OpConstant %uint 23
+; CHECK: %uint_2 = OpConstant %uint 2
+; CHECK: %uint_3 = OpConstant %uint 3
+; CHECK: %_ptr_Input_v4float = OpTypePointer Input %v4float
+; CHECK: %gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+; CHECK: %v4uint = OpTypeVector %uint 4
+; CHECK: %uint_5 = OpConstant %uint 5
+; CHECK: %uint_7 = OpConstant %uint 7
+; CHECK: %uint_8 = OpConstant %uint 8
+; CHECK: %uint_9 = OpConstant %uint 9
+; CHECK: %uint_49 = OpConstant %uint 49
+; CHECK: %136 = OpConstantNull %v4float
 )";
+  // clang-format on
 
-  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 %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 %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
-OpDecorate %51 DescriptorSet 7
-OpDecorate %51 Binding 0
-OpDecorate %gl_FragCoord BuiltIn FragCoord
-%void = OpTypeVoid
-%9 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%v2float = OpTypeVector %float 2
-%v4float = OpTypeVector %float 4
-%int = OpTypeInt 32 1
-%int_0 = OpConstant %int 0
-%15 = OpTypeImage %float 2D 0 0 0 1 Unknown
-%uint = OpTypeInt 32 0
-%uint_128 = OpConstant %uint 128
-%18 = OpTypeSampledImage %15
-%_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
-%_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
-%42 = OpTypeFunction %void %uint %uint %uint %uint
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_49 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_49 = OpTypePointer StorageBuffer %_struct_49
-%51 = OpVariable %_ptr_StorageBuffer__struct_49 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_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_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%uint_9 = OpConstant %uint 9
-%uint_49 = OpConstant %uint 49
-%97 = OpConstantNull %v4float
-)";
-
-  const std::string func_before =
-      R"(%MainPs = OpFunction %void None %3
+  const std::string main_func = R"(
+%MainPs = OpFunction %void None %3
 %5 = OpLabel
 %53 = OpLoad %v2float %i_vTextureCoords
 %63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
@@ -1075,91 +1008,31 @@
 %66 = OpLoad %39 %65
 %71 = OpImageSampleImplicitLod %v4float %66 %53
 OpStore %_entryPointOutput_vColor %71
+; CHECK-NOT: %71 = OpImageSampleImplicitLod %v4float %66 %53
+; CHECK-NOT: OpStore %_entryPointOutput_vColor %71
+; CHECK: %74 = OpULessThan %bool %64 %uint_128
+; CHECK: OpSelectionMerge %75 None
+; CHECK: OpBranchConditional %74 %76 %77
+; CHECK: %76 = OpLabel
+; CHECK: %78 = OpLoad %39 %65
+; CHECK: %79 = OpImageSampleImplicitLod %v4float %78 %53
+; CHECK: OpBranch %75
+; CHECK: %77 = OpLabel
+; CHECK: %135 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_49 %uint_0 %64 %uint_128
+; CHECK: OpBranch %75
+; CHECK: %75 = OpLabel
+; CHECK: %137 = OpPhi %v4float %79 %76 %136 %77
+; CHECK: OpStore %_entryPointOutput_vColor %137
 OpReturn
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%MainPs = OpFunction %void None %9
-%26 = OpLabel
-%27 = OpLoad %v2float %i_vTextureCoords
-%28 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
-%29 = OpLoad %uint %28
-%30 = OpAccessChain %_ptr_UniformConstant_18 %g_tColor %29
-%31 = OpLoad %18 %30
-%35 = OpULessThan %bool %29 %uint_128
-OpSelectionMerge %36 None
-OpBranchConditional %35 %37 %38
-%37 = OpLabel
-%39 = OpLoad %18 %30
-%40 = OpImageSampleImplicitLod %v4float %39 %27
-OpBranch %36
-%38 = OpLabel
-%96 = OpFunctionCall %void %41 %uint_49 %uint_0 %29 %uint_128
-OpBranch %36
-%36 = OpLabel
-%98 = OpPhi %v4float %40 %37 %97 %38
-OpStore %_entryPointOutput_vColor %98
-OpReturn
-OpFunctionEnd
-)";
+  const std::string output_func = kStreamWrite4Frag;
 
-  const std::string output_func =
-      R"(%41 = OpFunction %void None %42
-%43 = OpFunctionParameter %uint
-%44 = OpFunctionParameter %uint
-%45 = OpFunctionParameter %uint
-%46 = OpFunctionParameter %uint
-%47 = OpLabel
-%53 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_0
-%56 = OpAtomicIAdd %uint %53 %uint_4 %uint_0 %uint_10
-%57 = OpIAdd %uint %56 %uint_10
-%58 = OpArrayLength %uint %51 1
-%59 = OpULessThanEqual %bool %57 %58
-OpSelectionMerge %60 None
-OpBranchConditional %59 %61 %60
-%61 = OpLabel
-%62 = OpIAdd %uint %56 %uint_0
-%64 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %62
-OpStore %64 %uint_10
-%66 = OpIAdd %uint %56 %uint_1
-%67 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %66
-OpStore %67 %uint_23
-%69 = OpIAdd %uint %56 %uint_2
-%70 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %69
-OpStore %70 %43
-%72 = OpIAdd %uint %56 %uint_3
-%73 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %72
-OpStore %73 %uint_4
-%76 = OpLoad %v4float %gl_FragCoord
-%78 = OpBitcast %v4uint %76
-%79 = OpCompositeExtract %uint %78 0
-%80 = OpIAdd %uint %56 %uint_4
-%81 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %80
-OpStore %81 %79
-%82 = OpCompositeExtract %uint %78 1
-%84 = OpIAdd %uint %56 %uint_5
-%85 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %84
-OpStore %85 %82
-%87 = OpIAdd %uint %56 %uint_7
-%88 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %87
-OpStore %88 %44
-%90 = OpIAdd %uint %56 %uint_8
-%91 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %90
-OpStore %91 %45
-%93 = OpIAdd %uint %56 %uint_9
-%94 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %93
-OpStore %94 %46
-OpBranch %60
-%60 = 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, 7u, 23u, false, false, false, false, false);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(defs + main_func + output_func,
+                                               true, 7u, 23u, false, false,
+                                               false, false, false);
 }
 
 TEST_F(InstBindlessTest, InstrumentImageWrite) {
@@ -1167,12 +1040,15 @@
   // doing bindless image write. This test was created by editing the SPIR-V
   // from the Simple test.
 
-  const std::string defs_before =
-      R"(OpCapability Shader
+  // clang-format off
+  const std::string defs = R"(
+OpCapability Shader
 OpCapability StorageImageWriteWithoutFormat
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+; CHECK: OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
 OpExecutionMode %MainPs OriginUpperLeft
 OpSource HLSL 500
 OpName %MainPs "MainPs"
@@ -1188,6 +1064,9 @@
 OpDecorate %PerViewConstantBuffer_t Block
 OpDecorate %i_vTextureCoords Location 0
 OpDecorate %_entryPointOutput_vColor Location 0
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+)" + kOutputDecorations + R"(
+; CHECK: OpDecorate %gl_FragCoord BuiltIn FragCoord
 %void = OpTypeVoid
 %3 = OpTypeFunction %void
 %float = OpTypeFloat 32
@@ -1212,87 +1091,31 @@
 %i_vTextureCoords = OpVariable %_ptr_Input_v2int Input
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+; CHECK: uint_0 = OpConstant %uint 0
+; CHECK: bool = OpTypeBool
+; CHECK: %41 = OpTypeFunction %void %uint %uint %uint %uint
+; CHECK: _runtimearr_uint = OpTypeRuntimeArray %uint
+)" + kOutputGlobals + R"(
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %uint_10 = OpConstant %uint 10
+; CHECK: %uint_4 = OpConstant %uint 4
+; CHECK: %uint_1 = OpConstant %uint 1
+; CHECK: %uint_23 = OpConstant %uint 23
+; CHECK: %uint_2 = OpConstant %uint 2
+; CHECK: %uint_3 = OpConstant %uint 3
+; CHECK: %_ptr_Input_v4float = OpTypePointer Input %v4float
+; CHECK: %gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+; CHECK: %v4uint = OpTypeVector %uint 4
+; CHECK: %uint_5 = OpConstant %uint 5
+; CHECK: %uint_7 = OpConstant %uint 7
+; CHECK: %uint_8 = OpConstant %uint 8
+; CHECK: %uint_9 = OpConstant %uint 9
+; CHECK: %uint_51 = OpConstant %uint 51
 )";
+  // clang-format on
 
-  const std::string defs_after =
-      R"(OpCapability Shader
-OpCapability StorageImageWriteWithoutFormat
-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 %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 %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
-OpDecorate %50 DescriptorSet 7
-OpDecorate %50 Binding 0
-OpDecorate %gl_FragCoord BuiltIn FragCoord
-%void = OpTypeVoid
-%9 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%v2float = OpTypeVector %float 2
-%v4float = OpTypeVector %float 4
-%int = OpTypeInt 32 1
-%v2int = OpTypeVector %int 2
-%int_0 = OpConstant %int 0
-%16 = OpTypeImage %float 2D 0 0 0 0 Unknown
-%uint = OpTypeInt 32 0
-%uint_128 = OpConstant %uint 128
-%19 = OpConstantNull %v4float
-%_arr_16_uint_128 = OpTypeArray %16 %uint_128
-%_ptr_UniformConstant__arr_16_uint_128 = OpTypePointer UniformConstant %_arr_16_uint_128
-%g_tColor = OpVariable %_ptr_UniformConstant__arr_16_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_16 = OpTypePointer UniformConstant %16
-%_ptr_Input_v2int = OpTypePointer Input %v2int
-%i_vTextureCoords = OpVariable %_ptr_Input_v2int Input
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
-%uint_0 = OpConstant %uint 0
-%bool = OpTypeBool
-%41 = OpTypeFunction %void %uint %uint %uint %uint
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_48 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_48 = OpTypePointer StorageBuffer %_struct_48
-%50 = OpVariable %_ptr_StorageBuffer__struct_48 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_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_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%uint_9 = OpConstant %uint 9
-%uint_51 = OpConstant %uint 51
-)";
-
-  const std::string func_before =
-      R"(%MainPs = OpFunction %void None %3
+  const std::string main_func = R"(
+%MainPs = OpFunction %void None %3
 %5 = OpLabel
 %53 = OpLoad %v2int %i_vTextureCoords
 %63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
@@ -1301,90 +1124,30 @@
 %66 = OpLoad %20 %65
 OpImageWrite %66 %53 %80
 OpStore %_entryPointOutput_vColor %80
+; CHECK-NOT: OpImageWrite %66 %53 %80
+; CHECK-NOT: OpStore %_entryPointOutput_vColor %80
+; CHECK: %35 = OpULessThan %bool %30 %uint_128
+; CHECK: OpSelectionMerge %36 None
+; CHECK: OpBranchConditional %35 %37 %38
+; CHECK: %37 = OpLabel
+; CHECK: %39 = OpLoad %16 %31
+; CHECK: OpImageWrite %39 %28 %19
+; CHECK: OpBranch %36
+; CHECK: %38 = OpLabel
+; CHECK: %95 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_51 %uint_0 %30 %uint_128
+; CHECK: OpBranch %36
+; CHECK: %36 = OpLabel
+; CHECK: OpStore %_entryPointOutput_vColor %19
 OpReturn
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%MainPs = OpFunction %void None %9
-%27 = OpLabel
-%28 = OpLoad %v2int %i_vTextureCoords
-%29 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
-%30 = OpLoad %uint %29
-%31 = OpAccessChain %_ptr_UniformConstant_16 %g_tColor %30
-%32 = OpLoad %16 %31
-%35 = OpULessThan %bool %30 %uint_128
-OpSelectionMerge %36 None
-OpBranchConditional %35 %37 %38
-%37 = OpLabel
-%39 = OpLoad %16 %31
-OpImageWrite %39 %28 %19
-OpBranch %36
-%38 = OpLabel
-%95 = OpFunctionCall %void %40 %uint_51 %uint_0 %30 %uint_128
-OpBranch %36
-%36 = OpLabel
-OpStore %_entryPointOutput_vColor %19
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string output_func =
-      R"(%40 = OpFunction %void None %41
-%42 = OpFunctionParameter %uint
-%43 = OpFunctionParameter %uint
-%44 = OpFunctionParameter %uint
-%45 = OpFunctionParameter %uint
-%46 = OpLabel
-%52 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_0
-%55 = OpAtomicIAdd %uint %52 %uint_4 %uint_0 %uint_10
-%56 = OpIAdd %uint %55 %uint_10
-%57 = OpArrayLength %uint %50 1
-%58 = OpULessThanEqual %bool %56 %57
-OpSelectionMerge %59 None
-OpBranchConditional %58 %60 %59
-%60 = OpLabel
-%61 = OpIAdd %uint %55 %uint_0
-%63 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %61
-OpStore %63 %uint_10
-%65 = OpIAdd %uint %55 %uint_1
-%66 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %65
-OpStore %66 %uint_23
-%68 = OpIAdd %uint %55 %uint_2
-%69 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %68
-OpStore %69 %42
-%71 = OpIAdd %uint %55 %uint_3
-%72 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %71
-OpStore %72 %uint_4
-%75 = OpLoad %v4float %gl_FragCoord
-%77 = OpBitcast %v4uint %75
-%78 = OpCompositeExtract %uint %77 0
-%79 = OpIAdd %uint %55 %uint_4
-%80 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %79
-OpStore %80 %78
-%81 = OpCompositeExtract %uint %77 1
-%83 = OpIAdd %uint %55 %uint_5
-%84 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %83
-OpStore %84 %81
-%86 = OpIAdd %uint %55 %uint_7
-%87 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %86
-OpStore %87 %43
-%89 = OpIAdd %uint %55 %uint_8
-%90 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %89
-OpStore %90 %44
-%92 = OpIAdd %uint %55 %uint_9
-%93 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %92
-OpStore %93 %45
-OpBranch %59
-%59 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
+  const std::string output_func = kStreamWrite4Frag;
 
   // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBindlessCheckPass>(
-      defs_before + func_before, defs_after + func_after + output_func, true,
-      true, 7u, 23u, false, false, false, false, false);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(defs + main_func + output_func,
+                                               true, 7u, 23u, false, false,
+                                               false, false, false);
 }
 
 TEST_F(InstBindlessTest, InstrumentVertexSimple) {
@@ -1392,9 +1155,11 @@
   // doing bindless image write. This test was created by editing the SPIR-V
   // from the Simple test.
 
-  const std::string defs_before =
-      R"(OpCapability Shader
+  // clang-format off
+  const std::string defs = R"(
+OpCapability Shader
 OpCapability Sampled1D
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
 OpEntryPoint Vertex %main "main" %_ %coords2D
@@ -1413,6 +1178,10 @@
 OpMemberName %foo 0 "g_idx"
 OpName %__0 ""
 OpName %coords2D "coords2D"
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+)" + kOutputDecorations + R"(
+; CHECK: OpDecorate %gl_VertexIndex BuiltIn VertexIndex
+; CHECK: OpDecorate %gl_InstanceIndex BuiltIn InstanceIndex
 OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
 OpMemberDecorate %gl_PerVertex 1 BuiltIn PointSize
 OpMemberDecorate %gl_PerVertex 2 BuiltIn ClipDistance
@@ -1455,106 +1224,31 @@
 %v2float = OpTypeVector %float 2
 %_ptr_Input_v2float = OpTypePointer Input %v2float
 %coords2D = OpVariable %_ptr_Input_v2float Input
+; CHECK: %uint_0 = OpConstant %uint 0
+; CHECK: %bool = OpTypeBool
+; CHECK: %54 = OpTypeFunction %void %uint %uint %uint %uint
+; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+)" + kOutputGlobals + R"(
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %uint_10 = OpConstant %uint 10
+; CHECK: %uint_4 = OpConstant %uint 4
+; CHECK: %uint_23 = OpConstant %uint 23
+; CHECK: %uint_2 = OpConstant %uint 2
+; CHECK: %uint_3 = OpConstant %uint 3
+; CHECK: %_ptr_Input_uint = OpTypePointer Input %uint
+; CHECK: %gl_VertexIndex = OpVariable %_ptr_Input_uint Input
+; CHECK: %gl_InstanceIndex = OpVariable %_ptr_Input_uint Input
+; CHECK: %uint_5 = OpConstant %uint 5
+; CHECK: %uint_7 = OpConstant %uint 7
+; CHECK: %uint_8 = OpConstant %uint 8
+; CHECK: %uint_9 = OpConstant %uint 9
+; CHECK: %uint_74 = OpConstant %uint 74
+; CHECK: %106 = OpConstantNull %v4float
 )";
+  // clang-format on
 
-  const std::string defs_after =
-      R"(OpCapability Shader
-OpCapability Sampled1D
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Vertex %main "main" %_ %coords2D %gl_VertexIndex %gl_InstanceIndex
-OpSource GLSL 450
-OpName %main "main"
-OpName %lod "lod"
-OpName %coords1D "coords1D"
-OpName %gl_PerVertex "gl_PerVertex"
-OpMemberName %gl_PerVertex 0 "gl_Position"
-OpMemberName %gl_PerVertex 1 "gl_PointSize"
-OpMemberName %gl_PerVertex 2 "gl_ClipDistance"
-OpMemberName %gl_PerVertex 3 "gl_CullDistance"
-OpName %_ ""
-OpName %texSampler1D "texSampler1D"
-OpName %foo "foo"
-OpMemberName %foo 0 "g_idx"
-OpName %__0 ""
-OpName %coords2D "coords2D"
-OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
-OpMemberDecorate %gl_PerVertex 1 BuiltIn PointSize
-OpMemberDecorate %gl_PerVertex 2 BuiltIn ClipDistance
-OpMemberDecorate %gl_PerVertex 3 BuiltIn CullDistance
-OpDecorate %gl_PerVertex Block
-OpDecorate %texSampler1D DescriptorSet 0
-OpDecorate %texSampler1D Binding 3
-OpMemberDecorate %foo 0 Offset 0
-OpDecorate %foo Block
-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_VertexIndex BuiltIn VertexIndex
-OpDecorate %gl_InstanceIndex BuiltIn InstanceIndex
-%void = OpTypeVoid
-%12 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%_ptr_Function_float = OpTypePointer Function %float
-%float_3 = OpConstant %float 3
-%float_1_78900003 = OpConstant %float 1.78900003
-%v4float = OpTypeVector %float 4
-%uint = OpTypeInt 32 0
-%uint_1 = OpConstant %uint 1
-%_arr_float_uint_1 = OpTypeArray %float %uint_1
-%gl_PerVertex = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1
-%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
-%_ = OpVariable %_ptr_Output_gl_PerVertex Output
-%int = OpTypeInt 32 1
-%int_0 = OpConstant %int 0
-%24 = OpTypeImage %float 1D 0 0 0 1 Unknown
-%25 = OpTypeSampledImage %24
-%uint_128 = OpConstant %uint 128
-%_arr_25_uint_128 = OpTypeArray %25 %uint_128
-%_ptr_UniformConstant__arr_25_uint_128 = OpTypePointer UniformConstant %_arr_25_uint_128
-%texSampler1D = OpVariable %_ptr_UniformConstant__arr_25_uint_128 UniformConstant
-%foo = OpTypeStruct %int
-%_ptr_Uniform_foo = OpTypePointer Uniform %foo
-%__0 = OpVariable %_ptr_Uniform_foo Uniform
-%_ptr_Uniform_int = OpTypePointer Uniform %int
-%_ptr_UniformConstant_25 = OpTypePointer UniformConstant %25
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%v2float = OpTypeVector %float 2
-%_ptr_Input_v2float = OpTypePointer Input %v2float
-%coords2D = OpVariable %_ptr_Input_v2float Input
-%uint_0 = OpConstant %uint 0
-%bool = OpTypeBool
-%54 = OpTypeFunction %void %uint %uint %uint %uint
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_61 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_61 = OpTypePointer StorageBuffer %_struct_61
-%63 = OpVariable %_ptr_StorageBuffer__struct_61 StorageBuffer
-%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-%uint_10 = OpConstant %uint 10
-%uint_4 = OpConstant %uint 4
-%uint_23 = OpConstant %uint 23
-%uint_2 = OpConstant %uint 2
-%uint_3 = OpConstant %uint 3
-%_ptr_Input_uint = OpTypePointer Input %uint
-%gl_VertexIndex = OpVariable %_ptr_Input_uint Input
-%gl_InstanceIndex = OpVariable %_ptr_Input_uint Input
-%uint_5 = OpConstant %uint 5
-%uint_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%uint_9 = OpConstant %uint 9
-%uint_74 = OpConstant %uint 74
-%106 = OpConstantNull %v4float
-)";
-
-  const std::string func_before =
-      R"(%main = OpFunction %void None %3
+  const std::string main_func = R"(
+%main = OpFunction %void None %3
 %5 = OpLabel
 %lod = OpVariable %_ptr_Function_float Function
 %coords1D = OpVariable %_ptr_Function_float Function
@@ -1569,96 +1263,34 @@
 %38 = OpImageSampleExplicitLod %v4float %35 %36 Lod %37
 %40 = OpAccessChain %_ptr_Output_v4float %_ %int_0
 OpStore %40 %38
+; CHECK-NOT: %38 = OpImageSampleExplicitLod %v4float %35 %36 Lod %37
+; CHECK-NOT: %40 = OpAccessChain %_ptr_Output_v4float %_ %int_0
+; CHECK-NOT: OpStore %40 %38
+; CHECK: %46 = OpULessThan %bool %37 %uint_128
+; CHECK: OpSelectionMerge %47 None
+; CHECK: OpBranchConditional %46 %48 %49
+; CHECK: %48 = OpLabel
+; CHECK: %50 = OpLoad %25 %38
+; CHECK: %51 = OpImageSampleExplicitLod %v4float %50 %40 Lod %41
+; CHECK: OpBranch %47
+; CHECK: %49 = OpLabel
+; CHECK: %52 = OpBitcast %uint %37
+; CHECK: %105 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_74 %uint_0 %52 %uint_128
+; CHECK: OpBranch %47
+; CHECK: %47 = OpLabel
+; CHECK: %107 = OpPhi %v4float %51 %48 %106 %49
+; CHECK: %43 = OpAccessChain %_ptr_Output_v4float %_ %int_0
+; CHECK: OpStore %43 %107
 OpReturn
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%main = OpFunction %void None %12
-%35 = OpLabel
-%lod = OpVariable %_ptr_Function_float Function
-%coords1D = OpVariable %_ptr_Function_float Function
-OpStore %lod %float_3
-OpStore %coords1D %float_1_78900003
-%36 = OpAccessChain %_ptr_Uniform_int %__0 %int_0
-%37 = OpLoad %int %36
-%38 = OpAccessChain %_ptr_UniformConstant_25 %texSampler1D %37
-%39 = OpLoad %25 %38
-%40 = OpLoad %float %coords1D
-%41 = OpLoad %float %lod
-%46 = OpULessThan %bool %37 %uint_128
-OpSelectionMerge %47 None
-OpBranchConditional %46 %48 %49
-%48 = OpLabel
-%50 = OpLoad %25 %38
-%51 = OpImageSampleExplicitLod %v4float %50 %40 Lod %41
-OpBranch %47
-%49 = OpLabel
-%52 = OpBitcast %uint %37
-%105 = OpFunctionCall %void %53 %uint_74 %uint_0 %52 %uint_128
-OpBranch %47
-%47 = OpLabel
-%107 = OpPhi %v4float %51 %48 %106 %49
-%43 = OpAccessChain %_ptr_Output_v4float %_ %int_0
-OpStore %43 %107
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string output_func =
-      R"(%53 = OpFunction %void None %54
-%55 = OpFunctionParameter %uint
-%56 = OpFunctionParameter %uint
-%57 = OpFunctionParameter %uint
-%58 = OpFunctionParameter %uint
-%59 = OpLabel
-%65 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_0
-%68 = OpAtomicIAdd %uint %65 %uint_4 %uint_0 %uint_10
-%69 = OpIAdd %uint %68 %uint_10
-%70 = OpArrayLength %uint %63 1
-%71 = OpULessThanEqual %bool %69 %70
-OpSelectionMerge %72 None
-OpBranchConditional %71 %73 %72
-%73 = OpLabel
-%74 = OpIAdd %uint %68 %uint_0
-%75 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %74
-OpStore %75 %uint_10
-%77 = OpIAdd %uint %68 %uint_1
-%78 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %77
-OpStore %78 %uint_23
-%80 = OpIAdd %uint %68 %uint_2
-%81 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %80
-OpStore %81 %55
-%83 = OpIAdd %uint %68 %uint_3
-%84 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %83
-OpStore %84 %uint_0
-%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_InstanceIndex
-%93 = OpIAdd %uint %68 %uint_5
-%94 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %93
-OpStore %94 %91
-%96 = OpIAdd %uint %68 %uint_7
-%97 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %96
-OpStore %97 %56
-%99 = OpIAdd %uint %68 %uint_8
-%100 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %99
-OpStore %100 %57
-%102 = OpIAdd %uint %68 %uint_9
-%103 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %102
-OpStore %103 %58
-OpBranch %72
-%72 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
+  const std::string output_func = kStreamWrite4Vert;
 
   // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBindlessCheckPass>(
-      defs_before + func_before, defs_after + func_after + output_func, true,
-      true, 7u, 23u, false, false, false, false, false);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(defs + main_func + output_func,
+                                               true, 7u, 23u, false, false,
+                                               false, false, false);
 }
 
 TEST_F(InstBindlessTest, InstrumentTeseSimple) {
@@ -1680,13 +1312,13 @@
   //   gl_Position = adds[uniform_index_buffer.index].val;
   // }
   //
-  // clang-format on
 
-  const std::string defs_before =
-      R"(OpCapability Tessellation
+  const std::string defs = R"(
+OpCapability Tessellation
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
 OpEntryPoint TessellationEvaluation %main "main" %_
+; CHECK: OpEntryPoint TessellationEvaluation %main "main" %_ %gl_PrimitiveID %gl_TessCoord
 OpExecutionMode %main Triangles
 OpExecutionMode %main SpacingEqual
 OpExecutionMode %main VertexOrderCw
@@ -1718,6 +1350,10 @@
 OpDecorate %ufoo Block
 OpDecorate %uniform_index_buffer DescriptorSet 0
 OpDecorate %uniform_index_buffer Binding 0
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+)" + kOutputDecorations + R"(
+; CHECK: OpDecorate %gl_PrimitiveID BuiltIn PrimitiveId
+; CHECK: OpDecorate %gl_TessCoord BuiltIn TessCoord
 %void = OpTypeVoid
 %3 = OpTypeFunction %void
 %float = OpTypeFloat 32
@@ -1741,211 +1377,80 @@
 %_ptr_Uniform_uint = OpTypePointer Uniform %uint
 %_ptr_StorageBuffer_v4float = OpTypePointer StorageBuffer %v4float
 %_ptr_Output_v4float = OpTypePointer Output %v4float
+; CHECK: %uint_0 = OpConstant %uint 0
+; CHECK: %bool = OpTypeBool
+; CHECK: %40 = OpTypeFunction %void %uint %uint %uint %uint
+; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+)" + kOutputGlobals + R"(
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %uint_10 = OpConstant %uint 10
+; CHECK: %uint_4 = OpConstant %uint 4
+; CHECK: %uint_23 = OpConstant %uint 23
+; CHECK: %uint_2 = OpConstant %uint 2
+; CHECK: %uint_3 = OpConstant %uint 3
+; CHECK: %_ptr_Input_uint = OpTypePointer Input %uint
+; CHECK: %gl_PrimitiveID = OpVariable %_ptr_Input_uint Input
+; CHECK: %v3float = OpTypeVector %float 3
+; CHECK: %_ptr_Input_v3float = OpTypePointer Input %v3float
+; CHECK: %gl_TessCoord = OpVariable %_ptr_Input_v3float Input
+; CHECK: %v3uint = OpTypeVector %uint 3
+; CHECK: %uint_5 = OpConstant %uint 5
+; CHECK: %uint_6 = OpConstant %uint 6
+; CHECK: %uint_7 = OpConstant %uint 7
+; CHECK: %uint_8 = OpConstant %uint 8
+; CHECK: %uint_9 = OpConstant %uint 9
+; CHECK: %uint_63 = OpConstant %uint 63
+; CHECK: %101 = OpConstantNull %v4float
 )";
+  // clang-format on
 
-  const std::string defs_after =
-      R"(OpCapability Tessellation
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint TessellationEvaluation %main "main" %_ %gl_PrimitiveID %gl_TessCoord
-OpExecutionMode %main Triangles
-OpExecutionMode %main SpacingEqual
-OpExecutionMode %main VertexOrderCw
-OpSource GLSL 450
-OpSourceExtension "GL_EXT_nonuniform_qualifier"
-OpName %main "main"
-OpName %gl_PerVertex "gl_PerVertex"
-OpMemberName %gl_PerVertex 0 "gl_Position"
-OpMemberName %gl_PerVertex 1 "gl_PointSize"
-OpMemberName %gl_PerVertex 2 "gl_ClipDistance"
-OpMemberName %gl_PerVertex 3 "gl_CullDistance"
-OpName %_ ""
-OpName %bfoo "bfoo"
-OpMemberName %bfoo 0 "val"
-OpName %adds "adds"
-OpName %ufoo "ufoo"
-OpMemberName %ufoo 0 "index"
-OpName %uniform_index_buffer "uniform_index_buffer"
-OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
-OpMemberDecorate %gl_PerVertex 1 BuiltIn PointSize
-OpMemberDecorate %gl_PerVertex 2 BuiltIn ClipDistance
-OpMemberDecorate %gl_PerVertex 3 BuiltIn CullDistance
-OpDecorate %gl_PerVertex Block
-OpMemberDecorate %bfoo 0 Offset 0
-OpDecorate %bfoo Block
-OpDecorate %adds DescriptorSet 0
-OpDecorate %adds Binding 1
-OpMemberDecorate %ufoo 0 Offset 0
-OpDecorate %ufoo Block
-OpDecorate %uniform_index_buffer DescriptorSet 0
-OpDecorate %uniform_index_buffer Binding 0
-OpDecorate %_runtimearr_uint ArrayStride 4
-OpDecorate %_struct_47 Block
-OpMemberDecorate %_struct_47 0 Offset 0
-OpMemberDecorate %_struct_47 1 Offset 4
-OpDecorate %49 DescriptorSet 7
-OpDecorate %49 Binding 0
-OpDecorate %gl_PrimitiveID BuiltIn PrimitiveId
-OpDecorate %gl_TessCoord BuiltIn TessCoord
-%void = OpTypeVoid
-%10 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%v4float = OpTypeVector %float 4
-%uint = OpTypeInt 32 0
-%uint_1 = OpConstant %uint 1
-%_arr_float_uint_1 = OpTypeArray %float %uint_1
-%gl_PerVertex = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1
-%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
-%_ = OpVariable %_ptr_Output_gl_PerVertex Output
-%int = OpTypeInt 32 1
-%int_0 = OpConstant %int 0
-%bfoo = OpTypeStruct %v4float
-%uint_11 = OpConstant %uint 11
-%_arr_bfoo_uint_11 = OpTypeArray %bfoo %uint_11
-%_ptr_StorageBuffer__arr_bfoo_uint_11 = OpTypePointer StorageBuffer %_arr_bfoo_uint_11
-%adds = OpVariable %_ptr_StorageBuffer__arr_bfoo_uint_11 StorageBuffer
-%ufoo = OpTypeStruct %uint
-%_ptr_Uniform_ufoo = OpTypePointer Uniform %ufoo
-%uniform_index_buffer = OpVariable %_ptr_Uniform_ufoo Uniform
-%_ptr_Uniform_uint = OpTypePointer Uniform %uint
-%_ptr_StorageBuffer_v4float = OpTypePointer StorageBuffer %v4float
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%uint_0 = OpConstant %uint 0
-%bool = OpTypeBool
-%40 = OpTypeFunction %void %uint %uint %uint %uint
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_47 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_47 = OpTypePointer StorageBuffer %_struct_47
-%49 = OpVariable %_ptr_StorageBuffer__struct_47 StorageBuffer
-%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-%uint_10 = OpConstant %uint 10
-%uint_4 = OpConstant %uint 4
-%uint_23 = OpConstant %uint 23
-%uint_2 = OpConstant %uint 2
-%uint_3 = OpConstant %uint 3
-%_ptr_Input_uint = OpTypePointer Input %uint
-%gl_PrimitiveID = OpVariable %_ptr_Input_uint Input
-%v3float = OpTypeVector %float 3
-%_ptr_Input_v3float = OpTypePointer Input %v3float
-%gl_TessCoord = OpVariable %_ptr_Input_v3float Input
-%v3uint = OpTypeVector %uint 3
-%uint_5 = OpConstant %uint 5
-%uint_6 = OpConstant %uint 6
-%uint_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%uint_9 = OpConstant %uint 9
-%uint_63 = OpConstant %uint 63
-%101 = OpConstantNull %v4float
-)";
-
-  const std::string func_before =
-      R"(%main = OpFunction %void None %3
+  const std::string main_func = R"(
+%main = OpFunction %void None %3
 %5 = OpLabel
 %25 = OpAccessChain %_ptr_Uniform_uint %uniform_index_buffer %int_0
 %26 = OpLoad %uint %25
 %28 = OpAccessChain %_ptr_StorageBuffer_v4float %adds %26 %int_0
 %29 = OpLoad %v4float %28
+; CHECK-NOT: %29 = OpLoad %v4float %28
+; CHECK: %34 = OpULessThan %bool %28 %uint_11
+; CHECK: OpSelectionMerge %35 None
+; CHECK: OpBranchConditional %34 %36 %37
+; CHECK: %36 = OpLabel
+; CHECK: %38 = OpLoad %v4float %29
+; CHECK: OpBranch %35
+; CHECK: %37 = OpLabel
+; CHECK: %100 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_63 %uint_0 %28 %uint_11
+; CHECK: OpBranch %35
+; CHECK: %35 = OpLabel
+; CHECK: %102 = OpPhi %v4float %38 %36 %101 %37
 %31 = OpAccessChain %_ptr_Output_v4float %_ %int_0
 OpStore %31 %29
+; CHECK-NOT: OpStore %31 %29
+; CHECK: OpStore %31 %102
 OpReturn
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%main = OpFunction %void None %10
-%26 = OpLabel
-%27 = OpAccessChain %_ptr_Uniform_uint %uniform_index_buffer %int_0
-%28 = OpLoad %uint %27
-%29 = OpAccessChain %_ptr_StorageBuffer_v4float %adds %28 %int_0
-%34 = OpULessThan %bool %28 %uint_11
-OpSelectionMerge %35 None
-OpBranchConditional %34 %36 %37
-%36 = OpLabel
-%38 = OpLoad %v4float %29
-OpBranch %35
-%37 = OpLabel
-%100 = OpFunctionCall %void %39 %uint_63 %uint_0 %28 %uint_11
-OpBranch %35
-%35 = OpLabel
-%102 = OpPhi %v4float %38 %36 %101 %37
-%31 = OpAccessChain %_ptr_Output_v4float %_ %int_0
-OpStore %31 %102
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string output_func =
-      R"(%39 = OpFunction %void None %40
-%41 = OpFunctionParameter %uint
-%42 = OpFunctionParameter %uint
-%43 = OpFunctionParameter %uint
-%44 = OpFunctionParameter %uint
-%45 = OpLabel
-%51 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_0
-%54 = OpAtomicIAdd %uint %51 %uint_4 %uint_0 %uint_10
-%55 = OpIAdd %uint %54 %uint_10
-%56 = OpArrayLength %uint %49 1
-%57 = OpULessThanEqual %bool %55 %56
-OpSelectionMerge %58 None
-OpBranchConditional %57 %59 %58
-%59 = OpLabel
-%60 = OpIAdd %uint %54 %uint_0
-%61 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %60
-OpStore %61 %uint_10
-%63 = OpIAdd %uint %54 %uint_1
-%64 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %63
-OpStore %64 %uint_23
-%66 = OpIAdd %uint %54 %uint_2
-%67 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %66
-OpStore %67 %41
-%69 = OpIAdd %uint %54 %uint_3
-%70 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %69
-OpStore %70 %uint_2
-%73 = OpLoad %uint %gl_PrimitiveID
-%74 = OpIAdd %uint %54 %uint_4
-%75 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %74
-OpStore %75 %73
-%79 = OpLoad %v3float %gl_TessCoord
-%81 = OpBitcast %v3uint %79
-%82 = OpCompositeExtract %uint %81 0
-%83 = OpCompositeExtract %uint %81 1
-%85 = OpIAdd %uint %54 %uint_5
-%86 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %85
-OpStore %86 %82
-%88 = OpIAdd %uint %54 %uint_6
-%89 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %88
-OpStore %89 %83
-%91 = OpIAdd %uint %54 %uint_7
-%92 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %91
-OpStore %92 %42
-%94 = OpIAdd %uint %54 %uint_8
-%95 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %94
-OpStore %95 %43
-%97 = OpIAdd %uint %54 %uint_9
-%98 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %97
-OpStore %98 %44
-OpBranch %58
-%58 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
+  const std::string output_func = kStreamWrite4Tese;
 
   // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBindlessCheckPass>(
-      defs_before + func_before, defs_after + func_after + output_func, true,
-      true, 7u, 23u, false, false, false, false, false);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(defs + main_func + output_func,
+                                               true, 7u, 23u, false, false,
+                                               false, false, false);
 }
 
 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
+  // clang-format off
+  const std::string defs = R"(
+OpCapability Shader
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
 %2 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+; CHECK: OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
 OpExecutionMode %MainPs OriginUpperLeft
 %1 = OpString "foo5.frag"
 OpSource HLSL 500 %1
@@ -1974,6 +1479,9 @@
 OpDecorate %g_sAniso Binding 1
 OpDecorate %i_vTextureCoords Location 0
 OpDecorate %_entryPointOutput_vColor Location 0
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+)" + kOutputDecorations + R"(
+; CHECK: OpDecorate %gl_FragCoord BuiltIn FragCoord
 %void = OpTypeVoid
 %4 = OpTypeFunction %void
 %float = OpTypeFloat 32
@@ -2007,109 +1515,32 @@
 %i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+; CHECK: %uint_0 = OpConstant %uint 0
+; CHECK: %bool = OpTypeBool
+; CHECK: %70 = OpTypeFunction %void %uint %uint %uint %uint
+; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+)" + kOutputGlobals + R"(
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %uint_10 = OpConstant %uint 10
+; CHECK: %uint_4 = OpConstant %uint 4
+; CHECK: %uint_1 = OpConstant %uint 1
+; CHECK: %uint_23 = OpConstant %uint 23
+; CHECK: %uint_2 = OpConstant %uint 2
+; CHECK: %uint_3 = OpConstant %uint 3
+; CHECK: %_ptr_Input_v4float = OpTypePointer Input %v4float
+; CHECK: %gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+; CHECK: %v4uint = OpTypeVector %uint 4
+; CHECK: %uint_5 = OpConstant %uint 5
+; CHECK: %uint_7 = OpConstant %uint 7
+; CHECK: %uint_8 = OpConstant %uint 8
+; CHECK: %uint_9 = OpConstant %uint 9
+; CHECK: %uint_109 = OpConstant %uint 109
+; CHECK: %125 = OpConstantNull %v4float
 )";
+  // clang-format on
 
-  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_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_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_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%uint_9 = OpConstant %uint 9
-%uint_109 = OpConstant %uint 109
-%125 = OpConstantNull %v4float
-)";
-
-  const std::string func1_before =
-      R"(%MainPs = OpFunction %void None %4
+  const std::string func1 = R"(
+%MainPs = OpFunction %void None %4
 %6 = OpLabel
 %i_0 = OpVariable %_ptr_Function_PS_INPUT Function
 %param = OpVariable %_ptr_Function_PS_INPUT Function
@@ -2126,26 +1557,8 @@
 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
+  const std::string func2 = 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
@@ -2159,6 +1572,24 @@
 %43 = OpAccessChain %_ptr_Function_v2float %i %int_0
 %44 = OpLoad %v2float %43
 %45 = OpImageSampleImplicitLod %v4float %41 %44
+; CHECK-NOT: %45 = OpImageSampleImplicitLod %v4float %41 %44
+; CHECK: OpNoLine
+; CHECK: %62 = OpULessThan %bool %50 %uint_128
+; CHECK: OpSelectionMerge %63 None
+; CHECK: OpBranchConditional %62 %64 %65
+; CHECK: %64 = OpLabel
+; CHECK: %66 = OpLoad %27 %51
+; CHECK: %67 = OpSampledImage %37 %66 %53
+; CHECK: OpLine %5 24 0
+; CHECK: %68 = OpImageSampleImplicitLod %v4float %67 %56
+; CHECK: OpNoLine
+; CHECK: OpBranch %63
+; CHECK: %65 = OpLabel
+; CHECK: %124 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_109 %uint_0 %50 %uint_128
+; CHECK: OpBranch %63
+; CHECK: %63 = OpLabel
+; CHECK: %126 = OpPhi %v4float %68 %64 %125 %65
+; CHECK: OpLine %5 24 0
 %47 = OpAccessChain %_ptr_Function_v4float %ps_output %int_0
 OpStore %47 %45
 OpLine %1 25 0
@@ -2167,102 +1598,12 @@
 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
-OpNoLine
-%62 = OpULessThan %bool %50 %uint_128
-OpSelectionMerge %63 None
-OpBranchConditional %62 %64 %65
-%64 = OpLabel
-%66 = OpLoad %27 %51
-%67 = OpSampledImage %37 %66 %53
-OpLine %5 24 0
-%68 = OpImageSampleImplicitLod %v4float %67 %56
-OpNoLine
-OpBranch %63
-%65 = OpLabel
-%124 = OpFunctionCall %void %69 %uint_109 %uint_0 %50 %uint_128
-OpBranch %63
-%63 = OpLabel
-%126 = OpPhi %v4float %68 %64 %125 %65
-OpLine %5 24 0
-%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_10
-%85 = OpIAdd %uint %84 %uint_10
-%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_10
-%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_7
-%116 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %115
-OpStore %116 %72
-%118 = OpIAdd %uint %84 %uint_8
-%119 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %118
-OpStore %119 %73
-%121 = OpIAdd %uint %84 %uint_9
-%122 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %121
-OpStore %122 %74
-OpBranch %88
-%88 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
+  const std::string output_func = kStreamWrite4Frag;
 
   // 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, 7u, 23u,
-      false, false, false, false, false);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(
+      defs + func1 + func2 + output_func, true, 7u, 23u, false, false, false,
+      false, false);
 }
 
 TEST_F(InstBindlessTest, RuntimeArray) {
@@ -2270,13 +1611,16 @@
   // 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
+  // clang-format off
+  const std::string defs = R"(
+OpCapability Shader
 OpCapability RuntimeDescriptorArray
 OpExtension "SPV_EXT_descriptor_indexing"
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+; CHECK: OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
 OpExecutionMode %MainPs OriginUpperLeft
 OpSource HLSL 500
 OpName %MainPs "MainPs"
@@ -2295,6 +1639,9 @@
 OpDecorate %g_sAniso Binding 0
 OpDecorate %i_vTextureCoords Location 0
 OpDecorate %_entryPointOutput_vColor Location 0
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+)" + kInputDecorations + kOutputDecorations + R"(
+; CHECK: OpDecorate %gl_FragCoord BuiltIn FragCoord
 %void = OpTypeVoid
 %3 = OpTypeFunction %void
 %float = OpTypeFloat 32
@@ -2321,102 +1668,34 @@
 %i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+; CHECK: %uint_0 = OpConstant %uint 0
+; CHECK: %uint_2 = OpConstant %uint 2
+; CHECK: %41 = OpTypeFunction %uint %uint %uint
+; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+)" + kInputGlobals + R"(
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %bool = OpTypeBool
+; CHECK: %65 = OpTypeFunction %void %uint %uint %uint %uint
+)" + kOutputGlobals + R"(
+; CHECK: %uint_10 = OpConstant %uint 10
+; CHECK: %uint_4 = OpConstant %uint 4
+; CHECK: %uint_23 = OpConstant %uint 23
+; CHECK: %uint_3 = OpConstant %uint 3
+; CHECK: %_ptr_Input_v4float = OpTypePointer Input %v4float
+; CHECK: %gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+; CHECK: %v4uint = OpTypeVector %uint 4
+; CHECK: %uint_5 = OpConstant %uint 5
+; CHECK: %uint_7 = OpConstant %uint 7
+; CHECK: %uint_8 = OpConstant %uint 8
+; CHECK: %uint_9 = OpConstant %uint 9
+; CHECK: %uint_59 = OpConstant %uint 59
+; CHECK: %116 = OpConstantNull %v4float
+; CHECK: %119 = OpTypeFunction %uint %uint %uint %uint %uint
 )";
+  // clang-format on
 
-  const std::string defs_after =
-      R"(OpCapability Shader
-OpCapability RuntimeDescriptorArray
-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_10 = OpConstant %uint 10
-%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_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%uint_9 = OpConstant %uint 9
-%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
+  const std::string main_func = R"(
+%MainPs = OpFunction %void None %3
 %5 = OpLabel
 %53 = OpLoad %v2float %i_vTextureCoords
 %63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
@@ -2427,138 +1706,46 @@
 %68 = OpSampledImage %39 %66 %67
 %71 = OpImageSampleImplicitLod %v4float %68 %53
 OpStore %_entryPointOutput_vColor %71
+; CHECK-NOT: %71 = OpImageSampleImplicitLod %v4float %68 %53
+; CHECK-NOT: OpStore %_entryPointOutput_vColor %71
+; CHECK: %55 = OpFunctionCall %uint %inst_bindless_direct_read_2 %uint_2 %uint_2
+; CHECK: %57 = OpULessThan %bool %32 %55
+; CHECK: OpSelectionMerge %58 None
+; CHECK: OpBranchConditional %57 %59 %60
+; CHECK: %59 = OpLabel
+; CHECK: %61 = OpLoad %16 %33
+; CHECK: %62 = OpSampledImage %26 %61 %35
+; CHECK: %136 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_1 %uint_2 %32
+; CHECK: %137 = OpULessThan %bool %uint_0 %136
+; CHECK: OpSelectionMerge %138 None
+; CHECK: OpBranchConditional %137 %139 %140
+; CHECK: %139 = OpLabel
+; CHECK: %141 = OpLoad %16 %33
+; CHECK: %142 = OpSampledImage %26 %141 %35
+; CHECK: %143 = OpImageSampleImplicitLod %v4float %142 %30
+; CHECK: OpBranch %138
+; CHECK: %140 = OpLabel
+; CHECK: %144 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_59 %uint_1 %32 %uint_0
+; CHECK: OpBranch %138
+; CHECK: %138 = OpLabel
+; CHECK: %145 = OpPhi %v4float %143 %139 %116 %140
+; CHECK: OpBranch %58
+; CHECK: %60 = OpLabel
+; CHECK: %115 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_59 %uint_0 %32 %55
+; CHECK: OpBranch %58
+; CHECK: %58 = OpLabel
+; CHECK: %117 = OpPhi %v4float %145 %138 %116 %60
+; CHECK: OpStore %_entryPointOutput_vColor %117
 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 = OpULessThan %bool %uint_0 %136
-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_10
-%78 = OpIAdd %uint %77 %uint_10
-%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_10
-%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_7
-%107 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %106
-OpStore %107 %67
-%109 = OpIAdd %uint %77 %uint_8
-%110 = OpAccessChain %_ptr_StorageBuffer_uint %73 %uint_1 %109
-OpStore %110 %68
-%112 = OpIAdd %uint %77 %uint_9
-%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
-)";
+  const std::string new_funcs = kDirectRead2 + kStreamWrite4Frag + kDirectRead4;
 
   // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBindlessCheckPass>(
-      defs_before + func_before, defs_after + func_after + new_funcs, true,
-      true, 7u, 23u, true, true, false, false, false);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(defs + main_func + new_funcs,
+                                               true, 7u, 23u, true, true, false,
+                                               false, false);
 }
 
 TEST_F(InstBindlessTest, InstrumentInitCheckOnScalarDescriptor) {
@@ -2569,11 +1756,14 @@
   // does not have the extension enabled because it does not contain a
   // runtime array. This is the same shader as NoInstrumentNonBindless.
 
-  const std::string defs_before =
-      R"(OpCapability Shader
+  // clang-format off
+  const std::string defs = R"(
+OpCapability Shader
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+; CHECK: OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
 OpExecutionMode %MainPs OriginUpperLeft
 OpSource HLSL 500
 OpName %MainPs "MainPs"
@@ -2587,6 +1777,9 @@
 OpDecorate %g_sAniso Binding 0
 OpDecorate %i_vTextureCoords Location 0
 OpDecorate %_entryPointOutput_vColor Location 0
+; check: OpDecorate %_runtimearr_uint ArrayStride 4
+)" + kInputDecorations + kOutputDecorations + R"(
+; check: OpDecorate %gl_FragCoord BuiltIn FragCoord
 %void = OpTypeVoid
 %8 = OpTypeFunction %void
 %float = OpTypeFloat 32
@@ -2603,86 +1796,35 @@
 %i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+; CHECK: %uint = OpTypeInt 32 0
+; CHECK: %uint_0 = OpConstant %uint 0
+; CHECK: %28 = OpTypeFunction %uint %uint %uint %uint %uint
+; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+)" + kInputGlobals + R"(
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %bool = OpTypeBool
+; CHECK: %uint_1 = OpConstant %uint 1
+; CHECK: %61 = OpTypeFunction %void %uint %uint %uint %uint
+)" + kOutputGlobals + R"(
+; CHECK: %uint_10 = OpConstant %uint 10
+; CHECK: %uint_4 = OpConstant %uint 4
+; CHECK: %uint_23 = OpConstant %uint 23
+; CHECK: %uint_2 = OpConstant %uint 2
+; CHECK: %uint_3 = OpConstant %uint 3
+; CHECK: %_ptr_Input_v4float = OpTypePointer Input %v4float
+; CHECK: %gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+; CHECK: %v4uint = OpTypeVector %uint 4
+; CHECK: %uint_5 = OpConstant %uint 5
+; CHECK: %uint_7 = OpConstant %uint 7
+; CHECK: %uint_8 = OpConstant %uint 8
+; CHECK: %uint_9 = OpConstant %uint 9
+; CHECK: %uint_39 = OpConstant %uint 39
+; CHECK: %113 = OpConstantNull %v4float
 )";
+  // clang-format on
 
-  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 %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_10 = OpConstant %uint 10
-%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_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%uint_9 = OpConstant %uint 9
-%uint_39 = OpConstant %uint 39
-%113 = OpConstantNull %v4float
-)";
-
-  const std::string func_before =
-      R"(%MainPs = OpFunction %void None %8
+  const std::string main_func = R"(
+%MainPs = OpFunction %void None %8
 %19 = OpLabel
 %20 = OpLoad %v2float %i_vTextureCoords
 %21 = OpLoad %12 %g_tColor
@@ -2690,111 +1832,33 @@
 %23 = OpSampledImage %16 %21 %22
 %24 = OpImageSampleImplicitLod %v4float %23 %20
 OpStore %_entryPointOutput_vColor %24
+; CHECK-NOT: %24 = OpImageSampleImplicitLod %v4float %23 %20
+; CHECK-NOT: OpStore %_entryPointOutput_vColor %24
+; CHECK: %50 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_0 %uint_0
+; CHECK: %52 = OpULessThan %bool %uint_0 %50
+; CHECK: OpSelectionMerge %54 None
+; CHECK: OpBranchConditional %52 %55 %56
+; CHECK: %55 = OpLabel
+; CHECK: %57 = OpLoad %12 %g_tColor
+; CHECK: %58 = OpSampledImage %16 %57 %22
+; CHECK: %59 = OpImageSampleImplicitLod %v4float %58 %20
+; CHECK: OpBranch %54
+; CHECK: %56 = OpLabel
+; CHECK: %112 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_39 %uint_1 %uint_0 %uint_0
+; CHECK: OpBranch %54
+; CHECK: %54 = OpLabel
+; CHECK: %114 = OpPhi %v4float %59 %55 %113 %56
+; CHECK: OpStore %_entryPointOutput_vColor %114
 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 = OpULessThan %bool %uint_0 %50
-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_39 %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_10
-%74 = OpIAdd %uint %73 %uint_10
-%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_10
-%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_7
-%104 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %103
-OpStore %104 %63
-%106 = OpIAdd %uint %73 %uint_8
-%107 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %106
-OpStore %107 %64
-%109 = OpIAdd %uint %73 %uint_9
-%110 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_1 %109
-OpStore %110 %65
-OpBranch %77
-%77 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
+  const std::string new_funcs = kDirectRead4 + kStreamWrite4Frag;
 
   // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBindlessCheckPass>(
-      defs_before + func_before, defs_after + func_after + new_funcs, true,
-      true, 7u, 23u, true, true, false, false, false);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(defs + main_func + new_funcs,
+                                               true, 7u, 23u, true, true, false,
+                                               false, false);
 }
 
 TEST_F(InstBindlessTest, SPV14AddToEntryPoint) {
@@ -2927,15 +1991,18 @@
   //     b = uniformBuffer[nu_ii].a;
   // }
 
-  const std::string defs_before =
-      R"(OpCapability Shader
+  // clang-format off
+  const std::string defs = R"(
+OpCapability Shader
 OpCapability ShaderNonUniform
 OpCapability RuntimeDescriptorArray
 OpCapability UniformBufferArrayNonUniformIndexing
 OpExtension "SPV_EXT_descriptor_indexing"
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %main "main" %b %nu_ii
+; CHECK: OpEntryPoint Fragment %main "main" %b %nu_ii %gl_FragCoord
 OpExecutionMode %main OriginUpperLeft
 OpSource GLSL 450
 OpSourceExtension "GL_EXT_nonuniform_qualifier"
@@ -2955,6 +2022,12 @@
 OpDecorate %nu_ii NonUniform
 OpDecorate %16 NonUniform
 OpDecorate %20 NonUniform
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+)" + kInputDecorations + R"(
+; CHECK: OpDecorate %130 NonUniform
+)" + kOutputDecorations + R"(
+; CHECK: OpDecorate %gl_FragCoord BuiltIn FragCoord
+; CHECK: OpDecorate %127 NonUniform
 %void = OpTypeVoid
 %3 = OpTypeFunction %void
 %float = OpTypeFloat 32
@@ -2969,229 +2042,81 @@
 %nu_ii = OpVariable %_ptr_Input_int Input
 %int_0 = OpConstant %int 0
 %_ptr_Uniform_float = OpTypePointer Uniform %float
+; CHECK: %uint = OpTypeInt 32 0
+; CHECK: %uint_0 = OpConstant %uint 0
+; CHECK: %uint_1 = OpConstant %uint 1
+; CHECK: %uint_3 = OpConstant %uint 3
+; CHECK: %26 = OpTypeFunction %uint %uint %uint
+; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+)" + kInputGlobals + R"(
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %bool = OpTypeBool
+; CHECK: %49 = OpTypeFunction %void %uint %uint %uint %uint
+)" + kOutputGlobals + R"(
+; CHECK: %uint_10 = OpConstant %uint 10
+; CHECK: %uint_4 = OpConstant %uint 4
+; CHECK: %uint_23 = OpConstant %uint 23
+; CHECK: %uint_2 = OpConstant %uint 2
+; CHECK: %v4float = OpTypeVector %float 4
+; CHECK: %_ptr_Input_v4float = OpTypePointer Input %v4float
+; CHECK: %gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+; CHECK: %v4uint = OpTypeVector %uint 4
+; CHECK: %uint_5 = OpConstant %uint 5
+; CHECK: %uint_7 = OpConstant %uint 7
+; CHECK: %uint_8 = OpConstant %uint 8
+; CHECK: %uint_9 = OpConstant %uint 9
+; CHECK: %uint_45 = OpConstant %uint 45
+; CHECK: %101 = OpConstantNull %float
+; CHECK: %105 = OpTypeFunction %uint %uint %uint %uint %uint
 )";
+  // clang-format on
 
-  const std::string defs_after =
-      R"(OpCapability Shader
-OpCapability ShaderNonUniform
-OpCapability RuntimeDescriptorArray
-OpCapability UniformBufferArrayNonUniformIndexing
-OpExtension "SPV_EXT_descriptor_indexing"
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main" %b %nu_ii %gl_FragCoord
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 450
-OpSourceExtension "GL_EXT_nonuniform_qualifier"
-OpName %main "main"
-OpName %b "b"
-OpName %uname "uname"
-OpMemberName %uname 0 "a"
-OpName %uniformBuffer "uniformBuffer"
-OpName %nu_ii "nu_ii"
-OpDecorate %b Location 0
-OpMemberDecorate %uname 0 Offset 0
-OpDecorate %uname Block
-OpDecorate %uniformBuffer DescriptorSet 0
-OpDecorate %uniformBuffer Binding 3
-OpDecorate %nu_ii Flat
-OpDecorate %nu_ii Location 0
-OpDecorate %nu_ii NonUniform
-OpDecorate %7 NonUniform
-OpDecorate %102 NonUniform
-OpDecorate %_runtimearr_uint ArrayStride 4
-OpDecorate %_struct_31 Block
-OpMemberDecorate %_struct_31 0 Offset 0
-OpDecorate %33 DescriptorSet 7
-OpDecorate %33 Binding 1
-OpDecorate %130 NonUniform
-OpDecorate %_struct_55 Block
-OpMemberDecorate %_struct_55 0 Offset 0
-OpMemberDecorate %_struct_55 1 Offset 4
-OpDecorate %57 DescriptorSet 7
-OpDecorate %57 Binding 0
-OpDecorate %gl_FragCoord BuiltIn FragCoord
-OpDecorate %127 NonUniform
-%void = OpTypeVoid
-%10 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%_ptr_Output_float = OpTypePointer Output %float
-%b = OpVariable %_ptr_Output_float Output
-%uname = OpTypeStruct %float
-%_runtimearr_uname = OpTypeRuntimeArray %uname
-%_ptr_Uniform__runtimearr_uname = OpTypePointer Uniform %_runtimearr_uname
-%uniformBuffer = OpVariable %_ptr_Uniform__runtimearr_uname Uniform
-%int = OpTypeInt 32 1
-%_ptr_Input_int = OpTypePointer Input %int
-%nu_ii = OpVariable %_ptr_Input_int Input
-%int_0 = OpConstant %int 0
-%_ptr_Uniform_float = OpTypePointer Uniform %float
-%uint = OpTypeInt 32 0
-%uint_0 = OpConstant %uint 0
-%uint_1 = OpConstant %uint 1
-%uint_3 = OpConstant %uint 3
-%26 = OpTypeFunction %uint %uint %uint
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_31 = OpTypeStruct %_runtimearr_uint
-%_ptr_StorageBuffer__struct_31 = OpTypePointer StorageBuffer %_struct_31
-%33 = OpVariable %_ptr_StorageBuffer__struct_31 StorageBuffer
-%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-%bool = OpTypeBool
-%49 = OpTypeFunction %void %uint %uint %uint %uint
-%_struct_55 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_55 = OpTypePointer StorageBuffer %_struct_55
-%57 = OpVariable %_ptr_StorageBuffer__struct_55 StorageBuffer
-%uint_10 = OpConstant %uint 10
-%uint_4 = OpConstant %uint 4
-%uint_23 = OpConstant %uint 23
-%uint_2 = OpConstant %uint 2
-%v4float = OpTypeVector %float 4
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
-%v4uint = OpTypeVector %uint 4
-%uint_5 = OpConstant %uint 5
-%uint_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%uint_9 = OpConstant %uint 9
-%uint_45 = OpConstant %uint 45
-%101 = OpConstantNull %float
-%105 = OpTypeFunction %uint %uint %uint %uint %uint
-)";
-
-  const std::string func_before =
-      R"(%main = OpFunction %void None %3
+  const std::string main_func = R"(
+%main = OpFunction %void None %3
 %5 = OpLabel
 %16 = OpLoad %int %nu_ii
 %19 = OpAccessChain %_ptr_Uniform_float %uniformBuffer %16 %int_0
 %20 = OpLoad %float %19
 OpStore %b %20
+; CHECK-NOT: %20 = OpLoad %float %19
+; CHECK-NOT: OpStore %b %20
+; CHECK: %40 = OpFunctionCall %uint %inst_bindless_direct_read_2 %uint_1 %uint_3
+; CHECK: %42 = OpULessThan %bool %7 %40
+; CHECK: OpSelectionMerge %43 None
+; CHECK: OpBranchConditional %42 %44 %45
+; CHECK: %44 = OpLabel
+; CHECK: %103 = OpBitcast %uint %7
+; CHECK: %122 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_3 %103
+; CHECK: %123 = OpULessThan %bool %uint_0 %122
+; CHECK: OpSelectionMerge %124 None
+; CHECK: OpBranchConditional %123 %125 %126
+; CHECK: %125 = OpLabel
+; CHECK: %127 = OpLoad %float %20
+; CHECK: OpBranch %124
+; CHECK: %126 = OpLabel
+; CHECK: %128 = OpBitcast %uint %7
+; CHECK: %129 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_45 %uint_1 %128 %uint_0
+; CHECK: OpBranch %124
+; CHECK: %124 = OpLabel
+; CHECK: %130 = OpPhi %float %127 %125 %101 %126
+; CHECK: OpBranch %43
+; CHECK: %45 = OpLabel
+; CHECK: %47 = OpBitcast %uint %7
+; CHECK: %100 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_45 %uint_0 %47 %40
+; CHECK: OpBranch %43
+; CHECK: %43 = OpLabel
+; CHECK: %102 = OpPhi %float %130 %124 %101 %45
+; CHECK: OpStore %b %102
 OpReturn
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%main = OpFunction %void None %10
-%19 = OpLabel
-%7 = OpLoad %int %nu_ii
-%20 = OpAccessChain %_ptr_Uniform_float %uniformBuffer %7 %int_0
-%40 = OpFunctionCall %uint %25 %uint_1 %uint_3
-%42 = OpULessThan %bool %7 %40
-OpSelectionMerge %43 None
-OpBranchConditional %42 %44 %45
-%44 = OpLabel
-%103 = OpBitcast %uint %7
-%122 = OpFunctionCall %uint %104 %uint_0 %uint_0 %uint_3 %103
-%123 = OpULessThan %bool %uint_0 %122
-OpSelectionMerge %124 None
-OpBranchConditional %123 %125 %126
-%125 = OpLabel
-%127 = OpLoad %float %20
-OpBranch %124
-%126 = OpLabel
-%128 = OpBitcast %uint %7
-%129 = OpFunctionCall %void %48 %uint_45 %uint_1 %128 %uint_0
-OpBranch %124
-%124 = OpLabel
-%130 = OpPhi %float %127 %125 %101 %126
-OpBranch %43
-%45 = OpLabel
-%47 = OpBitcast %uint %7
-%100 = OpFunctionCall %void %48 %uint_45 %uint_0 %47 %40
-OpBranch %43
-%43 = OpLabel
-%102 = OpPhi %float %130 %124 %101 %45
-OpStore %b %102
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string new_funcs =
-      R"(%25 = OpFunction %uint None %26
-%27 = OpFunctionParameter %uint
-%28 = OpFunctionParameter %uint
-%29 = OpLabel
-%35 = OpAccessChain %_ptr_StorageBuffer_uint %33 %uint_0 %27
-%36 = OpLoad %uint %35
-%37 = OpIAdd %uint %36 %28
-%38 = OpAccessChain %_ptr_StorageBuffer_uint %33 %uint_0 %37
-%39 = OpLoad %uint %38
-OpReturnValue %39
-OpFunctionEnd
-%48 = OpFunction %void None %49
-%50 = OpFunctionParameter %uint
-%51 = OpFunctionParameter %uint
-%52 = OpFunctionParameter %uint
-%53 = OpFunctionParameter %uint
-%54 = OpLabel
-%58 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_0
-%61 = OpAtomicIAdd %uint %58 %uint_4 %uint_0 %uint_10
-%62 = OpIAdd %uint %61 %uint_10
-%63 = OpArrayLength %uint %57 1
-%64 = OpULessThanEqual %bool %62 %63
-OpSelectionMerge %65 None
-OpBranchConditional %64 %66 %65
-%66 = OpLabel
-%67 = OpIAdd %uint %61 %uint_0
-%68 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %67
-OpStore %68 %uint_10
-%70 = OpIAdd %uint %61 %uint_1
-%71 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %70
-OpStore %71 %uint_23
-%73 = OpIAdd %uint %61 %uint_2
-%74 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %73
-OpStore %74 %50
-%75 = OpIAdd %uint %61 %uint_3
-%76 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %75
-OpStore %76 %uint_4
-%80 = OpLoad %v4float %gl_FragCoord
-%82 = OpBitcast %v4uint %80
-%83 = OpCompositeExtract %uint %82 0
-%84 = OpIAdd %uint %61 %uint_4
-%85 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %84
-OpStore %85 %83
-%86 = OpCompositeExtract %uint %82 1
-%88 = OpIAdd %uint %61 %uint_5
-%89 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %88
-OpStore %89 %86
-%91 = OpIAdd %uint %61 %uint_7
-%92 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %91
-OpStore %92 %51
-%94 = OpIAdd %uint %61 %uint_8
-%95 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %94
-OpStore %95 %52
-%97 = OpIAdd %uint %61 %uint_9
-%98 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %97
-OpStore %98 %53
-OpBranch %65
-%65 = OpLabel
-OpReturn
-OpFunctionEnd
-%104 = OpFunction %uint None %105
-%106 = OpFunctionParameter %uint
-%107 = OpFunctionParameter %uint
-%108 = OpFunctionParameter %uint
-%109 = OpFunctionParameter %uint
-%110 = OpLabel
-%111 = OpAccessChain %_ptr_StorageBuffer_uint %33 %uint_0 %106
-%112 = OpLoad %uint %111
-%113 = OpIAdd %uint %112 %107
-%114 = OpAccessChain %_ptr_StorageBuffer_uint %33 %uint_0 %113
-%115 = OpLoad %uint %114
-%116 = OpIAdd %uint %115 %108
-%117 = OpAccessChain %_ptr_StorageBuffer_uint %33 %uint_0 %116
-%118 = OpLoad %uint %117
-%119 = OpIAdd %uint %118 %109
-%120 = OpAccessChain %_ptr_StorageBuffer_uint %33 %uint_0 %119
-%121 = OpLoad %uint %120
-OpReturnValue %121
-OpFunctionEnd
-)";
+  const std::string new_funcs = kDirectRead2 + kStreamWrite4Frag + kDirectRead4;
 
   // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBindlessCheckPass>(
-      defs_before + func_before, defs_after + func_after + new_funcs, true,
-      true, 7u, 23u, true, true, false, false, false);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(defs + main_func + new_funcs,
+                                               true, 7u, 23u, true, true, false,
+                                               false, false);
 }
 
 TEST_F(InstBindlessTest, InstBoundsAndInitLoadUnsizedSSBOArrayDeprecated) {
@@ -3208,15 +2133,18 @@
   //     b = storageBuffer[nu_ii].b;
   // }
 
-  const std::string defs_before =
-      R"(OpCapability Shader
+  // clang-format off
+  const std::string defs = R"(
+OpCapability Shader
 OpCapability ShaderNonUniform
 OpCapability RuntimeDescriptorArray
 OpCapability StorageBufferArrayNonUniformIndexing
 OpExtension "SPV_EXT_descriptor_indexing"
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %main "main" %b %nu_ii
+; CHECK: OpEntryPoint Fragment %main "main" %b %nu_ii %gl_FragCoord
 OpExecutionMode %main OriginUpperLeft
 OpSource GLSL 450
 OpSourceExtension "GL_EXT_nonuniform_qualifier"
@@ -3236,6 +2164,12 @@
 OpDecorate %nu_ii NonUniform
 OpDecorate %16 NonUniform
 OpDecorate %20 NonUniform
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+)" + kInputDecorations + R"(
+; CHECK: OpDecorate %130 NonUniform
+)" + kOutputDecorations + R"(
+; CHECK: OpDecorate %gl_FragCoord BuiltIn FragCoord
+; CHECK: OpDecorate %127 NonUniform
 %void = OpTypeVoid
 %3 = OpTypeFunction %void
 %float = OpTypeFloat 32
@@ -3250,243 +2184,98 @@
 %nu_ii = OpVariable %_ptr_Input_int Input
 %int_0 = OpConstant %int 0
 %_ptr_StorageBuffer_float = OpTypePointer StorageBuffer %float
+; CHECK: %uint = OpTypeInt 32 0
+; CHECK: %uint_0 = OpConstant %uint 0
+; CHECK: %uint_1 = OpConstant %uint 1
+; CHECK: %uint_3 = OpConstant %uint 3
+; CHECK: %26 = OpTypeFunction %uint %uint %uint
+; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+)" + kInputGlobals + R"(
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %bool = OpTypeBool
+; CHECK: %49 = OpTypeFunction %void %uint %uint %uint %uint
+)" + kOutputGlobals + R"(
+; CHECK: %uint_10 = OpConstant %uint 10
+; CHECK: %uint_4 = OpConstant %uint 4
+; CHECK: %uint_23 = OpConstant %uint 23
+; CHECK: %uint_2 = OpConstant %uint 2
+; CHECK: %v4float = OpTypeVector %float 4
+; CHECK: %_ptr_Input_v4float = OpTypePointer Input %v4float
+; CHECK: %gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+; CHECK: %v4uint = OpTypeVector %uint 4
+; CHECK: %uint_5 = OpConstant %uint 5
+; CHECK: %uint_7 = OpConstant %uint 7
+; CHECK: %uint_8 = OpConstant %uint 8
+; CHECK: %uint_9 = OpConstant %uint 9
+; CHECK: %uint_45 = OpConstant %uint 45
+; CHECK: %101 = OpConstantNull %float
+; CHECK: %105 = OpTypeFunction %uint %uint %uint %uint %uint
 )";
+  // clang-format on
 
-  const std::string defs_after =
-      R"(OpCapability Shader
-OpCapability ShaderNonUniform
-OpCapability RuntimeDescriptorArray
-OpCapability StorageBufferArrayNonUniformIndexing
-OpExtension "SPV_EXT_descriptor_indexing"
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main" %b %nu_ii %gl_FragCoord
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 450
-OpSourceExtension "GL_EXT_nonuniform_qualifier"
-OpName %main "main"
-OpName %b "b"
-OpName %bname "bname"
-OpMemberName %bname 0 "a"
-OpName %storageBuffer "storageBuffer"
-OpName %nu_ii "nu_ii"
-OpDecorate %b Location 0
-OpMemberDecorate %bname 0 Offset 0
-OpDecorate %bname Block
-OpDecorate %storageBuffer DescriptorSet 0
-OpDecorate %storageBuffer Binding 3
-OpDecorate %nu_ii Flat
-OpDecorate %nu_ii Location 0
-OpDecorate %nu_ii NonUniform
-OpDecorate %7 NonUniform
-OpDecorate %102 NonUniform
-OpDecorate %_runtimearr_uint ArrayStride 4
-OpDecorate %_struct_31 Block
-OpMemberDecorate %_struct_31 0 Offset 0
-OpDecorate %33 DescriptorSet 7
-OpDecorate %33 Binding 1
-OpDecorate %130 NonUniform
-OpDecorate %_struct_55 Block
-OpMemberDecorate %_struct_55 0 Offset 0
-OpMemberDecorate %_struct_55 1 Offset 4
-OpDecorate %57 DescriptorSet 7
-OpDecorate %57 Binding 0
-OpDecorate %gl_FragCoord BuiltIn FragCoord
-OpDecorate %127 NonUniform
-%void = OpTypeVoid
-%10 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%_ptr_Output_float = OpTypePointer Output %float
-%b = OpVariable %_ptr_Output_float Output
-%bname = OpTypeStruct %float
-%_runtimearr_bname = OpTypeRuntimeArray %bname
-%_ptr_StorageBuffer__runtimearr_bname = OpTypePointer StorageBuffer %_runtimearr_bname
-%storageBuffer = OpVariable %_ptr_StorageBuffer__runtimearr_bname StorageBuffer
-%int = OpTypeInt 32 1
-%_ptr_Input_int = OpTypePointer Input %int
-%nu_ii = OpVariable %_ptr_Input_int Input
-%int_0 = OpConstant %int 0
-%_ptr_StorageBuffer_float = OpTypePointer StorageBuffer %float
-%uint = OpTypeInt 32 0
-%uint_0 = OpConstant %uint 0
-%uint_1 = OpConstant %uint 1
-%uint_3 = OpConstant %uint 3
-%26 = OpTypeFunction %uint %uint %uint
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_31 = OpTypeStruct %_runtimearr_uint
-%_ptr_StorageBuffer__struct_31 = OpTypePointer StorageBuffer %_struct_31
-%33 = OpVariable %_ptr_StorageBuffer__struct_31 StorageBuffer
-%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-%bool = OpTypeBool
-%49 = OpTypeFunction %void %uint %uint %uint %uint
-%_struct_55 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_55 = OpTypePointer StorageBuffer %_struct_55
-%57 = OpVariable %_ptr_StorageBuffer__struct_55 StorageBuffer
-%uint_10 = OpConstant %uint 10
-%uint_4 = OpConstant %uint 4
-%uint_23 = OpConstant %uint 23
-%uint_2 = OpConstant %uint 2
-%v4float = OpTypeVector %float 4
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
-%v4uint = OpTypeVector %uint 4
-%uint_5 = OpConstant %uint 5
-%uint_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%uint_9 = OpConstant %uint 9
-%uint_45 = OpConstant %uint 45
-%101 = OpConstantNull %float
-%105 = OpTypeFunction %uint %uint %uint %uint %uint
-)";
-
-  const std::string func_before =
-      R"(%main = OpFunction %void None %3
+  const std::string main_func = R"(
+%main = OpFunction %void None %3
 %5 = OpLabel
 %16 = OpLoad %int %nu_ii
 %19 = OpAccessChain %_ptr_StorageBuffer_float %storageBuffer %16 %int_0
 %20 = OpLoad %float %19
 OpStore %b %20
+; CHECK-NOT: %20 = OpLoad %float %19
+; CHECK-NOT: OpStore %b %20
+; CHECK: %40 = OpFunctionCall %uint %inst_bindless_direct_read_2 %uint_1 %uint_3
+; CHECK: %42 = OpULessThan %bool %7 %40
+; CHECK: OpSelectionMerge %43 None
+; CHECK: OpBranchConditional %42 %44 %45
+; CHECK: %44 = OpLabel
+; CHECK: %103 = OpBitcast %uint %7
+; CHECK: %122 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_3 %103
+; CHECK: %123 = OpULessThan %bool %uint_0 %122
+; CHECK: OpSelectionMerge %124 None
+; CHECK: OpBranchConditional %123 %125 %126
+; CHECK: %125 = OpLabel
+; CHECK: %127 = OpLoad %float %20
+; CHECK: OpBranch %124
+; CHECK: %126 = OpLabel
+; CHECK: %128 = OpBitcast %uint %7
+; CHECK: %129 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_45 %uint_1 %128 %uint_0
+; CHECK: OpBranch %124
+; CHECK: %124 = OpLabel
+; CHECK: %130 = OpPhi %float %127 %125 %101 %126
+; CHECK: OpBranch %43
+; CHECK: %45 = OpLabel
+; CHECK: %47 = OpBitcast %uint %7
+; CHECK: %100 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_45 %uint_0 %47 %40
+; CHECK: OpBranch %43
+; CHECK: %43 = OpLabel
+; CHECK: %102 = OpPhi %float %130 %124 %101 %45
+; CHECK: OpStore %b %102
 OpReturn
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%main = OpFunction %void None %10
-%19 = OpLabel
-%7 = OpLoad %int %nu_ii
-%20 = OpAccessChain %_ptr_StorageBuffer_float %storageBuffer %7 %int_0
-%40 = OpFunctionCall %uint %25 %uint_1 %uint_3
-%42 = OpULessThan %bool %7 %40
-OpSelectionMerge %43 None
-OpBranchConditional %42 %44 %45
-%44 = OpLabel
-%103 = OpBitcast %uint %7
-%122 = OpFunctionCall %uint %104 %uint_0 %uint_0 %uint_3 %103
-%123 = OpULessThan %bool %uint_0 %122
-OpSelectionMerge %124 None
-OpBranchConditional %123 %125 %126
-%125 = OpLabel
-%127 = OpLoad %float %20
-OpBranch %124
-%126 = OpLabel
-%128 = OpBitcast %uint %7
-%129 = OpFunctionCall %void %48 %uint_45 %uint_1 %128 %uint_0
-OpBranch %124
-%124 = OpLabel
-%130 = OpPhi %float %127 %125 %101 %126
-OpBranch %43
-%45 = OpLabel
-%47 = OpBitcast %uint %7
-%100 = OpFunctionCall %void %48 %uint_45 %uint_0 %47 %40
-OpBranch %43
-%43 = OpLabel
-%102 = OpPhi %float %130 %124 %101 %45
-OpStore %b %102
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string new_funcs =
-      R"(%25 = OpFunction %uint None %26
-%27 = OpFunctionParameter %uint
-%28 = OpFunctionParameter %uint
-%29 = OpLabel
-%35 = OpAccessChain %_ptr_StorageBuffer_uint %33 %uint_0 %27
-%36 = OpLoad %uint %35
-%37 = OpIAdd %uint %36 %28
-%38 = OpAccessChain %_ptr_StorageBuffer_uint %33 %uint_0 %37
-%39 = OpLoad %uint %38
-OpReturnValue %39
-OpFunctionEnd
-%48 = OpFunction %void None %49
-%50 = OpFunctionParameter %uint
-%51 = OpFunctionParameter %uint
-%52 = OpFunctionParameter %uint
-%53 = OpFunctionParameter %uint
-%54 = OpLabel
-%58 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_0
-%61 = OpAtomicIAdd %uint %58 %uint_4 %uint_0 %uint_10
-%62 = OpIAdd %uint %61 %uint_10
-%63 = OpArrayLength %uint %57 1
-%64 = OpULessThanEqual %bool %62 %63
-OpSelectionMerge %65 None
-OpBranchConditional %64 %66 %65
-%66 = OpLabel
-%67 = OpIAdd %uint %61 %uint_0
-%68 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %67
-OpStore %68 %uint_10
-%70 = OpIAdd %uint %61 %uint_1
-%71 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %70
-OpStore %71 %uint_23
-%73 = OpIAdd %uint %61 %uint_2
-%74 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %73
-OpStore %74 %50
-%75 = OpIAdd %uint %61 %uint_3
-%76 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %75
-OpStore %76 %uint_4
-%80 = OpLoad %v4float %gl_FragCoord
-%82 = OpBitcast %v4uint %80
-%83 = OpCompositeExtract %uint %82 0
-%84 = OpIAdd %uint %61 %uint_4
-%85 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %84
-OpStore %85 %83
-%86 = OpCompositeExtract %uint %82 1
-%88 = OpIAdd %uint %61 %uint_5
-%89 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %88
-OpStore %89 %86
-%91 = OpIAdd %uint %61 %uint_7
-%92 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %91
-OpStore %92 %51
-%94 = OpIAdd %uint %61 %uint_8
-%95 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %94
-OpStore %95 %52
-%97 = OpIAdd %uint %61 %uint_9
-%98 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %97
-OpStore %98 %53
-OpBranch %65
-%65 = OpLabel
-OpReturn
-OpFunctionEnd
-%104 = OpFunction %uint None %105
-%106 = OpFunctionParameter %uint
-%107 = OpFunctionParameter %uint
-%108 = OpFunctionParameter %uint
-%109 = OpFunctionParameter %uint
-%110 = OpLabel
-%111 = OpAccessChain %_ptr_StorageBuffer_uint %33 %uint_0 %106
-%112 = OpLoad %uint %111
-%113 = OpIAdd %uint %112 %107
-%114 = OpAccessChain %_ptr_StorageBuffer_uint %33 %uint_0 %113
-%115 = OpLoad %uint %114
-%116 = OpIAdd %uint %115 %108
-%117 = OpAccessChain %_ptr_StorageBuffer_uint %33 %uint_0 %116
-%118 = OpLoad %uint %117
-%119 = OpIAdd %uint %118 %109
-%120 = OpAccessChain %_ptr_StorageBuffer_uint %33 %uint_0 %119
-%121 = OpLoad %uint %120
-OpReturnValue %121
-OpFunctionEnd
-)";
+  const std::string new_funcs = kDirectRead2 + kStreamWrite4Frag + kDirectRead4;
 
   // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBindlessCheckPass>(
-      defs_before + func_before, defs_after + func_after + new_funcs, true,
-      true, 7u, 23u, true, true, false, false, false);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(defs + main_func + new_funcs,
+                                               true, 7u, 23u, true, true, false,
+                                               false, false);
 }
 
 TEST_F(InstBindlessTest, InstBoundsAndInitLoadUnsizedSSBOArray) {
   // Same as Deprecated but declaring as StorageBuffer Block
 
-  const std::string defs_before =
-      R"(OpCapability Shader
+  // clang-format off
+  const std::string defs = R"(
+OpCapability Shader
 OpCapability ShaderNonUniform
 OpCapability RuntimeDescriptorArray
 OpCapability StorageBufferArrayNonUniformIndexing
 OpExtension "SPV_EXT_descriptor_indexing"
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %main "main" %b %nu_ii
+; CHECK: OpEntryPoint Fragment %main "main" %b %nu_ii %gl_FragCoord
 OpExecutionMode %main OriginUpperLeft
 OpSource GLSL 450
 OpSourceExtension "GL_EXT_nonuniform_qualifier"
@@ -3506,6 +2295,12 @@
 OpDecorate %nu_ii NonUniform
 OpDecorate %16 NonUniform
 OpDecorate %20 NonUniform
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+)" + kInputDecorations + R"(
+; CHECK: OpDecorate %130 NonUniform
+)" + kOutputDecorations + R"(
+; CHECK: OpDecorate %gl_FragCoord BuiltIn FragCoord
+; CHECK: OpDecorate %127 NonUniform
 %void = OpTypeVoid
 %3 = OpTypeFunction %void
 %float = OpTypeFloat 32
@@ -3520,229 +2315,81 @@
 %nu_ii = OpVariable %_ptr_Input_int Input
 %int_0 = OpConstant %int 0
 %_ptr_StorageBuffer_float = OpTypePointer StorageBuffer %float
+; CHECK: %uint = OpTypeInt 32 0
+; CHECK: %uint_0 = OpConstant %uint 0
+; CHECK: %uint_1 = OpConstant %uint 1
+; CHECK: %uint_3 = OpConstant %uint 3
+; CHECK: %26 = OpTypeFunction %uint %uint %uint
+; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+)" + kInputGlobals + R"(
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %bool = OpTypeBool
+; CHECK: %49 = OpTypeFunction %void %uint %uint %uint %uint
+)" + kOutputGlobals + R"(
+; CHECK: %uint_10 = OpConstant %uint 10
+; CHECK: %uint_4 = OpConstant %uint 4
+; CHECK: %uint_23 = OpConstant %uint 23
+; CHECK: %uint_2 = OpConstant %uint 2
+; CHECK: %v4float = OpTypeVector %float 4
+; CHECK: %_ptr_Input_v4float = OpTypePointer Input %v4float
+; CHECK: %gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+; CHECK: %v4uint = OpTypeVector %uint 4
+; CHECK: %uint_5 = OpConstant %uint 5
+; CHECK: %uint_7 = OpConstant %uint 7
+; CHECK: %uint_8 = OpConstant %uint 8
+; CHECK: %uint_9 = OpConstant %uint 9
+; CHECK: %uint_45 = OpConstant %uint 45
+; CHECK: %101 = OpConstantNull %float
+; CHECK: %105 = OpTypeFunction %uint %uint %uint %uint %uint
 )";
+  // clang-format on
 
-  const std::string defs_after =
-      R"(OpCapability Shader
-OpCapability ShaderNonUniform
-OpCapability RuntimeDescriptorArray
-OpCapability StorageBufferArrayNonUniformIndexing
-OpExtension "SPV_EXT_descriptor_indexing"
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main" %b %nu_ii %gl_FragCoord
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 450
-OpSourceExtension "GL_EXT_nonuniform_qualifier"
-OpName %main "main"
-OpName %b "b"
-OpName %bname "bname"
-OpMemberName %bname 0 "a"
-OpName %storageBuffer "storageBuffer"
-OpName %nu_ii "nu_ii"
-OpDecorate %b Location 0
-OpMemberDecorate %bname 0 Offset 0
-OpDecorate %bname Block
-OpDecorate %storageBuffer DescriptorSet 0
-OpDecorate %storageBuffer Binding 3
-OpDecorate %nu_ii Flat
-OpDecorate %nu_ii Location 0
-OpDecorate %nu_ii NonUniform
-OpDecorate %7 NonUniform
-OpDecorate %102 NonUniform
-OpDecorate %_runtimearr_uint ArrayStride 4
-OpDecorate %_struct_31 Block
-OpMemberDecorate %_struct_31 0 Offset 0
-OpDecorate %33 DescriptorSet 7
-OpDecorate %33 Binding 1
-OpDecorate %130 NonUniform
-OpDecorate %_struct_55 Block
-OpMemberDecorate %_struct_55 0 Offset 0
-OpMemberDecorate %_struct_55 1 Offset 4
-OpDecorate %57 DescriptorSet 7
-OpDecorate %57 Binding 0
-OpDecorate %gl_FragCoord BuiltIn FragCoord
-OpDecorate %127 NonUniform
-%void = OpTypeVoid
-%10 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%_ptr_Output_float = OpTypePointer Output %float
-%b = OpVariable %_ptr_Output_float Output
-%bname = OpTypeStruct %float
-%_runtimearr_bname = OpTypeRuntimeArray %bname
-%_ptr_StorageBuffer__runtimearr_bname = OpTypePointer StorageBuffer %_runtimearr_bname
-%storageBuffer = OpVariable %_ptr_StorageBuffer__runtimearr_bname StorageBuffer
-%int = OpTypeInt 32 1
-%_ptr_Input_int = OpTypePointer Input %int
-%nu_ii = OpVariable %_ptr_Input_int Input
-%int_0 = OpConstant %int 0
-%_ptr_StorageBuffer_float = OpTypePointer StorageBuffer %float
-%uint = OpTypeInt 32 0
-%uint_0 = OpConstant %uint 0
-%uint_1 = OpConstant %uint 1
-%uint_3 = OpConstant %uint 3
-%26 = OpTypeFunction %uint %uint %uint
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_31 = OpTypeStruct %_runtimearr_uint
-%_ptr_StorageBuffer__struct_31 = OpTypePointer StorageBuffer %_struct_31
-%33 = OpVariable %_ptr_StorageBuffer__struct_31 StorageBuffer
-%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-%bool = OpTypeBool
-%49 = OpTypeFunction %void %uint %uint %uint %uint
-%_struct_55 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_55 = OpTypePointer StorageBuffer %_struct_55
-%57 = OpVariable %_ptr_StorageBuffer__struct_55 StorageBuffer
-%uint_10 = OpConstant %uint 10
-%uint_4 = OpConstant %uint 4
-%uint_23 = OpConstant %uint 23
-%uint_2 = OpConstant %uint 2
-%v4float = OpTypeVector %float 4
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
-%v4uint = OpTypeVector %uint 4
-%uint_5 = OpConstant %uint 5
-%uint_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%uint_9 = OpConstant %uint 9
-%uint_45 = OpConstant %uint 45
-%101 = OpConstantNull %float
-%105 = OpTypeFunction %uint %uint %uint %uint %uint
-)";
-
-  const std::string func_before =
-      R"(%main = OpFunction %void None %3
+  const std::string main_func = R"(
+%main = OpFunction %void None %3
 %5 = OpLabel
 %16 = OpLoad %int %nu_ii
 %19 = OpAccessChain %_ptr_StorageBuffer_float %storageBuffer %16 %int_0
 %20 = OpLoad %float %19
 OpStore %b %20
+; CHECK-NOT: %20 = OpLoad %float %19
+; CHECK-NOT: OpStore %b %20
+; CHECK: %40 = OpFunctionCall %uint %inst_bindless_direct_read_2 %uint_1 %uint_3
+; CHECK: %42 = OpULessThan %bool %7 %40
+; CHECK: OpSelectionMerge %43 None
+; CHECK: OpBranchConditional %42 %44 %45
+; CHECK: %44 = OpLabel
+; CHECK: %103 = OpBitcast %uint %7
+; CHECK: %122 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_3 %103
+; CHECK: %123 = OpULessThan %bool %uint_0 %122
+; CHECK: OpSelectionMerge %124 None
+; CHECK: OpBranchConditional %123 %125 %126
+; CHECK: %125 = OpLabel
+; CHECK: %127 = OpLoad %float %20
+; CHECK: OpBranch %124
+; CHECK: %126 = OpLabel
+; CHECK: %128 = OpBitcast %uint %7
+; CHECK: %129 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_45 %uint_1 %128 %uint_0
+; CHECK: OpBranch %124
+; CHECK: %124 = OpLabel
+; CHECK: %130 = OpPhi %float %127 %125 %101 %126
+; CHECK: OpBranch %43
+; CHECK: %45 = OpLabel
+; CHECK: %47 = OpBitcast %uint %7
+; CHECK: %100 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_45 %uint_0 %47 %40
+; CHECK: OpBranch %43
+; CHECK: %43 = OpLabel
+; CHECK: %102 = OpPhi %float %130 %124 %101 %45
+; CHECK: OpStore %b %102
 OpReturn
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%main = OpFunction %void None %10
-%19 = OpLabel
-%7 = OpLoad %int %nu_ii
-%20 = OpAccessChain %_ptr_StorageBuffer_float %storageBuffer %7 %int_0
-%40 = OpFunctionCall %uint %25 %uint_1 %uint_3
-%42 = OpULessThan %bool %7 %40
-OpSelectionMerge %43 None
-OpBranchConditional %42 %44 %45
-%44 = OpLabel
-%103 = OpBitcast %uint %7
-%122 = OpFunctionCall %uint %104 %uint_0 %uint_0 %uint_3 %103
-%123 = OpULessThan %bool %uint_0 %122
-OpSelectionMerge %124 None
-OpBranchConditional %123 %125 %126
-%125 = OpLabel
-%127 = OpLoad %float %20
-OpBranch %124
-%126 = OpLabel
-%128 = OpBitcast %uint %7
-%129 = OpFunctionCall %void %48 %uint_45 %uint_1 %128 %uint_0
-OpBranch %124
-%124 = OpLabel
-%130 = OpPhi %float %127 %125 %101 %126
-OpBranch %43
-%45 = OpLabel
-%47 = OpBitcast %uint %7
-%100 = OpFunctionCall %void %48 %uint_45 %uint_0 %47 %40
-OpBranch %43
-%43 = OpLabel
-%102 = OpPhi %float %130 %124 %101 %45
-OpStore %b %102
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string new_funcs =
-      R"(%25 = OpFunction %uint None %26
-%27 = OpFunctionParameter %uint
-%28 = OpFunctionParameter %uint
-%29 = OpLabel
-%35 = OpAccessChain %_ptr_StorageBuffer_uint %33 %uint_0 %27
-%36 = OpLoad %uint %35
-%37 = OpIAdd %uint %36 %28
-%38 = OpAccessChain %_ptr_StorageBuffer_uint %33 %uint_0 %37
-%39 = OpLoad %uint %38
-OpReturnValue %39
-OpFunctionEnd
-%48 = OpFunction %void None %49
-%50 = OpFunctionParameter %uint
-%51 = OpFunctionParameter %uint
-%52 = OpFunctionParameter %uint
-%53 = OpFunctionParameter %uint
-%54 = OpLabel
-%58 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_0
-%61 = OpAtomicIAdd %uint %58 %uint_4 %uint_0 %uint_10
-%62 = OpIAdd %uint %61 %uint_10
-%63 = OpArrayLength %uint %57 1
-%64 = OpULessThanEqual %bool %62 %63
-OpSelectionMerge %65 None
-OpBranchConditional %64 %66 %65
-%66 = OpLabel
-%67 = OpIAdd %uint %61 %uint_0
-%68 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %67
-OpStore %68 %uint_10
-%70 = OpIAdd %uint %61 %uint_1
-%71 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %70
-OpStore %71 %uint_23
-%73 = OpIAdd %uint %61 %uint_2
-%74 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %73
-OpStore %74 %50
-%75 = OpIAdd %uint %61 %uint_3
-%76 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %75
-OpStore %76 %uint_4
-%80 = OpLoad %v4float %gl_FragCoord
-%82 = OpBitcast %v4uint %80
-%83 = OpCompositeExtract %uint %82 0
-%84 = OpIAdd %uint %61 %uint_4
-%85 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %84
-OpStore %85 %83
-%86 = OpCompositeExtract %uint %82 1
-%88 = OpIAdd %uint %61 %uint_5
-%89 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %88
-OpStore %89 %86
-%91 = OpIAdd %uint %61 %uint_7
-%92 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %91
-OpStore %92 %51
-%94 = OpIAdd %uint %61 %uint_8
-%95 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %94
-OpStore %95 %52
-%97 = OpIAdd %uint %61 %uint_9
-%98 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %97
-OpStore %98 %53
-OpBranch %65
-%65 = OpLabel
-OpReturn
-OpFunctionEnd
-%104 = OpFunction %uint None %105
-%106 = OpFunctionParameter %uint
-%107 = OpFunctionParameter %uint
-%108 = OpFunctionParameter %uint
-%109 = OpFunctionParameter %uint
-%110 = OpLabel
-%111 = OpAccessChain %_ptr_StorageBuffer_uint %33 %uint_0 %106
-%112 = OpLoad %uint %111
-%113 = OpIAdd %uint %112 %107
-%114 = OpAccessChain %_ptr_StorageBuffer_uint %33 %uint_0 %113
-%115 = OpLoad %uint %114
-%116 = OpIAdd %uint %115 %108
-%117 = OpAccessChain %_ptr_StorageBuffer_uint %33 %uint_0 %116
-%118 = OpLoad %uint %117
-%119 = OpIAdd %uint %118 %109
-%120 = OpAccessChain %_ptr_StorageBuffer_uint %33 %uint_0 %119
-%121 = OpLoad %uint %120
-OpReturnValue %121
-OpFunctionEnd
-)";
+  const std::string new_funcs = kDirectRead2 + kStreamWrite4Frag + kDirectRead4;
 
   // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBindlessCheckPass>(
-      defs_before + func_before, defs_after + func_after + new_funcs, true,
-      true, 7u, 23u, true, true, false, false, false);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(defs + main_func + new_funcs,
+                                               true, 7u, 23u, true, true, false,
+                                               false, false);
 }
 
 TEST_F(InstBindlessTest, InstInitLoadUBOScalar) {
@@ -3757,12 +2404,15 @@
   //     b = uniformBuffer.a;
   // }
 
-  const std::string defs_before =
-      R"(OpCapability Shader
+  // clang-format off
+  const std::string defs = R"(
+OpCapability Shader
 OpExtension "SPV_EXT_descriptor_indexing"
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %main "main" %b
+; CHECK: OpEntryPoint Fragment %main "main" %b %gl_FragCoord
 OpExecutionMode %main OriginUpperLeft
 OpSource GLSL 450
 OpSourceExtension "GL_EXT_nonuniform_qualifier"
@@ -3776,6 +2426,9 @@
 OpDecorate %uname Block
 OpDecorate %uniformBuffer DescriptorSet 0
 OpDecorate %uniformBuffer Binding 3
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+)" + kInputDecorations + kOutputDecorations + R"(
+; CHECK: OpDecorate %gl_FragCoord BuiltIn FragCoord
 %void = OpTypeVoid
 %3 = OpTypeFunction %void
 %float = OpTypeFloat 32
@@ -3787,187 +2440,68 @@
 %int = OpTypeInt 32 1
 %int_0 = OpConstant %int 0
 %_ptr_Uniform_float = OpTypePointer Uniform %float
+; CHECK: %int = OpTypeInt 32 1
+; CHECK: %int_0 = OpConstant %int 0
+; CHECK: %_ptr_Uniform_float = OpTypePointer Uniform %float
+; CHECK: %uint = OpTypeInt 32 0
+; CHECK: %uint_0 = OpConstant %uint 0
+; CHECK: %uint_3 = OpConstant %uint 3
+; CHECK: %21 = OpTypeFunction %uint %uint %uint %uint %uint
+; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+)" + kInputGlobals + R"(
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %bool = OpTypeBool
+; CHECK: %uint_1 = OpConstant %uint 1
+; CHECK: %52 = OpTypeFunction %void %uint %uint %uint %uint
+)" + kOutputGlobals + R"(
+; CHECK: %uint_10 = OpConstant %uint 10
+; CHECK: %uint_4 = OpConstant %uint 4
+; CHECK: %uint_23 = OpConstant %uint 23
+; CHECK: %uint_2 = OpConstant %uint 2
+; CHECK: %v4float = OpTypeVector %float 4
+; CHECK: %_ptr_Input_v4float = OpTypePointer Input %v4float
+; CHECK: %gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+; CHECK: %v4uint = OpTypeVector %uint 4
+; CHECK: %uint_5 = OpConstant %uint 5
+; CHECK: %uint_7 = OpConstant %uint 7
+; CHECK: %uint_8 = OpConstant %uint 8
+; CHECK: %uint_9 = OpConstant %uint 9
+; CHECK: %uint_32 = OpConstant %uint 32
+; CHECK: %104 = OpConstantNull %float
 )";
+  // clang-format on
 
-  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 %main "main" %b %gl_FragCoord
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 450
-OpSourceExtension "GL_EXT_nonuniform_qualifier"
-OpName %main "main"
-OpName %b "b"
-OpName %uname "uname"
-OpMemberName %uname 0 "a"
-OpName %uniformBuffer "uniformBuffer"
-OpDecorate %b Location 0
-OpMemberDecorate %uname 0 Offset 0
-OpDecorate %uname Block
-OpDecorate %uniformBuffer DescriptorSet 0
-OpDecorate %uniformBuffer Binding 3
-OpDecorate %_runtimearr_uint ArrayStride 4
-OpDecorate %_struct_28 Block
-OpMemberDecorate %_struct_28 0 Offset 0
-OpDecorate %30 DescriptorSet 7
-OpDecorate %30 Binding 1
-OpDecorate %_struct_58 Block
-OpMemberDecorate %_struct_58 0 Offset 0
-OpMemberDecorate %_struct_58 1 Offset 4
-OpDecorate %60 DescriptorSet 7
-OpDecorate %60 Binding 0
-OpDecorate %gl_FragCoord BuiltIn FragCoord
-%void = OpTypeVoid
-%7 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%_ptr_Output_float = OpTypePointer Output %float
-%b = OpVariable %_ptr_Output_float Output
-%uname = OpTypeStruct %float
-%_ptr_Uniform_uname = OpTypePointer Uniform %uname
-%uniformBuffer = OpVariable %_ptr_Uniform_uname Uniform
-%int = OpTypeInt 32 1
-%int_0 = OpConstant %int 0
-%_ptr_Uniform_float = OpTypePointer Uniform %float
-%uint = OpTypeInt 32 0
-%uint_0 = OpConstant %uint 0
-%uint_3 = OpConstant %uint 3
-%21 = OpTypeFunction %uint %uint %uint %uint %uint
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_28 = OpTypeStruct %_runtimearr_uint
-%_ptr_StorageBuffer__struct_28 = OpTypePointer StorageBuffer %_struct_28
-%30 = OpVariable %_ptr_StorageBuffer__struct_28 StorageBuffer
-%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-%bool = OpTypeBool
-%uint_1 = OpConstant %uint 1
-%52 = OpTypeFunction %void %uint %uint %uint %uint
-%_struct_58 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_58 = OpTypePointer StorageBuffer %_struct_58
-%60 = OpVariable %_ptr_StorageBuffer__struct_58 StorageBuffer
-%uint_10 = OpConstant %uint 10
-%uint_4 = OpConstant %uint 4
-%uint_23 = OpConstant %uint 23
-%uint_2 = OpConstant %uint 2
-%v4float = OpTypeVector %float 4
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
-%v4uint = OpTypeVector %uint 4
-%uint_5 = OpConstant %uint 5
-%uint_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%uint_9 = OpConstant %uint 9
-%uint_32 = OpConstant %uint 32
-%104 = OpConstantNull %float
-)";
-
-  const std::string func_before =
-      R"(%main = OpFunction %void None %3
+  const std::string main_func = R"(
+%main = OpFunction %void None %3
 %5 = OpLabel
 %15 = OpAccessChain %_ptr_Uniform_float %uniformBuffer %int_0
 %16 = OpLoad %float %15
 OpStore %b %16
+; CHECK-NOT: %16 = OpLoad %float %15
+; CHECK-NOT: OpStore %b %16
+; CHECK: %43 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_3 %uint_0
+; CHECK: %45 = OpULessThan %bool %uint_0 %43
+; CHECK: OpSelectionMerge %47 None
+; CHECK: OpBranchConditional %45 %48 %49
+; CHECK: %48 = OpLabel
+; CHECK: %50 = OpLoad %float %15
+; CHECK: OpBranch %47
+; CHECK: %49 = OpLabel
+; CHECK: %103 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_32 %uint_1 %uint_0 %uint_0
+; CHECK: OpBranch %47
+; CHECK: %47 = OpLabel
+; CHECK: %105 = OpPhi %float %50 %48 %104 %49
+; CHECK: OpStore %b %105
 OpReturn
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%main = OpFunction %void None %7
-%14 = OpLabel
-%15 = OpAccessChain %_ptr_Uniform_float %uniformBuffer %int_0
-%43 = OpFunctionCall %uint %20 %uint_0 %uint_0 %uint_3 %uint_0
-%45 = OpULessThan %bool %uint_0 %43
-OpSelectionMerge %47 None
-OpBranchConditional %45 %48 %49
-%48 = OpLabel
-%50 = OpLoad %float %15
-OpBranch %47
-%49 = OpLabel
-%103 = OpFunctionCall %void %51 %uint_32 %uint_1 %uint_0 %uint_0
-OpBranch %47
-%47 = OpLabel
-%105 = OpPhi %float %50 %48 %104 %49
-OpStore %b %105
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string new_funcs =
-      R"(%20 = OpFunction %uint None %21
-%22 = OpFunctionParameter %uint
-%23 = OpFunctionParameter %uint
-%24 = OpFunctionParameter %uint
-%25 = OpFunctionParameter %uint
-%26 = OpLabel
-%32 = OpAccessChain %_ptr_StorageBuffer_uint %30 %uint_0 %22
-%33 = OpLoad %uint %32
-%34 = OpIAdd %uint %33 %23
-%35 = OpAccessChain %_ptr_StorageBuffer_uint %30 %uint_0 %34
-%36 = OpLoad %uint %35
-%37 = OpIAdd %uint %36 %24
-%38 = OpAccessChain %_ptr_StorageBuffer_uint %30 %uint_0 %37
-%39 = OpLoad %uint %38
-%40 = OpIAdd %uint %39 %25
-%41 = OpAccessChain %_ptr_StorageBuffer_uint %30 %uint_0 %40
-%42 = OpLoad %uint %41
-OpReturnValue %42
-OpFunctionEnd
-%51 = OpFunction %void None %52
-%53 = OpFunctionParameter %uint
-%54 = OpFunctionParameter %uint
-%55 = OpFunctionParameter %uint
-%56 = OpFunctionParameter %uint
-%57 = OpLabel
-%61 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_0
-%64 = OpAtomicIAdd %uint %61 %uint_4 %uint_0 %uint_10
-%65 = OpIAdd %uint %64 %uint_10
-%66 = OpArrayLength %uint %60 1
-%67 = OpULessThanEqual %bool %65 %66
-OpSelectionMerge %68 None
-OpBranchConditional %67 %69 %68
-%69 = OpLabel
-%70 = OpIAdd %uint %64 %uint_0
-%71 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %70
-OpStore %71 %uint_10
-%73 = OpIAdd %uint %64 %uint_1
-%74 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %73
-OpStore %74 %uint_23
-%76 = OpIAdd %uint %64 %uint_2
-%77 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %76
-OpStore %77 %53
-%78 = OpIAdd %uint %64 %uint_3
-%79 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %78
-OpStore %79 %uint_4
-%83 = OpLoad %v4float %gl_FragCoord
-%85 = OpBitcast %v4uint %83
-%86 = OpCompositeExtract %uint %85 0
-%87 = OpIAdd %uint %64 %uint_4
-%88 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %87
-OpStore %88 %86
-%89 = OpCompositeExtract %uint %85 1
-%91 = OpIAdd %uint %64 %uint_5
-%92 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %91
-OpStore %92 %89
-%94 = OpIAdd %uint %64 %uint_7
-%95 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %94
-OpStore %95 %54
-%97 = OpIAdd %uint %64 %uint_8
-%98 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %97
-OpStore %98 %55
-%100 = OpIAdd %uint %64 %uint_9
-%101 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %100
-OpStore %101 %56
-OpBranch %68
-%68 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
+  const std::string new_funcs = kDirectRead4 + kStreamWrite4Frag;
 
   // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBindlessCheckPass>(
-      defs_before + func_before, defs_after + func_after + new_funcs, true,
-      true, 7u, 23u, true, true, false, false, false);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(defs + main_func + new_funcs,
+                                               true, 7u, 23u, true, true, false,
+                                               false, false);
 }
 
 TEST_F(InstBindlessTest, InstBoundsInitStoreUnsizedSSBOArray) {
@@ -3984,15 +2518,17 @@
   //     storageBuffer[nu_ii].b = b;
   // }
 
-  const std::string defs_before =
-      R"(OpCapability Shader
+  // clang-format off
+  const std::string defs = R"(OpCapability Shader
 OpCapability ShaderNonUniform
 OpCapability RuntimeDescriptorArray
 OpCapability StorageBufferArrayNonUniformIndexing
 OpExtension "SPV_EXT_descriptor_indexing"
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %main "main" %nu_ii %b
+; CHECK: OpEntryPoint Fragment %main "main" %nu_ii %b %gl_FragCoord
 OpExecutionMode %main OriginUpperLeft
 OpSource GLSL 450
 OpSourceExtension "GL_EXT_nonuniform_qualifier"
@@ -4011,6 +2547,9 @@
 OpDecorate %nu_ii NonUniform
 OpDecorate %14 NonUniform
 OpDecorate %b Location 1
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+)" + kInputDecorations + kOutputDecorations + R"(
+; CHECK: OpDecorate %gl_FragCoord BuiltIn FragCoord
 %void = OpTypeVoid
 %3 = OpTypeFunction %void
 %float = OpTypeFloat 32
@@ -4025,223 +2564,76 @@
 %_ptr_Input_float = OpTypePointer Input %float
 %b = OpVariable %_ptr_Input_float Input
 %_ptr_Uniform_float = OpTypePointer Uniform %float
+; CHECK: %uint = OpTypeInt 32 0
+; CHECK: %uint_0 = OpConstant %uint 0
+; CHECK: %uint_1 = OpConstant %uint 1
+; CHECK: %uint_4 = OpConstant %uint 4
+; CHECK: %26 = OpTypeFunction %uint %uint %uint
+; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+)" + kInputGlobals + R"(
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %bool = OpTypeBool
+; CHECK: %48 = OpTypeFunction %void %uint %uint %uint %uint
+)" + kOutputGlobals + R"(
+; CHECK: %uint_10 = OpConstant %uint 10
+; CHECK: %uint_23 = OpConstant %uint 23
+; CHECK: %uint_2 = OpConstant %uint 2
+; CHECK: %uint_3 = OpConstant %uint 3
+; CHECK: %v4float = OpTypeVector %float 4
+; CHECK: %_ptr_Input_v4float = OpTypePointer Input %v4float
+; CHECK: %gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+; CHECK: %v4uint = OpTypeVector %uint 4
+; CHECK: %uint_5 = OpConstant %uint 5
+; CHECK: %uint_7 = OpConstant %uint 7
+; CHECK: %uint_8 = OpConstant %uint 8
+; CHECK: %uint_9 = OpConstant %uint 9
+; CHECK: %uint_45 = OpConstant %uint 45
+; CHECK: %102 = OpTypeFunction %uint %uint %uint %uint %uint
 )";
+  // clang-format on
 
-  const std::string defs_after =
-      R"(OpCapability Shader
-OpCapability ShaderNonUniform
-OpCapability RuntimeDescriptorArray
-OpCapability StorageBufferArrayNonUniformIndexing
-OpExtension "SPV_EXT_descriptor_indexing"
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main" %nu_ii %b %gl_FragCoord
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 450
-OpSourceExtension "GL_EXT_nonuniform_qualifier"
-OpName %main "main"
-OpName %bname "bname"
-OpMemberName %bname 0 "b"
-OpName %storageBuffer "storageBuffer"
-OpName %nu_ii "nu_ii"
-OpName %b "b"
-OpMemberDecorate %bname 0 Offset 0
-OpDecorate %bname BufferBlock
-OpDecorate %storageBuffer DescriptorSet 0
-OpDecorate %storageBuffer Binding 4
-OpDecorate %nu_ii Flat
-OpDecorate %nu_ii Location 0
-OpDecorate %nu_ii NonUniform
-OpDecorate %7 NonUniform
-OpDecorate %b Location 1
-OpDecorate %_runtimearr_uint ArrayStride 4
-OpDecorate %_struct_31 Block
-OpMemberDecorate %_struct_31 0 Offset 0
-OpDecorate %33 DescriptorSet 7
-OpDecorate %33 Binding 1
-OpDecorate %_struct_54 Block
-OpMemberDecorate %_struct_54 0 Offset 0
-OpMemberDecorate %_struct_54 1 Offset 4
-OpDecorate %56 DescriptorSet 7
-OpDecorate %56 Binding 0
-OpDecorate %gl_FragCoord BuiltIn FragCoord
-%void = OpTypeVoid
-%9 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%bname = OpTypeStruct %float
-%_runtimearr_bname = OpTypeRuntimeArray %bname
-%_ptr_Uniform__runtimearr_bname = OpTypePointer Uniform %_runtimearr_bname
-%storageBuffer = OpVariable %_ptr_Uniform__runtimearr_bname Uniform
-%int = OpTypeInt 32 1
-%_ptr_Input_int = OpTypePointer Input %int
-%nu_ii = OpVariable %_ptr_Input_int Input
-%int_0 = OpConstant %int 0
-%_ptr_Input_float = OpTypePointer Input %float
-%b = OpVariable %_ptr_Input_float Input
-%_ptr_Uniform_float = OpTypePointer Uniform %float
-%uint = OpTypeInt 32 0
-%uint_0 = OpConstant %uint 0
-%uint_1 = OpConstant %uint 1
-%uint_4 = OpConstant %uint 4
-%26 = OpTypeFunction %uint %uint %uint
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_31 = OpTypeStruct %_runtimearr_uint
-%_ptr_StorageBuffer__struct_31 = OpTypePointer StorageBuffer %_struct_31
-%33 = OpVariable %_ptr_StorageBuffer__struct_31 StorageBuffer
-%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-%bool = OpTypeBool
-%48 = OpTypeFunction %void %uint %uint %uint %uint
-%_struct_54 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_54 = OpTypePointer StorageBuffer %_struct_54
-%56 = OpVariable %_ptr_StorageBuffer__struct_54 StorageBuffer
-%uint_10 = OpConstant %uint 10
-%uint_23 = OpConstant %uint 23
-%uint_2 = OpConstant %uint 2
-%uint_3 = OpConstant %uint 3
-%v4float = OpTypeVector %float 4
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
-%v4uint = OpTypeVector %uint 4
-%uint_5 = OpConstant %uint 5
-%uint_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%uint_9 = OpConstant %uint 9
-%uint_45 = OpConstant %uint 45
-%102 = OpTypeFunction %uint %uint %uint %uint %uint
-)";
-
-  const std::string func_before =
-      R"(%main = OpFunction %void None %3
+  const std::string main_func = R"(
+%main = OpFunction %void None %3
 %5 = OpLabel
 %14 = OpLoad %int %nu_ii
 %18 = OpLoad %float %b
 %20 = OpAccessChain %_ptr_Uniform_float %storageBuffer %14 %int_0
 OpStore %20 %18
+; CHECK-NOT: OpStore %20 %18
+; CHECK: %40 = OpFunctionCall %uint %inst_bindless_direct_read_2 %uint_1 %uint_4
+; CHECK: %42 = OpULessThan %bool %7 %40
+; CHECK: OpSelectionMerge %43 None
+; CHECK: OpBranchConditional %42 %44 %45
+; CHECK: %44 = OpLabel
+; CHECK: %100 = OpBitcast %uint %7
+; CHECK: %119 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_4 %100
+; CHECK: %120 = OpULessThan %bool %uint_0 %119
+; CHECK: OpSelectionMerge %121 None
+; CHECK: OpBranchConditional %120 %122 %123
+; CHECK: %122 = OpLabel
+; CHECK: OpStore %20 %19
+; CHECK: OpBranch %121
+; CHECK: %123 = OpLabel
+; CHECK: %124 = OpBitcast %uint %7
+; CHECK: %125 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_45 %uint_1 %124 %uint_0
+; CHECK: OpBranch %121
+; CHECK: %121 = OpLabel
+; CHECK: OpBranch %43
+; CHECK: %45 = OpLabel
+; CHECK: %46 = OpBitcast %uint %7
+; CHECK: %99 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_45 %uint_0 %46 %40
+; CHECK: OpBranch %43
+; CHECK: %43 = OpLabel
 OpReturn
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%main = OpFunction %void None %9
-%18 = OpLabel
-%7 = OpLoad %int %nu_ii
-%19 = OpLoad %float %b
-%20 = OpAccessChain %_ptr_Uniform_float %storageBuffer %7 %int_0
-%40 = OpFunctionCall %uint %25 %uint_1 %uint_4
-%42 = OpULessThan %bool %7 %40
-OpSelectionMerge %43 None
-OpBranchConditional %42 %44 %45
-%44 = OpLabel
-%100 = OpBitcast %uint %7
-%119 = OpFunctionCall %uint %101 %uint_0 %uint_0 %uint_4 %100
-%120 = OpULessThan %bool %uint_0 %119
-OpSelectionMerge %121 None
-OpBranchConditional %120 %122 %123
-%122 = OpLabel
-OpStore %20 %19
-OpBranch %121
-%123 = OpLabel
-%124 = OpBitcast %uint %7
-%125 = OpFunctionCall %void %47 %uint_45 %uint_1 %124 %uint_0
-OpBranch %121
-%121 = OpLabel
-OpBranch %43
-%45 = OpLabel
-%46 = OpBitcast %uint %7
-%99 = OpFunctionCall %void %47 %uint_45 %uint_0 %46 %40
-OpBranch %43
-%43 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string new_funcs =
-      R"(%25 = OpFunction %uint None %26
-%27 = OpFunctionParameter %uint
-%28 = OpFunctionParameter %uint
-%29 = OpLabel
-%35 = OpAccessChain %_ptr_StorageBuffer_uint %33 %uint_0 %27
-%36 = OpLoad %uint %35
-%37 = OpIAdd %uint %36 %28
-%38 = OpAccessChain %_ptr_StorageBuffer_uint %33 %uint_0 %37
-%39 = OpLoad %uint %38
-OpReturnValue %39
-OpFunctionEnd
-%47 = OpFunction %void None %48
-%49 = OpFunctionParameter %uint
-%50 = OpFunctionParameter %uint
-%51 = OpFunctionParameter %uint
-%52 = OpFunctionParameter %uint
-%53 = OpLabel
-%57 = OpAccessChain %_ptr_StorageBuffer_uint %56 %uint_0
-%59 = OpAtomicIAdd %uint %57 %uint_4 %uint_0 %uint_10
-%60 = OpIAdd %uint %59 %uint_10
-%61 = OpArrayLength %uint %56 1
-%62 = OpULessThanEqual %bool %60 %61
-OpSelectionMerge %63 None
-OpBranchConditional %62 %64 %63
-%64 = OpLabel
-%65 = OpIAdd %uint %59 %uint_0
-%66 = OpAccessChain %_ptr_StorageBuffer_uint %56 %uint_1 %65
-OpStore %66 %uint_10
-%68 = OpIAdd %uint %59 %uint_1
-%69 = OpAccessChain %_ptr_StorageBuffer_uint %56 %uint_1 %68
-OpStore %69 %uint_23
-%71 = OpIAdd %uint %59 %uint_2
-%72 = OpAccessChain %_ptr_StorageBuffer_uint %56 %uint_1 %71
-OpStore %72 %49
-%74 = OpIAdd %uint %59 %uint_3
-%75 = OpAccessChain %_ptr_StorageBuffer_uint %56 %uint_1 %74
-OpStore %75 %uint_4
-%79 = OpLoad %v4float %gl_FragCoord
-%81 = OpBitcast %v4uint %79
-%82 = OpCompositeExtract %uint %81 0
-%83 = OpIAdd %uint %59 %uint_4
-%84 = OpAccessChain %_ptr_StorageBuffer_uint %56 %uint_1 %83
-OpStore %84 %82
-%85 = OpCompositeExtract %uint %81 1
-%87 = OpIAdd %uint %59 %uint_5
-%88 = OpAccessChain %_ptr_StorageBuffer_uint %56 %uint_1 %87
-OpStore %88 %85
-%90 = OpIAdd %uint %59 %uint_7
-%91 = OpAccessChain %_ptr_StorageBuffer_uint %56 %uint_1 %90
-OpStore %91 %50
-%93 = OpIAdd %uint %59 %uint_8
-%94 = OpAccessChain %_ptr_StorageBuffer_uint %56 %uint_1 %93
-OpStore %94 %51
-%96 = OpIAdd %uint %59 %uint_9
-%97 = OpAccessChain %_ptr_StorageBuffer_uint %56 %uint_1 %96
-OpStore %97 %52
-OpBranch %63
-%63 = OpLabel
-OpReturn
-OpFunctionEnd
-%101 = OpFunction %uint None %102
-%103 = OpFunctionParameter %uint
-%104 = OpFunctionParameter %uint
-%105 = OpFunctionParameter %uint
-%106 = OpFunctionParameter %uint
-%107 = OpLabel
-%108 = OpAccessChain %_ptr_StorageBuffer_uint %33 %uint_0 %103
-%109 = OpLoad %uint %108
-%110 = OpIAdd %uint %109 %104
-%111 = OpAccessChain %_ptr_StorageBuffer_uint %33 %uint_0 %110
-%112 = OpLoad %uint %111
-%113 = OpIAdd %uint %112 %105
-%114 = OpAccessChain %_ptr_StorageBuffer_uint %33 %uint_0 %113
-%115 = OpLoad %uint %114
-%116 = OpIAdd %uint %115 %106
-%117 = OpAccessChain %_ptr_StorageBuffer_uint %33 %uint_0 %116
-%118 = OpLoad %uint %117
-OpReturnValue %118
-OpFunctionEnd
-)";
+  const std::string new_funcs = kDirectRead2 + kStreamWrite4Frag + kDirectRead4;
 
   // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBindlessCheckPass>(
-      defs_before + func_before, defs_after + func_after + new_funcs, true,
-      true, 7u, 23u, true, true, false, false, false);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(defs + main_func + new_funcs,
+                                               true, 7u, 23u, true, true, false,
+                                               false, false);
 }
 
 TEST_F(InstBindlessTest, InstBoundsInitLoadSizedUBOArray) {
@@ -4258,14 +2650,17 @@
   //     b = uniformBuffer[nu_ii].a;
   // }
 
-  const std::string defs_before =
-      R"(OpCapability Shader
+  // clang-format off
+  const std::string defs = R"(
+OpCapability Shader
 OpCapability ShaderNonUniform
 OpCapability UniformBufferArrayNonUniformIndexing
 OpExtension "SPV_EXT_descriptor_indexing"
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %main "main" %b %nu_ii
+; CHECK: OpEntryPoint Fragment %main "main" %b %nu_ii %gl_FragCoord
 OpExecutionMode %main OriginUpperLeft
 OpSource GLSL 450
 OpSourceExtension "GL_EXT_nonuniform_qualifier"
@@ -4285,6 +2680,11 @@
 OpDecorate %nu_ii NonUniform
 OpDecorate %18 NonUniform
 OpDecorate %22 NonUniform
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+)" + kOutputDecorations + R"(
+; CHECK: OpDecorate %gl_FragCoord BuiltIn FragCoord
+)" + kInputDecorations + R"(
+; CHECK: OpDecorate %117 NonUniform
 %void = OpTypeVoid
 %3 = OpTypeFunction %void
 %float = OpTypeFloat 32
@@ -4301,216 +2701,77 @@
 %nu_ii = OpVariable %_ptr_Input_int Input
 %int_0 = OpConstant %int 0
 %_ptr_Uniform_float = OpTypePointer Uniform %float
-)";
+; CHECK: %uint_0 = OpConstant %uint 0
+; CHECK: %bool = OpTypeBool
+; CHECK: %32 = OpTypeFunction %void %uint %uint %uint %uint
+; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+)" + kOutputGlobals + R"(
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %uint_10 = OpConstant %uint 10
+; CHECK: %uint_4 = OpConstant %uint 4
+; CHECK: %uint_1 = OpConstant %uint 1
+; CHECK: %uint_23 = OpConstant %uint 23
+; CHECK: %uint_2 = OpConstant %uint 2
+; CHECK: %uint_3 = OpConstant %uint 3
+; CHECK: %v4float = OpTypeVector %float 4
+; CHECK: %_ptr_Input_v4float = OpTypePointer Input %v4float
+; CHECK: %gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+; CHECK: %v4uint = OpTypeVector %uint 4
+; CHECK: %uint_5 = OpConstant %uint 5
+; CHECK: %uint_7 = OpConstant %uint 7
+; CHECK: %uint_8 = OpConstant %uint 8
+; CHECK: %uint_9 = OpConstant %uint 9
+; CHECK: %uint_46 = OpConstant %uint 46
+; CHECK: %88 = OpConstantNull %float
+; CHECK: %92 = OpTypeFunction %uint %uint %uint %uint %uint
+)" + kInputGlobals;
+  // clang-format on
 
-  const std::string defs_after =
-      R"(OpCapability Shader
-OpCapability ShaderNonUniform
-OpCapability UniformBufferArrayNonUniformIndexing
-OpExtension "SPV_EXT_descriptor_indexing"
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main" %b %nu_ii %gl_FragCoord
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 450
-OpSourceExtension "GL_EXT_nonuniform_qualifier"
-OpName %main "main"
-OpName %b "b"
-OpName %uname "uname"
-OpMemberName %uname 0 "a"
-OpName %uniformBuffer "uniformBuffer"
-OpName %nu_ii "nu_ii"
-OpDecorate %b Location 0
-OpMemberDecorate %uname 0 Offset 0
-OpDecorate %uname Block
-OpDecorate %uniformBuffer DescriptorSet 0
-OpDecorate %uniformBuffer Binding 3
-OpDecorate %nu_ii Flat
-OpDecorate %nu_ii Location 0
-OpDecorate %nu_ii NonUniform
-OpDecorate %7 NonUniform
-OpDecorate %89 NonUniform
-OpDecorate %120 NonUniform
-OpDecorate %_runtimearr_uint ArrayStride 4
-OpDecorate %_struct_39 Block
-OpMemberDecorate %_struct_39 0 Offset 0
-OpMemberDecorate %_struct_39 1 Offset 4
-OpDecorate %41 DescriptorSet 7
-OpDecorate %41 Binding 0
-OpDecorate %gl_FragCoord BuiltIn FragCoord
-OpDecorate %_struct_98 Block
-OpMemberDecorate %_struct_98 0 Offset 0
-OpDecorate %100 DescriptorSet 7
-OpDecorate %100 Binding 1
-OpDecorate %117 NonUniform
-%void = OpTypeVoid
-%10 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%_ptr_Output_float = OpTypePointer Output %float
-%b = OpVariable %_ptr_Output_float Output
-%uname = OpTypeStruct %float
-%uint = OpTypeInt 32 0
-%uint_128 = OpConstant %uint 128
-%_arr_uname_uint_128 = OpTypeArray %uname %uint_128
-%_ptr_Uniform__arr_uname_uint_128 = OpTypePointer Uniform %_arr_uname_uint_128
-%uniformBuffer = OpVariable %_ptr_Uniform__arr_uname_uint_128 Uniform
-%int = OpTypeInt 32 1
-%_ptr_Input_int = OpTypePointer Input %int
-%nu_ii = OpVariable %_ptr_Input_int Input
-%int_0 = OpConstant %int 0
-%_ptr_Uniform_float = OpTypePointer Uniform %float
-%uint_0 = OpConstant %uint 0
-%bool = OpTypeBool
-%32 = OpTypeFunction %void %uint %uint %uint %uint
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_39 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_39 = OpTypePointer StorageBuffer %_struct_39
-%41 = OpVariable %_ptr_StorageBuffer__struct_39 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_3 = OpConstant %uint 3
-%v4float = OpTypeVector %float 4
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
-%v4uint = OpTypeVector %uint 4
-%uint_5 = OpConstant %uint 5
-%uint_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%uint_9 = OpConstant %uint 9
-%uint_46 = OpConstant %uint 46
-%88 = OpConstantNull %float
-%92 = OpTypeFunction %uint %uint %uint %uint %uint
-%_struct_98 = OpTypeStruct %_runtimearr_uint
-%_ptr_StorageBuffer__struct_98 = OpTypePointer StorageBuffer %_struct_98
-%100 = OpVariable %_ptr_StorageBuffer__struct_98 StorageBuffer
-)";
-
-  const std::string func_before =
-      R"(%main = OpFunction %void None %3
+  const std::string main_func = R"(
+%main = OpFunction %void None %3
 %5 = OpLabel
 %18 = OpLoad %int %nu_ii
 %21 = OpAccessChain %_ptr_Uniform_float %uniformBuffer %18 %int_0
 %22 = OpLoad %float %21
 OpStore %b %22
+; CHECK-NOT: %22 = OpLoad %float %21
+; CHECK-NOT: OpStore %b %22
+; CHECK: %25 = OpULessThan %bool %7 %uint_128
+; CHECK: OpSelectionMerge %26 None
+; CHECK: OpBranchConditional %25 %27 %28
+; CHECK: %27 = OpLabel
+; CHECK: %90 = OpBitcast %uint %7
+; CHECK: %112 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_3 %90
+; CHECK: %113 = OpULessThan %bool %uint_0 %112
+; CHECK: OpSelectionMerge %114 None
+; CHECK: OpBranchConditional %113 %115 %116
+; CHECK: %115 = OpLabel
+; CHECK: %117 = OpLoad %float %22
+; CHECK: OpBranch %114
+; CHECK: %116 = OpLabel
+; CHECK: %118 = OpBitcast %uint %7
+; CHECK: %119 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_46 %uint_1 %118 %uint_0
+; CHECK: OpBranch %114
+; CHECK: %114 = OpLabel
+; CHECK: %120 = OpPhi %float %117 %115 %88 %116
+; CHECK: OpBranch %26
+; CHECK: %28 = OpLabel
+; CHECK: %30 = OpBitcast %uint %7
+; CHECK: %87 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_46 %uint_0 %30 %uint_128
+; CHECK: OpBranch %26
+; CHECK: %26 = OpLabel
+; CHECK: %89 = OpPhi %float %120 %114 %88 %28
+; CHECK: OpStore %b %89
 OpReturn
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%main = OpFunction %void None %10
-%21 = OpLabel
-%7 = OpLoad %int %nu_ii
-%22 = OpAccessChain %_ptr_Uniform_float %uniformBuffer %7 %int_0
-%25 = OpULessThan %bool %7 %uint_128
-OpSelectionMerge %26 None
-OpBranchConditional %25 %27 %28
-%27 = OpLabel
-%90 = OpBitcast %uint %7
-%112 = OpFunctionCall %uint %91 %uint_0 %uint_0 %uint_3 %90
-%113 = OpULessThan %bool %uint_0 %112
-OpSelectionMerge %114 None
-OpBranchConditional %113 %115 %116
-%115 = OpLabel
-%117 = OpLoad %float %22
-OpBranch %114
-%116 = OpLabel
-%118 = OpBitcast %uint %7
-%119 = OpFunctionCall %void %31 %uint_46 %uint_1 %118 %uint_0
-OpBranch %114
-%114 = OpLabel
-%120 = OpPhi %float %117 %115 %88 %116
-OpBranch %26
-%28 = OpLabel
-%30 = OpBitcast %uint %7
-%87 = OpFunctionCall %void %31 %uint_46 %uint_0 %30 %uint_128
-OpBranch %26
-%26 = OpLabel
-%89 = OpPhi %float %120 %114 %88 %28
-OpStore %b %89
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string new_funcs =
-      R"(%31 = OpFunction %void None %32
-%33 = OpFunctionParameter %uint
-%34 = OpFunctionParameter %uint
-%35 = OpFunctionParameter %uint
-%36 = OpFunctionParameter %uint
-%37 = OpLabel
-%43 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0
-%46 = OpAtomicIAdd %uint %43 %uint_4 %uint_0 %uint_10
-%47 = OpIAdd %uint %46 %uint_10
-%48 = OpArrayLength %uint %41 1
-%49 = OpULessThanEqual %bool %47 %48
-OpSelectionMerge %50 None
-OpBranchConditional %49 %51 %50
-%51 = OpLabel
-%52 = OpIAdd %uint %46 %uint_0
-%54 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_1 %52
-OpStore %54 %uint_10
-%56 = OpIAdd %uint %46 %uint_1
-%57 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_1 %56
-OpStore %57 %uint_23
-%59 = OpIAdd %uint %46 %uint_2
-%60 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_1 %59
-OpStore %60 %33
-%62 = OpIAdd %uint %46 %uint_3
-%63 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_1 %62
-OpStore %63 %uint_4
-%67 = OpLoad %v4float %gl_FragCoord
-%69 = OpBitcast %v4uint %67
-%70 = OpCompositeExtract %uint %69 0
-%71 = OpIAdd %uint %46 %uint_4
-%72 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_1 %71
-OpStore %72 %70
-%73 = OpCompositeExtract %uint %69 1
-%75 = OpIAdd %uint %46 %uint_5
-%76 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_1 %75
-OpStore %76 %73
-%78 = OpIAdd %uint %46 %uint_7
-%79 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_1 %78
-OpStore %79 %34
-%81 = OpIAdd %uint %46 %uint_8
-%82 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_1 %81
-OpStore %82 %35
-%84 = OpIAdd %uint %46 %uint_9
-%85 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_1 %84
-OpStore %85 %36
-OpBranch %50
-%50 = OpLabel
-OpReturn
-OpFunctionEnd
-%91 = OpFunction %uint None %92
-%93 = OpFunctionParameter %uint
-%94 = OpFunctionParameter %uint
-%95 = OpFunctionParameter %uint
-%96 = OpFunctionParameter %uint
-%97 = OpLabel
-%101 = OpAccessChain %_ptr_StorageBuffer_uint %100 %uint_0 %93
-%102 = OpLoad %uint %101
-%103 = OpIAdd %uint %102 %94
-%104 = OpAccessChain %_ptr_StorageBuffer_uint %100 %uint_0 %103
-%105 = OpLoad %uint %104
-%106 = OpIAdd %uint %105 %95
-%107 = OpAccessChain %_ptr_StorageBuffer_uint %100 %uint_0 %106
-%108 = OpLoad %uint %107
-%109 = OpIAdd %uint %108 %96
-%110 = OpAccessChain %_ptr_StorageBuffer_uint %100 %uint_0 %109
-%111 = OpLoad %uint %110
-OpReturnValue %111
-OpFunctionEnd
-)";
+  const std::string new_funcs = kStreamWrite4Frag + kDirectRead4;
 
   // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBindlessCheckPass>(
-      defs_before + func_before, defs_after + func_after + new_funcs, true,
-      true, 7u, 23u, true, true, false, false, false);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(defs + main_func + new_funcs,
+                                               true, 7u, 23u, true, true, false,
+                                               false, false);
 }
 
 TEST_F(InstBindlessTest,
@@ -4532,13 +2793,16 @@
   //    sbo.red = imageLoad(images[sbo.index], ivec2(0, 0)).r;
   // }
 
-  const std::string defs_before =
-      R"(OpCapability Shader
+  // clang-format off
+  const std::string defs = R"(
+OpCapability Shader
 OpCapability RuntimeDescriptorArray
 OpExtension "SPV_EXT_descriptor_indexing"
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
 OpEntryPoint GLCompute %main "main"
+; CHECK: OpEntryPoint GLCompute %main "main" %gl_GlobalInvocationID
 OpExecutionMode %main LocalSize 1 1 1
 OpSource GLSL 450
 OpSourceExtension "GL_EXT_nonuniform_qualifier"
@@ -4556,6 +2820,9 @@
 OpDecorate %images DescriptorSet 0
 OpDecorate %images Binding 1
 OpDecorate %images NonWritable
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+)" + kInputDecorations + kOutputDecorations + R"(
+; CHECK: OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
 %void = OpTypeVoid
 %3 = OpTypeFunction %void
 %uint = OpTypeInt 32 0
@@ -4577,100 +2844,38 @@
 %v4float = OpTypeVector %float 4
 %uint_0 = OpConstant %uint 0
 %_ptr_Uniform_float = OpTypePointer Uniform %float
+; CHECK: %uint_1 = OpConstant %uint 1
+; CHECK: %34 = OpTypeFunction %uint %uint %uint
+; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+)" + kInputGlobals + R"(
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %bool = OpTypeBool
+; CHECK: %57 = OpTypeFunction %void %uint %uint %uint %uint
+)" + kOutputGlobals + R"(
+; CHECK: %uint_10 = OpConstant %uint 10
+; CHECK: %uint_4 = OpConstant %uint 4
+; CHECK: %uint_23 = OpConstant %uint 23
+; CHECK: %uint_2 = OpConstant %uint 2
+; CHECK: %uint_5 = OpConstant %uint 5
+; CHECK: %uint_3 = OpConstant %uint 3
+; CHECK: %v3uint = OpTypeVector %uint 3
+; CHECK: %_ptr_Input_v3uint = OpTypePointer Input %v3uint
+; CHECK: %gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
+; CHECK: %uint_6 = OpConstant %uint 6
+; CHECK: %uint_7 = OpConstant %uint 7
+; CHECK: %uint_8 = OpConstant %uint 8
+; CHECK: %uint_9 = OpConstant %uint 9
+; CHECK: %uint_50 = OpConstant %uint 50
+; CHECK: %112 = OpConstantNull %v4float
+; CHECK: %115 = OpTypeFunction %uint %uint %uint %uint %uint
+; CHECK: %uint_47 = OpConstant %uint 47
+; CHECK: %140 = OpConstantNull %uint
+; CHECK: %uint_53 = OpConstant %uint 53
 )";
+  // clang-format on
 
-  const std::string defs_after =
-      R"(OpCapability Shader
-OpCapability RuntimeDescriptorArray
-OpExtension "SPV_EXT_descriptor_indexing"
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %main "main" %gl_GlobalInvocationID
-OpExecutionMode %main LocalSize 1 1 1
-OpSource GLSL 450
-OpSourceExtension "GL_EXT_nonuniform_qualifier"
-OpName %main "main"
-OpName %Input "Input"
-OpMemberName %Input 0 "index"
-OpMemberName %Input 1 "red"
-OpName %sbo "sbo"
-OpName %images "images"
-OpMemberDecorate %Input 0 Offset 0
-OpMemberDecorate %Input 1 Offset 4
-OpDecorate %Input BufferBlock
-OpDecorate %sbo DescriptorSet 0
-OpDecorate %sbo Binding 0
-OpDecorate %images DescriptorSet 0
-OpDecorate %images Binding 1
-OpDecorate %images NonWritable
-OpDecorate %_runtimearr_uint ArrayStride 4
-OpDecorate %_struct_39 Block
-OpMemberDecorate %_struct_39 0 Offset 0
-OpDecorate %41 DescriptorSet 7
-OpDecorate %41 Binding 1
-OpDecorate %_struct_63 Block
-OpMemberDecorate %_struct_63 0 Offset 0
-OpMemberDecorate %_struct_63 1 Offset 4
-OpDecorate %65 DescriptorSet 7
-OpDecorate %65 Binding 0
-OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
-%void = OpTypeVoid
-%7 = OpTypeFunction %void
-%uint = OpTypeInt 32 0
-%float = OpTypeFloat 32
-%Input = OpTypeStruct %uint %float
-%_ptr_Uniform_Input = OpTypePointer Uniform %Input
-%sbo = OpVariable %_ptr_Uniform_Input Uniform
-%int = OpTypeInt 32 1
-%int_1 = OpConstant %int 1
-%13 = OpTypeImage %float 2D 0 0 0 2 Rgba32f
-%_runtimearr_13 = OpTypeRuntimeArray %13
-%_ptr_UniformConstant__runtimearr_13 = OpTypePointer UniformConstant %_runtimearr_13
-%images = OpVariable %_ptr_UniformConstant__runtimearr_13 UniformConstant
-%int_0 = OpConstant %int 0
-%_ptr_Uniform_uint = OpTypePointer Uniform %uint
-%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
-%v2int = OpTypeVector %int 2
-%20 = OpConstantComposite %v2int %int_0 %int_0
-%v4float = OpTypeVector %float 4
-%uint_0 = OpConstant %uint 0
-%_ptr_Uniform_float = OpTypePointer Uniform %float
-%uint_1 = OpConstant %uint 1
-%34 = OpTypeFunction %uint %uint %uint
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_39 = OpTypeStruct %_runtimearr_uint
-%_ptr_StorageBuffer__struct_39 = OpTypePointer StorageBuffer %_struct_39
-%41 = OpVariable %_ptr_StorageBuffer__struct_39 StorageBuffer
-%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-%bool = OpTypeBool
-%57 = OpTypeFunction %void %uint %uint %uint %uint
-%_struct_63 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_63 = OpTypePointer StorageBuffer %_struct_63
-%65 = OpVariable %_ptr_StorageBuffer__struct_63 StorageBuffer
-%uint_10 = OpConstant %uint 10
-%uint_4 = OpConstant %uint 4
-%uint_23 = OpConstant %uint 23
-%uint_2 = OpConstant %uint 2
-%uint_5 = OpConstant %uint 5
-%uint_3 = OpConstant %uint 3
-%v3uint = OpTypeVector %uint 3
-%_ptr_Input_v3uint = OpTypePointer Input %v3uint
-%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
-%uint_6 = OpConstant %uint 6
-%uint_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%uint_9 = OpConstant %uint 9
-%uint_50 = OpConstant %uint 50
-%112 = OpConstantNull %v4float
-%115 = OpTypeFunction %uint %uint %uint %uint %uint
-%uint_47 = OpConstant %uint 47
-%140 = OpConstantNull %uint
-%uint_53 = OpConstant %uint 53
-)";
-
-  const std::string func_before =
-      R"(%main = OpFunction %void None %3
+  const std::string main_func = R"(
+%main = OpFunction %void None %3
 %5 = OpLabel
 %19 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
 %20 = OpLoad %uint %19
@@ -4680,159 +2885,70 @@
 %29 = OpCompositeExtract %float %27 0
 %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
 OpStore %31 %29
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string func_after =
-      R"(%main = OpFunction %void None %7
-%24 = OpLabel
-%25 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
-%132 = OpFunctionCall %uint %114 %uint_0 %uint_0 %uint_0 %uint_0
-%133 = OpULessThan %bool %uint_0 %132
-OpSelectionMerge %134 None
-OpBranchConditional %133 %135 %136
-%135 = OpLabel
-%137 = OpLoad %uint %25
-OpBranch %134
-%136 = OpLabel
-%139 = OpFunctionCall %void %56 %uint_47 %uint_1 %uint_0 %uint_0
-OpBranch %134
-%134 = OpLabel
-%141 = OpPhi %uint %137 %135 %140 %136
-%27 = OpAccessChain %_ptr_UniformConstant_13 %images %141
-%28 = OpLoad %13 %27
-%48 = OpFunctionCall %uint %33 %uint_1 %uint_1
-%50 = OpULessThan %bool %141 %48
-OpSelectionMerge %51 None
-OpBranchConditional %50 %52 %53
-%52 = OpLabel
-%54 = OpLoad %13 %27
-%142 = OpFunctionCall %uint %114 %uint_0 %uint_0 %uint_1 %141
-%143 = OpULessThan %bool %uint_0 %142
-OpSelectionMerge %144 None
-OpBranchConditional %143 %145 %146
-%145 = OpLabel
-%147 = OpLoad %13 %27
-%148 = OpImageRead %v4float %147 %20
-OpBranch %144
-%146 = OpLabel
-%149 = OpFunctionCall %void %56 %uint_50 %uint_1 %141 %uint_0
-OpBranch %144
-%144 = OpLabel
-%150 = OpPhi %v4float %148 %145 %112 %146
-OpBranch %51
-%53 = OpLabel
-%111 = OpFunctionCall %void %56 %uint_50 %uint_0 %141 %48
-OpBranch %51
-%51 = OpLabel
-%113 = OpPhi %v4float %150 %144 %112 %53
-%30 = OpCompositeExtract %float %113 0
-%31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
-%151 = OpFunctionCall %uint %114 %uint_0 %uint_0 %uint_0 %uint_0
-%152 = OpULessThan %bool %uint_0 %151
-OpSelectionMerge %153 None
-OpBranchConditional %152 %154 %155
-%154 = OpLabel
-OpStore %31 %30
-OpBranch %153
-%155 = OpLabel
-%157 = OpFunctionCall %void %56 %uint_53 %uint_1 %uint_0 %uint_0
-OpBranch %153
-%153 = OpLabel
+; CHECK-NOT: OpStore %31 %29
+; CHECK: %132 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_0 %uint_0
+; CHECK: %133 = OpULessThan %bool %uint_0 %132
+; CHECK: OpSelectionMerge %134 None
+; CHECK: OpBranchConditional %133 %135 %136
+; CHECK: %135 = OpLabel
+; CHECK: %137 = OpLoad %uint %25
+; CHECK: OpBranch %134
+; CHECK: %136 = OpLabel
+; CHECK: %139 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_47 %uint_1 %uint_0 %uint_0
+; CHECK: OpBranch %134
+; CHECK: %134 = OpLabel
+; CHECK: %141 = OpPhi %uint %137 %135 %140 %136
+; CHECK: %27 = OpAccessChain %_ptr_UniformConstant_13 %images %141
+; CHECK: %28 = OpLoad %13 %27
+; CHECK: %48 = OpFunctionCall %uint %inst_bindless_direct_read_2 %uint_1 %uint_1
+; CHECK: %50 = OpULessThan %bool %141 %48
+; CHECK: OpSelectionMerge %51 None
+; CHECK: OpBranchConditional %50 %52 %53
+; CHECK: %52 = OpLabel
+; CHECK: %54 = OpLoad %13 %27
+; CHECK: %142 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_1 %141
+; CHECK: %143 = OpULessThan %bool %uint_0 %142
+; CHECK: OpSelectionMerge %144 None
+; CHECK: OpBranchConditional %143 %145 %146
+; CHECK: %145 = OpLabel
+; CHECK: %147 = OpLoad %13 %27
+; CHECK: %148 = OpImageRead %v4float %147 %20
+; CHECK: OpBranch %144
+; CHECK: %146 = OpLabel
+; CHECK: %149 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_50 %uint_1 %141 %uint_0
+; CHECK: OpBranch %144
+; CHECK: %144 = OpLabel
+; CHECK: %150 = OpPhi %v4float %148 %145 %112 %146
+; CHECK: OpBranch %51
+; CHECK: %53 = OpLabel
+; CHECK: %111 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_50 %uint_0 %141 %48
+; CHECK: OpBranch %51
+; CHECK: %51 = OpLabel
+; CHECK: %113 = OpPhi %v4float %150 %144 %112 %53
+; CHECK: %30 = OpCompositeExtract %float %113 0
+; CHECK: %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
+; CHECK: %151 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_0 %uint_0
+; CHECK: %152 = OpULessThan %bool %uint_0 %151
+; CHECK: OpSelectionMerge %153 None
+; CHECK: OpBranchConditional %152 %154 %155
+; CHECK: %154 = OpLabel
+; CHECK: OpStore %31 %30
+; CHECK: OpBranch %153
+; CHECK: %155 = OpLabel
+; CHECK: %157 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_53 %uint_1 %uint_0 %uint_0
+; CHECK: OpBranch %153
+; CHECK: %153 = OpLabel
 OpReturn
 OpFunctionEnd
 )";
 
   const std::string new_funcs =
-      R"(%33 = OpFunction %uint None %34
-%35 = OpFunctionParameter %uint
-%36 = OpFunctionParameter %uint
-%37 = OpLabel
-%43 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %35
-%44 = OpLoad %uint %43
-%45 = OpIAdd %uint %44 %36
-%46 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %45
-%47 = OpLoad %uint %46
-OpReturnValue %47
-OpFunctionEnd
-%56 = OpFunction %void None %57
-%58 = OpFunctionParameter %uint
-%59 = OpFunctionParameter %uint
-%60 = OpFunctionParameter %uint
-%61 = OpFunctionParameter %uint
-%62 = OpLabel
-%66 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_0
-%69 = OpAtomicIAdd %uint %66 %uint_4 %uint_0 %uint_10
-%70 = OpIAdd %uint %69 %uint_10
-%71 = OpArrayLength %uint %65 1
-%72 = OpULessThanEqual %bool %70 %71
-OpSelectionMerge %73 None
-OpBranchConditional %72 %74 %73
-%74 = OpLabel
-%75 = OpIAdd %uint %69 %uint_0
-%76 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %75
-OpStore %76 %uint_10
-%78 = OpIAdd %uint %69 %uint_1
-%79 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %78
-OpStore %79 %uint_23
-%81 = OpIAdd %uint %69 %uint_2
-%82 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %81
-OpStore %82 %58
-%85 = OpIAdd %uint %69 %uint_3
-%86 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %85
-OpStore %86 %uint_5
-%90 = OpLoad %v3uint %gl_GlobalInvocationID
-%91 = OpCompositeExtract %uint %90 0
-%92 = OpCompositeExtract %uint %90 1
-%93 = OpCompositeExtract %uint %90 2
-%94 = OpIAdd %uint %69 %uint_4
-%95 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %94
-OpStore %95 %91
-%96 = OpIAdd %uint %69 %uint_5
-%97 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %96
-OpStore %97 %92
-%99 = OpIAdd %uint %69 %uint_6
-%100 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %99
-OpStore %100 %93
-%102 = OpIAdd %uint %69 %uint_7
-%103 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %102
-OpStore %103 %59
-%105 = OpIAdd %uint %69 %uint_8
-%106 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %105
-OpStore %106 %60
-%108 = OpIAdd %uint %69 %uint_9
-%109 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %108
-OpStore %109 %61
-OpBranch %73
-%73 = OpLabel
-OpReturn
-OpFunctionEnd
-%114 = OpFunction %uint None %115
-%116 = OpFunctionParameter %uint
-%117 = OpFunctionParameter %uint
-%118 = OpFunctionParameter %uint
-%119 = OpFunctionParameter %uint
-%120 = OpLabel
-%121 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %116
-%122 = OpLoad %uint %121
-%123 = OpIAdd %uint %122 %117
-%124 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %123
-%125 = OpLoad %uint %124
-%126 = OpIAdd %uint %125 %118
-%127 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %126
-%128 = OpLoad %uint %127
-%129 = OpIAdd %uint %128 %119
-%130 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %129
-%131 = OpLoad %uint %130
-OpReturnValue %131
-OpFunctionEnd
-)";
+      kDirectRead2 + kStreamWrite4Compute + kDirectRead4;
 
   // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBindlessCheckPass>(
-      defs_before + func_before, defs_after + func_after + new_funcs, true,
-      true, 7u, 23u, true, true, false, false, false);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(defs + main_func + new_funcs,
+                                               true, 7u, 23u, true, true, false,
+                                               false, false);
 }
 
 TEST_F(InstBindlessTest,
@@ -4853,14 +2969,17 @@
   //    sbo.red = imageLoad(images[sbo.index], ivec2(0, 0)).r;
   // }
 
-  const std::string defs_before =
-      R"(OpCapability RuntimeDescriptorArray
+  // clang-format off
+  const std::string defs = R"(
+OpCapability RuntimeDescriptorArray
 OpCapability RayTracingNV
 OpExtension "SPV_EXT_descriptor_indexing"
 OpExtension "SPV_NV_ray_tracing"
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
 OpEntryPoint RayGenerationNV %main "main"
+; CHECK: OpEntryPoint RayGenerationNV %main "main" %89
 OpSource GLSL 460
 OpSourceExtension "GL_EXT_nonuniform_qualifier"
 OpSourceExtension "GL_NV_ray_tracing"
@@ -4878,51 +2997,11 @@
 OpDecorate %images DescriptorSet 0
 OpDecorate %images Binding 1
 OpDecorate %images NonWritable
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+)" + kInputDecorations + kOutputDecorations + R"(
+; CHECK: OpDecorate %89 BuiltIn LaunchIdNV
 %void = OpTypeVoid
-)";
-
-  const std::string defs_after =
-      R"(OpCapability RuntimeDescriptorArray
-OpCapability RayTracingNV
-OpExtension "SPV_EXT_descriptor_indexing"
-OpExtension "SPV_NV_ray_tracing"
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint RayGenerationNV %main "main" %89
-OpSource GLSL 460
-OpSourceExtension "GL_EXT_nonuniform_qualifier"
-OpSourceExtension "GL_NV_ray_tracing"
-OpName %main "main"
-OpName %StorageBuffer "StorageBuffer"
-OpMemberName %StorageBuffer 0 "index"
-OpMemberName %StorageBuffer 1 "red"
-OpName %sbo "sbo"
-OpName %images "images"
-OpMemberDecorate %StorageBuffer 0 Offset 0
-OpMemberDecorate %StorageBuffer 1 Offset 4
-OpDecorate %StorageBuffer BufferBlock
-OpDecorate %sbo DescriptorSet 0
-OpDecorate %sbo Binding 0
-OpDecorate %images DescriptorSet 0
-OpDecorate %images Binding 1
-OpDecorate %images NonWritable
-OpDecorate %_runtimearr_uint ArrayStride 4
-OpDecorate %_struct_39 Block
-OpMemberDecorate %_struct_39 0 Offset 0
-OpDecorate %41 DescriptorSet 7
-OpDecorate %41 Binding 1
-OpDecorate %_struct_63 Block
-OpMemberDecorate %_struct_63 0 Offset 0
-OpMemberDecorate %_struct_63 1 Offset 4
-OpDecorate %65 DescriptorSet 7
-OpDecorate %65 Binding 0
-OpDecorate %89 BuiltIn LaunchIdNV
-%void = OpTypeVoid
-)";
-
-  const std::string func_before =
-      R"(%3 = OpTypeFunction %void
+%3 = OpTypeFunction %void
 %uint = OpTypeInt 32 0
 %float = OpTypeFloat 32
 %StorageBuffer = OpTypeStruct %uint %float
@@ -4942,6 +3021,38 @@
 %v4float = OpTypeVector %float 4
 %uint_0 = OpConstant %uint 0
 %_ptr_Uniform_float = OpTypePointer Uniform %float
+; CHECK: %uint_1 = OpConstant %uint 1
+; CHECK: %34 = OpTypeFunction %uint %uint %uint
+; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+)" + kInputGlobals + R"(
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %bool = OpTypeBool
+; CHECK: %57 = OpTypeFunction %void %uint %uint %uint %uint
+)" + kOutputGlobals + R"(
+; CHECK: %uint_10 = OpConstant %uint 10
+; CHECK: %uint_4 = OpConstant %uint 4
+; CHECK: %uint_23 = OpConstant %uint 23
+; CHECK: %uint_2 = OpConstant %uint 2
+; CHECK: %uint_5313 = OpConstant %uint 5313
+; CHECK: %uint_3 = OpConstant %uint 3
+; CHECK: %v3uint = OpTypeVector %uint 3
+; CHECK: %_ptr_Input_v3uint = OpTypePointer Input %v3uint
+; CHECK: %89 = OpVariable %_ptr_Input_v3uint Input
+; CHECK: %uint_5 = OpConstant %uint 5
+; CHECK: %uint_6 = OpConstant %uint 6
+; CHECK: %uint_7 = OpConstant %uint 7
+; CHECK: %uint_8 = OpConstant %uint 8
+; CHECK: %uint_9 = OpConstant %uint 9
+; CHECK: %uint_51 = OpConstant %uint 51
+; CHECK: %113 = OpConstantNull %v4float
+; CHECK: %116 = OpTypeFunction %uint %uint %uint %uint %uint
+; CHECK: %uint_48 = OpConstant %uint 48
+; CHECK: %141 = OpConstantNull %uint
+; CHECK: %uint_54 = OpConstant %uint 54
+)";
+  // clang-format on
+
+  const std::string main_func = R"(
 %main = OpFunction %void None %3
 %5 = OpLabel
 %19 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
@@ -4952,211 +3063,69 @@
 %29 = OpCompositeExtract %float %27 0
 %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
 OpStore %31 %29
+; CHECK-NOT: OpStore %31 %29
+; CHECK: %133 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_0 %uint_0
+; CHECK: %134 = OpULessThan %bool %uint_0 %133
+; CHECK: OpSelectionMerge %135 None
+; CHECK: OpBranchConditional %134 %136 %137
+; CHECK: %136 = OpLabel
+; CHECK: %138 = OpLoad %uint %25
+; CHECK: OpBranch %135
+; CHECK: %137 = OpLabel
+; CHECK: %140 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_48 %uint_1 %uint_0 %uint_0
+; CHECK: OpBranch %135
+; CHECK: %135 = OpLabel
+; CHECK: %142 = OpPhi %uint %138 %136 %141 %137
+; CHECK: %27 = OpAccessChain %_ptr_UniformConstant_13 %images %142
+; CHECK: %28 = OpLoad %13 %27
+; CHECK: %48 = OpFunctionCall %uint %inst_bindless_direct_read_2 %uint_1 %uint_1
+; CHECK: %50 = OpULessThan %bool %142 %48
+; CHECK: OpSelectionMerge %51 None
+; CHECK: OpBranchConditional %50 %52 %53
+; CHECK: %52 = OpLabel
+; CHECK: %54 = OpLoad %13 %27
+; CHECK: %143 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_1 %142
+; CHECK: %144 = OpULessThan %bool %uint_0 %143
+; CHECK: OpSelectionMerge %145 None
+; CHECK: OpBranchConditional %144 %146 %147
+; CHECK: %146 = OpLabel
+; CHECK: %148 = OpLoad %13 %27
+; CHECK: %149 = OpImageRead %v4float %148 %20
+; CHECK: OpBranch %145
+; CHECK: %147 = OpLabel
+; CHECK: %150 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_51 %uint_1 %142 %uint_0
+; CHECK: OpBranch %145
+; CHECK: %145 = OpLabel
+; CHECK: %151 = OpPhi %v4float %149 %146 %113 %147
+; CHECK: OpBranch %51
+; CHECK: %53 = OpLabel
+; CHECK: %112 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_51 %uint_0 %142 %48
+; CHECK: OpBranch %51
+; CHECK: %51 = OpLabel
+; CHECK: %114 = OpPhi %v4float %151 %145 %113 %53
+; CHECK: %30 = OpCompositeExtract %float %114 0
+; CHECK: %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
+; CHECK: %152 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_0 %uint_0
+; CHECK: %153 = OpULessThan %bool %uint_0 %152
+; CHECK: OpSelectionMerge %154 None
+; CHECK: OpBranchConditional %153 %155 %156
+; CHECK: %155 = OpLabel
+; CHECK: OpStore %31 %30
+; CHECK: OpBranch %154
+; CHECK: %156 = OpLabel
+; CHECK: %158 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_54 %uint_1 %uint_0 %uint_0
+; CHECK: OpBranch %154
+; CHECK: %154 = OpLabel
 OpReturn
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%7 = OpTypeFunction %void
-%uint = OpTypeInt 32 0
-%float = OpTypeFloat 32
-%StorageBuffer = OpTypeStruct %uint %float
-%_ptr_Uniform_StorageBuffer = OpTypePointer Uniform %StorageBuffer
-%sbo = OpVariable %_ptr_Uniform_StorageBuffer Uniform
-%int = OpTypeInt 32 1
-%int_1 = OpConstant %int 1
-%13 = OpTypeImage %float 2D 0 0 0 2 Rgba32f
-%_runtimearr_13 = OpTypeRuntimeArray %13
-%_ptr_UniformConstant__runtimearr_13 = OpTypePointer UniformConstant %_runtimearr_13
-%images = OpVariable %_ptr_UniformConstant__runtimearr_13 UniformConstant
-%int_0 = OpConstant %int 0
-%_ptr_Uniform_uint = OpTypePointer Uniform %uint
-%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
-%v2int = OpTypeVector %int 2
-%20 = OpConstantComposite %v2int %int_0 %int_0
-%v4float = OpTypeVector %float 4
-%uint_0 = OpConstant %uint 0
-%_ptr_Uniform_float = OpTypePointer Uniform %float
-%uint_1 = OpConstant %uint 1
-%34 = OpTypeFunction %uint %uint %uint
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_39 = OpTypeStruct %_runtimearr_uint
-%_ptr_StorageBuffer__struct_39 = OpTypePointer StorageBuffer %_struct_39
-%41 = OpVariable %_ptr_StorageBuffer__struct_39 StorageBuffer
-%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-%bool = OpTypeBool
-%57 = OpTypeFunction %void %uint %uint %uint %uint
-%_struct_63 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_63 = OpTypePointer StorageBuffer %_struct_63
-%65 = OpVariable %_ptr_StorageBuffer__struct_63 StorageBuffer
-%uint_10 = OpConstant %uint 10
-%uint_4 = OpConstant %uint 4
-%uint_23 = OpConstant %uint 23
-%uint_2 = OpConstant %uint 2
-%uint_5313 = OpConstant %uint 5313
-%uint_3 = OpConstant %uint 3
-%v3uint = OpTypeVector %uint 3
-%_ptr_Input_v3uint = OpTypePointer Input %v3uint
-%89 = OpVariable %_ptr_Input_v3uint Input
-%uint_5 = OpConstant %uint 5
-%uint_6 = OpConstant %uint 6
-%uint_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%uint_9 = OpConstant %uint 9
-%uint_51 = OpConstant %uint 51
-%113 = OpConstantNull %v4float
-%116 = OpTypeFunction %uint %uint %uint %uint %uint
-%uint_48 = OpConstant %uint 48
-%141 = OpConstantNull %uint
-%uint_54 = OpConstant %uint 54
-%main = OpFunction %void None %7
-%24 = OpLabel
-%25 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
-%133 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0
-%134 = OpULessThan %bool %uint_0 %133
-OpSelectionMerge %135 None
-OpBranchConditional %134 %136 %137
-%136 = OpLabel
-%138 = OpLoad %uint %25
-OpBranch %135
-%137 = OpLabel
-%140 = OpFunctionCall %void %56 %uint_48 %uint_1 %uint_0 %uint_0
-OpBranch %135
-%135 = OpLabel
-%142 = OpPhi %uint %138 %136 %141 %137
-%27 = OpAccessChain %_ptr_UniformConstant_13 %images %142
-%28 = OpLoad %13 %27
-%48 = OpFunctionCall %uint %33 %uint_1 %uint_1
-%50 = OpULessThan %bool %142 %48
-OpSelectionMerge %51 None
-OpBranchConditional %50 %52 %53
-%52 = OpLabel
-%54 = OpLoad %13 %27
-%143 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_1 %142
-%144 = OpULessThan %bool %uint_0 %143
-OpSelectionMerge %145 None
-OpBranchConditional %144 %146 %147
-%146 = OpLabel
-%148 = OpLoad %13 %27
-%149 = OpImageRead %v4float %148 %20
-OpBranch %145
-%147 = OpLabel
-%150 = OpFunctionCall %void %56 %uint_51 %uint_1 %142 %uint_0
-OpBranch %145
-%145 = OpLabel
-%151 = OpPhi %v4float %149 %146 %113 %147
-OpBranch %51
-%53 = OpLabel
-%112 = OpFunctionCall %void %56 %uint_51 %uint_0 %142 %48
-OpBranch %51
-%51 = OpLabel
-%114 = OpPhi %v4float %151 %145 %113 %53
-%30 = OpCompositeExtract %float %114 0
-%31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
-%152 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0
-%153 = OpULessThan %bool %uint_0 %152
-OpSelectionMerge %154 None
-OpBranchConditional %153 %155 %156
-%155 = OpLabel
-OpStore %31 %30
-OpBranch %154
-%156 = OpLabel
-%158 = OpFunctionCall %void %56 %uint_54 %uint_1 %uint_0 %uint_0
-OpBranch %154
-%154 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string new_funcs =
-      R"(%33 = OpFunction %uint None %34
-%35 = OpFunctionParameter %uint
-%36 = OpFunctionParameter %uint
-%37 = OpLabel
-%43 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %35
-%44 = OpLoad %uint %43
-%45 = OpIAdd %uint %44 %36
-%46 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %45
-%47 = OpLoad %uint %46
-OpReturnValue %47
-OpFunctionEnd
-%56 = OpFunction %void None %57
-%58 = OpFunctionParameter %uint
-%59 = OpFunctionParameter %uint
-%60 = OpFunctionParameter %uint
-%61 = OpFunctionParameter %uint
-%62 = OpLabel
-%66 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_0
-%69 = OpAtomicIAdd %uint %66 %uint_4 %uint_0 %uint_10
-%70 = OpIAdd %uint %69 %uint_10
-%71 = OpArrayLength %uint %65 1
-%72 = OpULessThanEqual %bool %70 %71
-OpSelectionMerge %73 None
-OpBranchConditional %72 %74 %73
-%74 = OpLabel
-%75 = OpIAdd %uint %69 %uint_0
-%76 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %75
-OpStore %76 %uint_10
-%78 = OpIAdd %uint %69 %uint_1
-%79 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %78
-OpStore %79 %uint_23
-%81 = OpIAdd %uint %69 %uint_2
-%82 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %81
-OpStore %82 %58
-%85 = OpIAdd %uint %69 %uint_3
-%86 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %85
-OpStore %86 %uint_5313
-%90 = OpLoad %v3uint %89
-%91 = OpCompositeExtract %uint %90 0
-%92 = OpCompositeExtract %uint %90 1
-%93 = OpCompositeExtract %uint %90 2
-%94 = OpIAdd %uint %69 %uint_4
-%95 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %94
-OpStore %95 %91
-%97 = OpIAdd %uint %69 %uint_5
-%98 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %97
-OpStore %98 %92
-%100 = OpIAdd %uint %69 %uint_6
-%101 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %100
-OpStore %101 %93
-%103 = OpIAdd %uint %69 %uint_7
-%104 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %103
-OpStore %104 %59
-%106 = OpIAdd %uint %69 %uint_8
-%107 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %106
-OpStore %107 %60
-%109 = OpIAdd %uint %69 %uint_9
-%110 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %109
-OpStore %110 %61
-OpBranch %73
-%73 = OpLabel
-OpReturn
-OpFunctionEnd
-%115 = OpFunction %uint None %116
-%117 = OpFunctionParameter %uint
-%118 = OpFunctionParameter %uint
-%119 = OpFunctionParameter %uint
-%120 = OpFunctionParameter %uint
-%121 = OpLabel
-%122 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %117
-%123 = OpLoad %uint %122
-%124 = OpIAdd %uint %123 %118
-%125 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %124
-%126 = OpLoad %uint %125
-%127 = OpIAdd %uint %126 %119
-%128 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %127
-%129 = OpLoad %uint %128
-%130 = OpIAdd %uint %129 %120
-%131 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %130
-%132 = OpLoad %uint %131
-OpReturnValue %132
-OpFunctionEnd
-)";
+  const std::string new_funcs = kDirectRead2 + kStreamWrite4Ray + kDirectRead4;
 
   // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBindlessCheckPass>(
-      defs_before + func_before, defs_after + func_after + new_funcs, true,
-      true, 7u, 23u, true, true, false, false, false);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(defs + main_func + new_funcs,
+                                               true, 7u, 23u, true, true, false,
+                                               false, false);
 }
 
 TEST_F(InstBindlessTest,
@@ -5177,14 +3146,17 @@
   //    sbo.red = imageLoad(images[sbo.index], ivec2(0, 0)).r;
   // }
 
-  const std::string defs_before =
-      R"(OpCapability RuntimeDescriptorArray
+  // clang-format off
+  const std::string defs = R"(
+OpCapability RuntimeDescriptorArray
 OpCapability RayTracingNV
 OpExtension "SPV_EXT_descriptor_indexing"
 OpExtension "SPV_NV_ray_tracing"
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
 OpEntryPoint IntersectionNV %main "main"
+; CHECK: OpEntryPoint IntersectionNV %main "main" %89
 OpSource GLSL 460
 OpSourceExtension "GL_EXT_nonuniform_qualifier"
 OpSourceExtension "GL_NV_ray_tracing"
@@ -5202,51 +3174,11 @@
 OpDecorate %images DescriptorSet 0
 OpDecorate %images Binding 1
 OpDecorate %images NonWritable
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+)" + kInputDecorations + kOutputDecorations + R"(
+; CHECK: OpDecorate %89 BuiltIn LaunchIdNV
 %void = OpTypeVoid
-)";
-
-  const std::string defs_after =
-      R"(OpCapability RuntimeDescriptorArray
-OpCapability RayTracingNV
-OpExtension "SPV_EXT_descriptor_indexing"
-OpExtension "SPV_NV_ray_tracing"
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint IntersectionNV %main "main" %89
-OpSource GLSL 460
-OpSourceExtension "GL_EXT_nonuniform_qualifier"
-OpSourceExtension "GL_NV_ray_tracing"
-OpName %main "main"
-OpName %StorageBuffer "StorageBuffer"
-OpMemberName %StorageBuffer 0 "index"
-OpMemberName %StorageBuffer 1 "red"
-OpName %sbo "sbo"
-OpName %images "images"
-OpMemberDecorate %StorageBuffer 0 Offset 0
-OpMemberDecorate %StorageBuffer 1 Offset 4
-OpDecorate %StorageBuffer BufferBlock
-OpDecorate %sbo DescriptorSet 0
-OpDecorate %sbo Binding 0
-OpDecorate %images DescriptorSet 0
-OpDecorate %images Binding 1
-OpDecorate %images NonWritable
-OpDecorate %_runtimearr_uint ArrayStride 4
-OpDecorate %_struct_39 Block
-OpMemberDecorate %_struct_39 0 Offset 0
-OpDecorate %41 DescriptorSet 7
-OpDecorate %41 Binding 1
-OpDecorate %_struct_63 Block
-OpMemberDecorate %_struct_63 0 Offset 0
-OpMemberDecorate %_struct_63 1 Offset 4
-OpDecorate %65 DescriptorSet 7
-OpDecorate %65 Binding 0
-OpDecorate %89 BuiltIn LaunchIdNV
-%void = OpTypeVoid
-)";
-
-  const std::string func_before =
-      R"(%3 = OpTypeFunction %void
+%3 = OpTypeFunction %void
 %uint = OpTypeInt 32 0
 %float = OpTypeFloat 32
 %StorageBuffer = OpTypeStruct %uint %float
@@ -5266,6 +3198,37 @@
 %v4float = OpTypeVector %float 4
 %uint_0 = OpConstant %uint 0
 %_ptr_Uniform_float = OpTypePointer Uniform %float
+; CHECK: %uint_1 = OpConstant %uint 1
+; CHECK: %34 = OpTypeFunction %uint %uint %uint
+; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+)" + kInputGlobals + R"(
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %bool = OpTypeBool
+)" + kOutputGlobals + R"(
+; CHECK: %uint_10 = OpConstant %uint 10
+; CHECK: %uint_4 = OpConstant %uint 4
+; CHECK: %uint_23 = OpConstant %uint 23
+; CHECK: %uint_2 = OpConstant %uint 2
+; CHECK: %uint_5314 = OpConstant %uint 5314
+; CHECK: %uint_3 = OpConstant %uint 3
+; CHECK: %v3uint = OpTypeVector %uint 3
+; CHECK: %_ptr_Input_v3uint = OpTypePointer Input %v3uint
+; CHECK: %89 = OpVariable %_ptr_Input_v3uint Input
+; CHECK: %uint_5 = OpConstant %uint 5
+; CHECK: %uint_6 = OpConstant %uint 6
+; CHECK: %uint_7 = OpConstant %uint 7
+; CHECK: %uint_8 = OpConstant %uint 8
+; CHECK: %uint_9 = OpConstant %uint 9
+; CHECK: %uint_51 = OpConstant %uint 51
+; CHECK: %113 = OpConstantNull %v4float
+; CHECK: %116 = OpTypeFunction %uint %uint %uint %uint %uint
+; CHECK: %uint_48 = OpConstant %uint 48
+; CHECK: %141 = OpConstantNull %uint
+; CHECK: %uint_54 = OpConstant %uint 54
+)";
+  // clang-format on
+
+  const std::string main_func = R"(
 %main = OpFunction %void None %3
 %5 = OpLabel
 %19 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
@@ -5276,211 +3239,69 @@
 %29 = OpCompositeExtract %float %27 0
 %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
 OpStore %31 %29
+; CHECK-NOT: OpStore %31 %29
+; CHECK: %133 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_0 %uint_0
+; CHECK: %134 = OpULessThan %bool %uint_0 %133
+; CHECK: OpSelectionMerge %135 None
+; CHECK: OpBranchConditional %134 %136 %137
+; CHECK: %136 = OpLabel
+; CHECK: %138 = OpLoad %uint %25
+; CHECK: OpBranch %135
+; CHECK: %137 = OpLabel
+; CHECK: %140 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_48 %uint_1 %uint_0 %uint_0
+; CHECK: OpBranch %135
+; CHECK: %135 = OpLabel
+; CHECK: %142 = OpPhi %uint %138 %136 %141 %137
+; CHECK: %27 = OpAccessChain %_ptr_UniformConstant_13 %images %142
+; CHECK: %28 = OpLoad %13 %27
+; CHECK: %48 = OpFunctionCall %uint %inst_bindless_direct_read_2 %uint_1 %uint_1
+; CHECK: %50 = OpULessThan %bool %142 %48
+; CHECK: OpSelectionMerge %51 None
+; CHECK: OpBranchConditional %50 %52 %53
+; CHECK: %52 = OpLabel
+; CHECK: %54 = OpLoad %13 %27
+; CHECK: %143 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_1 %142
+; CHECK: %144 = OpULessThan %bool %uint_0 %143
+; CHECK: OpSelectionMerge %145 None
+; CHECK: OpBranchConditional %144 %146 %147
+; CHECK: %146 = OpLabel
+; CHECK: %148 = OpLoad %13 %27
+; CHECK: %149 = OpImageRead %v4float %148 %20
+; CHECK: OpBranch %145
+; CHECK: %147 = OpLabel
+; CHECK: %150 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_51 %uint_1 %142 %uint_0
+; CHECK: OpBranch %145
+; CHECK: %145 = OpLabel
+; CHECK: %151 = OpPhi %v4float %149 %146 %113 %147
+; CHECK: OpBranch %51
+; CHECK: %53 = OpLabel
+; CHECK: %112 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_51 %uint_0 %142 %48
+; CHECK: OpBranch %51
+; CHECK: %51 = OpLabel
+; CHECK: %114 = OpPhi %v4float %151 %145 %113 %53
+; CHECK: %30 = OpCompositeExtract %float %114 0
+; CHECK: %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
+; CHECK: %152 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_0 %uint_0
+; CHECK: %153 = OpULessThan %bool %uint_0 %152
+; CHECK: OpSelectionMerge %154 None
+; CHECK: OpBranchConditional %153 %155 %156
+; CHECK: %155 = OpLabel
+; CHECK: OpStore %31 %30
+; CHECK: OpBranch %154
+; CHECK: %156 = OpLabel
+; CHECK: %158 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_54 %uint_1 %uint_0 %uint_0
+; CHECK: OpBranch %154
+; CHECK: %154 = OpLabel
 OpReturn
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%7 = OpTypeFunction %void
-%uint = OpTypeInt 32 0
-%float = OpTypeFloat 32
-%StorageBuffer = OpTypeStruct %uint %float
-%_ptr_Uniform_StorageBuffer = OpTypePointer Uniform %StorageBuffer
-%sbo = OpVariable %_ptr_Uniform_StorageBuffer Uniform
-%int = OpTypeInt 32 1
-%int_1 = OpConstant %int 1
-%13 = OpTypeImage %float 2D 0 0 0 2 Rgba32f
-%_runtimearr_13 = OpTypeRuntimeArray %13
-%_ptr_UniformConstant__runtimearr_13 = OpTypePointer UniformConstant %_runtimearr_13
-%images = OpVariable %_ptr_UniformConstant__runtimearr_13 UniformConstant
-%int_0 = OpConstant %int 0
-%_ptr_Uniform_uint = OpTypePointer Uniform %uint
-%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
-%v2int = OpTypeVector %int 2
-%20 = OpConstantComposite %v2int %int_0 %int_0
-%v4float = OpTypeVector %float 4
-%uint_0 = OpConstant %uint 0
-%_ptr_Uniform_float = OpTypePointer Uniform %float
-%uint_1 = OpConstant %uint 1
-%34 = OpTypeFunction %uint %uint %uint
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_39 = OpTypeStruct %_runtimearr_uint
-%_ptr_StorageBuffer__struct_39 = OpTypePointer StorageBuffer %_struct_39
-%41 = OpVariable %_ptr_StorageBuffer__struct_39 StorageBuffer
-%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-%bool = OpTypeBool
-%57 = OpTypeFunction %void %uint %uint %uint %uint
-%_struct_63 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_63 = OpTypePointer StorageBuffer %_struct_63
-%65 = OpVariable %_ptr_StorageBuffer__struct_63 StorageBuffer
-%uint_10 = OpConstant %uint 10
-%uint_4 = OpConstant %uint 4
-%uint_23 = OpConstant %uint 23
-%uint_2 = OpConstant %uint 2
-%uint_5314 = OpConstant %uint 5314
-%uint_3 = OpConstant %uint 3
-%v3uint = OpTypeVector %uint 3
-%_ptr_Input_v3uint = OpTypePointer Input %v3uint
-%89 = OpVariable %_ptr_Input_v3uint Input
-%uint_5 = OpConstant %uint 5
-%uint_6 = OpConstant %uint 6
-%uint_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%uint_9 = OpConstant %uint 9
-%uint_51 = OpConstant %uint 51
-%113 = OpConstantNull %v4float
-%116 = OpTypeFunction %uint %uint %uint %uint %uint
-%uint_48 = OpConstant %uint 48
-%141 = OpConstantNull %uint
-%uint_54 = OpConstant %uint 54
-%main = OpFunction %void None %7
-%24 = OpLabel
-%25 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
-%133 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0
-%134 = OpULessThan %bool %uint_0 %133
-OpSelectionMerge %135 None
-OpBranchConditional %134 %136 %137
-%136 = OpLabel
-%138 = OpLoad %uint %25
-OpBranch %135
-%137 = OpLabel
-%140 = OpFunctionCall %void %56 %uint_48 %uint_1 %uint_0 %uint_0
-OpBranch %135
-%135 = OpLabel
-%142 = OpPhi %uint %138 %136 %141 %137
-%27 = OpAccessChain %_ptr_UniformConstant_13 %images %142
-%28 = OpLoad %13 %27
-%48 = OpFunctionCall %uint %33 %uint_1 %uint_1
-%50 = OpULessThan %bool %142 %48
-OpSelectionMerge %51 None
-OpBranchConditional %50 %52 %53
-%52 = OpLabel
-%54 = OpLoad %13 %27
-%143 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_1 %142
-%144 = OpULessThan %bool %uint_0 %143
-OpSelectionMerge %145 None
-OpBranchConditional %144 %146 %147
-%146 = OpLabel
-%148 = OpLoad %13 %27
-%149 = OpImageRead %v4float %148 %20
-OpBranch %145
-%147 = OpLabel
-%150 = OpFunctionCall %void %56 %uint_51 %uint_1 %142 %uint_0
-OpBranch %145
-%145 = OpLabel
-%151 = OpPhi %v4float %149 %146 %113 %147
-OpBranch %51
-%53 = OpLabel
-%112 = OpFunctionCall %void %56 %uint_51 %uint_0 %142 %48
-OpBranch %51
-%51 = OpLabel
-%114 = OpPhi %v4float %151 %145 %113 %53
-%30 = OpCompositeExtract %float %114 0
-%31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
-%152 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0
-%153 = OpULessThan %bool %uint_0 %152
-OpSelectionMerge %154 None
-OpBranchConditional %153 %155 %156
-%155 = OpLabel
-OpStore %31 %30
-OpBranch %154
-%156 = OpLabel
-%158 = OpFunctionCall %void %56 %uint_54 %uint_1 %uint_0 %uint_0
-OpBranch %154
-%154 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string new_funcs =
-      R"(%33 = OpFunction %uint None %34
-%35 = OpFunctionParameter %uint
-%36 = OpFunctionParameter %uint
-%37 = OpLabel
-%43 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %35
-%44 = OpLoad %uint %43
-%45 = OpIAdd %uint %44 %36
-%46 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %45
-%47 = OpLoad %uint %46
-OpReturnValue %47
-OpFunctionEnd
-%56 = OpFunction %void None %57
-%58 = OpFunctionParameter %uint
-%59 = OpFunctionParameter %uint
-%60 = OpFunctionParameter %uint
-%61 = OpFunctionParameter %uint
-%62 = OpLabel
-%66 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_0
-%69 = OpAtomicIAdd %uint %66 %uint_4 %uint_0 %uint_10
-%70 = OpIAdd %uint %69 %uint_10
-%71 = OpArrayLength %uint %65 1
-%72 = OpULessThanEqual %bool %70 %71
-OpSelectionMerge %73 None
-OpBranchConditional %72 %74 %73
-%74 = OpLabel
-%75 = OpIAdd %uint %69 %uint_0
-%76 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %75
-OpStore %76 %uint_10
-%78 = OpIAdd %uint %69 %uint_1
-%79 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %78
-OpStore %79 %uint_23
-%81 = OpIAdd %uint %69 %uint_2
-%82 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %81
-OpStore %82 %58
-%85 = OpIAdd %uint %69 %uint_3
-%86 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %85
-OpStore %86 %uint_5314
-%90 = OpLoad %v3uint %89
-%91 = OpCompositeExtract %uint %90 0
-%92 = OpCompositeExtract %uint %90 1
-%93 = OpCompositeExtract %uint %90 2
-%94 = OpIAdd %uint %69 %uint_4
-%95 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %94
-OpStore %95 %91
-%97 = OpIAdd %uint %69 %uint_5
-%98 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %97
-OpStore %98 %92
-%100 = OpIAdd %uint %69 %uint_6
-%101 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %100
-OpStore %101 %93
-%103 = OpIAdd %uint %69 %uint_7
-%104 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %103
-OpStore %104 %59
-%106 = OpIAdd %uint %69 %uint_8
-%107 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %106
-OpStore %107 %60
-%109 = OpIAdd %uint %69 %uint_9
-%110 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %109
-OpStore %110 %61
-OpBranch %73
-%73 = OpLabel
-OpReturn
-OpFunctionEnd
-%115 = OpFunction %uint None %116
-%117 = OpFunctionParameter %uint
-%118 = OpFunctionParameter %uint
-%119 = OpFunctionParameter %uint
-%120 = OpFunctionParameter %uint
-%121 = OpLabel
-%122 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %117
-%123 = OpLoad %uint %122
-%124 = OpIAdd %uint %123 %118
-%125 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %124
-%126 = OpLoad %uint %125
-%127 = OpIAdd %uint %126 %119
-%128 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %127
-%129 = OpLoad %uint %128
-%130 = OpIAdd %uint %129 %120
-%131 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %130
-%132 = OpLoad %uint %131
-OpReturnValue %132
-OpFunctionEnd
-)";
+  const std::string new_funcs = kDirectRead2 + kStreamWrite4Ray + kDirectRead4;
 
   // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBindlessCheckPass>(
-      defs_before + func_before, defs_after + func_after + new_funcs, true,
-      true, 7u, 23u, true, true, false, false, false);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(defs + main_func + new_funcs,
+                                               true, 7u, 23u, true, true, false,
+                                               false, false);
 }
 
 TEST_F(InstBindlessTest,
@@ -5501,14 +3322,17 @@
   //    sbo.red = imageLoad(images[sbo.index], ivec2(0, 0)).r;
   // }
 
-  const std::string defs_before =
-      R"(OpCapability RuntimeDescriptorArray
+  // clang-format off
+  const std::string defs = R"(
+OpCapability RuntimeDescriptorArray
 OpCapability RayTracingNV
 OpExtension "SPV_EXT_descriptor_indexing"
 OpExtension "SPV_NV_ray_tracing"
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
 OpEntryPoint AnyHitNV %main "main"
+; CHECK: OpEntryPoint AnyHitNV %main "main" %89
 OpSource GLSL 460
 OpSourceExtension "GL_EXT_nonuniform_qualifier"
 OpSourceExtension "GL_NV_ray_tracing"
@@ -5526,51 +3350,11 @@
 OpDecorate %images DescriptorSet 0
 OpDecorate %images Binding 1
 OpDecorate %images NonWritable
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+)" + kInputDecorations + kOutputDecorations + R"(
+; CHECK: OpDecorate %89 BuiltIn LaunchIdNV
 %void = OpTypeVoid
-)";
-
-  const std::string defs_after =
-      R"(OpCapability RuntimeDescriptorArray
-OpCapability RayTracingNV
-OpExtension "SPV_EXT_descriptor_indexing"
-OpExtension "SPV_NV_ray_tracing"
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint AnyHitNV %main "main" %89
-OpSource GLSL 460
-OpSourceExtension "GL_EXT_nonuniform_qualifier"
-OpSourceExtension "GL_NV_ray_tracing"
-OpName %main "main"
-OpName %StorageBuffer "StorageBuffer"
-OpMemberName %StorageBuffer 0 "index"
-OpMemberName %StorageBuffer 1 "red"
-OpName %sbo "sbo"
-OpName %images "images"
-OpMemberDecorate %StorageBuffer 0 Offset 0
-OpMemberDecorate %StorageBuffer 1 Offset 4
-OpDecorate %StorageBuffer BufferBlock
-OpDecorate %sbo DescriptorSet 0
-OpDecorate %sbo Binding 0
-OpDecorate %images DescriptorSet 0
-OpDecorate %images Binding 1
-OpDecorate %images NonWritable
-OpDecorate %_runtimearr_uint ArrayStride 4
-OpDecorate %_struct_39 Block
-OpMemberDecorate %_struct_39 0 Offset 0
-OpDecorate %41 DescriptorSet 7
-OpDecorate %41 Binding 1
-OpDecorate %_struct_63 Block
-OpMemberDecorate %_struct_63 0 Offset 0
-OpMemberDecorate %_struct_63 1 Offset 4
-OpDecorate %65 DescriptorSet 7
-OpDecorate %65 Binding 0
-OpDecorate %89 BuiltIn LaunchIdNV
-%void = OpTypeVoid
-)";
-
-  const std::string func_before =
-      R"(%3 = OpTypeFunction %void
+%3 = OpTypeFunction %void
 %uint = OpTypeInt 32 0
 %float = OpTypeFloat 32
 %StorageBuffer = OpTypeStruct %uint %float
@@ -5590,6 +3374,38 @@
 %v4float = OpTypeVector %float 4
 %uint_0 = OpConstant %uint 0
 %_ptr_Uniform_float = OpTypePointer Uniform %float
+; CHECK: %uint_1 = OpConstant %uint 1
+; CHECK: %34 = OpTypeFunction %uint %uint %uint
+; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+)" + kInputGlobals + R"(
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %bool = OpTypeBool
+; CHECK: %57 = OpTypeFunction %void %uint %uint %uint %uint
+)" + kOutputGlobals + R"(
+; CHECK: %uint_10 = OpConstant %uint 10
+; CHECK: %uint_4 = OpConstant %uint 4
+; CHECK: %uint_23 = OpConstant %uint 23
+; CHECK: %uint_2 = OpConstant %uint 2
+; CHECK: %uint_5315 = OpConstant %uint 5315
+; CHECK: %uint_3 = OpConstant %uint 3
+; CHECK: %v3uint = OpTypeVector %uint 3
+; CHECK: %_ptr_Input_v3uint = OpTypePointer Input %v3uint
+; CHECK: %89 = OpVariable %_ptr_Input_v3uint Input
+; CHECK: %uint_5 = OpConstant %uint 5
+; CHECK: %uint_6 = OpConstant %uint 6
+; CHECK: %uint_7 = OpConstant %uint 7
+; CHECK: %uint_8 = OpConstant %uint 8
+; CHECK: %uint_9 = OpConstant %uint 9
+; CHECK: %uint_51 = OpConstant %uint 51
+; CHECK: %113 = OpConstantNull %v4float
+; CHECK: %116 = OpTypeFunction %uint %uint %uint %uint %uint
+; CHECK: %uint_48 = OpConstant %uint 48
+; CHECK: %141 = OpConstantNull %uint
+; CHECK: %uint_54 = OpConstant %uint 54
+)";
+  // clang-format on
+
+  const std::string main_func = R"(
 %main = OpFunction %void None %3
 %5 = OpLabel
 %19 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
@@ -5600,211 +3416,69 @@
 %29 = OpCompositeExtract %float %27 0
 %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
 OpStore %31 %29
+; CHECK-NOT: OpStore %31 %29
+; CHECK: %133 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_0 %uint_0
+; CHECK: %134 = OpULessThan %bool %uint_0 %133
+; CHECK: OpSelectionMerge %135 None
+; CHECK: OpBranchConditional %134 %136 %137
+; CHECK: %136 = OpLabel
+; CHECK: %138 = OpLoad %uint %25
+; CHECK: OpBranch %135
+; CHECK: %137 = OpLabel
+; CHECK: %140 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_48 %uint_1 %uint_0 %uint_0
+; CHECK: OpBranch %135
+; CHECK: %135 = OpLabel
+; CHECK: %142 = OpPhi %uint %138 %136 %141 %137
+; CHECK: %27 = OpAccessChain %_ptr_UniformConstant_13 %images %142
+; CHECK: %28 = OpLoad %13 %27
+; CHECK: %48 = OpFunctionCall %uint %inst_bindless_direct_read_2 %uint_1 %uint_1
+; CHECK: %50 = OpULessThan %bool %142 %48
+; CHECK: OpSelectionMerge %51 None
+; CHECK: OpBranchConditional %50 %52 %53
+; CHECK: %52 = OpLabel
+; CHECK: %54 = OpLoad %13 %27
+; CHECK: %143 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_1 %142
+; CHECK: %144 = OpULessThan %bool %uint_0 %143
+; CHECK: OpSelectionMerge %145 None
+; CHECK: OpBranchConditional %144 %146 %147
+; CHECK: %146 = OpLabel
+; CHECK: %148 = OpLoad %13 %27
+; CHECK: %149 = OpImageRead %v4float %148 %20
+; CHECK: OpBranch %145
+; CHECK: %147 = OpLabel
+; CHECK: %150 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_51 %uint_1 %142 %uint_0
+; CHECK: OpBranch %145
+; CHECK: %145 = OpLabel
+; CHECK: %151 = OpPhi %v4float %149 %146 %113 %147
+; CHECK: OpBranch %51
+; CHECK: %53 = OpLabel
+; CHECK: %112 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_51 %uint_0 %142 %48
+; CHECK: OpBranch %51
+; CHECK: %51 = OpLabel
+; CHECK: %114 = OpPhi %v4float %151 %145 %113 %53
+; CHECK: %30 = OpCompositeExtract %float %114 0
+; CHECK: %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
+; CHECK: %152 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_0 %uint_0
+; CHECK: %153 = OpULessThan %bool %uint_0 %152
+; CHECK: OpSelectionMerge %154 None
+; CHECK: OpBranchConditional %153 %155 %156
+; CHECK: %155 = OpLabel
+; CHECK: OpStore %31 %30
+; CHECK: OpBranch %154
+; CHECK: %156 = OpLabel
+; CHECK: %158 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_54 %uint_1 %uint_0 %uint_0
+; CHECK: OpBranch %154
+; CHECK: %154 = OpLabel
 OpReturn
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%7 = OpTypeFunction %void
-%uint = OpTypeInt 32 0
-%float = OpTypeFloat 32
-%StorageBuffer = OpTypeStruct %uint %float
-%_ptr_Uniform_StorageBuffer = OpTypePointer Uniform %StorageBuffer
-%sbo = OpVariable %_ptr_Uniform_StorageBuffer Uniform
-%int = OpTypeInt 32 1
-%int_1 = OpConstant %int 1
-%13 = OpTypeImage %float 2D 0 0 0 2 Rgba32f
-%_runtimearr_13 = OpTypeRuntimeArray %13
-%_ptr_UniformConstant__runtimearr_13 = OpTypePointer UniformConstant %_runtimearr_13
-%images = OpVariable %_ptr_UniformConstant__runtimearr_13 UniformConstant
-%int_0 = OpConstant %int 0
-%_ptr_Uniform_uint = OpTypePointer Uniform %uint
-%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
-%v2int = OpTypeVector %int 2
-%20 = OpConstantComposite %v2int %int_0 %int_0
-%v4float = OpTypeVector %float 4
-%uint_0 = OpConstant %uint 0
-%_ptr_Uniform_float = OpTypePointer Uniform %float
-%uint_1 = OpConstant %uint 1
-%34 = OpTypeFunction %uint %uint %uint
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_39 = OpTypeStruct %_runtimearr_uint
-%_ptr_StorageBuffer__struct_39 = OpTypePointer StorageBuffer %_struct_39
-%41 = OpVariable %_ptr_StorageBuffer__struct_39 StorageBuffer
-%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-%bool = OpTypeBool
-%57 = OpTypeFunction %void %uint %uint %uint %uint
-%_struct_63 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_63 = OpTypePointer StorageBuffer %_struct_63
-%65 = OpVariable %_ptr_StorageBuffer__struct_63 StorageBuffer
-%uint_10 = OpConstant %uint 10
-%uint_4 = OpConstant %uint 4
-%uint_23 = OpConstant %uint 23
-%uint_2 = OpConstant %uint 2
-%uint_5315 = OpConstant %uint 5315
-%uint_3 = OpConstant %uint 3
-%v3uint = OpTypeVector %uint 3
-%_ptr_Input_v3uint = OpTypePointer Input %v3uint
-%89 = OpVariable %_ptr_Input_v3uint Input
-%uint_5 = OpConstant %uint 5
-%uint_6 = OpConstant %uint 6
-%uint_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%uint_9 = OpConstant %uint 9
-%uint_51 = OpConstant %uint 51
-%113 = OpConstantNull %v4float
-%116 = OpTypeFunction %uint %uint %uint %uint %uint
-%uint_48 = OpConstant %uint 48
-%141 = OpConstantNull %uint
-%uint_54 = OpConstant %uint 54
-%main = OpFunction %void None %7
-%24 = OpLabel
-%25 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
-%133 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0
-%134 = OpULessThan %bool %uint_0 %133
-OpSelectionMerge %135 None
-OpBranchConditional %134 %136 %137
-%136 = OpLabel
-%138 = OpLoad %uint %25
-OpBranch %135
-%137 = OpLabel
-%140 = OpFunctionCall %void %56 %uint_48 %uint_1 %uint_0 %uint_0
-OpBranch %135
-%135 = OpLabel
-%142 = OpPhi %uint %138 %136 %141 %137
-%27 = OpAccessChain %_ptr_UniformConstant_13 %images %142
-%28 = OpLoad %13 %27
-%48 = OpFunctionCall %uint %33 %uint_1 %uint_1
-%50 = OpULessThan %bool %142 %48
-OpSelectionMerge %51 None
-OpBranchConditional %50 %52 %53
-%52 = OpLabel
-%54 = OpLoad %13 %27
-%143 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_1 %142
-%144 = OpULessThan %bool %uint_0 %143
-OpSelectionMerge %145 None
-OpBranchConditional %144 %146 %147
-%146 = OpLabel
-%148 = OpLoad %13 %27
-%149 = OpImageRead %v4float %148 %20
-OpBranch %145
-%147 = OpLabel
-%150 = OpFunctionCall %void %56 %uint_51 %uint_1 %142 %uint_0
-OpBranch %145
-%145 = OpLabel
-%151 = OpPhi %v4float %149 %146 %113 %147
-OpBranch %51
-%53 = OpLabel
-%112 = OpFunctionCall %void %56 %uint_51 %uint_0 %142 %48
-OpBranch %51
-%51 = OpLabel
-%114 = OpPhi %v4float %151 %145 %113 %53
-%30 = OpCompositeExtract %float %114 0
-%31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
-%152 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0
-%153 = OpULessThan %bool %uint_0 %152
-OpSelectionMerge %154 None
-OpBranchConditional %153 %155 %156
-%155 = OpLabel
-OpStore %31 %30
-OpBranch %154
-%156 = OpLabel
-%158 = OpFunctionCall %void %56 %uint_54 %uint_1 %uint_0 %uint_0
-OpBranch %154
-%154 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string new_funcs =
-      R"(%33 = OpFunction %uint None %34
-%35 = OpFunctionParameter %uint
-%36 = OpFunctionParameter %uint
-%37 = OpLabel
-%43 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %35
-%44 = OpLoad %uint %43
-%45 = OpIAdd %uint %44 %36
-%46 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %45
-%47 = OpLoad %uint %46
-OpReturnValue %47
-OpFunctionEnd
-%56 = OpFunction %void None %57
-%58 = OpFunctionParameter %uint
-%59 = OpFunctionParameter %uint
-%60 = OpFunctionParameter %uint
-%61 = OpFunctionParameter %uint
-%62 = OpLabel
-%66 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_0
-%69 = OpAtomicIAdd %uint %66 %uint_4 %uint_0 %uint_10
-%70 = OpIAdd %uint %69 %uint_10
-%71 = OpArrayLength %uint %65 1
-%72 = OpULessThanEqual %bool %70 %71
-OpSelectionMerge %73 None
-OpBranchConditional %72 %74 %73
-%74 = OpLabel
-%75 = OpIAdd %uint %69 %uint_0
-%76 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %75
-OpStore %76 %uint_10
-%78 = OpIAdd %uint %69 %uint_1
-%79 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %78
-OpStore %79 %uint_23
-%81 = OpIAdd %uint %69 %uint_2
-%82 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %81
-OpStore %82 %58
-%85 = OpIAdd %uint %69 %uint_3
-%86 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %85
-OpStore %86 %uint_5315
-%90 = OpLoad %v3uint %89
-%91 = OpCompositeExtract %uint %90 0
-%92 = OpCompositeExtract %uint %90 1
-%93 = OpCompositeExtract %uint %90 2
-%94 = OpIAdd %uint %69 %uint_4
-%95 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %94
-OpStore %95 %91
-%97 = OpIAdd %uint %69 %uint_5
-%98 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %97
-OpStore %98 %92
-%100 = OpIAdd %uint %69 %uint_6
-%101 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %100
-OpStore %101 %93
-%103 = OpIAdd %uint %69 %uint_7
-%104 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %103
-OpStore %104 %59
-%106 = OpIAdd %uint %69 %uint_8
-%107 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %106
-OpStore %107 %60
-%109 = OpIAdd %uint %69 %uint_9
-%110 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %109
-OpStore %110 %61
-OpBranch %73
-%73 = OpLabel
-OpReturn
-OpFunctionEnd
-%115 = OpFunction %uint None %116
-%117 = OpFunctionParameter %uint
-%118 = OpFunctionParameter %uint
-%119 = OpFunctionParameter %uint
-%120 = OpFunctionParameter %uint
-%121 = OpLabel
-%122 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %117
-%123 = OpLoad %uint %122
-%124 = OpIAdd %uint %123 %118
-%125 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %124
-%126 = OpLoad %uint %125
-%127 = OpIAdd %uint %126 %119
-%128 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %127
-%129 = OpLoad %uint %128
-%130 = OpIAdd %uint %129 %120
-%131 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %130
-%132 = OpLoad %uint %131
-OpReturnValue %132
-OpFunctionEnd
-)";
+  const std::string new_funcs = kDirectRead2 + kStreamWrite4Ray + kDirectRead4;
 
   // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBindlessCheckPass>(
-      defs_before + func_before, defs_after + func_after + new_funcs, true,
-      true, 7u, 23u, true, true, false, false, false);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(defs + main_func + new_funcs,
+                                               true, 7u, 23u, true, true, false,
+                                               false, false);
 }
 
 TEST_F(InstBindlessTest,
@@ -5825,14 +3499,17 @@
   //    sbo.red = imageLoad(images[sbo.index], ivec2(0, 0)).r;
   // }
 
-  const std::string defs_before =
-      R"(OpCapability RuntimeDescriptorArray
+  // clang-format off
+  const std::string defs = R"(
+OpCapability RuntimeDescriptorArray
 OpCapability RayTracingNV
 OpExtension "SPV_EXT_descriptor_indexing"
 OpExtension "SPV_NV_ray_tracing"
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
 OpEntryPoint ClosestHitNV %main "main"
+; CHECK: OpEntryPoint ClosestHitNV %main "main" %89
 OpSource GLSL 460
 OpSourceExtension "GL_EXT_nonuniform_qualifier"
 OpSourceExtension "GL_NV_ray_tracing"
@@ -5850,51 +3527,11 @@
 OpDecorate %images DescriptorSet 0
 OpDecorate %images Binding 1
 OpDecorate %images NonWritable
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+)" + kInputDecorations + kOutputDecorations + R"(
+; CHECK: OpDecorate %89 BuiltIn LaunchIdNV
 %void = OpTypeVoid
-)";
-
-  const std::string defs_after =
-      R"(OpCapability RuntimeDescriptorArray
-OpCapability RayTracingNV
-OpExtension "SPV_EXT_descriptor_indexing"
-OpExtension "SPV_NV_ray_tracing"
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint ClosestHitNV %main "main" %89
-OpSource GLSL 460
-OpSourceExtension "GL_EXT_nonuniform_qualifier"
-OpSourceExtension "GL_NV_ray_tracing"
-OpName %main "main"
-OpName %StorageBuffer "StorageBuffer"
-OpMemberName %StorageBuffer 0 "index"
-OpMemberName %StorageBuffer 1 "red"
-OpName %sbo "sbo"
-OpName %images "images"
-OpMemberDecorate %StorageBuffer 0 Offset 0
-OpMemberDecorate %StorageBuffer 1 Offset 4
-OpDecorate %StorageBuffer BufferBlock
-OpDecorate %sbo DescriptorSet 0
-OpDecorate %sbo Binding 0
-OpDecorate %images DescriptorSet 0
-OpDecorate %images Binding 1
-OpDecorate %images NonWritable
-OpDecorate %_runtimearr_uint ArrayStride 4
-OpDecorate %_struct_39 Block
-OpMemberDecorate %_struct_39 0 Offset 0
-OpDecorate %41 DescriptorSet 7
-OpDecorate %41 Binding 1
-OpDecorate %_struct_63 Block
-OpMemberDecorate %_struct_63 0 Offset 0
-OpMemberDecorate %_struct_63 1 Offset 4
-OpDecorate %65 DescriptorSet 7
-OpDecorate %65 Binding 0
-OpDecorate %89 BuiltIn LaunchIdNV
-%void = OpTypeVoid
-)";
-
-  const std::string func_before =
-      R"(%3 = OpTypeFunction %void
+%3 = OpTypeFunction %void
 %uint = OpTypeInt 32 0
 %float = OpTypeFloat 32
 %StorageBuffer = OpTypeStruct %uint %float
@@ -5914,6 +3551,38 @@
 %v4float = OpTypeVector %float 4
 %uint_0 = OpConstant %uint 0
 %_ptr_Uniform_float = OpTypePointer Uniform %float
+; CHECK: %uint_1 = OpConstant %uint 1
+; CHECK: %34 = OpTypeFunction %uint %uint %uint
+; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+)" + kInputGlobals + R"(
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %bool = OpTypeBool
+; CHECK: %57 = OpTypeFunction %void %uint %uint %uint %uint
+)" + kOutputGlobals + R"(
+; CHECK: %uint_10 = OpConstant %uint 10
+; CHECK: %uint_4 = OpConstant %uint 4
+; CHECK: %uint_23 = OpConstant %uint 23
+; CHECK: %uint_2 = OpConstant %uint 2
+; CHECK: %uint_5316 = OpConstant %uint 5316
+; CHECK: %uint_3 = OpConstant %uint 3
+; CHECK: %v3uint = OpTypeVector %uint 3
+; CHECK: %_ptr_Input_v3uint = OpTypePointer Input %v3uint
+; CHECK: %89 = OpVariable %_ptr_Input_v3uint Input
+; CHECK: %uint_5 = OpConstant %uint 5
+; CHECK: %uint_6 = OpConstant %uint 6
+; CHECK: %uint_7 = OpConstant %uint 7
+; CHECK: %uint_8 = OpConstant %uint 8
+; CHECK: %uint_9 = OpConstant %uint 9
+; CHECK: %uint_51 = OpConstant %uint 51
+; CHECK: %113 = OpConstantNull %v4float
+; CHECK: %116 = OpTypeFunction %uint %uint %uint %uint %uint
+; CHECK: %uint_48 = OpConstant %uint 48
+; CHECK: %141 = OpConstantNull %uint
+; CHECK: %uint_54 = OpConstant %uint 54
+)";
+  // clang-format on
+
+  const std::string main_func = R"(
 %main = OpFunction %void None %3
 %5 = OpLabel
 %19 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
@@ -5924,211 +3593,69 @@
 %29 = OpCompositeExtract %float %27 0
 %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
 OpStore %31 %29
+; CHECK-NOT: OpStore %31 %29
+; CHECK: %133 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_0 %uint_0
+; CHECK: %134 = OpULessThan %bool %uint_0 %133
+; CHECK: OpSelectionMerge %135 None
+; CHECK: OpBranchConditional %134 %136 %137
+; CHECK: %136 = OpLabel
+; CHECK: %138 = OpLoad %uint %25
+; CHECK: OpBranch %135
+; CHECK: %137 = OpLabel
+; CHECK: %140 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_48 %uint_1 %uint_0 %uint_0
+; CHECK: OpBranch %135
+; CHECK: %135 = OpLabel
+; CHECK: %142 = OpPhi %uint %138 %136 %141 %137
+; CHECK: %27 = OpAccessChain %_ptr_UniformConstant_13 %images %142
+; CHECK: %28 = OpLoad %13 %27
+; CHECK: %48 = OpFunctionCall %uint %inst_bindless_direct_read_2 %uint_1 %uint_1
+; CHECK: %50 = OpULessThan %bool %142 %48
+; CHECK: OpSelectionMerge %51 None
+; CHECK: OpBranchConditional %50 %52 %53
+; CHECK: %52 = OpLabel
+; CHECK: %54 = OpLoad %13 %27
+; CHECK: %143 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_1 %142
+; CHECK: %144 = OpULessThan %bool %uint_0 %143
+; CHECK: OpSelectionMerge %145 None
+; CHECK: OpBranchConditional %144 %146 %147
+; CHECK: %146 = OpLabel
+; CHECK: %148 = OpLoad %13 %27
+; CHECK: %149 = OpImageRead %v4float %148 %20
+; CHECK: OpBranch %145
+; CHECK: %147 = OpLabel
+; CHECK: %150 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_51 %uint_1 %142 %uint_0
+; CHECK: OpBranch %145
+; CHECK: %145 = OpLabel
+; CHECK: %151 = OpPhi %v4float %149 %146 %113 %147
+; CHECK: OpBranch %51
+; CHECK: %53 = OpLabel
+; CHECK: %112 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_51 %uint_0 %142 %48
+; CHECK: OpBranch %51
+; CHECK: %51 = OpLabel
+; CHECK: %114 = OpPhi %v4float %151 %145 %113 %53
+; CHECK: %30 = OpCompositeExtract %float %114 0
+; CHECK: %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
+; CHECK: %152 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_0 %uint_0
+; CHECK: %153 = OpULessThan %bool %uint_0 %152
+; CHECK: OpSelectionMerge %154 None
+; CHECK: OpBranchConditional %153 %155 %156
+; CHECK: %155 = OpLabel
+; CHECK: OpStore %31 %30
+; CHECK: OpBranch %154
+; CHECK: %156 = OpLabel
+; CHECK: %158 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_54 %uint_1 %uint_0 %uint_0
+; CHECK: OpBranch %154
+; CHECK: %154 = OpLabel
 OpReturn
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%7 = OpTypeFunction %void
-%uint = OpTypeInt 32 0
-%float = OpTypeFloat 32
-%StorageBuffer = OpTypeStruct %uint %float
-%_ptr_Uniform_StorageBuffer = OpTypePointer Uniform %StorageBuffer
-%sbo = OpVariable %_ptr_Uniform_StorageBuffer Uniform
-%int = OpTypeInt 32 1
-%int_1 = OpConstant %int 1
-%13 = OpTypeImage %float 2D 0 0 0 2 Rgba32f
-%_runtimearr_13 = OpTypeRuntimeArray %13
-%_ptr_UniformConstant__runtimearr_13 = OpTypePointer UniformConstant %_runtimearr_13
-%images = OpVariable %_ptr_UniformConstant__runtimearr_13 UniformConstant
-%int_0 = OpConstant %int 0
-%_ptr_Uniform_uint = OpTypePointer Uniform %uint
-%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
-%v2int = OpTypeVector %int 2
-%20 = OpConstantComposite %v2int %int_0 %int_0
-%v4float = OpTypeVector %float 4
-%uint_0 = OpConstant %uint 0
-%_ptr_Uniform_float = OpTypePointer Uniform %float
-%uint_1 = OpConstant %uint 1
-%34 = OpTypeFunction %uint %uint %uint
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_39 = OpTypeStruct %_runtimearr_uint
-%_ptr_StorageBuffer__struct_39 = OpTypePointer StorageBuffer %_struct_39
-%41 = OpVariable %_ptr_StorageBuffer__struct_39 StorageBuffer
-%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-%bool = OpTypeBool
-%57 = OpTypeFunction %void %uint %uint %uint %uint
-%_struct_63 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_63 = OpTypePointer StorageBuffer %_struct_63
-%65 = OpVariable %_ptr_StorageBuffer__struct_63 StorageBuffer
-%uint_10 = OpConstant %uint 10
-%uint_4 = OpConstant %uint 4
-%uint_23 = OpConstant %uint 23
-%uint_2 = OpConstant %uint 2
-%uint_5316 = OpConstant %uint 5316
-%uint_3 = OpConstant %uint 3
-%v3uint = OpTypeVector %uint 3
-%_ptr_Input_v3uint = OpTypePointer Input %v3uint
-%89 = OpVariable %_ptr_Input_v3uint Input
-%uint_5 = OpConstant %uint 5
-%uint_6 = OpConstant %uint 6
-%uint_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%uint_9 = OpConstant %uint 9
-%uint_51 = OpConstant %uint 51
-%113 = OpConstantNull %v4float
-%116 = OpTypeFunction %uint %uint %uint %uint %uint
-%uint_48 = OpConstant %uint 48
-%141 = OpConstantNull %uint
-%uint_54 = OpConstant %uint 54
-%main = OpFunction %void None %7
-%24 = OpLabel
-%25 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
-%133 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0
-%134 = OpULessThan %bool %uint_0 %133
-OpSelectionMerge %135 None
-OpBranchConditional %134 %136 %137
-%136 = OpLabel
-%138 = OpLoad %uint %25
-OpBranch %135
-%137 = OpLabel
-%140 = OpFunctionCall %void %56 %uint_48 %uint_1 %uint_0 %uint_0
-OpBranch %135
-%135 = OpLabel
-%142 = OpPhi %uint %138 %136 %141 %137
-%27 = OpAccessChain %_ptr_UniformConstant_13 %images %142
-%28 = OpLoad %13 %27
-%48 = OpFunctionCall %uint %33 %uint_1 %uint_1
-%50 = OpULessThan %bool %142 %48
-OpSelectionMerge %51 None
-OpBranchConditional %50 %52 %53
-%52 = OpLabel
-%54 = OpLoad %13 %27
-%143 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_1 %142
-%144 = OpULessThan %bool %uint_0 %143
-OpSelectionMerge %145 None
-OpBranchConditional %144 %146 %147
-%146 = OpLabel
-%148 = OpLoad %13 %27
-%149 = OpImageRead %v4float %148 %20
-OpBranch %145
-%147 = OpLabel
-%150 = OpFunctionCall %void %56 %uint_51 %uint_1 %142 %uint_0
-OpBranch %145
-%145 = OpLabel
-%151 = OpPhi %v4float %149 %146 %113 %147
-OpBranch %51
-%53 = OpLabel
-%112 = OpFunctionCall %void %56 %uint_51 %uint_0 %142 %48
-OpBranch %51
-%51 = OpLabel
-%114 = OpPhi %v4float %151 %145 %113 %53
-%30 = OpCompositeExtract %float %114 0
-%31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
-%152 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0
-%153 = OpULessThan %bool %uint_0 %152
-OpSelectionMerge %154 None
-OpBranchConditional %153 %155 %156
-%155 = OpLabel
-OpStore %31 %30
-OpBranch %154
-%156 = OpLabel
-%158 = OpFunctionCall %void %56 %uint_54 %uint_1 %uint_0 %uint_0
-OpBranch %154
-%154 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string new_funcs =
-      R"(%33 = OpFunction %uint None %34
-%35 = OpFunctionParameter %uint
-%36 = OpFunctionParameter %uint
-%37 = OpLabel
-%43 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %35
-%44 = OpLoad %uint %43
-%45 = OpIAdd %uint %44 %36
-%46 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %45
-%47 = OpLoad %uint %46
-OpReturnValue %47
-OpFunctionEnd
-%56 = OpFunction %void None %57
-%58 = OpFunctionParameter %uint
-%59 = OpFunctionParameter %uint
-%60 = OpFunctionParameter %uint
-%61 = OpFunctionParameter %uint
-%62 = OpLabel
-%66 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_0
-%69 = OpAtomicIAdd %uint %66 %uint_4 %uint_0 %uint_10
-%70 = OpIAdd %uint %69 %uint_10
-%71 = OpArrayLength %uint %65 1
-%72 = OpULessThanEqual %bool %70 %71
-OpSelectionMerge %73 None
-OpBranchConditional %72 %74 %73
-%74 = OpLabel
-%75 = OpIAdd %uint %69 %uint_0
-%76 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %75
-OpStore %76 %uint_10
-%78 = OpIAdd %uint %69 %uint_1
-%79 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %78
-OpStore %79 %uint_23
-%81 = OpIAdd %uint %69 %uint_2
-%82 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %81
-OpStore %82 %58
-%85 = OpIAdd %uint %69 %uint_3
-%86 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %85
-OpStore %86 %uint_5316
-%90 = OpLoad %v3uint %89
-%91 = OpCompositeExtract %uint %90 0
-%92 = OpCompositeExtract %uint %90 1
-%93 = OpCompositeExtract %uint %90 2
-%94 = OpIAdd %uint %69 %uint_4
-%95 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %94
-OpStore %95 %91
-%97 = OpIAdd %uint %69 %uint_5
-%98 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %97
-OpStore %98 %92
-%100 = OpIAdd %uint %69 %uint_6
-%101 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %100
-OpStore %101 %93
-%103 = OpIAdd %uint %69 %uint_7
-%104 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %103
-OpStore %104 %59
-%106 = OpIAdd %uint %69 %uint_8
-%107 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %106
-OpStore %107 %60
-%109 = OpIAdd %uint %69 %uint_9
-%110 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %109
-OpStore %110 %61
-OpBranch %73
-%73 = OpLabel
-OpReturn
-OpFunctionEnd
-%115 = OpFunction %uint None %116
-%117 = OpFunctionParameter %uint
-%118 = OpFunctionParameter %uint
-%119 = OpFunctionParameter %uint
-%120 = OpFunctionParameter %uint
-%121 = OpLabel
-%122 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %117
-%123 = OpLoad %uint %122
-%124 = OpIAdd %uint %123 %118
-%125 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %124
-%126 = OpLoad %uint %125
-%127 = OpIAdd %uint %126 %119
-%128 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %127
-%129 = OpLoad %uint %128
-%130 = OpIAdd %uint %129 %120
-%131 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %130
-%132 = OpLoad %uint %131
-OpReturnValue %132
-OpFunctionEnd
-)";
+  const std::string new_funcs = kDirectRead2 + kStreamWrite4Ray + kDirectRead4;
 
   // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBindlessCheckPass>(
-      defs_before + func_before, defs_after + func_after + new_funcs, true,
-      true, 7u, 23u, true, true, false, false, false);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(defs + main_func + new_funcs,
+                                               true, 7u, 23u, true, true, false,
+                                               false, false);
 }
 
 TEST_F(InstBindlessTest,
@@ -6149,14 +3676,17 @@
   //    sbo.red = imageLoad(images[sbo.index], ivec2(0, 0)).r;
   // }
 
-  const std::string defs_before =
-      R"(OpCapability RuntimeDescriptorArray
+  // clang-format off
+  const std::string defs = R"(
+OpCapability RuntimeDescriptorArray
 OpCapability RayTracingNV
 OpExtension "SPV_EXT_descriptor_indexing"
 OpExtension "SPV_NV_ray_tracing"
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
 OpEntryPoint MissNV %main "main"
+; CHECK: OpEntryPoint MissNV %main "main" %89
 OpSource GLSL 460
 OpSourceExtension "GL_EXT_nonuniform_qualifier"
 OpSourceExtension "GL_NV_ray_tracing"
@@ -6174,51 +3704,11 @@
 OpDecorate %images DescriptorSet 0
 OpDecorate %images Binding 1
 OpDecorate %images NonWritable
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+)" + kInputDecorations + kOutputDecorations + R"(
+; CHECK: OpDecorate %89 BuiltIn LaunchIdNV
 %void = OpTypeVoid
-)";
-
-  const std::string defs_after =
-      R"(OpCapability RuntimeDescriptorArray
-OpCapability RayTracingNV
-OpExtension "SPV_EXT_descriptor_indexing"
-OpExtension "SPV_NV_ray_tracing"
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint MissNV %main "main" %89
-OpSource GLSL 460
-OpSourceExtension "GL_EXT_nonuniform_qualifier"
-OpSourceExtension "GL_NV_ray_tracing"
-OpName %main "main"
-OpName %StorageBuffer "StorageBuffer"
-OpMemberName %StorageBuffer 0 "index"
-OpMemberName %StorageBuffer 1 "red"
-OpName %sbo "sbo"
-OpName %images "images"
-OpMemberDecorate %StorageBuffer 0 Offset 0
-OpMemberDecorate %StorageBuffer 1 Offset 4
-OpDecorate %StorageBuffer BufferBlock
-OpDecorate %sbo DescriptorSet 0
-OpDecorate %sbo Binding 0
-OpDecorate %images DescriptorSet 0
-OpDecorate %images Binding 1
-OpDecorate %images NonWritable
-OpDecorate %_runtimearr_uint ArrayStride 4
-OpDecorate %_struct_39 Block
-OpMemberDecorate %_struct_39 0 Offset 0
-OpDecorate %41 DescriptorSet 7
-OpDecorate %41 Binding 1
-OpDecorate %_struct_63 Block
-OpMemberDecorate %_struct_63 0 Offset 0
-OpMemberDecorate %_struct_63 1 Offset 4
-OpDecorate %65 DescriptorSet 7
-OpDecorate %65 Binding 0
-OpDecorate %89 BuiltIn LaunchIdNV
-%void = OpTypeVoid
-)";
-
-  const std::string func_before =
-      R"(%3 = OpTypeFunction %void
+%3 = OpTypeFunction %void
 %uint = OpTypeInt 32 0
 %float = OpTypeFloat 32
 %StorageBuffer = OpTypeStruct %uint %float
@@ -6238,6 +3728,38 @@
 %v4float = OpTypeVector %float 4
 %uint_0 = OpConstant %uint 0
 %_ptr_Uniform_float = OpTypePointer Uniform %float
+; CHECK: %uint_1 = OpConstant %uint 1
+; CHECK: %34 = OpTypeFunction %uint %uint %uint
+; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+)" + kInputGlobals + R"(
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %bool = OpTypeBool
+; CHECK: %57 = OpTypeFunction %void %uint %uint %uint %uint
+)" + kOutputGlobals + R"(
+; CHECK: %uint_10 = OpConstant %uint 10
+; CHECK: %uint_4 = OpConstant %uint 4
+; CHECK: %uint_23 = OpConstant %uint 23
+; CHECK: %uint_2 = OpConstant %uint 2
+; CHECK: %uint_5317 = OpConstant %uint 5317
+; CHECK: %uint_3 = OpConstant %uint 3
+; CHECK: %v3uint = OpTypeVector %uint 3
+; CHECK: %_ptr_Input_v3uint = OpTypePointer Input %v3uint
+; CHECK: %89 = OpVariable %_ptr_Input_v3uint Input
+; CHECK: %uint_5 = OpConstant %uint 5
+; CHECK: %uint_6 = OpConstant %uint 6
+; CHECK: %uint_7 = OpConstant %uint 7
+; CHECK: %uint_8 = OpConstant %uint 8
+; CHECK: %uint_9 = OpConstant %uint 9
+; CHECK: %uint_51 = OpConstant %uint 51
+; CHECK: %113 = OpConstantNull %v4float
+; CHECK: %116 = OpTypeFunction %uint %uint %uint %uint %uint
+; CHECK: %uint_48 = OpConstant %uint 48
+; CHECK: %141 = OpConstantNull %uint
+; CHECK: %uint_54 = OpConstant %uint 54
+)";
+  // clang-format on
+
+  const std::string main_func = R"(
 %main = OpFunction %void None %3
 %5 = OpLabel
 %19 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
@@ -6248,211 +3770,69 @@
 %29 = OpCompositeExtract %float %27 0
 %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
 OpStore %31 %29
+; CHECK-NOT OpStore %31 %29
+; CHECK: %133 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_0 %uint_0
+; CHECK: %134 = OpULessThan %bool %uint_0 %133
+; CHECK: OpSelectionMerge %135 None
+; CHECK: OpBranchConditional %134 %136 %137
+; CHECK: %136 = OpLabel
+; CHECK: %138 = OpLoad %uint %25
+; CHECK: OpBranch %135
+; CHECK: %137 = OpLabel
+; CHECK: %140 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_48 %uint_1 %uint_0 %uint_0
+; CHECK: OpBranch %135
+; CHECK: %135 = OpLabel
+; CHECK: %142 = OpPhi %uint %138 %136 %141 %137
+; CHECK: %27 = OpAccessChain %_ptr_UniformConstant_13 %images %142
+; CHECK: %28 = OpLoad %13 %27
+; CHECK: %48 = OpFunctionCall %uint %inst_bindless_direct_read_2 %uint_1 %uint_1
+; CHECK: %50 = OpULessThan %bool %142 %48
+; CHECK: OpSelectionMerge %51 None
+; CHECK: OpBranchConditional %50 %52 %53
+; CHECK: %52 = OpLabel
+; CHECK: %54 = OpLoad %13 %27
+; CHECK: %143 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_1 %142
+; CHECK: %144 = OpULessThan %bool %uint_0 %143
+; CHECK: OpSelectionMerge %145 None
+; CHECK: OpBranchConditional %144 %146 %147
+; CHECK: %146 = OpLabel
+; CHECK: %148 = OpLoad %13 %27
+; CHECK: %149 = OpImageRead %v4float %148 %20
+; CHECK: OpBranch %145
+; CHECK: %147 = OpLabel
+; CHECK: %150 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_51 %uint_1 %142 %uint_0
+; CHECK: OpBranch %145
+; CHECK: %145 = OpLabel
+; CHECK: %151 = OpPhi %v4float %149 %146 %113 %147
+; CHECK: OpBranch %51
+; CHECK: %53 = OpLabel
+; CHECK: %112 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_51 %uint_0 %142 %48
+; CHECK: OpBranch %51
+; CHECK: %51 = OpLabel
+; CHECK: %114 = OpPhi %v4float %151 %145 %113 %53
+; CHECK: %30 = OpCompositeExtract %float %114 0
+; CHECK: %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
+; CHECK: %152 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_0 %uint_0
+; CHECK: %153 = OpULessThan %bool %uint_0 %152
+; CHECK: OpSelectionMerge %154 None
+; CHECK: OpBranchConditional %153 %155 %156
+; CHECK: %155 = OpLabel
+; CHECK: OpStore %31 %30
+; CHECK: OpBranch %154
+; CHECK: %156 = OpLabel
+; CHECK: %158 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_54 %uint_1 %uint_0 %uint_0
+; CHECK: OpBranch %154
+; CHECK: %154 = OpLabel
 OpReturn
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%7 = OpTypeFunction %void
-%uint = OpTypeInt 32 0
-%float = OpTypeFloat 32
-%StorageBuffer = OpTypeStruct %uint %float
-%_ptr_Uniform_StorageBuffer = OpTypePointer Uniform %StorageBuffer
-%sbo = OpVariable %_ptr_Uniform_StorageBuffer Uniform
-%int = OpTypeInt 32 1
-%int_1 = OpConstant %int 1
-%13 = OpTypeImage %float 2D 0 0 0 2 Rgba32f
-%_runtimearr_13 = OpTypeRuntimeArray %13
-%_ptr_UniformConstant__runtimearr_13 = OpTypePointer UniformConstant %_runtimearr_13
-%images = OpVariable %_ptr_UniformConstant__runtimearr_13 UniformConstant
-%int_0 = OpConstant %int 0
-%_ptr_Uniform_uint = OpTypePointer Uniform %uint
-%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
-%v2int = OpTypeVector %int 2
-%20 = OpConstantComposite %v2int %int_0 %int_0
-%v4float = OpTypeVector %float 4
-%uint_0 = OpConstant %uint 0
-%_ptr_Uniform_float = OpTypePointer Uniform %float
-%uint_1 = OpConstant %uint 1
-%34 = OpTypeFunction %uint %uint %uint
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_39 = OpTypeStruct %_runtimearr_uint
-%_ptr_StorageBuffer__struct_39 = OpTypePointer StorageBuffer %_struct_39
-%41 = OpVariable %_ptr_StorageBuffer__struct_39 StorageBuffer
-%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-%bool = OpTypeBool
-%57 = OpTypeFunction %void %uint %uint %uint %uint
-%_struct_63 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_63 = OpTypePointer StorageBuffer %_struct_63
-%65 = OpVariable %_ptr_StorageBuffer__struct_63 StorageBuffer
-%uint_10 = OpConstant %uint 10
-%uint_4 = OpConstant %uint 4
-%uint_23 = OpConstant %uint 23
-%uint_2 = OpConstant %uint 2
-%uint_5317 = OpConstant %uint 5317
-%uint_3 = OpConstant %uint 3
-%v3uint = OpTypeVector %uint 3
-%_ptr_Input_v3uint = OpTypePointer Input %v3uint
-%89 = OpVariable %_ptr_Input_v3uint Input
-%uint_5 = OpConstant %uint 5
-%uint_6 = OpConstant %uint 6
-%uint_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%uint_9 = OpConstant %uint 9
-%uint_51 = OpConstant %uint 51
-%113 = OpConstantNull %v4float
-%116 = OpTypeFunction %uint %uint %uint %uint %uint
-%uint_48 = OpConstant %uint 48
-%141 = OpConstantNull %uint
-%uint_54 = OpConstant %uint 54
-%main = OpFunction %void None %7
-%24 = OpLabel
-%25 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
-%133 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0
-%134 = OpULessThan %bool %uint_0 %133
-OpSelectionMerge %135 None
-OpBranchConditional %134 %136 %137
-%136 = OpLabel
-%138 = OpLoad %uint %25
-OpBranch %135
-%137 = OpLabel
-%140 = OpFunctionCall %void %56 %uint_48 %uint_1 %uint_0 %uint_0
-OpBranch %135
-%135 = OpLabel
-%142 = OpPhi %uint %138 %136 %141 %137
-%27 = OpAccessChain %_ptr_UniformConstant_13 %images %142
-%28 = OpLoad %13 %27
-%48 = OpFunctionCall %uint %33 %uint_1 %uint_1
-%50 = OpULessThan %bool %142 %48
-OpSelectionMerge %51 None
-OpBranchConditional %50 %52 %53
-%52 = OpLabel
-%54 = OpLoad %13 %27
-%143 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_1 %142
-%144 = OpULessThan %bool %uint_0 %143
-OpSelectionMerge %145 None
-OpBranchConditional %144 %146 %147
-%146 = OpLabel
-%148 = OpLoad %13 %27
-%149 = OpImageRead %v4float %148 %20
-OpBranch %145
-%147 = OpLabel
-%150 = OpFunctionCall %void %56 %uint_51 %uint_1 %142 %uint_0
-OpBranch %145
-%145 = OpLabel
-%151 = OpPhi %v4float %149 %146 %113 %147
-OpBranch %51
-%53 = OpLabel
-%112 = OpFunctionCall %void %56 %uint_51 %uint_0 %142 %48
-OpBranch %51
-%51 = OpLabel
-%114 = OpPhi %v4float %151 %145 %113 %53
-%30 = OpCompositeExtract %float %114 0
-%31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
-%152 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0
-%153 = OpULessThan %bool %uint_0 %152
-OpSelectionMerge %154 None
-OpBranchConditional %153 %155 %156
-%155 = OpLabel
-OpStore %31 %30
-OpBranch %154
-%156 = OpLabel
-%158 = OpFunctionCall %void %56 %uint_54 %uint_1 %uint_0 %uint_0
-OpBranch %154
-%154 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string new_funcs =
-      R"(%33 = OpFunction %uint None %34
-%35 = OpFunctionParameter %uint
-%36 = OpFunctionParameter %uint
-%37 = OpLabel
-%43 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %35
-%44 = OpLoad %uint %43
-%45 = OpIAdd %uint %44 %36
-%46 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %45
-%47 = OpLoad %uint %46
-OpReturnValue %47
-OpFunctionEnd
-%56 = OpFunction %void None %57
-%58 = OpFunctionParameter %uint
-%59 = OpFunctionParameter %uint
-%60 = OpFunctionParameter %uint
-%61 = OpFunctionParameter %uint
-%62 = OpLabel
-%66 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_0
-%69 = OpAtomicIAdd %uint %66 %uint_4 %uint_0 %uint_10
-%70 = OpIAdd %uint %69 %uint_10
-%71 = OpArrayLength %uint %65 1
-%72 = OpULessThanEqual %bool %70 %71
-OpSelectionMerge %73 None
-OpBranchConditional %72 %74 %73
-%74 = OpLabel
-%75 = OpIAdd %uint %69 %uint_0
-%76 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %75
-OpStore %76 %uint_10
-%78 = OpIAdd %uint %69 %uint_1
-%79 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %78
-OpStore %79 %uint_23
-%81 = OpIAdd %uint %69 %uint_2
-%82 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %81
-OpStore %82 %58
-%85 = OpIAdd %uint %69 %uint_3
-%86 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %85
-OpStore %86 %uint_5317
-%90 = OpLoad %v3uint %89
-%91 = OpCompositeExtract %uint %90 0
-%92 = OpCompositeExtract %uint %90 1
-%93 = OpCompositeExtract %uint %90 2
-%94 = OpIAdd %uint %69 %uint_4
-%95 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %94
-OpStore %95 %91
-%97 = OpIAdd %uint %69 %uint_5
-%98 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %97
-OpStore %98 %92
-%100 = OpIAdd %uint %69 %uint_6
-%101 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %100
-OpStore %101 %93
-%103 = OpIAdd %uint %69 %uint_7
-%104 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %103
-OpStore %104 %59
-%106 = OpIAdd %uint %69 %uint_8
-%107 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %106
-OpStore %107 %60
-%109 = OpIAdd %uint %69 %uint_9
-%110 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %109
-OpStore %110 %61
-OpBranch %73
-%73 = OpLabel
-OpReturn
-OpFunctionEnd
-%115 = OpFunction %uint None %116
-%117 = OpFunctionParameter %uint
-%118 = OpFunctionParameter %uint
-%119 = OpFunctionParameter %uint
-%120 = OpFunctionParameter %uint
-%121 = OpLabel
-%122 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %117
-%123 = OpLoad %uint %122
-%124 = OpIAdd %uint %123 %118
-%125 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %124
-%126 = OpLoad %uint %125
-%127 = OpIAdd %uint %126 %119
-%128 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %127
-%129 = OpLoad %uint %128
-%130 = OpIAdd %uint %129 %120
-%131 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %130
-%132 = OpLoad %uint %131
-OpReturnValue %132
-OpFunctionEnd
-)";
+  const std::string new_funcs = kDirectRead2 + kStreamWrite4Ray + kDirectRead4;
 
   // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBindlessCheckPass>(
-      defs_before + func_before, defs_after + func_after + new_funcs, true,
-      true, 7u, 23u, true, true, false, false, false);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(defs + main_func + new_funcs,
+                                               true, 7u, 23u, true, true, false,
+                                               false, false);
 }
 
 TEST_F(InstBindlessTest,
@@ -6473,14 +3853,17 @@
   //    sbo.red = imageLoad(images[sbo.index], ivec2(0, 0)).r;
   // }
 
-  const std::string defs_before =
-      R"(OpCapability RuntimeDescriptorArray
+  // clang-format off
+  const std::string defs = R"(
+OpCapability RuntimeDescriptorArray
 OpCapability RayTracingNV
 OpExtension "SPV_EXT_descriptor_indexing"
 OpExtension "SPV_NV_ray_tracing"
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
 OpEntryPoint CallableNV %main "main"
+; CHECK: OpEntryPoint CallableNV %main "main" %89
 OpSource GLSL 460
 OpSourceExtension "GL_EXT_nonuniform_qualifier"
 OpSourceExtension "GL_NV_ray_tracing"
@@ -6498,51 +3881,11 @@
 OpDecorate %images DescriptorSet 0
 OpDecorate %images Binding 1
 OpDecorate %images NonWritable
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+)" + kInputDecorations + kOutputDecorations + R"(
+; CHECK: OpDecorate %89 BuiltIn LaunchIdNV
 %void = OpTypeVoid
-)";
-
-  const std::string defs_after =
-      R"(OpCapability RuntimeDescriptorArray
-OpCapability RayTracingNV
-OpExtension "SPV_EXT_descriptor_indexing"
-OpExtension "SPV_NV_ray_tracing"
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint CallableNV %main "main" %89
-OpSource GLSL 460
-OpSourceExtension "GL_EXT_nonuniform_qualifier"
-OpSourceExtension "GL_NV_ray_tracing"
-OpName %main "main"
-OpName %StorageBuffer "StorageBuffer"
-OpMemberName %StorageBuffer 0 "index"
-OpMemberName %StorageBuffer 1 "red"
-OpName %sbo "sbo"
-OpName %images "images"
-OpMemberDecorate %StorageBuffer 0 Offset 0
-OpMemberDecorate %StorageBuffer 1 Offset 4
-OpDecorate %StorageBuffer BufferBlock
-OpDecorate %sbo DescriptorSet 0
-OpDecorate %sbo Binding 0
-OpDecorate %images DescriptorSet 0
-OpDecorate %images Binding 1
-OpDecorate %images NonWritable
-OpDecorate %_runtimearr_uint ArrayStride 4
-OpDecorate %_struct_39 Block
-OpMemberDecorate %_struct_39 0 Offset 0
-OpDecorate %41 DescriptorSet 7
-OpDecorate %41 Binding 1
-OpDecorate %_struct_63 Block
-OpMemberDecorate %_struct_63 0 Offset 0
-OpMemberDecorate %_struct_63 1 Offset 4
-OpDecorate %65 DescriptorSet 7
-OpDecorate %65 Binding 0
-OpDecorate %89 BuiltIn LaunchIdNV
-%void = OpTypeVoid
-)";
-
-  const std::string func_before =
-      R"(%3 = OpTypeFunction %void
+%3 = OpTypeFunction %void
 %uint = OpTypeInt 32 0
 %float = OpTypeFloat 32
 %StorageBuffer = OpTypeStruct %uint %float
@@ -6562,6 +3905,38 @@
 %v4float = OpTypeVector %float 4
 %uint_0 = OpConstant %uint 0
 %_ptr_Uniform_float = OpTypePointer Uniform %float
+; CHECK: %uint_1 = OpConstant %uint 1
+; CHECK: %34 = OpTypeFunction %uint %uint %uint
+; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+)" + kInputGlobals + R"(
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %bool = OpTypeBool
+; CHECK: %57 = OpTypeFunction %void %uint %uint %uint %uint
+)" + kOutputGlobals + R"(
+; CHECK: %uint_10 = OpConstant %uint 10
+; CHECK: %uint_4 = OpConstant %uint 4
+; CHECK: %uint_23 = OpConstant %uint 23
+; CHECK: %uint_2 = OpConstant %uint 2
+; CHECK: %uint_5318 = OpConstant %uint 5318
+; CHECK: %uint_3 = OpConstant %uint 3
+; CHECK: %v3uint = OpTypeVector %uint 3
+; CHECK: %_ptr_Input_v3uint = OpTypePointer Input %v3uint
+; CHECK: %89 = OpVariable %_ptr_Input_v3uint Input
+; CHECK: %uint_5 = OpConstant %uint 5
+; CHECK: %uint_6 = OpConstant %uint 6
+; CHECK: %uint_7 = OpConstant %uint 7
+; CHECK: %uint_8 = OpConstant %uint 8
+; CHECK: %uint_9 = OpConstant %uint 9
+; CHECK: %uint_51 = OpConstant %uint 51
+; CHECK: %113 = OpConstantNull %v4float
+; CHECK: %116 = OpTypeFunction %uint %uint %uint %uint %uint
+; CHECK: %uint_48 = OpConstant %uint 48
+; CHECK: %141 = OpConstantNull %uint
+; CHECK: %uint_54 = OpConstant %uint 54
+)";
+  // clang-format on
+
+  const std::string main_func = R"(
 %main = OpFunction %void None %3
 %5 = OpLabel
 %19 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
@@ -6572,211 +3947,69 @@
 %29 = OpCompositeExtract %float %27 0
 %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
 OpStore %31 %29
+; CHECK-NOT: OpStore %31 %29
+; CHECK: %133 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_0 %uint_0
+; CHECK: %134 = OpULessThan %bool %uint_0 %133
+; CHECK: OpSelectionMerge %135 None
+; CHECK: OpBranchConditional %134 %136 %137
+; CHECK: %136 = OpLabel
+; CHECK: %138 = OpLoad %uint %25
+; CHECK: OpBranch %135
+; CHECK: %137 = OpLabel
+; CHECK: %140 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_48 %uint_1 %uint_0 %uint_0
+; CHECK: OpBranch %135
+; CHECK: %135 = OpLabel
+; CHECK: %142 = OpPhi %uint %138 %136 %141 %137
+; CHECK: %27 = OpAccessChain %_ptr_UniformConstant_13 %images %142
+; CHECK: %28 = OpLoad %13 %27
+; CHECK: %48 = OpFunctionCall %uint %inst_bindless_direct_read_2 %uint_1 %uint_1
+; CHECK: %50 = OpULessThan %bool %142 %48
+; CHECK: OpSelectionMerge %51 None
+; CHECK: OpBranchConditional %50 %52 %53
+; CHECK: %52 = OpLabel
+; CHECK: %54 = OpLoad %13 %27
+; CHECK: %143 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_1 %142
+; CHECK: %144 = OpULessThan %bool %uint_0 %143
+; CHECK: OpSelectionMerge %145 None
+; CHECK: OpBranchConditional %144 %146 %147
+; CHECK: %146 = OpLabel
+; CHECK: %148 = OpLoad %13 %27
+; CHECK: %149 = OpImageRead %v4float %148 %20
+; CHECK: OpBranch %145
+; CHECK: %147 = OpLabel
+; CHECK: %150 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_51 %uint_1 %142 %uint_0
+; CHECK: OpBranch %145
+; CHECK: %145 = OpLabel
+; CHECK: %151 = OpPhi %v4float %149 %146 %113 %147
+; CHECK: OpBranch %51
+; CHECK: %53 = OpLabel
+; CHECK: %112 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_51 %uint_0 %142 %48
+; CHECK: OpBranch %51
+; CHECK: %51 = OpLabel
+; CHECK: %114 = OpPhi %v4float %151 %145 %113 %53
+; CHECK: %30 = OpCompositeExtract %float %114 0
+; CHECK: %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
+; CHECK: %152 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_0 %uint_0
+; CHECK: %153 = OpULessThan %bool %uint_0 %152
+; CHECK: OpSelectionMerge %154 None
+; CHECK: OpBranchConditional %153 %155 %156
+; CHECK: %155 = OpLabel
+; CHECK: OpStore %31 %30
+; CHECK: OpBranch %154
+; CHECK: %156 = OpLabel
+; CHECK: %158 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_54 %uint_1 %uint_0 %uint_0
+; CHECK: OpBranch %154
+; CHECK: %154 = OpLabel
 OpReturn
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%7 = OpTypeFunction %void
-%uint = OpTypeInt 32 0
-%float = OpTypeFloat 32
-%StorageBuffer = OpTypeStruct %uint %float
-%_ptr_Uniform_StorageBuffer = OpTypePointer Uniform %StorageBuffer
-%sbo = OpVariable %_ptr_Uniform_StorageBuffer Uniform
-%int = OpTypeInt 32 1
-%int_1 = OpConstant %int 1
-%13 = OpTypeImage %float 2D 0 0 0 2 Rgba32f
-%_runtimearr_13 = OpTypeRuntimeArray %13
-%_ptr_UniformConstant__runtimearr_13 = OpTypePointer UniformConstant %_runtimearr_13
-%images = OpVariable %_ptr_UniformConstant__runtimearr_13 UniformConstant
-%int_0 = OpConstant %int 0
-%_ptr_Uniform_uint = OpTypePointer Uniform %uint
-%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
-%v2int = OpTypeVector %int 2
-%20 = OpConstantComposite %v2int %int_0 %int_0
-%v4float = OpTypeVector %float 4
-%uint_0 = OpConstant %uint 0
-%_ptr_Uniform_float = OpTypePointer Uniform %float
-%uint_1 = OpConstant %uint 1
-%34 = OpTypeFunction %uint %uint %uint
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_39 = OpTypeStruct %_runtimearr_uint
-%_ptr_StorageBuffer__struct_39 = OpTypePointer StorageBuffer %_struct_39
-%41 = OpVariable %_ptr_StorageBuffer__struct_39 StorageBuffer
-%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-%bool = OpTypeBool
-%57 = OpTypeFunction %void %uint %uint %uint %uint
-%_struct_63 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_63 = OpTypePointer StorageBuffer %_struct_63
-%65 = OpVariable %_ptr_StorageBuffer__struct_63 StorageBuffer
-%uint_10 = OpConstant %uint 10
-%uint_4 = OpConstant %uint 4
-%uint_23 = OpConstant %uint 23
-%uint_2 = OpConstant %uint 2
-%uint_5318 = OpConstant %uint 5318
-%uint_3 = OpConstant %uint 3
-%v3uint = OpTypeVector %uint 3
-%_ptr_Input_v3uint = OpTypePointer Input %v3uint
-%89 = OpVariable %_ptr_Input_v3uint Input
-%uint_5 = OpConstant %uint 5
-%uint_6 = OpConstant %uint 6
-%uint_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%uint_9 = OpConstant %uint 9
-%uint_51 = OpConstant %uint 51
-%113 = OpConstantNull %v4float
-%116 = OpTypeFunction %uint %uint %uint %uint %uint
-%uint_48 = OpConstant %uint 48
-%141 = OpConstantNull %uint
-%uint_54 = OpConstant %uint 54
-%main = OpFunction %void None %7
-%24 = OpLabel
-%25 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
-%133 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0
-%134 = OpULessThan %bool %uint_0 %133
-OpSelectionMerge %135 None
-OpBranchConditional %134 %136 %137
-%136 = OpLabel
-%138 = OpLoad %uint %25
-OpBranch %135
-%137 = OpLabel
-%140 = OpFunctionCall %void %56 %uint_48 %uint_1 %uint_0 %uint_0
-OpBranch %135
-%135 = OpLabel
-%142 = OpPhi %uint %138 %136 %141 %137
-%27 = OpAccessChain %_ptr_UniformConstant_13 %images %142
-%28 = OpLoad %13 %27
-%48 = OpFunctionCall %uint %33 %uint_1 %uint_1
-%50 = OpULessThan %bool %142 %48
-OpSelectionMerge %51 None
-OpBranchConditional %50 %52 %53
-%52 = OpLabel
-%54 = OpLoad %13 %27
-%143 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_1 %142
-%144 = OpULessThan %bool %uint_0 %143
-OpSelectionMerge %145 None
-OpBranchConditional %144 %146 %147
-%146 = OpLabel
-%148 = OpLoad %13 %27
-%149 = OpImageRead %v4float %148 %20
-OpBranch %145
-%147 = OpLabel
-%150 = OpFunctionCall %void %56 %uint_51 %uint_1 %142 %uint_0
-OpBranch %145
-%145 = OpLabel
-%151 = OpPhi %v4float %149 %146 %113 %147
-OpBranch %51
-%53 = OpLabel
-%112 = OpFunctionCall %void %56 %uint_51 %uint_0 %142 %48
-OpBranch %51
-%51 = OpLabel
-%114 = OpPhi %v4float %151 %145 %113 %53
-%30 = OpCompositeExtract %float %114 0
-%31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
-%152 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0
-%153 = OpULessThan %bool %uint_0 %152
-OpSelectionMerge %154 None
-OpBranchConditional %153 %155 %156
-%155 = OpLabel
-OpStore %31 %30
-OpBranch %154
-%156 = OpLabel
-%158 = OpFunctionCall %void %56 %uint_54 %uint_1 %uint_0 %uint_0
-OpBranch %154
-%154 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string new_funcs =
-      R"(%33 = OpFunction %uint None %34
-%35 = OpFunctionParameter %uint
-%36 = OpFunctionParameter %uint
-%37 = OpLabel
-%43 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %35
-%44 = OpLoad %uint %43
-%45 = OpIAdd %uint %44 %36
-%46 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %45
-%47 = OpLoad %uint %46
-OpReturnValue %47
-OpFunctionEnd
-%56 = OpFunction %void None %57
-%58 = OpFunctionParameter %uint
-%59 = OpFunctionParameter %uint
-%60 = OpFunctionParameter %uint
-%61 = OpFunctionParameter %uint
-%62 = OpLabel
-%66 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_0
-%69 = OpAtomicIAdd %uint %66 %uint_4 %uint_0 %uint_10
-%70 = OpIAdd %uint %69 %uint_10
-%71 = OpArrayLength %uint %65 1
-%72 = OpULessThanEqual %bool %70 %71
-OpSelectionMerge %73 None
-OpBranchConditional %72 %74 %73
-%74 = OpLabel
-%75 = OpIAdd %uint %69 %uint_0
-%76 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %75
-OpStore %76 %uint_10
-%78 = OpIAdd %uint %69 %uint_1
-%79 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %78
-OpStore %79 %uint_23
-%81 = OpIAdd %uint %69 %uint_2
-%82 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %81
-OpStore %82 %58
-%85 = OpIAdd %uint %69 %uint_3
-%86 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %85
-OpStore %86 %uint_5318
-%90 = OpLoad %v3uint %89
-%91 = OpCompositeExtract %uint %90 0
-%92 = OpCompositeExtract %uint %90 1
-%93 = OpCompositeExtract %uint %90 2
-%94 = OpIAdd %uint %69 %uint_4
-%95 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %94
-OpStore %95 %91
-%97 = OpIAdd %uint %69 %uint_5
-%98 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %97
-OpStore %98 %92
-%100 = OpIAdd %uint %69 %uint_6
-%101 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %100
-OpStore %101 %93
-%103 = OpIAdd %uint %69 %uint_7
-%104 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %103
-OpStore %104 %59
-%106 = OpIAdd %uint %69 %uint_8
-%107 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %106
-OpStore %107 %60
-%109 = OpIAdd %uint %69 %uint_9
-%110 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %109
-OpStore %110 %61
-OpBranch %73
-%73 = OpLabel
-OpReturn
-OpFunctionEnd
-%115 = OpFunction %uint None %116
-%117 = OpFunctionParameter %uint
-%118 = OpFunctionParameter %uint
-%119 = OpFunctionParameter %uint
-%120 = OpFunctionParameter %uint
-%121 = OpLabel
-%122 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %117
-%123 = OpLoad %uint %122
-%124 = OpIAdd %uint %123 %118
-%125 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %124
-%126 = OpLoad %uint %125
-%127 = OpIAdd %uint %126 %119
-%128 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %127
-%129 = OpLoad %uint %128
-%130 = OpIAdd %uint %129 %120
-%131 = OpAccessChain %_ptr_StorageBuffer_uint %41 %uint_0 %130
-%132 = OpLoad %uint %131
-OpReturnValue %132
-OpFunctionEnd
-)";
+  const std::string new_funcs = kDirectRead2 + kStreamWrite4Ray + kDirectRead4;
 
   // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBindlessCheckPass>(
-      defs_before + func_before, defs_after + func_after + new_funcs, true,
-      true, 7u, 23u, true, true, false, false, false);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(defs + main_func + new_funcs,
+                                               true, 7u, 23u, true, true, false,
+                                               false, false);
 }
 
 TEST_F(InstBindlessTest, InstBoundsInitSameBlockOpReplication) {
@@ -6806,16 +4039,17 @@
   //   outColor = vec4(x, y, 0.0, 0.0);
   // }
   //
-  // clang-format on
 
-  const std::string defs_before =
-      R"(OpCapability Shader
+  const std::string defs = R"(
+OpCapability Shader
 OpCapability ShaderNonUniformEXT
 OpCapability SampledImageArrayNonUniformIndexingEXT
 OpExtension "SPV_EXT_descriptor_indexing"
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %main "main" %inTexcoord %outColor
+; CHECK: OpEntryPoint Fragment %main "main" %inTexcoord %outColor %gl_FragCoord
 OpExecutionMode %main OriginUpperLeft
 OpSource GLSL 450
 OpSourceExtension "GL_EXT_nonuniform_qualifier"
@@ -6845,6 +4079,12 @@
 OpDecorate %uniforms DescriptorSet 0
 OpDecorate %uniforms Binding 0
 OpDecorate %outColor Location 0
+; CHECK: OpDecorate %63 NonUniform
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+)" + kOutputDecorations + R"(
+; CHECK: OpDecorate %gl_FragCoord BuiltIn FragCoord
+)" + kInputDecorations + R"(
+; CHECK: OpDecorate %151 NonUniform
 %void = OpTypeVoid
 %3 = OpTypeFunction %void
 %int = OpTypeInt 32 1
@@ -6876,122 +4116,35 @@
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %outColor = OpVariable %_ptr_Output_v4float Output
 %float_0 = OpConstant %float 0
+; CHECK: %bool = OpTypeBool
+; CHECK: %68 = OpTypeFunction %void %uint %uint %uint %uint
+; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+)" + kOutputGlobals + R"(
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %uint_10 = OpConstant %uint 10
+; CHECK: %uint_4 = OpConstant %uint 4
+; CHECK: %uint_1 = OpConstant %uint 1
+; CHECK: %uint_23 = OpConstant %uint 23
+; CHECK: %uint_2 = OpConstant %uint 2
+; CHECK: %uint_3 = OpConstant %uint 3
+; CHECK: %_ptr_Input_v4float = OpTypePointer Input %v4float
+; CHECK: %gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+; CHECK: %v4uint = OpTypeVector %uint 4
+; CHECK: %uint_5 = OpConstant %uint 5
+; CHECK: %uint_7 = OpConstant %uint 7
+; CHECK: %uint_9 = OpConstant %uint 9
+; CHECK: %uint_79 = OpConstant %uint 79
+; CHECK: %122 = OpConstantNull %v4float
+; CHECK: %126 = OpTypeFunction %uint %uint %uint %uint %uint
+)" + kInputGlobals + R"(
+; CHECK: %uint_87 = OpConstant %uint 87
+; CHECK: %165 = OpConstantNull %v2float
+; CHECK: %uint_89 = OpConstant %uint 89
 )";
+  // clang-format on
 
-  const std::string defs_after =
-      R"(OpCapability Shader
-OpCapability ShaderNonUniform
-OpCapability SampledImageArrayNonUniformIndexing
-OpExtension "SPV_EXT_descriptor_indexing"
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main" %inTexcoord %outColor %gl_FragCoord
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 450
-OpSourceExtension "GL_EXT_nonuniform_qualifier"
-OpName %main "main"
-OpName %index "index"
-OpName %x "x"
-OpName %uniformTexArr "uniformTexArr"
-OpName %uniformSampler "uniformSampler"
-OpName %inTexcoord "inTexcoord"
-OpName %y "y"
-OpName %uniformTex "uniformTex"
-OpName %Uniforms "Uniforms"
-OpMemberName %Uniforms 0 "var0"
-OpName %uniforms "uniforms"
-OpName %outColor "outColor"
-OpDecorate %uniformTexArr DescriptorSet 0
-OpDecorate %uniformTexArr Binding 3
-OpDecorate %19 NonUniform
-OpDecorate %22 NonUniform
-OpDecorate %uniformSampler DescriptorSet 0
-OpDecorate %uniformSampler Binding 1
-OpDecorate %inTexcoord Location 0
-OpDecorate %uniformTex DescriptorSet 0
-OpDecorate %uniformTex Binding 2
-OpMemberDecorate %Uniforms 0 Offset 0
-OpDecorate %Uniforms Block
-OpDecorate %uniforms DescriptorSet 0
-OpDecorate %uniforms Binding 0
-OpDecorate %outColor Location 0
-OpDecorate %63 NonUniform
-OpDecorate %_runtimearr_uint ArrayStride 4
-OpDecorate %_struct_75 Block
-OpMemberDecorate %_struct_75 0 Offset 0
-OpMemberDecorate %_struct_75 1 Offset 4
-OpDecorate %77 DescriptorSet 7
-OpDecorate %77 Binding 0
-OpDecorate %gl_FragCoord BuiltIn FragCoord
-OpDecorate %_struct_132 Block
-OpMemberDecorate %_struct_132 0 Offset 0
-OpDecorate %134 DescriptorSet 7
-OpDecorate %134 Binding 1
-OpDecorate %151 NonUniform
-%void = OpTypeVoid
-%3 = OpTypeFunction %void
-%int = OpTypeInt 32 1
-%_ptr_Function_int = OpTypePointer Function %int
-%int_0 = OpConstant %int 0
-%float = OpTypeFloat 32
-%_ptr_Function_float = OpTypePointer Function %float
-%13 = OpTypeImage %float 2D 0 0 0 1 Unknown
-%uint = OpTypeInt 32 0
-%uint_8 = OpConstant %uint 8
-%_arr_13_uint_8 = OpTypeArray %13 %uint_8
-%_ptr_UniformConstant__arr_13_uint_8 = OpTypePointer UniformConstant %_arr_13_uint_8
-%uniformTexArr = OpVariable %_ptr_UniformConstant__arr_13_uint_8 UniformConstant
-%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
-%23 = OpTypeSampler
-%_ptr_UniformConstant_23 = OpTypePointer UniformConstant %23
-%uniformSampler = OpVariable %_ptr_UniformConstant_23 UniformConstant
-%27 = OpTypeSampledImage %13
-%v2float = OpTypeVector %float 2
-%_ptr_Input_v2float = OpTypePointer Input %v2float
-%inTexcoord = OpVariable %_ptr_Input_v2float Input
-%v4float = OpTypeVector %float 4
-%uint_0 = OpConstant %uint 0
-%uniformTex = OpVariable %_ptr_UniformConstant_13 UniformConstant
-%Uniforms = OpTypeStruct %v2float
-%_ptr_Uniform_Uniforms = OpTypePointer Uniform %Uniforms
-%uniforms = OpVariable %_ptr_Uniform_Uniforms Uniform
-%_ptr_Uniform_v2float = OpTypePointer Uniform %v2float
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%outColor = OpVariable %_ptr_Output_v4float Output
-%float_0 = OpConstant %float 0
-%bool = OpTypeBool
-%68 = OpTypeFunction %void %uint %uint %uint %uint
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_75 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_75 = OpTypePointer StorageBuffer %_struct_75
-%77 = OpVariable %_ptr_StorageBuffer__struct_75 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_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_7 = OpConstant %uint 7
-%uint_9 = OpConstant %uint 9
-%uint_79 = OpConstant %uint 79
-%122 = OpConstantNull %v4float
-%126 = OpTypeFunction %uint %uint %uint %uint %uint
-%_struct_132 = OpTypeStruct %_runtimearr_uint
-%_ptr_StorageBuffer__struct_132 = OpTypePointer StorageBuffer %_struct_132
-%134 = OpVariable %_ptr_StorageBuffer__struct_132 StorageBuffer
-%uint_87 = OpConstant %uint 87
-%165 = OpConstantNull %v2float
-%uint_89 = OpConstant %uint 89
-)";
-
-  const std::string func_before =
-      R"(%main = OpFunction %void None %3
+  const std::string main_func = R"(
+%main = OpFunction %void None %3
 %5 = OpLabel
 %index = OpVariable %_ptr_Function_int Function
 %x = OpVariable %_ptr_Function_float Function
@@ -7015,6 +4168,36 @@
 %49 = OpFMul %v2float %42 %48
 %50 = OpImageSampleImplicitLod %v4float %41 %49
 %51 = OpCompositeExtract %float %50 0
+; CHECK-NOT: %51 = OpCompositeExtract %float %50 0
+; CHECK: %157 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_0 %uint_0
+; CHECK: %158 = OpULessThan %bool %uint_0 %157
+; CHECK: OpSelectionMerge %159 None
+; CHECK: OpBranchConditional %158 %160 %161
+; CHECK: %160 = OpLabel
+; CHECK: %162 = OpLoad %v2float %47
+; CHECK: OpBranch %159
+; CHECK: %161 = OpLabel
+; CHECK: %164 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_87 %uint_1 %uint_0 %uint_0
+; CHECK: OpBranch %159
+; CHECK: %159 = OpLabel
+; CHECK: %166 = OpPhi %v2float %162 %160 %165 %161
+; CHECK: %49 = OpFMul %v2float %42 %166
+; CHECK: %167 = OpSampledImage %27 %39 %40
+; CHECK: %168 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_2 %uint_0
+; CHECK: %169 = OpULessThan %bool %uint_0 %168
+; CHECK: OpSelectionMerge %170 None
+; CHECK: OpBranchConditional %169 %171 %172
+; CHECK: %171 = OpLabel
+; CHECK: %173 = OpLoad %13 %uniformTex
+; CHECK: %174 = OpSampledImage %27 %173 %40
+; CHECK: %175 = OpImageSampleImplicitLod %v4float %174 %49
+; CHECK: OpBranch %170
+; CHECK: %172 = OpLabel
+; CHECK: %177 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_89 %uint_1 %uint_0 %uint_0
+; CHECK: OpBranch %170
+; CHECK: %170 = OpLabel
+; CHECK: %178 = OpPhi %v4float %175 %171 %122 %172
+; CHECK: %51 = OpCompositeExtract %float %178 0
 OpStore %y %51
 %54 = OpLoad %float %x
 %55 = OpLoad %float %y
@@ -7024,168 +4207,12 @@
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%main = OpFunction %void None %3
-%5 = OpLabel
-%index = OpVariable %_ptr_Function_int Function
-%x = OpVariable %_ptr_Function_float Function
-%y = OpVariable %_ptr_Function_float Function
-OpStore %index %int_0
-%19 = OpLoad %int %index
-%21 = OpAccessChain %_ptr_UniformConstant_13 %uniformTexArr %19
-%22 = OpLoad %13 %21
-%26 = OpLoad %23 %uniformSampler
-%28 = OpSampledImage %27 %22 %26
-%32 = OpLoad %v2float %inTexcoord
-%59 = OpULessThan %bool %19 %uint_8
-OpSelectionMerge %60 None
-OpBranchConditional %59 %61 %62
-%61 = OpLabel
-%63 = OpLoad %13 %21
-%64 = OpSampledImage %27 %63 %26
-%124 = OpBitcast %uint %19
-%146 = OpFunctionCall %uint %125 %uint_0 %uint_0 %uint_3 %124
-%147 = OpULessThan %bool %uint_0 %146
-OpSelectionMerge %148 None
-OpBranchConditional %147 %149 %150
-%149 = OpLabel
-%151 = OpLoad %13 %21
-%152 = OpSampledImage %27 %151 %26
-%153 = OpImageSampleImplicitLod %v4float %152 %32
-OpBranch %148
-%150 = OpLabel
-%154 = OpBitcast %uint %19
-%155 = OpFunctionCall %void %67 %uint_79 %uint_1 %154 %uint_0
-OpBranch %148
-%148 = OpLabel
-%156 = OpPhi %v4float %153 %149 %122 %150
-OpBranch %60
-%62 = OpLabel
-%66 = OpBitcast %uint %19
-%121 = OpFunctionCall %void %67 %uint_79 %uint_0 %66 %uint_8
-OpBranch %60
-%60 = OpLabel
-%123 = OpPhi %v4float %156 %148 %122 %62
-%36 = OpCompositeExtract %float %123 0
-OpStore %x %36
-%39 = OpLoad %13 %uniformTex
-%40 = OpLoad %23 %uniformSampler
-%41 = OpSampledImage %27 %39 %40
-%42 = OpLoad %v2float %inTexcoord
-%47 = OpAccessChain %_ptr_Uniform_v2float %uniforms %int_0
-%157 = OpFunctionCall %uint %125 %uint_0 %uint_0 %uint_0 %uint_0
-%158 = OpULessThan %bool %uint_0 %157
-OpSelectionMerge %159 None
-OpBranchConditional %158 %160 %161
-%160 = OpLabel
-%162 = OpLoad %v2float %47
-OpBranch %159
-%161 = OpLabel
-%164 = OpFunctionCall %void %67 %uint_87 %uint_1 %uint_0 %uint_0
-OpBranch %159
-%159 = OpLabel
-%166 = OpPhi %v2float %162 %160 %165 %161
-%49 = OpFMul %v2float %42 %166
-%167 = OpSampledImage %27 %39 %40
-%168 = OpFunctionCall %uint %125 %uint_0 %uint_0 %uint_2 %uint_0
-%169 = OpULessThan %bool %uint_0 %168
-OpSelectionMerge %170 None
-OpBranchConditional %169 %171 %172
-%171 = OpLabel
-%173 = OpLoad %13 %uniformTex
-%174 = OpSampledImage %27 %173 %40
-%175 = OpImageSampleImplicitLod %v4float %174 %49
-OpBranch %170
-%172 = OpLabel
-%177 = OpFunctionCall %void %67 %uint_89 %uint_1 %uint_0 %uint_0
-OpBranch %170
-%170 = OpLabel
-%178 = OpPhi %v4float %175 %171 %122 %172
-%51 = OpCompositeExtract %float %178 0
-OpStore %y %51
-%54 = OpLoad %float %x
-%55 = OpLoad %float %y
-%57 = OpCompositeConstruct %v4float %54 %55 %float_0 %float_0
-OpStore %outColor %57
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string new_funcs =
-      R"(%67 = OpFunction %void None %68
-%69 = OpFunctionParameter %uint
-%70 = OpFunctionParameter %uint
-%71 = OpFunctionParameter %uint
-%72 = OpFunctionParameter %uint
-%73 = OpLabel
-%79 = OpAccessChain %_ptr_StorageBuffer_uint %77 %uint_0
-%82 = OpAtomicIAdd %uint %79 %uint_4 %uint_0 %uint_10
-%83 = OpIAdd %uint %82 %uint_10
-%84 = OpArrayLength %uint %77 1
-%85 = OpULessThanEqual %bool %83 %84
-OpSelectionMerge %86 None
-OpBranchConditional %85 %87 %86
-%87 = OpLabel
-%88 = OpIAdd %uint %82 %uint_0
-%90 = OpAccessChain %_ptr_StorageBuffer_uint %77 %uint_1 %88
-OpStore %90 %uint_10
-%92 = OpIAdd %uint %82 %uint_1
-%93 = OpAccessChain %_ptr_StorageBuffer_uint %77 %uint_1 %92
-OpStore %93 %uint_23
-%95 = OpIAdd %uint %82 %uint_2
-%96 = OpAccessChain %_ptr_StorageBuffer_uint %77 %uint_1 %95
-OpStore %96 %69
-%98 = OpIAdd %uint %82 %uint_3
-%99 = OpAccessChain %_ptr_StorageBuffer_uint %77 %uint_1 %98
-OpStore %99 %uint_4
-%102 = OpLoad %v4float %gl_FragCoord
-%104 = OpBitcast %v4uint %102
-%105 = OpCompositeExtract %uint %104 0
-%106 = OpIAdd %uint %82 %uint_4
-%107 = OpAccessChain %_ptr_StorageBuffer_uint %77 %uint_1 %106
-OpStore %107 %105
-%108 = OpCompositeExtract %uint %104 1
-%110 = OpIAdd %uint %82 %uint_5
-%111 = OpAccessChain %_ptr_StorageBuffer_uint %77 %uint_1 %110
-OpStore %111 %108
-%113 = OpIAdd %uint %82 %uint_7
-%114 = OpAccessChain %_ptr_StorageBuffer_uint %77 %uint_1 %113
-OpStore %114 %70
-%115 = OpIAdd %uint %82 %uint_8
-%116 = OpAccessChain %_ptr_StorageBuffer_uint %77 %uint_1 %115
-OpStore %116 %71
-%118 = OpIAdd %uint %82 %uint_9
-%119 = OpAccessChain %_ptr_StorageBuffer_uint %77 %uint_1 %118
-OpStore %119 %72
-OpBranch %86
-%86 = OpLabel
-OpReturn
-OpFunctionEnd
-%125 = OpFunction %uint None %126
-%127 = OpFunctionParameter %uint
-%128 = OpFunctionParameter %uint
-%129 = OpFunctionParameter %uint
-%130 = OpFunctionParameter %uint
-%131 = OpLabel
-%135 = OpAccessChain %_ptr_StorageBuffer_uint %134 %uint_0 %127
-%136 = OpLoad %uint %135
-%137 = OpIAdd %uint %136 %128
-%138 = OpAccessChain %_ptr_StorageBuffer_uint %134 %uint_0 %137
-%139 = OpLoad %uint %138
-%140 = OpIAdd %uint %139 %129
-%141 = OpAccessChain %_ptr_StorageBuffer_uint %134 %uint_0 %140
-%142 = OpLoad %uint %141
-%143 = OpIAdd %uint %142 %130
-%144 = OpAccessChain %_ptr_StorageBuffer_uint %134 %uint_0 %143
-%145 = OpLoad %uint %144
-OpReturnValue %145
-OpFunctionEnd
-)";
+  const std::string new_funcs = kStreamWrite4Frag + kDirectRead4;
 
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBindlessCheckPass>(
-      defs_before + func_before, defs_after + func_after + new_funcs, true,
-      true, 7u, 23u, true, true, false, false, false);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(defs + main_func + new_funcs,
+                                               true, 7u, 23u, true, true, false,
+                                               false, false);
 }
 
 TEST_F(InstBindlessTest, MultipleUniformNonAggregateRefsNoDescInit) {
@@ -7225,13 +4252,14 @@
   //   return ps_output;
   // }
 
+  // clang-format off
   const std::string text = R"(
                OpCapability Shader
 ;CHECK:        OpExtension "SPV_KHR_storage_buffer_storage_class"
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %MainPs "MainPs" %_ %__0 %g_tColor %g_sAniso %i_vTextureCoords %_entryPointOutput_vColor
-;CHECK:        OpEntryPoint Fragment %MainPs "MainPs" %_ %__0 %g_tColor %g_sAniso %i_vTextureCoords %_entryPointOutput_vColor %130 %157 %gl_FragCoord
+;CHECK:        OpEntryPoint Fragment %MainPs "MainPs" %_ %__0 %g_tColor %g_sAniso %i_vTextureCoords %_entryPointOutput_vColor %inst_bindless_input_buffer %inst_bindless_output_buffer %gl_FragCoord
                OpExecutionMode %MainPs OriginUpperLeft
                OpSource HLSL 500
                OpName %MainPs "MainPs"
@@ -7260,15 +4288,7 @@
                OpDecorate %i_vTextureCoords Location 0
                OpDecorate %_entryPointOutput_vColor Location 0
  ;CHECK:       OpDecorate %_runtimearr_uint ArrayStride 4
- ;CHECK:       OpDecorate %_struct_128 Block
- ;CHECK:       OpMemberDecorate %_struct_128 0 Offset 0
- ;CHECK:       OpDecorate %130 DescriptorSet 7
- ;CHECK:       OpDecorate %130 Binding 1
- ;CHECK:       OpDecorate %_struct_155 Block
- ;CHECK:       OpMemberDecorate %_struct_155 0 Offset 0
- ;CHECK:       OpMemberDecorate %_struct_155 1 Offset 4
- ;CHECK:       OpDecorate %157 DescriptorSet 7
- ;CHECK:       OpDecorate %157 Binding 0
+)" + kInputDecorations + kOutputDecorations + R"(
  ;CHECK:       OpDecorate %gl_FragCoord BuiltIn FragCoord
        %void = OpTypeVoid
           %3 = OpTypeFunction %void
@@ -7304,15 +4324,11 @@
  ;CHECK:      %uint_1 = OpConstant %uint 1
  ;CHECK:         %122 = OpTypeFunction %uint %uint %uint %uint
  ;CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
- ;CHECK: %_struct_128 = OpTypeStruct %_runtimearr_uint
- ;CHECK: %_ptr_StorageBuffer__struct_128 = OpTypePointer StorageBuffer %_struct_128
- ;CHECK:         %130 = OpVariable %_ptr_StorageBuffer__struct_128 StorageBuffer
+ )" + kInputGlobals + R"(
  ;CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
  ;CHECK:     %uint_4 = OpConstant %uint 4
  ;CHECK:         %148 = OpTypeFunction %void %uint %uint %uint %uint %uint
- ;CHECK: %_struct_155 = OpTypeStruct %uint %_runtimearr_uint
- ;CHECK: %_ptr_StorageBuffer__struct_155 = OpTypePointer StorageBuffer %_struct_155
- ;CHECK:        %157 = OpVariable %_ptr_StorageBuffer__struct_155 StorageBuffer
+ )" + kOutputGlobals + R"(
  ;CHECK:    %uint_11 = OpConstant %uint 11
  ;CHECK:    %uint_23 = OpConstant %uint 23
  ;CHECK:     %uint_2 = OpConstant %uint 2
@@ -7329,7 +4345,7 @@
  ;CHECK:    %uint_75 = OpConstant %uint 75
      %MainPs = OpFunction %void None %3
           %5 = OpLabel
- ;CHECK: %140 = OpFunctionCall %uint %121 %uint_1 %uint_1 %uint_0
+ ;CHECK: %140 = OpFunctionCall %uint %inst_bindless_direct_read_3 %uint_1 %uint_1 %uint_0
  ;CHECK:        OpBranch %117
  ;CHECK: %117 = OpLabel
  ;CHECK:        OpBranch %116
@@ -7352,7 +4368,7 @@
  ;CHECK:        %146 = OpLoad %v2float %86
  ;CHECK:               OpBranch %143
  ;CHECK:        %145 = OpLabel
- ;CHECK:        %201 = OpFunctionCall %void %147 %uint_71 %uint_4 %uint_0 %119 %140
+ ;CHECK:        %201 = OpFunctionCall %void %inst_bindless_stream_write_5 %uint_71 %uint_4 %uint_0 %119 %140
  ;CHECK:               OpBranch %143
  ;CHECK:        %143 = OpLabel
  ;CHECK:        %203 = OpPhi %v2float %146 %144 %202 %145
@@ -7369,7 +4385,7 @@
  ;CHECK:        %209 = OpLoad %v2float %89
  ;CHECK:               OpBranch %206
  ;CHECK:        %208 = OpLabel
- ;CHECK:        %211 = OpFunctionCall %void %147 %uint_75 %uint_4 %uint_0 %204 %140
+ ;CHECK:        %211 = OpFunctionCall %void %inst_bindless_stream_write_5 %uint_75 %uint_4 %uint_0 %204 %140
  ;CHECK:               OpBranch %206
  ;CHECK:        %206 = OpLabel
  ;CHECK:        %212 = OpPhi %v2float %209 %207 %202 %208
@@ -7386,75 +4402,8 @@
                OpStore %_entryPointOutput_vColor %100
                OpReturn
                OpFunctionEnd
- ;CHECK:        %121 = OpFunction %uint None %122
- ;CHECK:        %123 = OpFunctionParameter %uint
- ;CHECK:        %124 = OpFunctionParameter %uint
- ;CHECK:        %125 = OpFunctionParameter %uint
- ;CHECK:        %126 = OpLabel
- ;CHECK:        %132 = OpAccessChain %_ptr_StorageBuffer_uint %130 %uint_0 %123
- ;CHECK:        %133 = OpLoad %uint %132
- ;CHECK:        %134 = OpIAdd %uint %133 %124
- ;CHECK:        %135 = OpAccessChain %_ptr_StorageBuffer_uint %130 %uint_0 %134
- ;CHECK:        %136 = OpLoad %uint %135
- ;CHECK:        %137 = OpIAdd %uint %136 %125
- ;CHECK:        %138 = OpAccessChain %_ptr_StorageBuffer_uint %130 %uint_0 %137
- ;CHECK:        %139 = OpLoad %uint %138
- ;CHECK:               OpReturnValue %139
- ;CHECK:               OpFunctionEnd
- ;CHECK:        %147 = OpFunction %void None %148
- ;CHECK:        %149 = OpFunctionParameter %uint
- ;CHECK:        %150 = OpFunctionParameter %uint
- ;CHECK:        %151 = OpFunctionParameter %uint
- ;CHECK:        %152 = OpFunctionParameter %uint
- ;CHECK:        %153 = OpFunctionParameter %uint
- ;CHECK:        %154 = OpLabel
- ;CHECK:        %158 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_0
- ;CHECK:        %160 = OpAtomicIAdd %uint %158 %uint_4 %uint_0 %uint_11
- ;CHECK:        %161 = OpIAdd %uint %160 %uint_11
- ;CHECK:        %162 = OpArrayLength %uint %157 1
- ;CHECK:        %163 = OpULessThanEqual %bool %161 %162
- ;CHECK:               OpSelectionMerge %164 None
- ;CHECK:               OpBranchConditional %163 %165 %164
- ;CHECK:        %165 = OpLabel
- ;CHECK:        %166 = OpIAdd %uint %160 %uint_0
- ;CHECK:        %167 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %166
- ;CHECK:               OpStore %167 %uint_11
- ;CHECK:        %169 = OpIAdd %uint %160 %uint_1
- ;CHECK:        %170 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %169
- ;CHECK:               OpStore %170 %uint_23
- ;CHECK:        %172 = OpIAdd %uint %160 %uint_2
- ;CHECK:        %173 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %172
- ;CHECK:               OpStore %173 %149
- ;CHECK:        %175 = OpIAdd %uint %160 %uint_3
- ;CHECK:        %176 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %175
- ;CHECK:               OpStore %176 %uint_4
- ;CHECK:        %179 = OpLoad %v4float %gl_FragCoord
- ;CHECK:        %181 = OpBitcast %v4uint %179
- ;CHECK:        %182 = OpCompositeExtract %uint %181 0
- ;CHECK:        %183 = OpIAdd %uint %160 %uint_4
- ;CHECK:        %184 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %183
- ;CHECK:               OpStore %184 %182
- ;CHECK:        %185 = OpCompositeExtract %uint %181 1
- ;CHECK:        %187 = OpIAdd %uint %160 %uint_5
- ;CHECK:        %188 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %187
- ;CHECK:               OpStore %188 %185
- ;CHECK:        %189 = OpIAdd %uint %160 %uint_7
- ;CHECK:        %190 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %189
- ;CHECK:               OpStore %190 %150
- ;CHECK:        %192 = OpIAdd %uint %160 %uint_8
- ;CHECK:        %193 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %192
- ;CHECK:               OpStore %193 %151
- ;CHECK:        %195 = OpIAdd %uint %160 %uint_9
- ;CHECK:        %196 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %195
- ;CHECK:               OpStore %196 %152
- ;CHECK:        %198 = OpIAdd %uint %160 %uint_10
- ;CHECK:        %199 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %198
- ;CHECK:               OpStore %199 %153
- ;CHECK:               OpBranch %164
- ;CHECK:        %164 = OpLabel
- ;CHECK:               OpReturn
- ;CHECK:               OpFunctionEnd
- )";
+)" + kDirectRead3 + kStreamWrite5Frag;
+  // clang-format on
 
   SetTargetEnv(SPV_ENV_VULKAN_1_2);
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
@@ -7498,6 +4447,7 @@
   //   return ps_output;
   // }
 
+  // clang-format off
   const std::string text = R"(
                OpCapability Shader
 ;CHECK:               OpExtension "SPV_KHR_storage_buffer_storage_class"
@@ -7540,15 +4490,7 @@
                OpDecorate %i_vTextureCoords Location 0
                OpDecorate %_entryPointOutput_vColor Location 0
 ;CHECK:               OpDecorate %_runtimearr_uint ArrayStride 4
-;CHECK:               OpDecorate %_struct_111 Block
-;CHECK:               OpMemberDecorate %_struct_111 0 Offset 0
-;CHECK:               OpDecorate %113 DescriptorSet 7
-;CHECK:               OpDecorate %113 Binding 1
-;CHECK:               OpDecorate %_struct_139 Block
-;CHECK:               OpMemberDecorate %_struct_139 0 Offset 0
-;CHECK:               OpMemberDecorate %_struct_139 1 Offset 4
-;CHECK:               OpDecorate %141 DescriptorSet 7
-;CHECK:               OpDecorate %141 Binding 0
+)" + kInputDecorations + kOutputDecorations + R"(
 ;CHECK:               OpDecorate %gl_FragCoord BuiltIn FragCoord
        %void = OpTypeVoid
           %3 = OpTypeFunction %void
@@ -7591,16 +4533,12 @@
 ;CHECK:     %uint_1 = OpConstant %uint 1
 ;CHECK:        %105 = OpTypeFunction %uint %uint %uint %uint
 ;CHECK:%_runtimearr_uint = OpTypeRuntimeArray %uint
-;CHECK:%_struct_111 = OpTypeStruct %_runtimearr_uint
-;CHECK:%_ptr_StorageBuffer__struct_111 = OpTypePointer StorageBuffer %_struct_111
-;CHECK:        %113 = OpVariable %_ptr_StorageBuffer__struct_111 StorageBuffer
+)" + kInputGlobals + R"(
 ;CHECK:%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
 ;CHECK:       %bool = OpTypeBool
 ;CHECK:     %uint_4 = OpConstant %uint 4
 ;CHECK:        %132 = OpTypeFunction %void %uint %uint %uint %uint %uint
-;CHECK:%_struct_139 = OpTypeStruct %uint %_runtimearr_uint
-;CHECK:%_ptr_StorageBuffer__struct_139 = OpTypePointer StorageBuffer %_struct_139
-;CHECK:        %141 = OpVariable %_ptr_StorageBuffer__struct_139 StorageBuffer
+)" + kOutputGlobals + R"(
 ;CHECK:    %uint_11 = OpConstant %uint 11
 ;CHECK:    %uint_23 = OpConstant %uint 23
 ;CHECK:     %uint_3 = OpConstant %uint 3
@@ -7615,7 +4553,7 @@
 ;CHECK:        %185 = OpConstantNull %v2float
      %MainPs = OpFunction %void None %3
           %5 = OpLabel
-;CHECK:        %123 = OpFunctionCall %uint %104 %uint_1 %uint_2 %uint_0
+;CHECK:        %123 = OpFunctionCall %uint %inst_bindless_direct_read_3 %uint_1 %uint_2 %uint_0
 ;CHECK:               OpBranch %93
 ;CHECK:         %93 = OpLabel
 ;CHECK:               OpBranch %92
@@ -7637,7 +4575,7 @@
 ;CHECK:        %130 = OpLoad %v2float %81
 ;CHECK:               OpBranch %127
 ;CHECK:        %129 = OpLabel
-;CHECK:        %184 = OpFunctionCall %void %131 %uint_78 %uint_4 %uint_0 %101 %123
+;CHECK:        %184 = OpFunctionCall %void %inst_bindless_stream_write_5 %uint_78 %uint_4 %uint_0 %101 %123
 ;CHECK:               OpBranch %127
 ;CHECK:        %127 = OpLabel
 ;CHECK:        %186 = OpPhi %v2float %130 %128 %185 %129
@@ -7651,75 +4589,8 @@
                OpStore %_entryPointOutput_vColor %91
                OpReturn
                OpFunctionEnd
-;CHECK:        %104 = OpFunction %uint None %105
-;CHECK:        %106 = OpFunctionParameter %uint
-;CHECK:        %107 = OpFunctionParameter %uint
-;CHECK:        %108 = OpFunctionParameter %uint
-;CHECK:        %109 = OpLabel
-;CHECK:        %115 = OpAccessChain %_ptr_StorageBuffer_uint %113 %uint_0 %106
-;CHECK:        %116 = OpLoad %uint %115
-;CHECK:        %117 = OpIAdd %uint %116 %107
-;CHECK:        %118 = OpAccessChain %_ptr_StorageBuffer_uint %113 %uint_0 %117
-;CHECK:        %119 = OpLoad %uint %118
-;CHECK:        %120 = OpIAdd %uint %119 %108
-;CHECK:        %121 = OpAccessChain %_ptr_StorageBuffer_uint %113 %uint_0 %120
-;CHECK:        %122 = OpLoad %uint %121
-;CHECK:               OpReturnValue %122
-;CHECK:               OpFunctionEnd
-;CHECK:        %131 = OpFunction %void None %132
-;CHECK:        %133 = OpFunctionParameter %uint
-;CHECK:        %134 = OpFunctionParameter %uint
-;CHECK:        %135 = OpFunctionParameter %uint
-;CHECK:        %136 = OpFunctionParameter %uint
-;CHECK:        %137 = OpFunctionParameter %uint
-;CHECK:        %138 = OpLabel
-;CHECK:        %142 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_0
-;CHECK:        %144 = OpAtomicIAdd %uint %142 %uint_4 %uint_0 %uint_11
-;CHECK:        %145 = OpIAdd %uint %144 %uint_11
-;CHECK:        %146 = OpArrayLength %uint %141 1
-;CHECK:        %147 = OpULessThanEqual %bool %145 %146
-;CHECK:               OpSelectionMerge %148 None
-;CHECK:               OpBranchConditional %147 %149 %148
-;CHECK:        %149 = OpLabel
-;CHECK:        %150 = OpIAdd %uint %144 %uint_0
-;CHECK:        %151 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %150
-;CHECK:               OpStore %151 %uint_11
-;CHECK:        %153 = OpIAdd %uint %144 %uint_1
-;CHECK:        %154 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %153
-;CHECK:               OpStore %154 %uint_23
-;CHECK:        %155 = OpIAdd %uint %144 %uint_2
-;CHECK:        %156 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %155
-;CHECK:               OpStore %156 %133
-;CHECK:        %158 = OpIAdd %uint %144 %uint_3
-;CHECK:        %159 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %158
-;CHECK:               OpStore %159 %uint_4
-;CHECK:        %162 = OpLoad %v4float %gl_FragCoord
-;CHECK:        %164 = OpBitcast %v4uint %162
-;CHECK:        %165 = OpCompositeExtract %uint %164 0
-;CHECK:        %166 = OpIAdd %uint %144 %uint_4
-;CHECK:        %167 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %166
-;CHECK:               OpStore %167 %165
-;CHECK:        %168 = OpCompositeExtract %uint %164 1
-;CHECK:        %170 = OpIAdd %uint %144 %uint_5
-;CHECK:        %171 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %170
-;CHECK:               OpStore %171 %168
-;CHECK:        %172 = OpIAdd %uint %144 %uint_7
-;CHECK:        %173 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %172
-;CHECK:               OpStore %173 %134
-;CHECK:        %175 = OpIAdd %uint %144 %uint_8
-;CHECK:        %176 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %175
-;CHECK:               OpStore %176 %135
-;CHECK:        %178 = OpIAdd %uint %144 %uint_9
-;CHECK:        %179 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %178
-;CHECK:               OpStore %179 %136
-;CHECK:        %181 = OpIAdd %uint %144 %uint_10
-;CHECK:        %182 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %181
-;CHECK:               OpStore %182 %137
-;CHECK:               OpBranch %148
-;CHECK:        %148 = OpLabel
-;CHECK:               OpReturn
-;CHECK:               OpFunctionEnd
- )";
+)" + kDirectRead3 + kStreamWrite5Frag;
+  // clang-format on
 
   SetTargetEnv(SPV_ENV_VULKAN_1_2);
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
@@ -7733,13 +4604,14 @@
   //
   // Same source as UniformArrayRefNoDescInit
 
+  // clang-format off
   const std::string text = R"(
                OpCapability Shader
 ;CHECK:               OpExtension "SPV_KHR_storage_buffer_storage_class"
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %MainPs "MainPs" %_ %__0 %g_tColor %g_sAniso %i_vTextureCoords %_entryPointOutput_vColor
-;CHECK:               OpEntryPoint Fragment %MainPs "MainPs" %_ %__0 %g_tColor %g_sAniso %i_vTextureCoords %_entryPointOutput_vColor %113 %144 %gl_FragCoord
+;CHECK:        OpEntryPoint Fragment %MainPs "MainPs" %_ %__0 %g_tColor %g_sAniso %i_vTextureCoords %_entryPointOutput_vColor %inst_bindless_input_buffer %inst_bindless_output_buffer %gl_FragCoord
                OpExecutionMode %MainPs OriginUpperLeft
                OpSource HLSL 500
                OpName %MainPs "MainPs"
@@ -7776,15 +4648,7 @@
                OpDecorate %i_vTextureCoords Location 0
                OpDecorate %_entryPointOutput_vColor Location 0
 ;CHECK:               OpDecorate %_runtimearr_uint ArrayStride 4
-;CHECK:               OpDecorate %_struct_111 Block
-;CHECK:               OpMemberDecorate %_struct_111 0 Offset 0
-;CHECK:               OpDecorate %113 DescriptorSet 7
-;CHECK:               OpDecorate %113 Binding 1
-;CHECK:               OpDecorate %_struct_142 Block
-;CHECK:               OpMemberDecorate %_struct_142 0 Offset 0
-;CHECK:               OpMemberDecorate %_struct_142 1 Offset 4
-;CHECK:               OpDecorate %144 DescriptorSet 7
-;CHECK:               OpDecorate %144 Binding 0
+)" + kInputDecorations + kOutputDecorations + R"(
 ;CHECK:               OpDecorate %gl_FragCoord BuiltIn FragCoord
        %void = OpTypeVoid
           %3 = OpTypeFunction %void
@@ -7826,16 +4690,12 @@
 ;CHECK:     %uint_2 = OpConstant %uint 2
 ;CHECK:        %104 = OpTypeFunction %uint %uint %uint %uint %uint
 ;CHECK:%_runtimearr_uint = OpTypeRuntimeArray %uint
-;CHECK:%_struct_111 = OpTypeStruct %_runtimearr_uint
-;CHECK:%_ptr_StorageBuffer__struct_111 = OpTypePointer StorageBuffer %_struct_111
-;CHECK:        %113 = OpVariable %_ptr_StorageBuffer__struct_111 StorageBuffer
+)" + kInputGlobals + R"(
 ;CHECK:%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
 ;CHECK:       %bool = OpTypeBool
 ;CHECK:     %uint_4 = OpConstant %uint 4
 ;CHECK:        %135 = OpTypeFunction %void %uint %uint %uint %uint %uint
-;CHECK:%_struct_142 = OpTypeStruct %uint %_runtimearr_uint
-;CHECK:%_ptr_StorageBuffer__struct_142 = OpTypePointer StorageBuffer %_struct_142
-;CHECK:        %144 = OpVariable %_ptr_StorageBuffer__struct_142 StorageBuffer
+)" + kOutputGlobals + R"(
 ;CHECK:    %uint_11 = OpConstant %uint 11
 ;CHECK:     %uint_1 = OpConstant %uint 1
 ;CHECK:    %uint_23 = OpConstant %uint 23
@@ -7853,8 +4713,8 @@
 ;CHECK:        %201 = OpConstantNull %v4float
      %MainPs = OpFunction %void None %3
           %5 = OpLabel
-;CHECK:        %126 = OpFunctionCall %uint %103 %uint_0 %uint_0 %uint_2 %uint_0
-;CHECK:        %191 = OpFunctionCall %uint %103 %uint_0 %uint_0 %uint_0 %uint_0
+;CHECK:        %126 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_2 %uint_0
+;CHECK:        %191 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_0 %uint_0
 ;CHECK:               OpBranch %93
 ;CHECK:         %93 = OpLabel
 ;CHECK:               OpBranch %92
@@ -7878,7 +4738,7 @@
 ;CHECK:        %133 = OpLoad %v2float %81
 ;CHECK:               OpBranch %130
 ;CHECK:        %132 = OpLabel
-;CHECK:        %188 = OpFunctionCall %void %134 %uint_78 %uint_4 %uint_0 %101 %126
+;CHECK:        %188 = OpFunctionCall %void %inst_bindless_stream_write_5 %uint_78 %uint_4 %uint_0 %101 %126
 ;CHECK:               OpBranch %130
 ;CHECK:        %130 = OpLabel
 ;CHECK:        %190 = OpPhi %v2float %133 %131 %189 %132
@@ -7899,86 +4759,15 @@
 ;CHECK:        %198 = OpImageSampleImplicitLod %v4float %197 %86
 ;CHECK:               OpBranch %193
 ;CHECK:        %195 = OpLabel
-;CHECK:        %200 = OpFunctionCall %void %134 %uint_83 %uint_1 %uint_0 %uint_0 %uint_0
+;CHECK:        %200 = OpFunctionCall %void %inst_bindless_stream_write_5 %uint_83 %uint_1 %uint_0 %uint_0 %uint_0
 ;CHECK:               OpBranch %193
 ;CHECK:        %193 = OpLabel
 ;CHECK:        %202 = OpPhi %v4float %198 %194 %201 %195
 ;CHECK:               OpStore %_entryPointOutput_vColor %202
                OpReturn
                OpFunctionEnd
-;CHECK:        %103 = OpFunction %uint None %104
-;CHECK:        %105 = OpFunctionParameter %uint
-;CHECK:        %106 = OpFunctionParameter %uint
-;CHECK:        %107 = OpFunctionParameter %uint
-;CHECK:        %108 = OpFunctionParameter %uint
-;CHECK:        %109 = OpLabel
-;CHECK:        %115 = OpAccessChain %_ptr_StorageBuffer_uint %113 %uint_0 %105
-;CHECK:        %116 = OpLoad %uint %115
-;CHECK:        %117 = OpIAdd %uint %116 %106
-;CHECK:        %118 = OpAccessChain %_ptr_StorageBuffer_uint %113 %uint_0 %117
-;CHECK:        %119 = OpLoad %uint %118
-;CHECK:        %120 = OpIAdd %uint %119 %107
-;CHECK:        %121 = OpAccessChain %_ptr_StorageBuffer_uint %113 %uint_0 %120
-;CHECK:        %122 = OpLoad %uint %121
-;CHECK:        %123 = OpIAdd %uint %122 %108
-;CHECK:        %124 = OpAccessChain %_ptr_StorageBuffer_uint %113 %uint_0 %123
-;CHECK:        %125 = OpLoad %uint %124
-;CHECK:               OpReturnValue %125
-;CHECK:               OpFunctionEnd
-;CHECK:        %134 = OpFunction %void None %135
-;CHECK:        %136 = OpFunctionParameter %uint
-;CHECK:        %137 = OpFunctionParameter %uint
-;CHECK:        %138 = OpFunctionParameter %uint
-;CHECK:        %139 = OpFunctionParameter %uint
-;CHECK:        %140 = OpFunctionParameter %uint
-;CHECK:        %141 = OpLabel
-;CHECK:        %145 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_0
-;CHECK:        %147 = OpAtomicIAdd %uint %145 %uint_4 %uint_0 %uint_11
-;CHECK:        %148 = OpIAdd %uint %147 %uint_11
-;CHECK:        %149 = OpArrayLength %uint %144 1
-;CHECK:        %150 = OpULessThanEqual %bool %148 %149
-;CHECK:               OpSelectionMerge %151 None
-;CHECK:               OpBranchConditional %150 %152 %151
-;CHECK:        %152 = OpLabel
-;CHECK:        %153 = OpIAdd %uint %147 %uint_0
-;CHECK:        %155 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %153
-;CHECK:               OpStore %155 %uint_11
-;CHECK:        %157 = OpIAdd %uint %147 %uint_1
-;CHECK:        %158 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %157
-;CHECK:               OpStore %158 %uint_23
-;CHECK:        %159 = OpIAdd %uint %147 %uint_2
-;CHECK:        %160 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %159
-;CHECK:               OpStore %160 %136
-;CHECK:        %162 = OpIAdd %uint %147 %uint_3
-;CHECK:        %163 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %162
-;CHECK:               OpStore %163 %uint_4
-;CHECK:        %166 = OpLoad %v4float %gl_FragCoord
-;CHECK:        %168 = OpBitcast %v4uint %166
-;CHECK:        %169 = OpCompositeExtract %uint %168 0
-;CHECK:        %170 = OpIAdd %uint %147 %uint_4
-;CHECK:        %171 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %170
-;CHECK:               OpStore %171 %169
-;CHECK:        %172 = OpCompositeExtract %uint %168 1
-;CHECK:        %174 = OpIAdd %uint %147 %uint_5
-;CHECK:        %175 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %174
-;CHECK:               OpStore %175 %172
-;CHECK:        %176 = OpIAdd %uint %147 %uint_7
-;CHECK:        %177 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %176
-;CHECK:               OpStore %177 %137
-;CHECK:        %179 = OpIAdd %uint %147 %uint_8
-;CHECK:        %180 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %179
-;CHECK:               OpStore %180 %138
-;CHECK:        %182 = OpIAdd %uint %147 %uint_9
-;CHECK:        %183 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %182
-;CHECK:               OpStore %183 %139
-;CHECK:        %185 = OpIAdd %uint %147 %uint_10
-;CHECK:        %186 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %185
-;CHECK:               OpStore %186 %140
-;CHECK:               OpBranch %151
-;CHECK:        %151 = OpLabel
-;CHECK:               OpReturn
-;CHECK:               OpFunctionEnd
- )";
+)" + kDirectRead4 + kStreamWrite5Frag;
+  // clang-format on
 
   SetTargetEnv(SPV_ENV_VULKAN_1_2);
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
@@ -7992,6 +4781,7 @@
   //
   // Use Simple source with min16uint g_nDataIdx
 
+  // clang-format off
   const std::string text = R"(
                OpCapability Shader
                OpCapability Int16
@@ -8000,7 +4790,7 @@
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %MainPs "MainPs" %g_tColor %_ %g_sAniso %i_vTextureCoords %_entryPointOutput_vColor
-;CHECK:               OpEntryPoint Fragment %MainPs "MainPs" %g_tColor %_ %g_sAniso %i_vTextureCoords %_entryPointOutput_vColor %60 %gl_FragCoord %119
+;CHECK:        OpEntryPoint Fragment %MainPs "MainPs" %g_tColor %_ %g_sAniso %i_vTextureCoords %_entryPointOutput_vColor %inst_bindless_output_buffer %gl_FragCoord %inst_bindless_input_buffer
                OpExecutionMode %MainPs OriginUpperLeft
                OpSource HLSL 500
                OpName %MainPs "MainPs"
@@ -8020,16 +4810,9 @@
                OpDecorate %i_vTextureCoords Location 0
                OpDecorate %_entryPointOutput_vColor Location 0
 ;CHECK:               OpDecorate %_runtimearr_uint ArrayStride 4
-;CHECK:               OpDecorate %_struct_58 Block
-;CHECK:               OpMemberDecorate %_struct_58 0 Offset 0
-;CHECK:               OpMemberDecorate %_struct_58 1 Offset 4
-;CHECK:               OpDecorate %60 DescriptorSet 7
-;CHECK:               OpDecorate %60 Binding 0
+)" + kOutputDecorations + R"(
 ;CHECK:               OpDecorate %gl_FragCoord BuiltIn FragCoord
-;CHECK:               OpDecorate %_struct_117 Block
-;CHECK:               OpMemberDecorate %_struct_117 0 Offset 0
-;CHECK:               OpDecorate %119 DescriptorSet 7
-;CHECK:               OpDecorate %119 Binding 1
+)" + kInputDecorations + R"(
        %void = OpTypeVoid
          %10 = OpTypeFunction %void
       %float = OpTypeFloat 32
@@ -8061,9 +4844,7 @@
 ;CHECK:       %bool = OpTypeBool
 ;CHECK:         %51 = OpTypeFunction %void %uint %uint %uint %uint
 ;CHECK:%_runtimearr_uint = OpTypeRuntimeArray %uint
-;CHECK: %_struct_58 = OpTypeStruct %uint %_runtimearr_uint
-;CHECK:%_ptr_StorageBuffer__struct_58 = OpTypePointer StorageBuffer %_struct_58
-;CHECK:         %60 = OpVariable %_ptr_StorageBuffer__struct_58 StorageBuffer
+)" + kOutputGlobals + R"(
 ;CHECK:%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
 ;CHECK:    %uint_10 = OpConstant %uint 10
 ;CHECK:     %uint_4 = OpConstant %uint 4
@@ -8081,9 +4862,7 @@
 ;CHECK:    %uint_60 = OpConstant %uint 60
 ;CHECK:        %106 = OpConstantNull %v4float
 ;CHECK:        %111 = OpTypeFunction %uint %uint %uint %uint %uint
-;CHECK:%_struct_117 = OpTypeStruct %_runtimearr_uint
-;CHECK:%_ptr_StorageBuffer__struct_117 = OpTypePointer StorageBuffer %_struct_117
-;CHECK:        %119 = OpVariable %_ptr_StorageBuffer__struct_117 StorageBuffer
+)" + kInputGlobals + R"(
      %MainPs = OpFunction %void None %10
          %30 = OpLabel
 ;CHECK:               OpBranch %108
@@ -8109,7 +4888,7 @@
 ;CHECK:         %47 = OpLoad %16 %34
 ;CHECK:         %48 = OpSampledImage %27 %47 %36
 ;CHECK:        %109 = OpUConvert %uint %33
-;CHECK:        %131 = OpFunctionCall %uint %110 %uint_0 %uint_0 %uint_0 %109
+;CHECK:        %131 = OpFunctionCall %uint %inst_bindless_direct_read_4 %uint_0 %uint_0 %uint_0 %109
 ;CHECK:        %132 = OpULessThan %bool %uint_0 %131
 ;CHECK:               OpSelectionMerge %133 None
 ;CHECK:               OpBranchConditional %132 %134 %135
@@ -8120,88 +4899,21 @@
 ;CHECK:               OpBranch %133
 ;CHECK:        %135 = OpLabel
 ;CHECK:        %139 = OpUConvert %uint %33
-;CHECK:        %140 = OpFunctionCall %void %50 %uint_60 %uint_1 %139 %uint_0
+;CHECK:        %140 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_60 %uint_1 %139 %uint_0
 ;CHECK:               OpBranch %133
 ;CHECK:        %133 = OpLabel
 ;CHECK:        %141 = OpPhi %v4float %138 %134 %106 %135
 ;CHECK:               OpBranch %44
 ;CHECK:         %46 = OpLabel
-;CHECK:        %105 = OpFunctionCall %void %50 %uint_60 %uint_0 %41 %uint_128
+;CHECK:        %105 = OpFunctionCall %void %inst_bindless_stream_write_4 %uint_60 %uint_0 %41 %uint_128
 ;CHECK:               OpBranch %44
 ;CHECK:         %44 = OpLabel
 ;CHECK:        %107 = OpPhi %v4float %141 %133 %106 %46
 ;CHECK:               OpStore %_entryPointOutput_vColor %107
                OpReturn
                OpFunctionEnd
-;CHECK:         %50 = OpFunction %void None %51
-;CHECK:         %52 = OpFunctionParameter %uint
-;CHECK:         %53 = OpFunctionParameter %uint
-;CHECK:         %54 = OpFunctionParameter %uint
-;CHECK:         %55 = OpFunctionParameter %uint
-;CHECK:         %56 = OpLabel
-;CHECK:         %62 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_0
-;CHECK:         %65 = OpAtomicIAdd %uint %62 %uint_4 %uint_0 %uint_10
-;CHECK:         %66 = OpIAdd %uint %65 %uint_10
-;CHECK:         %67 = OpArrayLength %uint %60 1
-;CHECK:         %68 = OpULessThanEqual %bool %66 %67
-;CHECK:               OpSelectionMerge %69 None
-;CHECK:               OpBranchConditional %68 %70 %69
-;CHECK:         %70 = OpLabel
-;CHECK:         %71 = OpIAdd %uint %65 %uint_0
-;CHECK:         %73 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %71
-;CHECK:               OpStore %73 %uint_10
-;CHECK:         %75 = OpIAdd %uint %65 %uint_1
-;CHECK:         %76 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %75
-;CHECK:               OpStore %76 %uint_23
-;CHECK:         %78 = OpIAdd %uint %65 %uint_2
-;CHECK:         %79 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %78
-;CHECK:               OpStore %79 %52
-;CHECK:         %81 = OpIAdd %uint %65 %uint_3
-;CHECK:         %82 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %81
-;CHECK:               OpStore %82 %uint_4
-;CHECK:         %85 = OpLoad %v4float %gl_FragCoord
-;CHECK:         %87 = OpBitcast %v4uint %85
-;CHECK:         %88 = OpCompositeExtract %uint %87 0
-;CHECK:         %89 = OpIAdd %uint %65 %uint_4
-;CHECK:         %90 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %89
-;CHECK:               OpStore %90 %88
-;CHECK:         %91 = OpCompositeExtract %uint %87 1
-;CHECK:         %93 = OpIAdd %uint %65 %uint_5
-;CHECK:         %94 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %93
-;CHECK:               OpStore %94 %91
-;CHECK:         %96 = OpIAdd %uint %65 %uint_7
-;CHECK:         %97 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %96
-;CHECK:               OpStore %97 %53
-;CHECK:         %99 = OpIAdd %uint %65 %uint_8
-;CHECK:        %100 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %99
-;CHECK:               OpStore %100 %54
-;CHECK:        %102 = OpIAdd %uint %65 %uint_9
-;CHECK:        %103 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %102
-;CHECK:               OpStore %103 %55
-;CHECK:               OpBranch %69
-;CHECK:         %69 = OpLabel
-;CHECK:               OpReturn
-;CHECK:               OpFunctionEnd
-;CHECK:        %110 = OpFunction %uint None %111
-;CHECK:        %112 = OpFunctionParameter %uint
-;CHECK:        %113 = OpFunctionParameter %uint
-;CHECK:        %114 = OpFunctionParameter %uint
-;CHECK:        %115 = OpFunctionParameter %uint
-;CHECK:        %116 = OpLabel
-;CHECK:        %120 = OpAccessChain %_ptr_StorageBuffer_uint %119 %uint_0 %112
-;CHECK:        %121 = OpLoad %uint %120
-;CHECK:        %122 = OpIAdd %uint %121 %113
-;CHECK:        %123 = OpAccessChain %_ptr_StorageBuffer_uint %119 %uint_0 %122
-;CHECK:        %124 = OpLoad %uint %123
-;CHECK:        %125 = OpIAdd %uint %124 %114
-;CHECK:        %126 = OpAccessChain %_ptr_StorageBuffer_uint %119 %uint_0 %125
-;CHECK:        %127 = OpLoad %uint %126
-;CHECK:        %128 = OpIAdd %uint %127 %115
-;CHECK:        %129 = OpAccessChain %_ptr_StorageBuffer_uint %119 %uint_0 %128
-;CHECK:        %130 = OpLoad %uint %129
-;CHECK:               OpReturnValue %130
-;CHECK:               OpFunctionEnd
- )";
+)" + kStreamWrite4Frag + kDirectRead4;
+  // clang-format on
 
   SetTargetEnv(SPV_ENV_VULKAN_1_2);
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
@@ -8245,6 +4957,7 @@
   //   return ps_output;
   // }
 
+  // clang-format off
   const std::string text = R"(
                OpCapability Shader
                OpCapability Int16
@@ -8253,7 +4966,7 @@
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %MainPs "MainPs" %_ %__0 %g_tColor %g_sAniso %i_vTextureCoords %_entryPointOutput_vColor
-;CHECK:               OpEntryPoint Fragment %MainPs "MainPs" %_ %__0 %g_tColor %g_sAniso %i_vTextureCoords %_entryPointOutput_vColor %69 %97 %gl_FragCoord
+;CHECK:        OpEntryPoint Fragment %MainPs "MainPs" %_ %__0 %g_tColor %g_sAniso %i_vTextureCoords %_entryPointOutput_vColor %inst_bindless_input_buffer %inst_bindless_output_buffer %gl_FragCoord
                OpExecutionMode %MainPs OriginUpperLeft
                OpSource HLSL 500
                OpName %MainPs "MainPs"
@@ -8290,15 +5003,7 @@
                OpDecorate %i_vTextureCoords Location 0
                OpDecorate %_entryPointOutput_vColor Location 0
 ;CHECK:               OpDecorate %_runtimearr_uint ArrayStride 4
-;CHECK:               OpDecorate %_struct_67 Block
-;CHECK:               OpMemberDecorate %_struct_67 0 Offset 0
-;CHECK:               OpDecorate %69 DescriptorSet 7
-;CHECK:               OpDecorate %69 Binding 1
-;CHECK:               OpDecorate %_struct_95 Block
-;CHECK:               OpMemberDecorate %_struct_95 0 Offset 0
-;CHECK:               OpMemberDecorate %_struct_95 1 Offset 4
-;CHECK:               OpDecorate %97 DescriptorSet 7
-;CHECK:               OpDecorate %97 Binding 0
+)" + kInputDecorations + kOutputDecorations + R"(
 ;CHECK:               OpDecorate %gl_FragCoord BuiltIn FragCoord
        %void = OpTypeVoid
          %14 = OpTypeFunction %void
@@ -8341,16 +5046,12 @@
 ;CHECK:     %uint_1 = OpConstant %uint 1
 ;CHECK:         %61 = OpTypeFunction %uint %uint %uint %uint
 ;CHECK:%_runtimearr_uint = OpTypeRuntimeArray %uint
-;CHECK: %_struct_67 = OpTypeStruct %_runtimearr_uint
-;CHECK:%_ptr_StorageBuffer__struct_67 = OpTypePointer StorageBuffer %_struct_67
-;CHECK:         %69 = OpVariable %_ptr_StorageBuffer__struct_67 StorageBuffer
+)" + kInputGlobals + R"(
 ;CHECK:%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
 ;CHECK:       %bool = OpTypeBool
 ;CHECK:     %uint_4 = OpConstant %uint 4
 ;CHECK:         %88 = OpTypeFunction %void %uint %uint %uint %uint %uint
-;CHECK: %_struct_95 = OpTypeStruct %uint %_runtimearr_uint
-;CHECK:%_ptr_StorageBuffer__struct_95 = OpTypePointer StorageBuffer %_struct_95
-;CHECK:         %97 = OpVariable %_ptr_StorageBuffer__struct_95 StorageBuffer
+)" + kOutputGlobals + R"(
 ;CHECK:    %uint_11 = OpConstant %uint 11
 ;CHECK:    %uint_23 = OpConstant %uint 23
 ;CHECK:     %uint_2 = OpConstant %uint 2
@@ -8366,7 +5067,7 @@
 ;CHECK:        %142 = OpConstantNull %v2float
      %MainPs = OpFunction %void None %14
          %37 = OpLabel
-;CHECK:         %79 = OpFunctionCall %uint %60 %uint_1 %uint_0 %uint_0
+;CHECK:         %79 = OpFunctionCall %uint %inst_bindless_direct_read_3 %uint_1 %uint_0 %uint_0
 ;CHECK:               OpBranch %49
 ;CHECK:         %49 = OpLabel
 ;CHECK:               OpBranch %48
@@ -8391,7 +5092,7 @@
 ;CHECK:         %86 = OpLoad %v2float %41
 ;CHECK:               OpBranch %83
 ;CHECK:         %85 = OpLabel
-;CHECK:        %141 = OpFunctionCall %void %87 %uint_81 %uint_4 %uint_0 %58 %79
+;CHECK:        %141 = OpFunctionCall %void %inst_bindless_stream_write_5 %uint_81 %uint_4 %uint_0 %58 %79
 ;CHECK:               OpBranch %83
 ;CHECK:         %83 = OpLabel
 ;CHECK:        %143 = OpPhi %v2float %86 %84 %142 %85
@@ -8403,75 +5104,8 @@
                OpStore %_entryPointOutput_vColor %47
                OpReturn
                OpFunctionEnd
-;CHECK:         %60 = OpFunction %uint None %61
-;CHECK:         %62 = OpFunctionParameter %uint
-;CHECK:         %63 = OpFunctionParameter %uint
-;CHECK:         %64 = OpFunctionParameter %uint
-;CHECK:         %65 = OpLabel
-;CHECK:         %71 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_0 %62
-;CHECK:         %72 = OpLoad %uint %71
-;CHECK:         %73 = OpIAdd %uint %72 %63
-;CHECK:         %74 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_0 %73
-;CHECK:         %75 = OpLoad %uint %74
-;CHECK:         %76 = OpIAdd %uint %75 %64
-;CHECK:         %77 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_0 %76
-;CHECK:         %78 = OpLoad %uint %77
-;CHECK:               OpReturnValue %78
-;CHECK:               OpFunctionEnd
-;CHECK:         %87 = OpFunction %void None %88
-;CHECK:         %89 = OpFunctionParameter %uint
-;CHECK:         %90 = OpFunctionParameter %uint
-;CHECK:         %91 = OpFunctionParameter %uint
-;CHECK:         %92 = OpFunctionParameter %uint
-;CHECK:         %93 = OpFunctionParameter %uint
-;CHECK:         %94 = OpLabel
-;CHECK:         %98 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_0
-;CHECK:        %100 = OpAtomicIAdd %uint %98 %uint_4 %uint_0 %uint_11
-;CHECK:        %101 = OpIAdd %uint %100 %uint_11
-;CHECK:        %102 = OpArrayLength %uint %97 1
-;CHECK:        %103 = OpULessThanEqual %bool %101 %102
-;CHECK:               OpSelectionMerge %104 None
-;CHECK:               OpBranchConditional %103 %105 %104
-;CHECK:        %105 = OpLabel
-;CHECK:        %106 = OpIAdd %uint %100 %uint_0
-;CHECK:        %107 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %106
-;CHECK:               OpStore %107 %uint_11
-;CHECK:        %109 = OpIAdd %uint %100 %uint_1
-;CHECK:        %110 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %109
-;CHECK:               OpStore %110 %uint_23
-;CHECK:        %112 = OpIAdd %uint %100 %uint_2
-;CHECK:        %113 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %112
-;CHECK:               OpStore %113 %89
-;CHECK:        %115 = OpIAdd %uint %100 %uint_3
-;CHECK:        %116 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %115
-;CHECK:               OpStore %116 %uint_4
-;CHECK:        %119 = OpLoad %v4float %gl_FragCoord
-;CHECK:        %121 = OpBitcast %v4uint %119
-;CHECK:        %122 = OpCompositeExtract %uint %121 0
-;CHECK:        %123 = OpIAdd %uint %100 %uint_4
-;CHECK:        %124 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %123
-;CHECK:               OpStore %124 %122
-;CHECK:        %125 = OpCompositeExtract %uint %121 1
-;CHECK:        %127 = OpIAdd %uint %100 %uint_5
-;CHECK:        %128 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %127
-;CHECK:               OpStore %128 %125
-;CHECK:        %129 = OpIAdd %uint %100 %uint_7
-;CHECK:        %130 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %129
-;CHECK:               OpStore %130 %90
-;CHECK:        %132 = OpIAdd %uint %100 %uint_8
-;CHECK:        %133 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %132
-;CHECK:               OpStore %133 %91
-;CHECK:        %135 = OpIAdd %uint %100 %uint_9
-;CHECK:        %136 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %135
-;CHECK:               OpStore %136 %92
-;CHECK:        %138 = OpIAdd %uint %100 %uint_10
-;CHECK:        %139 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %138
-;CHECK:               OpStore %139 %93
-;CHECK:               OpBranch %104
-;CHECK:        %104 = OpLabel
-;CHECK:               OpReturn
-;CHECK:               OpFunctionEnd
- )";
+               )" + kDirectRead3 + kStreamWrite5Frag;
+  // clang-format on
 
   SetTargetEnv(SPV_ENV_VULKAN_1_2);
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
@@ -8498,13 +5132,14 @@
   //    v_vtxResult = var[2][1];
   // }
 
-  const std::string text = R"(
+  // clang-format off
+  std::string text = R"(
                OpCapability Shader
 ;CHECK:               OpExtension "SPV_KHR_storage_buffer_storage_class"
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Vertex %main "main" %v_vtxResult %_ %a_position
-;CHECK:               OpEntryPoint Vertex %main "main" %v_vtxResult %_ %a_position %45 %72 %gl_VertexIndex %gl_InstanceIndex
+;CHECK:        OpEntryPoint Vertex %main "main" %v_vtxResult %_ %a_position %inst_bindless_input_buffer %inst_bindless_output_buffer %gl_VertexIndex %gl_InstanceIndex
                OpSource GLSL 450
                OpSourceExtension "GL_EXT_scalar_block_layout"
                OpName %main "main"
@@ -8527,16 +5162,9 @@
 ;CHECK:               OpDecorate %116 RelaxedPrecision
                OpDecorate %a_position Location 0
 ;CHECK:               OpDecorate %_runtimearr_uint ArrayStride 4
-;CHECK:               OpDecorate %_struct_43 Block
-;CHECK:               OpMemberDecorate %_struct_43 0 Offset 0
-;CHECK:               OpDecorate %45 DescriptorSet 7
-;CHECK:               OpDecorate %45 Binding 1
+)" + kInputDecorations + R"(
 ;CHECK:               OpDecorate %61 RelaxedPrecision
-;CHECK:               OpDecorate %_struct_70 Block
-;CHECK:               OpMemberDecorate %_struct_70 0 Offset 0
-;CHECK:               OpMemberDecorate %_struct_70 1 Offset 4
-;CHECK:               OpDecorate %72 DescriptorSet 7
-;CHECK:               OpDecorate %72 Binding 0
+)" + kOutputDecorations + R"(
 ;CHECK:               OpDecorate %gl_VertexIndex BuiltIn VertexIndex
 ;CHECK:               OpDecorate %gl_InstanceIndex BuiltIn InstanceIndex
        %void = OpTypeVoid
@@ -8564,15 +5192,11 @@
 ;CHECK;     %uint_3 = OpConstant %uint 3
 ;CHECK;         %37 = OpTypeFunction %uint %uint %uint %uint
 ;CHECK;%_runtimearr_uint = OpTypeRuntimeArray %uint
-;CHECK; %_struct_43 = OpTypeStruct %_runtimearr_uint
-;CHECK;%_ptr_StorageBuffer__struct_43 = OpTypePointer StorageBuffer %_struct_43
-;CHECK;         %45 = OpVariable %_ptr_StorageBuffer__struct_43 StorageBuffer
+)" + kInputGlobals + R"(
 ;CHECK;%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
 ;CHECK;       %bool = OpTypeBool
 ;CHECK;         %63 = OpTypeFunction %void %uint %uint %uint %uint %uint
-;CHECK; %_struct_70 = OpTypeStruct %uint %_runtimearr_uint
-;CHECK;%_ptr_StorageBuffer__struct_70 = OpTypePointer StorageBuffer %_struct_70
-;CHECK;         %72 = OpVariable %_ptr_StorageBuffer__struct_70 StorageBuffer
+)" + kOutputGlobals + R"(
 ;CHECK;    %uint_11 = OpConstant %uint 11
 ;CHECK;    %uint_23 = OpConstant %uint 23
 ;CHECK;     %uint_2 = OpConstant %uint 2
@@ -8588,7 +5212,7 @@
 ;CHECK;        %115 = OpConstantNull %float
        %main = OpFunction %void None %3
           %5 = OpLabel
-;CHECK:         %55 = OpFunctionCall %uint %36 %uint_1 %uint_0 %uint_0
+;CHECK:         %55 = OpFunctionCall %uint %inst_bindless_direct_read_3 %uint_1 %uint_0 %uint_0
 ;CHECK:               OpBranch %26
 ;CHECK:         %26 = OpLabel
 ;CHECK:               OpBranch %25
@@ -8608,7 +5232,7 @@
 ;CHECK:         %61 = OpLoad %float %20
 ;CHECK:               OpBranch %58
 ;CHECK:         %60 = OpLabel
-;CHECK:        %114 = OpFunctionCall %void %62 %uint_45 %uint_4 %uint_0 %35 %55
+;CHECK:        %114 = OpFunctionCall %void %inst_bindless_stream_write_5 %uint_45 %uint_4 %uint_0 %35 %55
 ;CHECK:               OpBranch %58
 ;CHECK:         %58 = OpLabel
 ;CHECK:        %116 = OpPhi %float %61 %59 %115 %60
@@ -8617,73 +5241,8 @@
 ;CHECK:               OpStore %v_vtxResult %116
                OpReturn
                OpFunctionEnd
-;CHECK:         %36 = OpFunction %uint None %37
-;CHECK:         %38 = OpFunctionParameter %uint
-;CHECK:         %39 = OpFunctionParameter %uint
-;CHECK:         %40 = OpFunctionParameter %uint
-;CHECK:         %41 = OpLabel
-;CHECK:         %47 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_0 %38
-;CHECK:         %48 = OpLoad %uint %47
-;CHECK:         %49 = OpIAdd %uint %48 %39
-;CHECK:         %50 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_0 %49
-;CHECK:         %51 = OpLoad %uint %50
-;CHECK:         %52 = OpIAdd %uint %51 %40
-;CHECK:         %53 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_0 %52
-;CHECK:         %54 = OpLoad %uint %53
-;CHECK:               OpReturnValue %54
-;CHECK:               OpFunctionEnd
-;CHECK:         %62 = OpFunction %void None %63
-;CHECK:         %64 = OpFunctionParameter %uint
-;CHECK:         %65 = OpFunctionParameter %uint
-;CHECK:         %66 = OpFunctionParameter %uint
-;CHECK:         %67 = OpFunctionParameter %uint
-;CHECK:         %68 = OpFunctionParameter %uint
-;CHECK:         %69 = OpLabel
-;CHECK:         %73 = OpAccessChain %_ptr_StorageBuffer_uint %72 %uint_0
-;CHECK:         %75 = OpAtomicIAdd %uint %73 %uint_4 %uint_0 %uint_11
-;CHECK:         %76 = OpIAdd %uint %75 %uint_11
-;CHECK:         %77 = OpArrayLength %uint %72 1
-;CHECK:         %78 = OpULessThanEqual %bool %76 %77
-;CHECK:               OpSelectionMerge %79 None
-;CHECK:               OpBranchConditional %78 %80 %79
-;CHECK:         %80 = OpLabel
-;CHECK:         %81 = OpIAdd %uint %75 %uint_0
-;CHECK:         %82 = OpAccessChain %_ptr_StorageBuffer_uint %72 %uint_1 %81
-;CHECK:               OpStore %82 %uint_11
-;CHECK:         %84 = OpIAdd %uint %75 %uint_1
-;CHECK:         %85 = OpAccessChain %_ptr_StorageBuffer_uint %72 %uint_1 %84
-;CHECK:               OpStore %85 %uint_23
-;CHECK:         %87 = OpIAdd %uint %75 %uint_2
-;CHECK:         %88 = OpAccessChain %_ptr_StorageBuffer_uint %72 %uint_1 %87
-;CHECK:               OpStore %88 %64
-;CHECK:         %89 = OpIAdd %uint %75 %uint_3
-;CHECK:         %90 = OpAccessChain %_ptr_StorageBuffer_uint %72 %uint_1 %89
-;CHECK:               OpStore %90 %uint_0
-;CHECK:         %93 = OpLoad %uint %gl_VertexIndex
-;CHECK:         %94 = OpIAdd %uint %75 %uint_4
-;CHECK:         %95 = OpAccessChain %_ptr_StorageBuffer_uint %72 %uint_1 %94
-;CHECK:               OpStore %95 %93
-;CHECK:         %97 = OpLoad %uint %gl_InstanceIndex
-;CHECK:         %99 = OpIAdd %uint %75 %uint_5
-;CHECK:        %100 = OpAccessChain %_ptr_StorageBuffer_uint %72 %uint_1 %99
-;CHECK:               OpStore %100 %97
-;CHECK:        %102 = OpIAdd %uint %75 %uint_7
-;CHECK:        %103 = OpAccessChain %_ptr_StorageBuffer_uint %72 %uint_1 %102
-;CHECK:               OpStore %103 %65
-;CHECK:        %105 = OpIAdd %uint %75 %uint_8
-;CHECK:        %106 = OpAccessChain %_ptr_StorageBuffer_uint %72 %uint_1 %105
-;CHECK:               OpStore %106 %66
-;CHECK:        %108 = OpIAdd %uint %75 %uint_9
-;CHECK:        %109 = OpAccessChain %_ptr_StorageBuffer_uint %72 %uint_1 %108
-;CHECK:               OpStore %109 %67
-;CHECK:        %111 = OpIAdd %uint %75 %uint_10
-;CHECK:        %112 = OpAccessChain %_ptr_StorageBuffer_uint %72 %uint_1 %111
-;CHECK:               OpStore %112 %68
-;CHECK:               OpBranch %79
-;CHECK:         %79 = OpLabel
-;CHECK:               OpReturn
-;CHECK:               OpFunctionEnd
- )";
+               )" + kDirectRead3 + kStreamWrite5Vert;
+  // clang-format on
 
   SetTargetEnv(SPV_ENV_VULKAN_1_2);
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
@@ -8710,13 +5269,14 @@
   //    v_vtxResult = var[2][1];
   // }
 
+  // clang-format off
   const std::string text = R"(
                OpCapability Shader
 ;CHECK:               OpExtension "SPV_KHR_storage_buffer_storage_class"
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Vertex %main "main" %v_vtxResult %_ %a_position
-;CHECK:               OpEntryPoint Vertex %main "main" %v_vtxResult %_ %a_position %45 %72 %gl_VertexIndex %gl_InstanceIndex
+;CHECK:        OpEntryPoint Vertex %main "main" %v_vtxResult %_ %a_position %inst_bindless_input_buffer %inst_bindless_output_buffer %gl_VertexIndex %gl_InstanceIndex
                OpSource GLSL 450
                OpSourceExtension "GL_EXT_scalar_block_layout"
                OpName %main "main"
@@ -8739,16 +5299,9 @@
 ;CHECK:               OpDecorate %115 RelaxedPrecision
                OpDecorate %a_position Location 0
 ;CHECK:               OpDecorate %_runtimearr_uint ArrayStride 4
-;CHECK:               OpDecorate %_struct_43 Block
-;CHECK:               OpMemberDecorate %_struct_43 0 Offset 0
-;CHECK:               OpDecorate %45 DescriptorSet 7
-;CHECK:               OpDecorate %45 Binding 1
+)" + kInputDecorations + R"(
 ;CHECK:               OpDecorate %61 RelaxedPrecision
-;CHECK:               OpDecorate %_struct_70 Block
-;CHECK:               OpMemberDecorate %_struct_70 0 Offset 0
-;CHECK:               OpMemberDecorate %_struct_70 1 Offset 4
-;CHECK:               OpDecorate %72 DescriptorSet 7
-;CHECK:               OpDecorate %72 Binding 0
+)" + kOutputDecorations + R"(
 ;CHECK:               OpDecorate %gl_VertexIndex BuiltIn VertexIndex
 ;CHECK:               OpDecorate %gl_InstanceIndex BuiltIn InstanceIndex
        %void = OpTypeVoid
@@ -8776,15 +5329,11 @@
 ;CHECK:     %uint_3 = OpConstant %uint 3
 ;CHECK:         %37 = OpTypeFunction %uint %uint %uint %uint
 ;CHECK:%_runtimearr_uint = OpTypeRuntimeArray %uint
-;CHECK: %_struct_43 = OpTypeStruct %_runtimearr_uint
-;CHECK:%_ptr_StorageBuffer__struct_43 = OpTypePointer StorageBuffer %_struct_43
-;CHECK:         %45 = OpVariable %_ptr_StorageBuffer__struct_43 StorageBuffer
+)" + kInputGlobals + R"(
 ;CHECK:%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
 ;CHECK:       %bool = OpTypeBool
 ;CHECK:         %63 = OpTypeFunction %void %uint %uint %uint %uint %uint
-;CHECK: %_struct_70 = OpTypeStruct %uint %_runtimearr_uint
-;CHECK:%_ptr_StorageBuffer__struct_70 = OpTypePointer StorageBuffer %_struct_70
-;CHECK:         %72 = OpVariable %_ptr_StorageBuffer__struct_70 StorageBuffer
+)" + kOutputGlobals + R"(
 ;CHECK:    %uint_11 = OpConstant %uint 11
 ;CHECK:    %uint_23 = OpConstant %uint 23
 ;CHECK:     %uint_2 = OpConstant %uint 2
@@ -8799,7 +5348,7 @@
 ;CHECK:        %114 = OpConstantNull %float
 %main = OpFunction %void None %3
           %5 = OpLabel
-;CHECK:         %55 = OpFunctionCall %uint %36 %uint_1 %uint_0 %uint_0
+;CHECK:         %55 = OpFunctionCall %uint %inst_bindless_direct_read_3 %uint_1 %uint_0 %uint_0
 ;CHECK:               OpBranch %26
 ;CHECK:         %26 = OpLabel
 ;CHECK:               OpBranch %25
@@ -8819,7 +5368,7 @@
 ;CHECK:         %61 = OpLoad %float %20
 ;CHECK:               OpBranch %58
 ;CHECK:         %60 = OpLabel
-;CHECK:        %113 = OpFunctionCall %void %62 %uint_45 %uint_4 %uint_0 %35 %55
+;CHECK:        %113 = OpFunctionCall %void %inst_bindless_stream_write_5 %uint_45 %uint_4 %uint_0 %35 %55
 ;CHECK:               OpBranch %58
 ;CHECK:         %58 = OpLabel
 ;CHECK:        %115 = OpPhi %float %61 %59 %114 %60
@@ -8828,75 +5377,11 @@
 ;CHECK:               OpStore %v_vtxResult %115
                OpReturn
                OpFunctionEnd
-;CHECK:         %36 = OpFunction %uint None %37
-;CHECK:         %38 = OpFunctionParameter %uint
-;CHECK:         %39 = OpFunctionParameter %uint
-;CHECK:         %40 = OpFunctionParameter %uint
-;CHECK:         %41 = OpLabel
-;CHECK:         %47 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_0 %38
-;CHECK:         %48 = OpLoad %uint %47
-;CHECK:         %49 = OpIAdd %uint %48 %39
-;CHECK:         %50 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_0 %49
-;CHECK:         %51 = OpLoad %uint %50
-;CHECK:         %52 = OpIAdd %uint %51 %40
-;CHECK:         %53 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_0 %52
-;CHECK:         %54 = OpLoad %uint %53
-;CHECK:               OpReturnValue %54
-;CHECK:               OpFunctionEnd
-;CHECK:         %62 = OpFunction %void None %63
-;CHECK:         %64 = OpFunctionParameter %uint
-;CHECK:         %65 = OpFunctionParameter %uint
-;CHECK:         %66 = OpFunctionParameter %uint
-;CHECK:         %67 = OpFunctionParameter %uint
-;CHECK:         %68 = OpFunctionParameter %uint
-;CHECK:         %69 = OpLabel
-;CHECK:         %73 = OpAccessChain %_ptr_StorageBuffer_uint %72 %uint_0
-;CHECK:         %75 = OpAtomicIAdd %uint %73 %uint_4 %uint_0 %uint_11
-;CHECK:         %76 = OpIAdd %uint %75 %uint_11
-;CHECK:         %77 = OpArrayLength %uint %72 1
-;CHECK:         %78 = OpULessThanEqual %bool %76 %77
-;CHECK:               OpSelectionMerge %79 None
-;CHECK:               OpBranchConditional %78 %80 %79
-;CHECK:         %80 = OpLabel
-;CHECK:         %81 = OpIAdd %uint %75 %uint_0
-;CHECK:         %82 = OpAccessChain %_ptr_StorageBuffer_uint %72 %uint_1 %81
-;CHECK:               OpStore %82 %uint_11
-;CHECK:         %84 = OpIAdd %uint %75 %uint_1
-;CHECK:         %85 = OpAccessChain %_ptr_StorageBuffer_uint %72 %uint_1 %84
-;CHECK:               OpStore %85 %uint_23
-;CHECK:         %87 = OpIAdd %uint %75 %uint_2
-;CHECK:         %88 = OpAccessChain %_ptr_StorageBuffer_uint %72 %uint_1 %87
-;CHECK:               OpStore %88 %64
-;CHECK:         %89 = OpIAdd %uint %75 %uint_3
-;CHECK:         %90 = OpAccessChain %_ptr_StorageBuffer_uint %72 %uint_1 %89
-;CHECK:               OpStore %90 %uint_0
-;CHECK:         %93 = OpLoad %uint %gl_VertexIndex
-;CHECK:         %94 = OpIAdd %uint %75 %uint_4
-;CHECK:         %95 = OpAccessChain %_ptr_StorageBuffer_uint %72 %uint_1 %94
-;CHECK:               OpStore %95 %93
-;CHECK:         %97 = OpLoad %uint %gl_InstanceIndex
-;CHECK:         %99 = OpIAdd %uint %75 %uint_5
-;CHECK:        %100 = OpAccessChain %_ptr_StorageBuffer_uint %72 %uint_1 %99
-;CHECK:               OpStore %100 %97
-;CHECK:        %102 = OpIAdd %uint %75 %uint_7
-;CHECK:        %103 = OpAccessChain %_ptr_StorageBuffer_uint %72 %uint_1 %102
-;CHECK:               OpStore %103 %65
-;CHECK:        %104 = OpIAdd %uint %75 %uint_8
-;CHECK:        %105 = OpAccessChain %_ptr_StorageBuffer_uint %72 %uint_1 %104
-;CHECK:               OpStore %105 %66
-;CHECK:        %107 = OpIAdd %uint %75 %uint_9
-;CHECK:        %108 = OpAccessChain %_ptr_StorageBuffer_uint %72 %uint_1 %107
-;CHECK:               OpStore %108 %67
-;CHECK:        %110 = OpIAdd %uint %75 %uint_10
-;CHECK:        %111 = OpAccessChain %_ptr_StorageBuffer_uint %72 %uint_1 %110
-;CHECK:               OpStore %111 %68
-;CHECK:               OpBranch %79
-;CHECK:         %79 = OpLabel
-;CHECK:               OpReturn
-;CHECK:               OpFunctionEnd
- )";
+               )" + kDirectRead3 + kStreamWrite5Vert;
+  // clang-format on
 
   SetTargetEnv(SPV_ENV_VULKAN_1_2);
+  ValidatorOptions()->uniform_buffer_standard_layout = true;
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
   SinglePassRunAndMatch<InstBindlessCheckPass>(text, true, 7u, 23u, false,
                                                false, true, false, true);
@@ -8921,13 +5406,14 @@
   //    v_vtxResult = var[2][3][1];
   // }
 
+  // clang-format off
   const std::string text = R"(
                OpCapability Shader
 ;CHECK:               OpExtension "SPV_KHR_storage_buffer_storage_class"
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Vertex %main "main" %v_vtxResult %_ %a_position
-;CHECK:               OpEntryPoint Vertex %main "main" %v_vtxResult %_ %a_position %54 %81 %gl_VertexIndex %gl_InstanceIndex
+;CHECK:        OpEntryPoint Vertex %main "main" %v_vtxResult %_ %a_position %inst_bindless_input_buffer %inst_bindless_output_buffer %gl_VertexIndex %gl_InstanceIndex
                OpSource GLSL 450
                OpSourceExtension "GL_EXT_scalar_block_layout"
                OpName %main "main"
@@ -8951,16 +5437,9 @@
 ;CHECK:               OpDecorate %125 RelaxedPrecision
                OpDecorate %a_position Location 0
 ;CHECK:               OpDecorate %_runtimearr_uint ArrayStride 4
-;CHECK:               OpDecorate %_struct_52 Block
-;CHECK:               OpMemberDecorate %_struct_52 0 Offset 0
-;CHECK:               OpDecorate %54 DescriptorSet 7
-;CHECK:               OpDecorate %54 Binding 1
+)" + kInputDecorations + R"(
 ;CHECK:               OpDecorate %70 RelaxedPrecision
-;CHECK:               OpDecorate %_struct_79 Block
-;CHECK:               OpMemberDecorate %_struct_79 0 Offset 0
-;CHECK:               OpMemberDecorate %_struct_79 1 Offset 4
-;CHECK:               OpDecorate %81 DescriptorSet 7
-;CHECK:               OpDecorate %81 Binding 0
+)" + kOutputDecorations + R"(
 ;CHECK:               OpDecorate %gl_VertexIndex BuiltIn VertexIndex
 ;CHECK:               OpDecorate %gl_InstanceIndex BuiltIn InstanceIndex
        %void = OpTypeVoid
@@ -8995,15 +5474,11 @@
 ;CHECK:     %uint_1 = OpConstant %uint 1
 ;CHECK:         %46 = OpTypeFunction %uint %uint %uint %uint
 ;CHECK:%_runtimearr_uint = OpTypeRuntimeArray %uint
-;CHECK: %_struct_52 = OpTypeStruct %_runtimearr_uint
-;CHECK:%_ptr_StorageBuffer__struct_52 = OpTypePointer StorageBuffer %_struct_52
-;CHECK:         %54 = OpVariable %_ptr_StorageBuffer__struct_52 StorageBuffer
+)" + kInputGlobals + R"(
 ;CHECK:%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
 ;CHECK:       %bool = OpTypeBool
 ;CHECK:         %72 = OpTypeFunction %void %uint %uint %uint %uint %uint
-;CHECK: %_struct_79 = OpTypeStruct %uint %_runtimearr_uint
-;CHECK:%_ptr_StorageBuffer__struct_79 = OpTypePointer StorageBuffer %_struct_79
-;CHECK:         %81 = OpVariable %_ptr_StorageBuffer__struct_79 StorageBuffer
+)" + kOutputGlobals + R"(
 ;CHECK:    %uint_11 = OpConstant %uint 11
 ;CHECK:    %uint_23 = OpConstant %uint 23
 ;CHECK:     %uint_2 = OpConstant %uint 2
@@ -9019,7 +5494,7 @@
 ;CHECK:        %124 = OpConstantNull %v2float
        %main = OpFunction %void None %3
           %5 = OpLabel
-;CHECK:         %64 = OpFunctionCall %uint %45 %uint_1 %uint_0 %uint_0
+;CHECK:         %64 = OpFunctionCall %uint %inst_bindless_direct_read_3 %uint_1 %uint_0 %uint_0
 ;CHECK:               OpBranch %31
 ;CHECK:         %31 = OpLabel
 ;CHECK:               OpBranch %30
@@ -9043,80 +5518,15 @@
 ;CHECK:         %70 = OpLoad %v2float %25
 ;CHECK:               OpBranch %67
 ;CHECK:         %69 = OpLabel
-;CHECK:        %123 = OpFunctionCall %void %71 %uint_51 %uint_4 %uint_0 %43 %64
+;CHECK:        %123 = OpFunctionCall %void %inst_bindless_stream_write_5 %uint_51 %uint_4 %uint_0 %43 %64
 ;CHECK:               OpBranch %67
 ;CHECK:         %67 = OpLabel
 ;CHECK:        %125 = OpPhi %v2float %70 %68 %124 %69
 ;CHECK:               OpStore %v_vtxResult %125
                OpReturn
                OpFunctionEnd
-;CHECK:         %45 = OpFunction %uint None %46
-;CHECK:         %47 = OpFunctionParameter %uint
-;CHECK:         %48 = OpFunctionParameter %uint
-;CHECK:         %49 = OpFunctionParameter %uint
-;CHECK:         %50 = OpLabel
-;CHECK:         %56 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_0 %47
-;CHECK:         %57 = OpLoad %uint %56
-;CHECK:         %58 = OpIAdd %uint %57 %48
-;CHECK:         %59 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_0 %58
-;CHECK:         %60 = OpLoad %uint %59
-;CHECK:         %61 = OpIAdd %uint %60 %49
-;CHECK:         %62 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_0 %61
-;CHECK:         %63 = OpLoad %uint %62
-;CHECK:               OpReturnValue %63
-;CHECK:               OpFunctionEnd
-;CHECK:         %71 = OpFunction %void None %72
-;CHECK:         %73 = OpFunctionParameter %uint
-;CHECK:         %74 = OpFunctionParameter %uint
-;CHECK:         %75 = OpFunctionParameter %uint
-;CHECK:         %76 = OpFunctionParameter %uint
-;CHECK:         %77 = OpFunctionParameter %uint
-;CHECK:         %78 = OpLabel
-;CHECK:         %82 = OpAccessChain %_ptr_StorageBuffer_uint %81 %uint_0
-;CHECK:         %84 = OpAtomicIAdd %uint %82 %uint_4 %uint_0 %uint_11
-;CHECK:         %85 = OpIAdd %uint %84 %uint_11
-;CHECK:         %86 = OpArrayLength %uint %81 1
-;CHECK:         %87 = OpULessThanEqual %bool %85 %86
-;CHECK:               OpSelectionMerge %88 None
-;CHECK:               OpBranchConditional %87 %89 %88
-;CHECK:         %89 = OpLabel
-;CHECK:         %90 = OpIAdd %uint %84 %uint_0
-;CHECK:         %91 = OpAccessChain %_ptr_StorageBuffer_uint %81 %uint_1 %90
-;CHECK:               OpStore %91 %uint_11
-;CHECK:         %93 = OpIAdd %uint %84 %uint_1
-;CHECK:         %94 = OpAccessChain %_ptr_StorageBuffer_uint %81 %uint_1 %93
-;CHECK:               OpStore %94 %uint_23
-;CHECK:         %96 = OpIAdd %uint %84 %uint_2
-;CHECK:         %97 = OpAccessChain %_ptr_StorageBuffer_uint %81 %uint_1 %96
-;CHECK:               OpStore %97 %73
-;CHECK:         %98 = OpIAdd %uint %84 %uint_3
-;CHECK:         %99 = OpAccessChain %_ptr_StorageBuffer_uint %81 %uint_1 %98
-;CHECK:               OpStore %99 %uint_0
-;CHECK:        %102 = OpLoad %uint %gl_VertexIndex
-;CHECK:        %103 = OpIAdd %uint %84 %uint_4
-;CHECK:        %104 = OpAccessChain %_ptr_StorageBuffer_uint %81 %uint_1 %103
-;CHECK:               OpStore %104 %102
-;CHECK:        %106 = OpLoad %uint %gl_InstanceIndex
-;CHECK:        %108 = OpIAdd %uint %84 %uint_5
-;CHECK:        %109 = OpAccessChain %_ptr_StorageBuffer_uint %81 %uint_1 %108
-;CHECK:               OpStore %109 %106
-;CHECK:        %111 = OpIAdd %uint %84 %uint_7
-;CHECK:        %112 = OpAccessChain %_ptr_StorageBuffer_uint %81 %uint_1 %111
-;CHECK:               OpStore %112 %74
-;CHECK:        %114 = OpIAdd %uint %84 %uint_8
-;CHECK:        %115 = OpAccessChain %_ptr_StorageBuffer_uint %81 %uint_1 %114
-;CHECK:               OpStore %115 %75
-;CHECK:        %117 = OpIAdd %uint %84 %uint_9
-;CHECK:        %118 = OpAccessChain %_ptr_StorageBuffer_uint %81 %uint_1 %117
-;CHECK:               OpStore %118 %76
-;CHECK:        %120 = OpIAdd %uint %84 %uint_10
-;CHECK:        %121 = OpAccessChain %_ptr_StorageBuffer_uint %81 %uint_1 %120
-;CHECK:               OpStore %121 %77
-;CHECK:               OpBranch %88
-;CHECK:         %88 = OpLabel
-;CHECK:               OpReturn
-;CHECK:               OpFunctionEnd
- )";
+               )" + kDirectRead3 + kStreamWrite5Vert;
+  // clang-format on
 
   SetTargetEnv(SPV_ENV_VULKAN_1_2);
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
@@ -9136,6 +5546,7 @@
   //    x = imageLoad(s, ii);
   // }
 
+  // clang-format off
   const std::string text = R"(
                           OpCapability Shader
                           OpCapability ImageBuffer
@@ -9157,11 +5568,7 @@
                           OpDecorate %ii Flat
                           OpDecorate %ii Location 13
 ;CHECK:                   OpDecorate %_runtimearr_uint ArrayStride 4
-;CHECK:                   OpDecorate %_struct_43 Block
-;CHECK:                   OpMemberDecorate %_struct_43 0 Offset 0
-;CHECK:                   OpMemberDecorate %_struct_43 1 Offset 4
-;CHECK:                   OpDecorate %45 DescriptorSet 7
-;CHECK:                   OpDecorate %45 Binding 0
+)" + kOutputDecorations + R"(
 ;CHECK:                   OpDecorate %gl_FragCoord BuiltIn FragCoord
                   %void = OpTypeVoid
                      %3 = OpTypeFunction %void
@@ -9181,9 +5588,7 @@
 ;CHECK:         %uint_7 = OpConstant %uint 7
 ;CHECK:             %35 = OpTypeFunction %void %uint %uint %uint %uint %uint
 ;CHECK:    %_runtimearr_uint = OpTypeRuntimeArray %uint
-;CHECK:     %_struct_43 = OpTypeStruct %uint %_runtimearr_uint
-;CHECK:    %_ptr_StorageBuffer__struct_43 = OpTypePointer StorageBuffer %_struct_43
-;CHECK:             %45 = OpVariable %_ptr_StorageBuffer__struct_43 StorageBuffer
+)" + kOutputGlobals + R"(
 ;CHECK:    %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
 ;CHECK:        %uint_11 = OpConstant %uint 11
 ;CHECK:         %uint_4 = OpConstant %uint 4
@@ -9224,67 +5629,15 @@
 ;CHECK:             %33 = OpImageRead %v4float %32 %17
 ;CHECK:                   OpBranch %29
 ;CHECK:             %31 = OpLabel
-;CHECK:             %92 = OpFunctionCall %void %34 %uint_33 %uint_7 %uint_0 %23 %25
+;CHECK:             %92 = OpFunctionCall %void %inst_bindless_stream_write_5 %uint_33 %uint_7 %uint_0 %23 %25
 ;CHECK:                   OpBranch %29
 ;CHECK:             %29 = OpLabel
 ;CHECK:             %94 = OpPhi %v4float %33 %30 %93 %31
 ;CHECK:                   OpStore %x %94
                           OpReturn
                           OpFunctionEnd
-;CHECK:             %34 = OpFunction %void None %35
-;CHECK:             %36 = OpFunctionParameter %uint
-;CHECK:             %37 = OpFunctionParameter %uint
-;CHECK:             %38 = OpFunctionParameter %uint
-;CHECK:             %39 = OpFunctionParameter %uint
-;CHECK:             %40 = OpFunctionParameter %uint
-;CHECK:             %41 = OpLabel
-;CHECK:             %47 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_0
-;CHECK:             %50 = OpAtomicIAdd %uint %47 %uint_4 %uint_0 %uint_11
-;CHECK:             %51 = OpIAdd %uint %50 %uint_11
-;CHECK:             %52 = OpArrayLength %uint %45 1
-;CHECK:             %53 = OpULessThanEqual %bool %51 %52
-;CHECK:                   OpSelectionMerge %54 None
-;CHECK:                   OpBranchConditional %53 %55 %54
-;CHECK:             %55 = OpLabel
-;CHECK:             %56 = OpIAdd %uint %50 %uint_0
-;CHECK:             %58 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %56
-;CHECK:                   OpStore %58 %uint_11
-;CHECK:             %60 = OpIAdd %uint %50 %uint_1
-;CHECK:             %61 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %60
-;CHECK:                   OpStore %61 %uint_23
-;CHECK:             %63 = OpIAdd %uint %50 %uint_2
-;CHECK:             %64 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %63
-;CHECK:                   OpStore %64 %36
-;CHECK:             %66 = OpIAdd %uint %50 %uint_3
-;CHECK:             %67 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %66
-;CHECK:                   OpStore %67 %uint_4
-;CHECK:             %70 = OpLoad %v4float %gl_FragCoord
-;CHECK:             %72 = OpBitcast %v4uint %70
-;CHECK:             %73 = OpCompositeExtract %uint %72 0
-;CHECK:             %74 = OpIAdd %uint %50 %uint_4
-;CHECK:             %75 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %74
-;CHECK:                   OpStore %75 %73
-;CHECK:             %76 = OpCompositeExtract %uint %72 1
-;CHECK:             %78 = OpIAdd %uint %50 %uint_5
-;CHECK:             %79 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %78
-;CHECK:                   OpStore %79 %76
-;CHECK:             %80 = OpIAdd %uint %50 %uint_7
-;CHECK:             %81 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %80
-;CHECK:                   OpStore %81 %37
-;CHECK:             %83 = OpIAdd %uint %50 %uint_8
-;CHECK:             %84 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %83
-;CHECK:                   OpStore %84 %38
-;CHECK:             %86 = OpIAdd %uint %50 %uint_9
-;CHECK:             %87 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %86
-;CHECK:                   OpStore %87 %39
-;CHECK:             %89 = OpIAdd %uint %50 %uint_10
-;CHECK:             %90 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %89
-;CHECK:                   OpStore %90 %40
-;CHECK:                   OpBranch %54
-;CHECK:             %54 = OpLabel
-;CHECK:                   OpReturn
-;CHECK:                   OpFunctionEnd
-  )";
+                          )" + kStreamWrite5Frag;
+  // clang-format on
 
   SetTargetEnv(SPV_ENV_VULKAN_1_2);
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
@@ -9304,6 +5657,7 @@
   //    imageStore(s, ii, x);
   // }
 
+  // clang-format off
   const std::string text = R"(
                           OpCapability Shader
                           OpCapability ImageBuffer
@@ -9312,7 +5666,7 @@
                      %1 = OpExtInstImport "GLSL.std.450"
                           OpMemoryModel Logical GLSL450
                           OpEntryPoint Fragment %main "main" %s %ii %x
-;CHECK:                   OpEntryPoint Fragment %main "main" %s %ii %x %44 %gl_FragCoord
+;CHECK:                   OpEntryPoint Fragment %main "main" %s %ii %x %inst_bindless_output_buffer %gl_FragCoord
                           OpExecutionMode %main OriginUpperLeft
                           OpSource GLSL 450
                           OpName %main "main"
@@ -9326,11 +5680,7 @@
                           OpDecorate %ii Location 13
                           OpDecorate %x Location 11
 ;CHECK:                   OpDecorate %_runtimearr_uint ArrayStride 4
-;CHECK:                   OpDecorate %_struct_42 Block
-;CHECK:                   OpMemberDecorate %_struct_42 0 Offset 0
-;CHECK:                   OpMemberDecorate %_struct_42 1 Offset 4
-;CHECK:                   OpDecorate %44 DescriptorSet 7
-;CHECK:                   OpDecorate %44 Binding 0
+)" + kOutputDecorations + R"(
 ;CHECK:                   OpDecorate %gl_FragCoord BuiltIn FragCoord
                   %void = OpTypeVoid
                      %3 = OpTypeFunction %void
@@ -9350,9 +5700,7 @@
 ;CHECK:         %uint_7 = OpConstant %uint 7
 ;CHECK:             %34 = OpTypeFunction %void %uint %uint %uint %uint %uint
 ;CHECK:    %_runtimearr_uint = OpTypeRuntimeArray %uint
-;CHECK:     %_struct_42 = OpTypeStruct %uint %_runtimearr_uint
-;CHECK:    %_ptr_StorageBuffer__struct_42 = OpTypePointer StorageBuffer %_struct_42
-;CHECK:             %44 = OpVariable %_ptr_StorageBuffer__struct_42 StorageBuffer
+)" + kOutputGlobals + R"(
 ;CHECK:    %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
 ;CHECK:        %uint_11 = OpConstant %uint 11
 ;CHECK:         %uint_4 = OpConstant %uint 4
@@ -9391,65 +5739,13 @@
 ;CHECK:                   OpImageWrite %32 %14 %18
 ;CHECK:                   OpBranch %29
 ;CHECK:             %31 = OpLabel
-;CHECK:             %91 = OpFunctionCall %void %33 %uint_34 %uint_7 %uint_0 %23 %25
+;CHECK:             %91 = OpFunctionCall %void %inst_bindless_stream_write_5 %uint_34 %uint_7 %uint_0 %23 %25
 ;CHECK:                   OpBranch %29
 ;CHECK:             %29 = OpLabel
                           OpReturn
                           OpFunctionEnd
-;CHECK:             %33 = OpFunction %void None %34
-;CHECK:             %35 = OpFunctionParameter %uint
-;CHECK:             %36 = OpFunctionParameter %uint
-;CHECK:             %37 = OpFunctionParameter %uint
-;CHECK:             %38 = OpFunctionParameter %uint
-;CHECK:             %39 = OpFunctionParameter %uint
-;CHECK:             %40 = OpLabel
-;CHECK:             %46 = OpAccessChain %_ptr_StorageBuffer_uint %44 %uint_0
-;CHECK:             %49 = OpAtomicIAdd %uint %46 %uint_4 %uint_0 %uint_11
-;CHECK:             %50 = OpIAdd %uint %49 %uint_11
-;CHECK:             %51 = OpArrayLength %uint %44 1
-;CHECK:             %52 = OpULessThanEqual %bool %50 %51
-;CHECK:                   OpSelectionMerge %53 None
-;CHECK:                   OpBranchConditional %52 %54 %53
-;CHECK:             %54 = OpLabel
-;CHECK:             %55 = OpIAdd %uint %49 %uint_0
-;CHECK:             %57 = OpAccessChain %_ptr_StorageBuffer_uint %44 %uint_1 %55
-;CHECK:                   OpStore %57 %uint_11
-;CHECK:             %59 = OpIAdd %uint %49 %uint_1
-;CHECK:             %60 = OpAccessChain %_ptr_StorageBuffer_uint %44 %uint_1 %59
-;CHECK:                   OpStore %60 %uint_23
-;CHECK:             %62 = OpIAdd %uint %49 %uint_2
-;CHECK:             %63 = OpAccessChain %_ptr_StorageBuffer_uint %44 %uint_1 %62
-;CHECK:                   OpStore %63 %35
-;CHECK:             %65 = OpIAdd %uint %49 %uint_3
-;CHECK:             %66 = OpAccessChain %_ptr_StorageBuffer_uint %44 %uint_1 %65
-;CHECK:                   OpStore %66 %uint_4
-;CHECK:             %69 = OpLoad %v4float %gl_FragCoord
-;CHECK:             %71 = OpBitcast %v4uint %69
-;CHECK:             %72 = OpCompositeExtract %uint %71 0
-;CHECK:             %73 = OpIAdd %uint %49 %uint_4
-;CHECK:             %74 = OpAccessChain %_ptr_StorageBuffer_uint %44 %uint_1 %73
-;CHECK:                   OpStore %74 %72
-;CHECK:             %75 = OpCompositeExtract %uint %71 1
-;CHECK:             %77 = OpIAdd %uint %49 %uint_5
-;CHECK:             %78 = OpAccessChain %_ptr_StorageBuffer_uint %44 %uint_1 %77
-;CHECK:                   OpStore %78 %75
-;CHECK:             %79 = OpIAdd %uint %49 %uint_7
-;CHECK:             %80 = OpAccessChain %_ptr_StorageBuffer_uint %44 %uint_1 %79
-;CHECK:                   OpStore %80 %36
-;CHECK:             %82 = OpIAdd %uint %49 %uint_8
-;CHECK:             %83 = OpAccessChain %_ptr_StorageBuffer_uint %44 %uint_1 %82
-;CHECK:                   OpStore %83 %37
-;CHECK:             %85 = OpIAdd %uint %49 %uint_9
-;CHECK:             %86 = OpAccessChain %_ptr_StorageBuffer_uint %44 %uint_1 %85
-;CHECK:                   OpStore %86 %38
-;CHECK:             %88 = OpIAdd %uint %49 %uint_10
-;CHECK:             %89 = OpAccessChain %_ptr_StorageBuffer_uint %44 %uint_1 %88
-;CHECK:                   OpStore %89 %39
-;CHECK:                   OpBranch %53
-;CHECK:             %53 = OpLabel
-;CHECK:                   OpReturn
-;CHECK:                   OpFunctionEnd
-  )";
+                          )" + kStreamWrite5Frag;
+  // clang-format on
 
   SetTargetEnv(SPV_ENV_VULKAN_1_2);
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
@@ -9469,6 +5765,7 @@
   //    x = texelFetch(s, ii);
   // }
 
+  // clang-format off
   const std::string text = R"(
                           OpCapability Shader
                           OpCapability SampledBuffer
@@ -9477,7 +5774,7 @@
                      %1 = OpExtInstImport "GLSL.std.450"
                           OpMemoryModel Logical GLSL450
                           OpEntryPoint Fragment %main "main" %x %s %ii
-;CHECK:                   OpEntryPoint Fragment %main "main" %x %s %ii %45 %gl_FragCoord
+;CHECK:                   OpEntryPoint Fragment %main "main" %x %s %ii %inst_bindless_output_buffer %gl_FragCoord
                           OpExecutionMode %main OriginUpperLeft
                           OpSource GLSL 450
                           OpName %main "main"
@@ -9490,11 +5787,7 @@
                           OpDecorate %ii Flat
                           OpDecorate %ii Location 13
 ;CHECK:                   OpDecorate %_runtimearr_uint ArrayStride 4
-;CHECK:                   OpDecorate %_struct_43 Block
-;CHECK:                   OpMemberDecorate %_struct_43 0 Offset 0
-;CHECK:                   OpMemberDecorate %_struct_43 1 Offset 4
-;CHECK:                   OpDecorate %45 DescriptorSet 7
-;CHECK:                   OpDecorate %45 Binding 0
+)" + kOutputDecorations + R"(
 ;CHECK:                   OpDecorate %gl_FragCoord BuiltIn FragCoord
                   %void = OpTypeVoid
                      %3 = OpTypeFunction %void
@@ -9514,9 +5807,7 @@
 ;CHECK:         %uint_6 = OpConstant %uint 6
 ;CHECK:             %35 = OpTypeFunction %void %uint %uint %uint %uint %uint
 ;CHECK:    %_runtimearr_uint = OpTypeRuntimeArray %uint
-;CHECK:     %_struct_43 = OpTypeStruct %uint %_runtimearr_uint
-;CHECK:    %_ptr_StorageBuffer__struct_43 = OpTypePointer StorageBuffer %_struct_43
-;CHECK:             %45 = OpVariable %_ptr_StorageBuffer__struct_43 StorageBuffer
+)" + kOutputGlobals + R"(
 ;CHECK:    %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
 ;CHECK:        %uint_11 = OpConstant %uint 11
 ;CHECK:         %uint_4 = OpConstant %uint 4
@@ -9558,67 +5849,15 @@
 ;CHECK:             %33 = OpImageFetch %v4float %32 %17
 ;CHECK:                   OpBranch %29
 ;CHECK:             %31 = OpLabel
-;CHECK:             %93 = OpFunctionCall %void %34 %uint_32 %uint_6 %uint_0 %23 %25
+;CHECK:             %93 = OpFunctionCall %void %inst_bindless_stream_write_5 %uint_32 %uint_6 %uint_0 %23 %25
 ;CHECK:                   OpBranch %29
 ;CHECK:             %29 = OpLabel
 ;CHECK:             %95 = OpPhi %v4float %33 %30 %94 %31
 ;CHECK:                   OpStore %x %95
                           OpReturn
                           OpFunctionEnd
-;CHECK:             %34 = OpFunction %void None %35
-;CHECK:             %36 = OpFunctionParameter %uint
-;CHECK:             %37 = OpFunctionParameter %uint
-;CHECK:             %38 = OpFunctionParameter %uint
-;CHECK:             %39 = OpFunctionParameter %uint
-;CHECK:             %40 = OpFunctionParameter %uint
-;CHECK:             %41 = OpLabel
-;CHECK:             %47 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_0
-;CHECK:             %50 = OpAtomicIAdd %uint %47 %uint_4 %uint_0 %uint_11
-;CHECK:             %51 = OpIAdd %uint %50 %uint_11
-;CHECK:             %52 = OpArrayLength %uint %45 1
-;CHECK:             %53 = OpULessThanEqual %bool %51 %52
-;CHECK:                   OpSelectionMerge %54 None
-;CHECK:                   OpBranchConditional %53 %55 %54
-;CHECK:             %55 = OpLabel
-;CHECK:             %56 = OpIAdd %uint %50 %uint_0
-;CHECK:             %58 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %56
-;CHECK:                   OpStore %58 %uint_11
-;CHECK:             %60 = OpIAdd %uint %50 %uint_1
-;CHECK:             %61 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %60
-;CHECK:                   OpStore %61 %uint_23
-;CHECK:             %63 = OpIAdd %uint %50 %uint_2
-;CHECK:             %64 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %63
-;CHECK:                   OpStore %64 %36
-;CHECK:             %66 = OpIAdd %uint %50 %uint_3
-;CHECK:             %67 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %66
-;CHECK:                   OpStore %67 %uint_4
-;CHECK:             %70 = OpLoad %v4float %gl_FragCoord
-;CHECK:             %72 = OpBitcast %v4uint %70
-;CHECK:             %73 = OpCompositeExtract %uint %72 0
-;CHECK:             %74 = OpIAdd %uint %50 %uint_4
-;CHECK:             %75 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %74
-;CHECK:                   OpStore %75 %73
-;CHECK:             %76 = OpCompositeExtract %uint %72 1
-;CHECK:             %78 = OpIAdd %uint %50 %uint_5
-;CHECK:             %79 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %78
-;CHECK:                   OpStore %79 %76
-;CHECK:             %81 = OpIAdd %uint %50 %uint_7
-;CHECK:             %82 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %81
-;CHECK:                   OpStore %82 %37
-;CHECK:             %84 = OpIAdd %uint %50 %uint_8
-;CHECK:             %85 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %84
-;CHECK:                   OpStore %85 %38
-;CHECK:             %87 = OpIAdd %uint %50 %uint_9
-;CHECK:             %88 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %87
-;CHECK:                   OpStore %88 %39
-;CHECK:             %90 = OpIAdd %uint %50 %uint_10
-;CHECK:             %91 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %90
-;CHECK:                   OpStore %91 %40
-;CHECK:                   OpBranch %54
-;CHECK:             %54 = OpLabel
-;CHECK:                   OpReturn
-;CHECK:                   OpFunctionEnd
-  )";
+                          )" + kStreamWrite5Frag;
+  // clang-format on
 
   SetTargetEnv(SPV_ENV_VULKAN_1_2);
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
@@ -9638,6 +5877,7 @@
   //    x = texelFetch(s, ii);
   // }
 
+  // clang-format off
   const std::string text = R"(
                           OpCapability Shader
                           OpCapability SampledBuffer
@@ -9646,7 +5886,7 @@
                      %1 = OpExtInstImport "GLSL.std.450"
                           OpMemoryModel Logical GLSL450
                           OpEntryPoint Fragment %main "main" %x %s %ii
-;CHECK:                   OpEntryPoint Fragment %main "main" %x %s %ii %48 %gl_FragCoord
+;CHECK:                   OpEntryPoint Fragment %main "main" %x %s %ii %inst_bindless_output_buffer %gl_FragCoord
                           OpExecutionMode %main OriginUpperLeft
                           OpSource GLSL 450
                           OpName %main "main"
@@ -9659,11 +5899,7 @@
                           OpDecorate %ii Flat
                           OpDecorate %ii Location 13
 ;CHECK:                   OpDecorate %_runtimearr_uint ArrayStride 4
-;CHECK:                   OpDecorate %_struct_46 Block
-;CHECK:                   OpMemberDecorate %_struct_46 0 Offset 0
-;CHECK:                   OpMemberDecorate %_struct_46 1 Offset 4
-;CHECK:                   OpDecorate %48 DescriptorSet 7
-;CHECK:                   OpDecorate %48 Binding 0
+)" + kOutputDecorations + R"(
 ;CHECK:                   OpDecorate %gl_FragCoord BuiltIn FragCoord
                   %void = OpTypeVoid
                      %3 = OpTypeFunction %void
@@ -9684,9 +5920,7 @@
 ;CHECK:         %uint_6 = OpConstant %uint 6
 ;CHECK:             %38 = OpTypeFunction %void %uint %uint %uint %uint %uint
 ;CHECK:    %_runtimearr_uint = OpTypeRuntimeArray %uint
-;CHECK:     %_struct_46 = OpTypeStruct %uint %_runtimearr_uint
-;CHECK:    %_ptr_StorageBuffer__struct_46 = OpTypePointer StorageBuffer %_struct_46
-;CHECK:             %48 = OpVariable %_ptr_StorageBuffer__struct_46 StorageBuffer
+)" + kOutputGlobals + R"(
 ;CHECK:    %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
 ;CHECK:        %uint_11 = OpConstant %uint 11
 ;CHECK:         %uint_4 = OpConstant %uint 4
@@ -9730,67 +5964,15 @@
 ;CHECK:             %36 = OpImageFetch %v4float %35 %18
 ;CHECK:                   OpBranch %31
 ;CHECK:             %33 = OpLabel
-;CHECK:             %96 = OpFunctionCall %void %37 %uint_34 %uint_6 %uint_0 %25 %27
+;CHECK:             %96 = OpFunctionCall %void %inst_bindless_stream_write_5 %uint_34 %uint_6 %uint_0 %25 %27
 ;CHECK:                   OpBranch %31
 ;CHECK:             %31 = OpLabel
 ;CHECK:             %98 = OpPhi %v4float %36 %32 %97 %33
 ;CHECK:                   OpStore %x %98
                           OpReturn
                           OpFunctionEnd
-;CHECK:             %37 = OpFunction %void None %38
-;CHECK:             %39 = OpFunctionParameter %uint
-;CHECK:             %40 = OpFunctionParameter %uint
-;CHECK:             %41 = OpFunctionParameter %uint
-;CHECK:             %42 = OpFunctionParameter %uint
-;CHECK:             %43 = OpFunctionParameter %uint
-;CHECK:             %44 = OpLabel
-;CHECK:             %50 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_0
-;CHECK:             %53 = OpAtomicIAdd %uint %50 %uint_4 %uint_0 %uint_11
-;CHECK:             %54 = OpIAdd %uint %53 %uint_11
-;CHECK:             %55 = OpArrayLength %uint %48 1
-;CHECK:             %56 = OpULessThanEqual %bool %54 %55
-;CHECK:                   OpSelectionMerge %57 None
-;CHECK:                   OpBranchConditional %56 %58 %57
-;CHECK:             %58 = OpLabel
-;CHECK:             %59 = OpIAdd %uint %53 %uint_0
-;CHECK:             %61 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_1 %59
-;CHECK:                   OpStore %61 %uint_11
-;CHECK:             %63 = OpIAdd %uint %53 %uint_1
-;CHECK:             %64 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_1 %63
-;CHECK:                   OpStore %64 %uint_23
-;CHECK:             %66 = OpIAdd %uint %53 %uint_2
-;CHECK:             %67 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_1 %66
-;CHECK:                   OpStore %67 %39
-;CHECK:             %69 = OpIAdd %uint %53 %uint_3
-;CHECK:             %70 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_1 %69
-;CHECK:                   OpStore %70 %uint_4
-;CHECK:             %73 = OpLoad %v4float %gl_FragCoord
-;CHECK:             %75 = OpBitcast %v4uint %73
-;CHECK:             %76 = OpCompositeExtract %uint %75 0
-;CHECK:             %77 = OpIAdd %uint %53 %uint_4
-;CHECK:             %78 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_1 %77
-;CHECK:                   OpStore %78 %76
-;CHECK:             %79 = OpCompositeExtract %uint %75 1
-;CHECK:             %81 = OpIAdd %uint %53 %uint_5
-;CHECK:             %82 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_1 %81
-;CHECK:                   OpStore %82 %79
-;CHECK:             %84 = OpIAdd %uint %53 %uint_7
-;CHECK:             %85 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_1 %84
-;CHECK:                   OpStore %85 %40
-;CHECK:             %87 = OpIAdd %uint %53 %uint_8
-;CHECK:             %88 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_1 %87
-;CHECK:                   OpStore %88 %41
-;CHECK:             %90 = OpIAdd %uint %53 %uint_9
-;CHECK:             %91 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_1 %90
-;CHECK:                   OpStore %91 %42
-;CHECK:             %93 = OpIAdd %uint %53 %uint_10
-;CHECK:             %94 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_1 %93
-;CHECK:                   OpStore %94 %43
-;CHECK:                   OpBranch %57
-;CHECK:             %57 = OpLabel
-;CHECK:                   OpReturn
-;CHECK:                   OpFunctionEnd
-  )";
+                          )" + kStreamWrite5Frag;
+  // clang-format on
 
   SetTargetEnv(SPV_ENV_VULKAN_1_2);
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
@@ -9811,6 +5993,7 @@
   //    x = texelFetch(samplerBuffer(tBuf, s), ii);
   // }
 
+  // clang-format off
   const std::string text = R"(
                           OpCapability Shader
                           OpCapability SampledBuffer
@@ -9819,7 +6002,7 @@
                      %1 = OpExtInstImport "GLSL.std.450"
                           OpMemoryModel Logical GLSL450
                           OpEntryPoint Fragment %main "main" %x %tBuf %s %ii
-;CHECK:                   OpEntryPoint Fragment %main "main" %x %tBuf %s %ii %54 %gl_FragCoord
+;CHECK:                   OpEntryPoint Fragment %main "main" %x %tBuf %s %ii %inst_bindless_output_buffer %gl_FragCoord
                           OpExecutionMode %main OriginUpperLeft
                           OpSource GLSL 450
                           OpName %main "main"
@@ -9835,11 +6018,7 @@
                           OpDecorate %ii Flat
                           OpDecorate %ii Location 13
 ;CHECK:                   OpDecorate %_runtimearr_uint ArrayStride 4
-;CHECK:                   OpDecorate %_struct_52 Block
-;CHECK:                   OpMemberDecorate %_struct_52 0 Offset 0
-;CHECK:                   OpMemberDecorate %_struct_52 1 Offset 4
-;CHECK:                   OpDecorate %54 DescriptorSet 7
-;CHECK:                   OpDecorate %54 Binding 0
+)" + kOutputDecorations + R"(
 ;CHECK:                   OpDecorate %gl_FragCoord BuiltIn FragCoord
                   %void = OpTypeVoid
                      %3 = OpTypeFunction %void
@@ -9863,9 +6042,7 @@
 ;CHECK:         %uint_6 = OpConstant %uint 6
 ;CHECK:             %44 = OpTypeFunction %void %uint %uint %uint %uint %uint
 ;CHECK:    %_runtimearr_uint = OpTypeRuntimeArray %uint
-;CHECK:     %_struct_52 = OpTypeStruct %uint %_runtimearr_uint
-;CHECK:    %_ptr_StorageBuffer__struct_52 = OpTypePointer StorageBuffer %_struct_52
-;CHECK:             %54 = OpVariable %_ptr_StorageBuffer__struct_52 StorageBuffer
+)" + kOutputGlobals + R"(
 ;CHECK:    %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
 ;CHECK:        %uint_11 = OpConstant %uint 11
 ;CHECK:         %uint_4 = OpConstant %uint 4
@@ -9912,67 +6089,15 @@
 ;CHECK:             %42 = OpImageFetch %v4float %41 %23
 ;CHECK:                   OpBranch %36
 ;CHECK:             %38 = OpLabel
-;CHECK:            %102 = OpFunctionCall %void %43 %uint_42 %uint_6 %uint_0 %30 %32
+;CHECK:            %102 = OpFunctionCall %void %inst_bindless_stream_write_5 %uint_42 %uint_6 %uint_0 %30 %32
 ;CHECK:                   OpBranch %36
 ;CHECK:             %36 = OpLabel
 ;CHECK:            %104 = OpPhi %v4float %42 %37 %103 %38
 ;CHECK:                   OpStore %x %104
                           OpReturn
                           OpFunctionEnd
-;CHECK:             %43 = OpFunction %void None %44
-;CHECK:             %45 = OpFunctionParameter %uint
-;CHECK:             %46 = OpFunctionParameter %uint
-;CHECK:             %47 = OpFunctionParameter %uint
-;CHECK:             %48 = OpFunctionParameter %uint
-;CHECK:             %49 = OpFunctionParameter %uint
-;CHECK:             %50 = OpLabel
-;CHECK:             %56 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_0
-;CHECK:             %59 = OpAtomicIAdd %uint %56 %uint_4 %uint_0 %uint_11
-;CHECK:             %60 = OpIAdd %uint %59 %uint_11
-;CHECK:             %61 = OpArrayLength %uint %54 1
-;CHECK:             %62 = OpULessThanEqual %bool %60 %61
-;CHECK:                   OpSelectionMerge %63 None
-;CHECK:                   OpBranchConditional %62 %64 %63
-;CHECK:             %64 = OpLabel
-;CHECK:             %65 = OpIAdd %uint %59 %uint_0
-;CHECK:             %67 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_1 %65
-;CHECK:                   OpStore %67 %uint_11
-;CHECK:             %69 = OpIAdd %uint %59 %uint_1
-;CHECK:             %70 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_1 %69
-;CHECK:                   OpStore %70 %uint_23
-;CHECK:             %72 = OpIAdd %uint %59 %uint_2
-;CHECK:             %73 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_1 %72
-;CHECK:                   OpStore %73 %45
-;CHECK:             %75 = OpIAdd %uint %59 %uint_3
-;CHECK:             %76 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_1 %75
-;CHECK:                   OpStore %76 %uint_4
-;CHECK:             %79 = OpLoad %v4float %gl_FragCoord
-;CHECK:             %81 = OpBitcast %v4uint %79
-;CHECK:             %82 = OpCompositeExtract %uint %81 0
-;CHECK:             %83 = OpIAdd %uint %59 %uint_4
-;CHECK:             %84 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_1 %83
-;CHECK:                   OpStore %84 %82
-;CHECK:             %85 = OpCompositeExtract %uint %81 1
-;CHECK:             %87 = OpIAdd %uint %59 %uint_5
-;CHECK:             %88 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_1 %87
-;CHECK:                   OpStore %88 %85
-;CHECK:             %90 = OpIAdd %uint %59 %uint_7
-;CHECK:             %91 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_1 %90
-;CHECK:                   OpStore %91 %46
-;CHECK:             %93 = OpIAdd %uint %59 %uint_8
-;CHECK:             %94 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_1 %93
-;CHECK:                   OpStore %94 %47
-;CHECK:             %96 = OpIAdd %uint %59 %uint_9
-;CHECK:             %97 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_1 %96
-;CHECK:                   OpStore %97 %48
-;CHECK:             %99 = OpIAdd %uint %59 %uint_10
-;CHECK:             %100 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_1 %99
-;CHECK:                   OpStore %100 %49
-;CHECK:                   OpBranch %63
-;CHECK:             %63 = OpLabel
-;CHECK:                   OpReturn
-;CHECK:                   OpFunctionEnd
-  )";
+                          )" + kStreamWrite5Frag;
+  // clang-format on
 
   SetTargetEnv(SPV_ENV_VULKAN_1_2);
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
@@ -9984,8 +6109,8 @@
 //
 //   Compute shader
 //   Geometry shader
-//   Tesselation control shader
-//   Tesselation eval shader
+//   Tessellation control shader
+//   Tessellation eval shader
 //   OpImage
 //   SampledImage variable
 
diff --git a/test/opt/inst_buff_addr_check_test.cpp b/test/opt/inst_buff_addr_check_test.cpp
index 95114b2..e095eb7 100644
--- a/test/opt/inst_buff_addr_check_test.cpp
+++ b/test/opt/inst_buff_addr_check_test.cpp
@@ -1,5 +1,5 @@
-// Copyright (c) 2019 Valve Corporation
-// Copyright (c) 2019 LunarG Inc.
+// Copyright (c) 2019-2022 Valve Corporation
+// Copyright (c) 2019-2022 LunarG Inc.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -27,6 +27,148 @@
 namespace opt {
 namespace {
 
+static const std::string kOutputDecorations = R"(
+; CHECK: OpDecorate [[output_buffer_type:%inst_buff_addr_OutputBuffer]] Block
+; CHECK: OpMemberDecorate [[output_buffer_type]] 0 Offset 0
+; CHECK: OpMemberDecorate [[output_buffer_type]] 1 Offset 4
+; CHECK: OpDecorate [[output_buffer_var:%\w+]] DescriptorSet 7
+; CHECK: OpDecorate [[output_buffer_var]] Binding 0
+)";
+
+static const std::string kOutputGlobals = R"(
+; CHECK: [[output_buffer_type]] = OpTypeStruct %uint %_runtimearr_uint
+; CHECK: [[output_ptr_type:%\w+]] = OpTypePointer StorageBuffer [[output_buffer_type]]
+; CHECK: [[output_buffer_var]] = OpVariable [[output_ptr_type]] StorageBuffer
+)";
+
+static const std::string kStreamWrite4Begin = R"(
+; CHECK: {{%\w+}} = OpFunction %void None {{%\w+}}
+; CHECK: [[param_1:%\w+]] = OpFunctionParameter %uint
+; CHECK: [[param_2:%\w+]] = OpFunctionParameter %uint
+; CHECK: [[param_3:%\w+]] = OpFunctionParameter %uint
+; CHECK: [[param_4:%\w+]] = OpFunctionParameter %uint
+; CHECK: {{%\w+}} = OpLabel
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_0
+; CHECK: {{%\w+}} = OpAtomicIAdd %uint {{%\w+}} %uint_4 %uint_0 %uint_10
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_10
+; CHECK: {{%\w+}} = OpArrayLength %uint [[output_buffer_var]] 1
+; CHECK: {{%\w+}} = OpULessThanEqual %bool {{%\w+}} {{%\w+}}
+; CHECK: OpSelectionMerge {{%\w+}} None
+; CHECK: OpBranchConditional {{%\w+}} {{%\w+}} {{%\w+}}
+; CHECK: {{%\w+}} = OpLabel
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_0
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} %uint_10
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_1
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} %uint_23
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_2
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} [[param_1]]
+)";
+
+static const std::string kStreamWrite4End = R"(
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_7
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} [[param_2]]
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_8
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} [[param_3]]
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_9
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} [[param_4]]
+; CHECK: OpBranch {{%\w+}}
+; CHECK: {{%\w+}} = OpLabel
+; CHECK: OpReturn
+; CHECK: OpFunctionEnd
+)";
+
+// clang-format off
+static const std::string kStreamWrite4Frag = kStreamWrite4Begin + R"(
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_3
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} %uint_4
+; CHECK: {{%\w+}} = OpLoad %v4float %gl_FragCoord
+; CHECK: {{%\w+}} = OpBitcast %v4uint {{%\w+}}
+; CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 0
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_4
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} {{%\w+}}
+; CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 1
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_5
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} {{%\w+}}
+)" + kStreamWrite4End;
+
+static const std::string kStreamWrite4Compute = kStreamWrite4Begin + R"(
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_3
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} %uint_5
+; CHECK: {{%\w+}} = OpLoad %v3uint %gl_GlobalInvocationID
+; CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 0
+; CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 1
+; CHECK: {{%\w+}} = OpCompositeExtract %uint {{%\w+}} 2
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_4
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} {{%\w+}}
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_5
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} {{%\w+}}
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_6
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_uint [[output_buffer_var]] %uint_1 {{%\w+}}
+; CHECK: OpStore {{%\w+}} {{%\w+}}
+)" + kStreamWrite4End;
+// clang-format on
+
+static const std::string kInputDecorations = R"(
+; CHECK: OpDecorate [[input_buffer_type:%inst_buff_addr_InputBuffer]] Block
+; CHECK: OpMemberDecorate [[input_buffer_type]] 0 Offset 0
+; CHECK: OpDecorate [[input_buffer_var:%\w+]] DescriptorSet 7
+; CHECK: OpDecorate [[input_buffer_var]] Binding 2
+)";
+
+static const std::string kInputGlobals = R"(
+; CHECK: [[input_buffer_type]] = OpTypeStruct %_runtimearr_ulong
+; CHECK: [[input_ptr_type:%\w+]] = OpTypePointer StorageBuffer [[input_buffer_type]]
+; CHECK: [[input_buffer_var]] = OpVariable [[input_ptr_type]] StorageBuffer
+)";
+
+static const std::string kSearchAndTest = R"(
+; CHECK: {{%\w+}} = OpFunction %bool None {{%\w+}}
+; CHECK: [[param_1:%\w+]] = OpFunctionParameter %ulong
+; CHECK: [[param_2:%\w+]] = OpFunctionParameter %uint
+; CHECK: {{%\w+}} = OpLabel
+; CHECK: OpBranch {{%\w+}}
+; CHECK: {{%\w+}} = OpLabel
+; CHECK: {{%\w+}} = OpPhi %uint %uint_1 {{%\w+}} {{%\w+}} {{%\w+}}
+; CHECK: OpLoopMerge {{%\w+}} {{%\w+}} None
+; CHECK: OpBranch {{%\w+}}
+; CHECK: {{%\w+}} = OpLabel
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} %uint_1
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_ulong [[input_buffer_var]] %uint_0 {{%\w+}}
+; CHECK: {{%\w+}} = OpLoad %ulong {{%\w+}}
+; CHECK: {{%\w+}} = OpUGreaterThan %bool {{%\w+}} [[param_1]]
+; CHECK: OpBranchConditional {{%\w+}} {{%\w+}} {{%\w+}}
+; CHECK: {{%\w+}} = OpLabel
+; CHECK: {{%\w+}} = OpISub %uint {{%\w+}} %uint_1
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_ulong [[input_buffer_var]] %uint_0 {{%\w+}}
+; CHECK: {{%\w+}} = OpLoad %ulong {{%\w+}}
+; CHECK: {{%\w+}} = OpISub %ulong [[param_1]] {{%\w+}}
+; CHECK: {{%\w+}} = OpUConvert %ulong [[param_2]]
+; CHECK: {{%\w+}} = OpIAdd %ulong {{%\w+}} {{%\w+}}
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_ulong [[input_buffer_var]] %uint_0 %uint_0
+; CHECK: {{%\w+}} = OpLoad %ulong {{%\w+}}
+; CHECK: {{%\w+}} = OpUConvert %uint {{%\w+}}
+; CHECK: {{%\w+}} = OpISub %uint {{%\w+}} %uint_1
+; CHECK: {{%\w+}} = OpIAdd %uint {{%\w+}} {{%\w+}}
+; CHECK: {{%\w+}} = OpAccessChain %_ptr_StorageBuffer_ulong [[input_buffer_var]] %uint_0 {{%\w+}}
+; CHECK: {{%\w+}} = OpLoad %ulong {{%\w+}}
+; CHECK: {{%\w+}} = OpULessThanEqual %bool {{%\w+}} {{%\w+}}
+; CHECK: OpReturnValue {{%\w+}}
+; CHECK: OpFunctionEnd
+)";
+// clang-format on
+
 using InstBuffAddrTest = PassTest<::testing::Test>;
 
 TEST_F(InstBuffAddrTest, InstPhysicalStorageBufferStore) {
@@ -49,13 +191,16 @@
   //     u_info.data.b = 0xca7;
   // }
 
-  const std::string defs_before =
-      R"(OpCapability Shader
+  const std::string defs = R"(
+OpCapability Shader
 OpCapability PhysicalStorageBufferAddresses
+; CHECK: OpCapability Int64
 OpExtension "SPV_EXT_physical_storage_buffer"
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel PhysicalStorageBuffer64 GLSL450
 OpEntryPoint GLCompute %main "main"
+; CHECK: OpEntryPoint GLCompute %main "main" %gl_GlobalInvocationID
 OpExecutionMode %main LocalSize 1 1 1
 OpSource GLSL 450
 OpSourceExtension "GL_EXT_buffer_reference"
@@ -67,6 +212,10 @@
 OpMemberName %bufStruct 0 "a"
 OpMemberName %bufStruct 1 "b"
 OpName %u_info "u_info"
+)";
+
+  // clang-format off
+  const std::string decorates = R"(
 OpMemberDecorate %ufoo 0 Offset 0
 OpMemberDecorate %ufoo 1 Offset 8
 OpDecorate %ufoo Block
@@ -76,6 +225,14 @@
 OpDecorate %bufStruct Block
 OpDecorate %u_info DescriptorSet 0
 OpDecorate %u_info Binding 0
+; CHECK: OpDecorate %_runtimearr_ulong ArrayStride 8
+)" + kInputDecorations + R"(
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+)" + kOutputDecorations + R"(
+; CHECK: OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
+)";
+
+  const std::string globals = R"(
 %void = OpTypeVoid
 %3 = OpTypeFunction %void
 OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_bufStruct PhysicalStorageBuffer
@@ -93,224 +250,71 @@
 %int_1 = OpConstant %int 1
 %int_3239 = OpConstant %int 3239
 %_ptr_PhysicalStorageBuffer_int = OpTypePointer PhysicalStorageBuffer %int
+; CHECK: %ulong = OpTypeInt 64 0
+; CHECK: %uint_4 = OpConstant %uint 4
+; CHECK: %bool = OpTypeBool
+; CHECK: %28 = OpTypeFunction %bool %ulong %uint
+; CHECK: %uint_1 = OpConstant %uint 1
+; CHECK: %_runtimearr_ulong = OpTypeRuntimeArray %ulong
+)" + kInputGlobals + R"(
+; CHECK: %_ptr_StorageBuffer_ulong = OpTypePointer StorageBuffer %ulong
+; CHECK: %uint_0 = OpConstant %uint 0
+; CHECK: %uint_32 = OpConstant %uint 32
+; CHECK: %70 = OpTypeFunction %void %uint %uint %uint %uint
+; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+)" + kOutputGlobals + R"(
+; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+; CHECK: %uint_10 = OpConstant %uint 10
+; CHECK: %uint_23 = OpConstant %uint 23
+; CHECK: %uint_5 = OpConstant %uint 5
+; CHECK: %uint_3 = OpConstant %uint 3
+; CHECK: %v3uint = OpTypeVector %uint 3
+; CHECK: %_ptr_Input_v3uint = OpTypePointer Input %v3uint
+; CHECK: %gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
+; CHECK: %uint_6 = OpConstant %uint 6
+; CHECK: %uint_7 = OpConstant %uint 7
+; CHECK: %uint_8 = OpConstant %uint 8
+; CHECK: %uint_9 = OpConstant %uint 9
+; CHECK: %uint_48 = OpConstant %uint 48
 )";
+// clang-format off
 
-  const std::string defs_after =
-      R"(OpCapability Shader
-OpCapability PhysicalStorageBufferAddresses
-OpCapability Int64
-OpExtension "SPV_EXT_physical_storage_buffer"
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel PhysicalStorageBuffer64 GLSL450
-OpEntryPoint GLCompute %main "main" %gl_GlobalInvocationID
-OpExecutionMode %main LocalSize 1 1 1
-OpSource GLSL 450
-OpSourceExtension "GL_EXT_buffer_reference"
-OpName %main "main"
-OpName %ufoo "ufoo"
-OpMemberName %ufoo 0 "data"
-OpMemberName %ufoo 1 "offset"
-OpName %bufStruct "bufStruct"
-OpMemberName %bufStruct 0 "a"
-OpMemberName %bufStruct 1 "b"
-OpName %u_info "u_info"
-OpMemberDecorate %ufoo 0 Offset 0
-OpMemberDecorate %ufoo 1 Offset 8
-OpDecorate %ufoo Block
-OpDecorate %_arr_int_uint_2 ArrayStride 16
-OpMemberDecorate %bufStruct 0 Offset 0
-OpMemberDecorate %bufStruct 1 Offset 32
-OpDecorate %bufStruct Block
-OpDecorate %u_info DescriptorSet 0
-OpDecorate %u_info Binding 0
-OpDecorate %_runtimearr_ulong ArrayStride 8
-OpDecorate %_struct_39 Block
-OpMemberDecorate %_struct_39 0 Offset 0
-OpDecorate %41 DescriptorSet 7
-OpDecorate %41 Binding 2
-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_GlobalInvocationID BuiltIn GlobalInvocationId
-%void = OpTypeVoid
-%8 = OpTypeFunction %void
-OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_bufStruct PhysicalStorageBuffer
-%uint = OpTypeInt 32 0
-%ufoo = OpTypeStruct %_ptr_PhysicalStorageBuffer_bufStruct %uint
-%int = OpTypeInt 32 1
-%uint_2 = OpConstant %uint 2
-%_arr_int_uint_2 = OpTypeArray %int %uint_2
-%bufStruct = OpTypeStruct %_arr_int_uint_2 %int
-%_ptr_PhysicalStorageBuffer_bufStruct = OpTypePointer PhysicalStorageBuffer %bufStruct
-%_ptr_Uniform_ufoo = OpTypePointer Uniform %ufoo
-%u_info = OpVariable %_ptr_Uniform_ufoo Uniform
-%int_0 = OpConstant %int 0
-%_ptr_Uniform__ptr_PhysicalStorageBuffer_bufStruct = OpTypePointer Uniform %_ptr_PhysicalStorageBuffer_bufStruct
-%int_1 = OpConstant %int 1
-%int_3239 = OpConstant %int 3239
-%_ptr_PhysicalStorageBuffer_int = OpTypePointer PhysicalStorageBuffer %int
-%ulong = OpTypeInt 64 0
-%uint_4 = OpConstant %uint 4
-%bool = OpTypeBool
-%28 = OpTypeFunction %bool %ulong %uint
-%uint_1 = OpConstant %uint 1
-%_runtimearr_ulong = OpTypeRuntimeArray %ulong
-%_struct_39 = OpTypeStruct %_runtimearr_ulong
-%_ptr_StorageBuffer__struct_39 = OpTypePointer StorageBuffer %_struct_39
-%41 = OpVariable %_ptr_StorageBuffer__struct_39 StorageBuffer
-%_ptr_StorageBuffer_ulong = OpTypePointer StorageBuffer %ulong
-%uint_0 = OpConstant %uint 0
-%uint_32 = OpConstant %uint 32
-%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_10 = OpConstant %uint 10
-%uint_23 = OpConstant %uint 23
-%uint_5 = OpConstant %uint 5
-%uint_3 = OpConstant %uint 3
-%v3uint = OpTypeVector %uint 3
-%_ptr_Input_v3uint = OpTypePointer Input %v3uint
-%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
-%uint_6 = OpConstant %uint 6
-%uint_7 = OpConstant %uint 7
-%uint_8 = OpConstant %uint 8
-%uint_9 = OpConstant %uint 9
-%uint_48 = OpConstant %uint 48
-)";
-
-  const std::string func_before =
-      R"(%main = OpFunction %void None %3
+  const std::string main_func = R"(
+%main = OpFunction %void None %3
 %5 = OpLabel
 %17 = OpAccessChain %_ptr_Uniform__ptr_PhysicalStorageBuffer_bufStruct %u_info %int_0
 %18 = OpLoad %_ptr_PhysicalStorageBuffer_bufStruct %17
 %22 = OpAccessChain %_ptr_PhysicalStorageBuffer_int %18 %int_1
+; CHECK-NOT: %17 = OpAccessChain %_ptr_Uniform__ptr_PhysicalStorageBuffer_bufStruct %u_info %int_0
+; CHECK-NOT: %18 = OpLoad %_ptr_PhysicalStorageBuffer_bufStruct %17
+; CHECK-NOT: %22 = OpAccessChain %_ptr_PhysicalStorageBuffer_int %18 %int_1
+; CHECK: %20 = OpAccessChain %_ptr_Uniform__ptr_PhysicalStorageBuffer_bufStruct %u_info %int_0
+; CHECK: %21 = OpLoad %_ptr_PhysicalStorageBuffer_bufStruct %20
+; CHECK: %22 = OpAccessChain %_ptr_PhysicalStorageBuffer_int %21 %int_1
+; CHECK: %24 = OpConvertPtrToU %ulong %22
+; CHECK: %61 = OpFunctionCall %bool %inst_buff_addr_search_and_test %24 %uint_4
+; CHECK: OpSelectionMerge %62 None
+; CHECK: OpBranchConditional %61 %63 %64
+; CHECK: %63 = OpLabel
 OpStore %22 %int_3239 Aligned 16
+; CHECK: OpStore %22 %int_3239 Aligned 16
+; CHECK: OpBranch %62
+; CHECK: %64 = OpLabel
+; CHECK: %65 = OpUConvert %uint %24
+; CHECK: %67 = OpShiftRightLogical %ulong %24 %uint_32
+; CHECK: %68 = OpUConvert %uint %67
+; CHECK: %124 = OpFunctionCall %void %inst_buff_addr_stream_write_4 %uint_48 %uint_2 %65 %68
+; CHECK: OpBranch %62
+; CHECK: %62 = OpLabel
 OpReturn
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%main = OpFunction %void None %8
-%19 = OpLabel
-%20 = OpAccessChain %_ptr_Uniform__ptr_PhysicalStorageBuffer_bufStruct %u_info %int_0
-%21 = OpLoad %_ptr_PhysicalStorageBuffer_bufStruct %20
-%22 = OpAccessChain %_ptr_PhysicalStorageBuffer_int %21 %int_1
-%24 = OpConvertPtrToU %ulong %22
-%61 = OpFunctionCall %bool %26 %24 %uint_4
-OpSelectionMerge %62 None
-OpBranchConditional %61 %63 %64
-%63 = OpLabel
-OpStore %22 %int_3239 Aligned 16
-OpBranch %62
-%64 = OpLabel
-%65 = OpUConvert %uint %24
-%67 = OpShiftRightLogical %ulong %24 %uint_32
-%68 = OpUConvert %uint %67
-%124 = OpFunctionCall %void %69 %uint_48 %uint_2 %65 %68
-OpBranch %62
-%62 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string new_funcs =
-      R"(%26 = OpFunction %bool None %28
-%29 = OpFunctionParameter %ulong
-%30 = OpFunctionParameter %uint
-%31 = OpLabel
-OpBranch %32
-%32 = OpLabel
-%34 = OpPhi %uint %uint_1 %31 %35 %33
-OpLoopMerge %37 %33 None
-OpBranch %33
-%33 = OpLabel
-%35 = OpIAdd %uint %34 %uint_1
-%44 = OpAccessChain %_ptr_StorageBuffer_ulong %41 %uint_0 %35
-%45 = OpLoad %ulong %44
-%46 = OpUGreaterThan %bool %45 %29
-OpBranchConditional %46 %37 %32
-%37 = OpLabel
-%47 = OpISub %uint %35 %uint_1
-%48 = OpAccessChain %_ptr_StorageBuffer_ulong %41 %uint_0 %47
-%49 = OpLoad %ulong %48
-%50 = OpISub %ulong %29 %49
-%51 = OpUConvert %ulong %30
-%52 = OpIAdd %ulong %50 %51
-%53 = OpAccessChain %_ptr_StorageBuffer_ulong %41 %uint_0 %uint_0
-%54 = OpLoad %ulong %53
-%55 = OpUConvert %uint %54
-%56 = OpISub %uint %47 %uint_1
-%57 = OpIAdd %uint %56 %55
-%58 = OpAccessChain %_ptr_StorageBuffer_ulong %41 %uint_0 %57
-%59 = OpLoad %ulong %58
-%60 = OpULessThanEqual %bool %52 %59
-OpReturnValue %60
-OpFunctionEnd
-%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
-%83 = OpAtomicIAdd %uint %81 %uint_4 %uint_0 %uint_10
-%84 = OpIAdd %uint %83 %uint_10
-%85 = OpArrayLength %uint %79 1
-%86 = OpULessThanEqual %bool %84 %85
-OpSelectionMerge %87 None
-OpBranchConditional %86 %88 %87
-%88 = OpLabel
-%89 = OpIAdd %uint %83 %uint_0
-%90 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %89
-OpStore %90 %uint_10
-%92 = OpIAdd %uint %83 %uint_1
-%93 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %92
-OpStore %93 %uint_23
-%94 = OpIAdd %uint %83 %uint_2
-%95 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %94
-OpStore %95 %71
-%98 = OpIAdd %uint %83 %uint_3
-%99 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %98
-OpStore %99 %uint_5
-%103 = OpLoad %v3uint %gl_GlobalInvocationID
-%104 = OpCompositeExtract %uint %103 0
-%105 = OpCompositeExtract %uint %103 1
-%106 = OpCompositeExtract %uint %103 2
-%107 = OpIAdd %uint %83 %uint_4
-%108 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %107
-OpStore %108 %104
-%109 = OpIAdd %uint %83 %uint_5
-%110 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %109
-OpStore %110 %105
-%112 = OpIAdd %uint %83 %uint_6
-%113 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %112
-OpStore %113 %106
-%115 = OpIAdd %uint %83 %uint_7
-%116 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %115
-OpStore %116 %72
-%118 = OpIAdd %uint %83 %uint_8
-%119 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %118
-OpStore %119 %73
-%121 = OpIAdd %uint %83 %uint_9
-%122 = OpAccessChain %_ptr_StorageBuffer_uint %79 %uint_1 %121
-OpStore %122 %74
-OpBranch %87
-%87 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
+  const std::string output_funcs = kSearchAndTest + kStreamWrite4Compute;
 
   // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBuffAddrCheckPass>(
-      defs_before + func_before, defs_after + func_after + new_funcs, true,
-      true, 7u, 23u);
+  SinglePassRunAndMatch<InstBuffAddrCheckPass>(
+      defs + decorates + globals + main_func + output_funcs, true, 7u, 23u);
 }
 
 TEST_F(InstBuffAddrTest, InstPhysicalStorageBufferLoadAndStore) {
@@ -337,9 +341,10 @@
   //   b.x = 531;
   // }
 
-  const std::string defs_before =
-      R"(OpCapability Shader
+  const std::string defs = R"(
+OpCapability Shader
 OpCapability PhysicalStorageBufferAddresses
+; CHECK: OpCapability Int64
 OpExtension "SPV_EXT_physical_storage_buffer"
 OpExtension "SPV_KHR_storage_buffer_storage_class"
 %1 = OpExtInstImport "GLSL.std.450"
@@ -349,12 +354,17 @@
 OpSource GLSL 450
 OpSourceExtension "GL_EXT_buffer_reference"
 OpName %main "main"
+; CHECK: OpEntryPoint GLCompute %main "main" %gl_GlobalInvocationID
 OpName %blockType "blockType"
 OpMemberName %blockType 0 "x"
 OpMemberName %blockType 1 "next"
 OpName %rootBlock "rootBlock"
 OpMemberName %rootBlock 0 "root"
 OpName %r "r"
+)";
+
+// clang-format off
+  const std::string decorates = R"(
 OpMemberDecorate %blockType 0 Offset 0
 OpMemberDecorate %blockType 1 Offset 8
 OpDecorate %blockType Block
@@ -362,6 +372,15 @@
 OpDecorate %rootBlock Block
 OpDecorate %r DescriptorSet 0
 OpDecorate %r Binding 0
+; CHECK: OpDecorate %_runtimearr_ulong ArrayStride 8
+)" + kInputDecorations + R"(
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+)" + kOutputDecorations + R"(
+; CHECK: OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
+)";
+  // clang-format on
+
+  const std::string globals = R"(
 %void = OpTypeVoid
 %3 = OpTypeFunction %void
 OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_blockType PhysicalStorageBuffer
@@ -377,99 +396,10 @@
 %_ptr_PhysicalStorageBuffer__ptr_PhysicalStorageBuffer_blockType = OpTypePointer PhysicalStorageBuffer %_ptr_PhysicalStorageBuffer_blockType
 %int_531 = OpConstant %int 531
 %_ptr_PhysicalStorageBuffer_int = OpTypePointer PhysicalStorageBuffer %int
-)";
+)" + kInputGlobals + kOutputGlobals;
 
-  const std::string defs_after =
-      R"(OpCapability Shader
-OpCapability PhysicalStorageBufferAddresses
-OpCapability Int64
-OpExtension "SPV_EXT_physical_storage_buffer"
-OpExtension "SPV_KHR_storage_buffer_storage_class"
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel PhysicalStorageBuffer64 GLSL450
-OpEntryPoint GLCompute %main "main" %gl_GlobalInvocationID
-OpExecutionMode %main LocalSize 1 1 1
-OpSource GLSL 450
-OpSourceExtension "GL_EXT_buffer_reference"
-OpName %main "main"
-OpName %blockType "blockType"
-OpMemberName %blockType 0 "x"
-OpMemberName %blockType 1 "next"
-OpName %rootBlock "rootBlock"
-OpMemberName %rootBlock 0 "root"
-OpName %r "r"
-OpMemberDecorate %blockType 0 Offset 0
-OpMemberDecorate %blockType 1 Offset 8
-OpDecorate %blockType Block
-OpMemberDecorate %rootBlock 0 Offset 0
-OpDecorate %rootBlock Block
-OpDecorate %r DescriptorSet 0
-OpDecorate %r Binding 0
-OpDecorate %_runtimearr_ulong ArrayStride 8
-OpDecorate %_struct_45 Block
-OpMemberDecorate %_struct_45 0 Offset 0
-OpDecorate %47 DescriptorSet 7
-OpDecorate %47 Binding 2
-OpDecorate %_runtimearr_uint ArrayStride 4
-OpDecorate %_struct_84 Block
-OpMemberDecorate %_struct_84 0 Offset 0
-OpMemberDecorate %_struct_84 1 Offset 4
-OpDecorate %86 DescriptorSet 7
-OpDecorate %86 Binding 0
-OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
-%void = OpTypeVoid
-%3 = OpTypeFunction %void
-OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_blockType PhysicalStorageBuffer
-%int = OpTypeInt 32 1
-%blockType = OpTypeStruct %int %_ptr_PhysicalStorageBuffer_blockType
-%_ptr_PhysicalStorageBuffer_blockType = OpTypePointer PhysicalStorageBuffer %blockType
-%rootBlock = OpTypeStruct %_ptr_PhysicalStorageBuffer_blockType
-%_ptr_StorageBuffer_rootBlock = OpTypePointer StorageBuffer %rootBlock
-%r = OpVariable %_ptr_StorageBuffer_rootBlock StorageBuffer
-%int_0 = OpConstant %int 0
-%_ptr_StorageBuffer__ptr_PhysicalStorageBuffer_blockType = OpTypePointer StorageBuffer %_ptr_PhysicalStorageBuffer_blockType
-%int_1 = OpConstant %int 1
-%_ptr_PhysicalStorageBuffer__ptr_PhysicalStorageBuffer_blockType = OpTypePointer PhysicalStorageBuffer %_ptr_PhysicalStorageBuffer_blockType
-%int_531 = OpConstant %int 531
-%_ptr_PhysicalStorageBuffer_int = OpTypePointer PhysicalStorageBuffer %int
-%uint = OpTypeInt 32 0
-%uint_2 = OpConstant %uint 2
-%ulong = OpTypeInt 64 0
-%uint_8 = OpConstant %uint 8
-%bool = OpTypeBool
-%34 = OpTypeFunction %bool %ulong %uint
-%uint_1 = OpConstant %uint 1
-%_runtimearr_ulong = OpTypeRuntimeArray %ulong
-%_struct_45 = OpTypeStruct %_runtimearr_ulong
-%_ptr_StorageBuffer__struct_45 = OpTypePointer StorageBuffer %_struct_45
-%47 = OpVariable %_ptr_StorageBuffer__struct_45 StorageBuffer
-%_ptr_StorageBuffer_ulong = OpTypePointer StorageBuffer %ulong
-%uint_0 = OpConstant %uint 0
-%uint_32 = OpConstant %uint 32
-%77 = OpTypeFunction %void %uint %uint %uint %uint
-%_runtimearr_uint = OpTypeRuntimeArray %uint
-%_struct_84 = OpTypeStruct %uint %_runtimearr_uint
-%_ptr_StorageBuffer__struct_84 = OpTypePointer StorageBuffer %_struct_84
-%86 = OpVariable %_ptr_StorageBuffer__struct_84 StorageBuffer
-%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
-%uint_10 = OpConstant %uint 10
-%uint_4 = OpConstant %uint 4
-%uint_23 = OpConstant %uint 23
-%uint_5 = OpConstant %uint 5
-%uint_3 = OpConstant %uint 3
-%v3uint = OpTypeVector %uint 3
-%_ptr_Input_v3uint = OpTypePointer Input %v3uint
-%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
-%uint_6 = OpConstant %uint 6
-%uint_7 = OpConstant %uint 7
-%uint_9 = OpConstant %uint 9
-%uint_44 = OpConstant %uint 44
-%132 = OpConstantNull %ulong
-%uint_46 = OpConstant %uint 46
-)";
-
-  const std::string func_before =
-      R"(%main = OpFunction %void None %3
+  const std::string main_func = R"(
+%main = OpFunction %void None %3
 %5 = OpLabel
 %16 = OpAccessChain %_ptr_StorageBuffer__ptr_PhysicalStorageBuffer_blockType %r %int_0
 %17 = OpLoad %_ptr_PhysicalStorageBuffer_blockType %16
@@ -477,142 +407,48 @@
 %22 = OpLoad %_ptr_PhysicalStorageBuffer_blockType %21 Aligned 8
 %26 = OpAccessChain %_ptr_PhysicalStorageBuffer_int %22 %int_0
 OpStore %26 %int_531 Aligned 16
+; CHECK-NOT: %22 = OpLoad %_ptr_PhysicalStorageBuffer_blockType %21 Aligned 8
+; CHECK-NOT: %26 = OpAccessChain %_ptr_PhysicalStorageBuffer_int %22 %int_0
+; CHECK: %30 = OpConvertPtrToU %ulong %21
+; CHECK: %67 = OpFunctionCall %bool %inst_buff_addr_search_and_test %30 %uint_8
+; CHECK: OpSelectionMerge %68 None
+; CHECK: OpBranchConditional %67 %69 %70
+; CHECK: %69 = OpLabel
+; CHECK: %71 = OpLoad %_ptr_PhysicalStorageBuffer_blockType %21 Aligned 8
+; CHECK: OpBranch %68
+; CHECK: %70 = OpLabel
+; CHECK: %72 = OpUConvert %uint %30
+; CHECK: %74 = OpShiftRightLogical %ulong %30 %uint_32
+; CHECK: %75 = OpUConvert %uint %74
+; CHECK: %131 = OpFunctionCall %void %inst_buff_addr_stream_write_4 %uint_44 %uint_2 %72 %75
+; CHECK: %133 = OpConvertUToPtr %_ptr_PhysicalStorageBuffer_blockType %132
+; CHECK: OpBranch %68
+; CHECK: %68 = OpLabel
+; CHECK: %134 = OpPhi %_ptr_PhysicalStorageBuffer_blockType %71 %69 %133 %70
+; CHECK: %26 = OpAccessChain %_ptr_PhysicalStorageBuffer_int %134 %int_0
+; CHECK: %135 = OpConvertPtrToU %ulong %26
+; CHECK: %136 = OpFunctionCall %bool %inst_buff_addr_search_and_test %135 %uint_4
+; CHECK: OpSelectionMerge %137 None
+; CHECK: OpBranchConditional %136 %138 %139
+; CHECK: %138 = OpLabel
+; CHECK: OpStore %26 %int_531 Aligned 16
+; CHECK: OpBranch %137
+; CHECK: %139 = OpLabel
+; CHECK: %140 = OpUConvert %uint %135
+; CHECK: %141 = OpShiftRightLogical %ulong %135 %uint_32
+; CHECK: %142 = OpUConvert %uint %141
+; CHECK: %144 = OpFunctionCall %void %inst_buff_addr_stream_write_4 %uint_46 %uint_2 %140 %142
+; CHECK: OpBranch %137
+; CHECK: %137 = OpLabel
 OpReturn
 OpFunctionEnd
 )";
 
-  const std::string func_after =
-      R"(%main = OpFunction %void None %3
-%5 = OpLabel
-%16 = OpAccessChain %_ptr_StorageBuffer__ptr_PhysicalStorageBuffer_blockType %r %int_0
-%17 = OpLoad %_ptr_PhysicalStorageBuffer_blockType %16
-%21 = OpAccessChain %_ptr_PhysicalStorageBuffer__ptr_PhysicalStorageBuffer_blockType %17 %int_1
-%30 = OpConvertPtrToU %ulong %21
-%67 = OpFunctionCall %bool %32 %30 %uint_8
-OpSelectionMerge %68 None
-OpBranchConditional %67 %69 %70
-%69 = OpLabel
-%71 = OpLoad %_ptr_PhysicalStorageBuffer_blockType %21 Aligned 8
-OpBranch %68
-%70 = OpLabel
-%72 = OpUConvert %uint %30
-%74 = OpShiftRightLogical %ulong %30 %uint_32
-%75 = OpUConvert %uint %74
-%131 = OpFunctionCall %void %76 %uint_44 %uint_2 %72 %75
-%133 = OpConvertUToPtr %_ptr_PhysicalStorageBuffer_blockType %132
-OpBranch %68
-%68 = OpLabel
-%134 = OpPhi %_ptr_PhysicalStorageBuffer_blockType %71 %69 %133 %70
-%26 = OpAccessChain %_ptr_PhysicalStorageBuffer_int %134 %int_0
-%135 = OpConvertPtrToU %ulong %26
-%136 = OpFunctionCall %bool %32 %135 %uint_4
-OpSelectionMerge %137 None
-OpBranchConditional %136 %138 %139
-%138 = OpLabel
-OpStore %26 %int_531 Aligned 16
-OpBranch %137
-%139 = OpLabel
-%140 = OpUConvert %uint %135
-%141 = OpShiftRightLogical %ulong %135 %uint_32
-%142 = OpUConvert %uint %141
-%144 = OpFunctionCall %void %76 %uint_46 %uint_2 %140 %142
-OpBranch %137
-%137 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string new_funcs =
-      R"(%32 = OpFunction %bool None %34
-%35 = OpFunctionParameter %ulong
-%36 = OpFunctionParameter %uint
-%37 = OpLabel
-OpBranch %38
-%38 = OpLabel
-%40 = OpPhi %uint %uint_1 %37 %41 %39
-OpLoopMerge %43 %39 None
-OpBranch %39
-%39 = OpLabel
-%41 = OpIAdd %uint %40 %uint_1
-%50 = OpAccessChain %_ptr_StorageBuffer_ulong %47 %uint_0 %41
-%51 = OpLoad %ulong %50
-%52 = OpUGreaterThan %bool %51 %35
-OpBranchConditional %52 %43 %38
-%43 = OpLabel
-%53 = OpISub %uint %41 %uint_1
-%54 = OpAccessChain %_ptr_StorageBuffer_ulong %47 %uint_0 %53
-%55 = OpLoad %ulong %54
-%56 = OpISub %ulong %35 %55
-%57 = OpUConvert %ulong %36
-%58 = OpIAdd %ulong %56 %57
-%59 = OpAccessChain %_ptr_StorageBuffer_ulong %47 %uint_0 %uint_0
-%60 = OpLoad %ulong %59
-%61 = OpUConvert %uint %60
-%62 = OpISub %uint %53 %uint_1
-%63 = OpIAdd %uint %62 %61
-%64 = OpAccessChain %_ptr_StorageBuffer_ulong %47 %uint_0 %63
-%65 = OpLoad %ulong %64
-%66 = OpULessThanEqual %bool %58 %65
-OpReturnValue %66
-OpFunctionEnd
-%76 = OpFunction %void None %77
-%78 = OpFunctionParameter %uint
-%79 = OpFunctionParameter %uint
-%80 = OpFunctionParameter %uint
-%81 = OpFunctionParameter %uint
-%82 = OpLabel
-%88 = OpAccessChain %_ptr_StorageBuffer_uint %86 %uint_0
-%91 = OpAtomicIAdd %uint %88 %uint_4 %uint_0 %uint_10
-%92 = OpIAdd %uint %91 %uint_10
-%93 = OpArrayLength %uint %86 1
-%94 = OpULessThanEqual %bool %92 %93
-OpSelectionMerge %95 None
-OpBranchConditional %94 %96 %95
-%96 = OpLabel
-%97 = OpIAdd %uint %91 %uint_0
-%98 = OpAccessChain %_ptr_StorageBuffer_uint %86 %uint_1 %97
-OpStore %98 %uint_10
-%100 = OpIAdd %uint %91 %uint_1
-%101 = OpAccessChain %_ptr_StorageBuffer_uint %86 %uint_1 %100
-OpStore %101 %uint_23
-%102 = OpIAdd %uint %91 %uint_2
-%103 = OpAccessChain %_ptr_StorageBuffer_uint %86 %uint_1 %102
-OpStore %103 %78
-%106 = OpIAdd %uint %91 %uint_3
-%107 = OpAccessChain %_ptr_StorageBuffer_uint %86 %uint_1 %106
-OpStore %107 %uint_5
-%111 = OpLoad %v3uint %gl_GlobalInvocationID
-%112 = OpCompositeExtract %uint %111 0
-%113 = OpCompositeExtract %uint %111 1
-%114 = OpCompositeExtract %uint %111 2
-%115 = OpIAdd %uint %91 %uint_4
-%116 = OpAccessChain %_ptr_StorageBuffer_uint %86 %uint_1 %115
-OpStore %116 %112
-%117 = OpIAdd %uint %91 %uint_5
-%118 = OpAccessChain %_ptr_StorageBuffer_uint %86 %uint_1 %117
-OpStore %118 %113
-%120 = OpIAdd %uint %91 %uint_6
-%121 = OpAccessChain %_ptr_StorageBuffer_uint %86 %uint_1 %120
-OpStore %121 %114
-%123 = OpIAdd %uint %91 %uint_7
-%124 = OpAccessChain %_ptr_StorageBuffer_uint %86 %uint_1 %123
-OpStore %124 %79
-%125 = OpIAdd %uint %91 %uint_8
-%126 = OpAccessChain %_ptr_StorageBuffer_uint %86 %uint_1 %125
-OpStore %126 %80
-%128 = OpIAdd %uint %91 %uint_9
-%129 = OpAccessChain %_ptr_StorageBuffer_uint %86 %uint_1 %128
-OpStore %129 %81
-OpBranch %95
-%95 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
+  const std::string output_funcs = kSearchAndTest + kStreamWrite4Compute;
 
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  SinglePassRunAndCheck<InstBuffAddrCheckPass>(
-      defs_before + func_before, defs_after + func_after + new_funcs, true,
-      true, 7u, 23u);
+  SinglePassRunAndMatch<InstBuffAddrCheckPass>(
+      defs + decorates + globals + main_func + output_funcs, true, 7u, 23u);
 }
 
 TEST_F(InstBuffAddrTest, StructLoad) {
@@ -643,7 +479,7 @@
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel PhysicalStorageBuffer64 GLSL450
 OpEntryPoint Fragment %main "main"
-; CHECK: OpEntryPoint Fragment %main "main" %60 %99 %gl_FragCoord
+; CHECK: OpEntryPoint Fragment %main "main" %inst_buff_addr_input_buffer %inst_buff_addr_output_buffer %gl_FragCoord
 OpExecutionMode %main OriginUpperLeft
 OpSource GLSL 450
 OpSourceExtension "GL_ARB_gpu_shader_int64"
@@ -657,27 +493,19 @@
 OpMemberName %TestBuffer 0 "test"
 )";
 
-  const std::string decorates =
-      R"(
+  // clang-format off
+  const std::string decorates = R"(
 OpMemberDecorate %Test_0 0 Offset 0
 OpMemberDecorate %TestBuffer 0 Offset 0
 OpDecorate %TestBuffer Block
 ; CHECK: OpDecorate %_runtimearr_ulong ArrayStride 8
-; CHECK: OpDecorate %_struct_58 Block
-; CHECK: OpMemberDecorate %_struct_58 0 Offset 0
-; CHECK: OpDecorate %60 DescriptorSet 7
-; CHECK: OpDecorate %60 Binding 2
+)" + kInputDecorations + R"(
 ; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
-; CHECK: OpDecorate %_struct_97 Block
-; CHECK: OpMemberDecorate %_struct_97 0 Offset 0
-; CHECK: OpMemberDecorate %_struct_97 1 Offset 4
-; CHECK: OpDecorate %99 DescriptorSet 7
-; CHECK: OpDecorate %99 Binding 0
+)" + kOutputDecorations + R"(
 ; CHECK: OpDecorate %gl_FragCoord BuiltIn FragCoord
 )";
 
-  const std::string globals =
-      R"(
+  const std::string globals = R"(
 %void = OpTypeVoid
 %3 = OpTypeFunction %void
 %ulong = OpTypeInt 64 0
@@ -692,15 +520,14 @@
 %_ptr_PhysicalStorageBuffer_Test_0 = OpTypePointer PhysicalStorageBuffer %Test_0
 %ulong_18446744073172680704 = OpConstant %ulong 18446744073172680704
 ; CHECK: %47 = OpTypeFunction %bool %ulong %uint
-; CHECK: %_struct_58 = OpTypeStruct %_runtimearr_ulong
-; CHECK: %60 = OpVariable %_ptr_StorageBuffer__struct_58 StorageBuffer
+)" + kInputGlobals + R"(
 ; CHECK: %90 = OpTypeFunction %void %uint %uint %uint %uint
-; CHECK: %_struct_97 = OpTypeStruct %uint %_runtimearr_uint
-; CHECK: %99 = OpVariable %_ptr_StorageBuffer__struct_97 StorageBuffer
+)" + kOutputGlobals + R"(
 ; CHECK: %143 = OpConstantNull %Test_0
 )";
+  // clang-format on
 
-  const std::string main =
+  const std::string main_func =
       R"(
 %main = OpFunction %void None %3
 %5 = OpLabel
@@ -709,7 +536,7 @@
 %39 = OpLoad %Test_0 %38 Aligned 16
 ; CHECK-NOT: %39 = OpLoad %Test_0 %38 Aligned 16
 ; CHECK: %43 = OpConvertPtrToU %ulong %38
-; CHECK: %80 = OpFunctionCall %bool %45 %43 %uint_4
+; CHECK: %80 = OpFunctionCall %bool %inst_buff_addr_search_and_test %43 %uint_4
 ; CHECK: OpSelectionMerge %81 None
 ; CHECK: OpBranchConditional %80 %82 %83
 ; CHECK: %82 = OpLabel
@@ -719,7 +546,7 @@
 ; CHECK: %85 = OpUConvert %uint %43
 ; CHECK: %87 = OpShiftRightLogical %ulong %43 %uint_32
 ; CHECK: %88 = OpUConvert %uint %87
-; CHECK: %142 = OpFunctionCall %void %89 %uint_37 %uint_2 %85 %88
+; CHECK: %142 = OpFunctionCall %void %inst_buff_addr_stream_write_4 %uint_37 %uint_2 %85 %88
 ; CHECK: OpBranch %81
 ; CHECK: %81 = OpLabel
 ; CHECK: %144 = OpPhi %Test_0 %84 %82 %143 %83
@@ -730,95 +557,12 @@
 OpFunctionEnd
 )";
 
-  const std::string output_funcs =
-      R"(
-; CHECK: %45 = OpFunction %bool None %47
-; CHECK: %48 = OpFunctionParameter %ulong
-; CHECK: %49 = OpFunctionParameter %uint
-; CHECK: %50 = OpLabel
-; CHECK: OpBranch %51
-; CHECK: %51 = OpLabel
-; CHECK: %53 = OpPhi %uint %uint_1 %50 %54 %52
-; CHECK: OpLoopMerge %56 %52 None
-; CHECK: OpBranch %52
-; CHECK: %52 = OpLabel
-; CHECK: %54 = OpIAdd %uint %53 %uint_1
-; CHECK: %63 = OpAccessChain %_ptr_StorageBuffer_ulong %60 %uint_0 %54
-; CHECK: %64 = OpLoad %ulong %63
-; CHECK: %65 = OpUGreaterThan %bool %64 %48
-; CHECK: OpBranchConditional %65 %56 %51
-; CHECK: %56 = OpLabel
-; CHECK: %66 = OpISub %uint %54 %uint_1
-; CHECK: %67 = OpAccessChain %_ptr_StorageBuffer_ulong %60 %uint_0 %66
-; CHECK: %68 = OpLoad %ulong %67
-; CHECK: %69 = OpISub %ulong %48 %68
-; CHECK: %70 = OpUConvert %ulong %49
-; CHECK: %71 = OpIAdd %ulong %69 %70
-; CHECK: %72 = OpAccessChain %_ptr_StorageBuffer_ulong %60 %uint_0 %uint_0
-; CHECK: %73 = OpLoad %ulong %72
-; CHECK: %74 = OpUConvert %uint %73
-; CHECK: %75 = OpISub %uint %66 %uint_1
-; CHECK: %76 = OpIAdd %uint %75 %74
-; CHECK: %77 = OpAccessChain %_ptr_StorageBuffer_ulong %60 %uint_0 %76
-; CHECK: %78 = OpLoad %ulong %77
-; CHECK: %79 = OpULessThanEqual %bool %71 %78
-; CHECK: OpReturnValue %79
-; CHECK: OpFunctionEnd
-; CHECK: %89 = OpFunction %void None %90
-; CHECK: %91 = OpFunctionParameter %uint
-; CHECK: %92 = OpFunctionParameter %uint
-; CHECK: %93 = OpFunctionParameter %uint
-; CHECK: %94 = OpFunctionParameter %uint
-; CHECK: %95 = OpLabel
-; CHECK: %101 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_0
-; CHECK: %103 = OpAtomicIAdd %uint %101 %uint_4 %uint_0 %uint_10
-; CHECK: %104 = OpIAdd %uint %103 %uint_10
-; CHECK: %105 = OpArrayLength %uint %99 1
-; CHECK: %106 = OpULessThanEqual %bool %104 %105
-; CHECK: OpSelectionMerge %107 None
-; CHECK: OpBranchConditional %106 %108 %107
-; CHECK: %108 = OpLabel
-; CHECK: %109 = OpIAdd %uint %103 %uint_0
-; CHECK: %110 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %109
-; CHECK: OpStore %110 %uint_10
-; CHECK: %112 = OpIAdd %uint %103 %uint_1
-; CHECK: %113 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %112
-; CHECK: OpStore %113 %uint_23
-; CHECK: %114 = OpIAdd %uint %103 %uint_2
-; CHECK: %115 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %114
-; CHECK: OpStore %115 %91
-; CHECK: %117 = OpIAdd %uint %103 %uint_3
-; CHECK: %118 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %117
-; CHECK: OpStore %118 %uint_4
-; CHECK: %122 = OpLoad %v4float %gl_FragCoord
-; CHECK: %124 = OpBitcast %v4uint %122
-; CHECK: %125 = OpCompositeExtract %uint %124 0
-; CHECK: %126 = OpIAdd %uint %103 %uint_4
-; CHECK: %127 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %126
-; CHECK: OpStore %127 %125
-; CHECK: %128 = OpCompositeExtract %uint %124 1
-; CHECK: %130 = OpIAdd %uint %103 %uint_5
-; CHECK: %131 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %130
-; CHECK: OpStore %131 %128
-; CHECK: %133 = OpIAdd %uint %103 %uint_7
-; CHECK: %134 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %133
-; CHECK: OpStore %134 %92
-; CHECK: %136 = OpIAdd %uint %103 %uint_8
-; CHECK: %137 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %136
-; CHECK: OpStore %137 %93
-; CHECK: %139 = OpIAdd %uint %103 %uint_9
-; CHECK: %140 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %139
-; CHECK: OpStore %140 %94
-; CHECK: OpBranch %107
-; CHECK: %107 = OpLabel
-; CHECK: OpReturn
-; CHECK: OpFunctionEnd
-)";
+  const std::string output_funcs = kSearchAndTest + kStreamWrite4Frag;
 
   SetTargetEnv(SPV_ENV_VULKAN_1_2);
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
   SinglePassRunAndMatch<InstBuffAddrCheckPass>(
-      defs + decorates + globals + main + output_funcs, true);
+      defs + decorates + globals + main_func + output_funcs, true);
 }
 
 }  // namespace
diff --git a/test/opt/inst_debug_printf_test.cpp b/test/opt/inst_debug_printf_test.cpp
index 8123ffb..57e5044 100644
--- a/test/opt/inst_debug_printf_test.cpp
+++ b/test/opt/inst_debug_printf_test.cpp
@@ -1,5 +1,5 @@
-// Copyright (c) 2020 Valve Corporation
-// Copyright (c) 2020 LunarG Inc.
+// Copyright (c) 2020-2022 Valve Corporation
+// Copyright (c) 2020-2022 LunarG Inc.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -26,6 +26,20 @@
 namespace opt {
 namespace {
 
+static const std::string kOutputDecorations = R"(
+; CHECK: OpDecorate [[output_buffer_type:%inst_printf_OutputBuffer]] Block
+; CHECK: OpMemberDecorate [[output_buffer_type]] 0 Offset 0
+; CHECK: OpMemberDecorate [[output_buffer_type]] 1 Offset 4
+; CHECK: OpDecorate [[output_buffer_var:%\w+]] DescriptorSet 7
+; CHECK: OpDecorate [[output_buffer_var]] Binding 3
+)";
+
+static const std::string kOutputGlobals = R"(
+; CHECK: [[output_buffer_type]] = OpTypeStruct %uint %_runtimearr_uint
+; CHECK: [[output_ptr_type:%\w+]] = OpTypePointer StorageBuffer [[output_buffer_type]]
+; CHECK: [[output_buffer_var]] = OpVariable [[output_ptr_type]] StorageBuffer
+)";
+
 using InstDebugPrintfTest = PassTest<::testing::Test>;
 
 TEST_F(InstDebugPrintfTest, V4Float32) {
@@ -65,6 +79,7 @@
 %5 = OpString "Color is %vn"
 )";
 
+  // clang-format off
   const std::string decorates =
       R"(OpDecorate %6 DescriptorSet 0
 OpDecorate %6 Binding 1
@@ -73,11 +88,7 @@
 OpDecorate %3 Location 0
 OpDecorate %4 Location 0
 ; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
-; CHECK: OpDecorate %_struct_47 Block
-; CHECK: OpMemberDecorate %_struct_47 0 Offset 0
-; CHECK: OpMemberDecorate %_struct_47 1 Offset 4
-; CHECK: OpDecorate %49 DescriptorSet 7
-; CHECK: OpDecorate %49 Binding 3
+)" + kOutputDecorations + R"(
 ; CHECK: OpDecorate %gl_FragCoord BuiltIn FragCoord
 )";
 
@@ -101,15 +112,14 @@
 ; CHECK: %uint = OpTypeInt 32 0
 ; CHECK: %38 = OpTypeFunction %void %uint %uint %uint %uint %uint %uint
 ; CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
-; CHECK: %_struct_47 = OpTypeStruct %uint %_runtimearr_uint
-; CHECK: %_ptr_StorageBuffer__struct_47 = OpTypePointer StorageBuffer %_struct_47
-; CHECK: %49 = OpVariable %_ptr_StorageBuffer__struct_47 StorageBuffer
+)" + kOutputGlobals + R"(
 ; CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
 ; CHECK: %bool = OpTypeBool
 ; CHECK: %_ptr_Input_v4float = OpTypePointer Input %v4float
 ; CHECK: %gl_FragCoord = OpVariable %_ptr_Input_v4float Input
 ; CHECK: %v4uint = OpTypeVector %uint 4
 )";
+  // clang-format on
 
   const std::string main =
       R"(%2 = OpFunction %void None %9
@@ -129,7 +139,7 @@
 ; CHECK: %34 = OpBitcast %uint %33
 ; CHECK: %35 = OpCompositeExtract %float %25 3
 ; CHECK: %36 = OpBitcast %uint %35
-; CHECK: %101 = OpFunctionCall %void %37 %uint_36 %uint_5 %30 %32 %34 %36
+; CHECK: %101 = OpFunctionCall %void %inst_printf_stream_write_6 %uint_36 %uint_5 %30 %32 %34 %36
 ; CHECK: OpBranch %102
 ; CHECK: %102 = OpLabel
 OpStore %4 %25
@@ -137,8 +147,8 @@
 OpFunctionEnd
 )";
 
-  const std::string output_func =
-      R"(; CHECK: %37 = OpFunction %void None %38
+  const std::string output_func = R"(
+; CHECK: %inst_printf_stream_write_6 = OpFunction %void None %38
 ; CHECK: %39 = OpFunctionParameter %uint
 ; CHECK: %40 = OpFunctionParameter %uint
 ; CHECK: %41 = OpFunctionParameter %uint
@@ -146,50 +156,50 @@
 ; CHECK: %43 = OpFunctionParameter %uint
 ; CHECK: %44 = OpFunctionParameter %uint
 ; CHECK: %45 = OpLabel
-; CHECK: %52 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_0
+; CHECK: %52 = OpAccessChain %_ptr_StorageBuffer_uint %inst_printf_output_buffer %uint_0
 ; CHECK: %55 = OpAtomicIAdd %uint %52 %uint_4 %uint_0 %uint_12
 ; CHECK: %56 = OpIAdd %uint %55 %uint_12
-; CHECK: %57 = OpArrayLength %uint %49 1
+; CHECK: %57 = OpArrayLength %uint %inst_printf_output_buffer 1
 ; CHECK: %59 = OpULessThanEqual %bool %56 %57
 ; CHECK: OpSelectionMerge %60 None
 ; CHECK: OpBranchConditional %59 %61 %60
 ; CHECK: %61 = OpLabel
 ; CHECK: %62 = OpIAdd %uint %55 %uint_0
-; CHECK: %64 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %62
+; CHECK: %64 = OpAccessChain %_ptr_StorageBuffer_uint %inst_printf_output_buffer %uint_1 %62
 ; CHECK: OpStore %64 %uint_12
 ; CHECK: %66 = OpIAdd %uint %55 %uint_1
-; CHECK: %67 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %66
+; CHECK: %67 = OpAccessChain %_ptr_StorageBuffer_uint %inst_printf_output_buffer %uint_1 %66
 ; CHECK: OpStore %67 %uint_23
 ; CHECK: %69 = OpIAdd %uint %55 %uint_2
-; CHECK: %70 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %69
+; CHECK: %70 = OpAccessChain %_ptr_StorageBuffer_uint %inst_printf_output_buffer %uint_1 %69
 ; CHECK: OpStore %70 %39
 ; CHECK: %72 = OpIAdd %uint %55 %uint_3
-; CHECK: %73 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %72
+; CHECK: %73 = OpAccessChain %_ptr_StorageBuffer_uint %inst_printf_output_buffer %uint_1 %72
 ; CHECK: OpStore %73 %uint_4
 ; CHECK: %76 = OpLoad %v4float %gl_FragCoord
 ; CHECK: %78 = OpBitcast %v4uint %76
 ; CHECK: %79 = OpCompositeExtract %uint %78 0
 ; CHECK: %80 = OpIAdd %uint %55 %uint_4
-; CHECK: %81 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %80
+; CHECK: %81 = OpAccessChain %_ptr_StorageBuffer_uint %inst_printf_output_buffer %uint_1 %80
 ; CHECK: OpStore %81 %79
 ; CHECK: %82 = OpCompositeExtract %uint %78 1
 ; CHECK: %83 = OpIAdd %uint %55 %uint_5
-; CHECK: %84 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %83
+; CHECK: %84 = OpAccessChain %_ptr_StorageBuffer_uint %inst_printf_output_buffer %uint_1 %83
 ; CHECK: OpStore %84 %82
 ; CHECK: %86 = OpIAdd %uint %55 %uint_7
-; CHECK: %87 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %86
+; CHECK: %87 = OpAccessChain %_ptr_StorageBuffer_uint %inst_printf_output_buffer %uint_1 %86
 ; CHECK: OpStore %87 %40
 ; CHECK: %89 = OpIAdd %uint %55 %uint_8
-; CHECK: %90 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %89
+; CHECK: %90 = OpAccessChain %_ptr_StorageBuffer_uint %inst_printf_output_buffer %uint_1 %89
 ; CHECK: OpStore %90 %41
 ; CHECK: %92 = OpIAdd %uint %55 %uint_9
-; CHECK: %93 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %92
+; CHECK: %93 = OpAccessChain %_ptr_StorageBuffer_uint %inst_printf_output_buffer %uint_1 %92
 ; CHECK: OpStore %93 %42
 ; CHECK: %95 = OpIAdd %uint %55 %uint_10
-; CHECK: %96 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %95
+; CHECK: %96 = OpAccessChain %_ptr_StorageBuffer_uint %inst_printf_output_buffer %uint_1 %95
 ; CHECK: OpStore %96 %43
 ; CHECK: %98 = OpIAdd %uint %55 %uint_11
-; CHECK: %99 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %98
+; CHECK: %99 = OpAccessChain %_ptr_StorageBuffer_uint %inst_printf_output_buffer %uint_1 %98
 ; CHECK: OpStore %99 %44
 ; CHECK: OpBranch %60
 ; CHECK: %60 = OpLabel
@@ -206,8 +216,8 @@
 //
 //   Compute shader
 //   Geometry shader
-//   Tesselation control shader
-//   Tesselation eval shader
+//   Tessellation control shader
+//   Tessellation eval shader
 //   Vertex shader
 
 }  // namespace
diff --git a/test/opt/instruction_test.cpp b/test/opt/instruction_test.cpp
index c5b92ef..dd749ab 100644
--- a/test/opt/instruction_test.cpp
+++ b/test/opt/instruction_test.cpp
@@ -62,12 +62,6 @@
   EXPECT_EQ(inst.end(), inst.begin());
 }
 
-TEST(InstructionTest, OperandAsCString) {
-  Operand::OperandData abcde{0x64636261, 0x65};
-  Operand operand(SPV_OPERAND_TYPE_LITERAL_STRING, std::move(abcde));
-  EXPECT_STREQ("abcde", operand.AsCString());
-}
-
 TEST(InstructionTest, OperandAsString) {
   Operand::OperandData abcde{0x64636261, 0x65};
   Operand operand(SPV_OPERAND_TYPE_LITERAL_STRING, std::move(abcde));
@@ -1531,6 +1525,45 @@
   EXPECT_EQ(false, inst->IsVulkanStorageTexelBuffer());
 }
 
+TEST_F(DescriptorTypeTest, GetShader100DebugOpcode) {
+  const std::string text = R"(
+              OpCapability Shader
+         %1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+         %2 = OpString "ps.hlsl"
+         %3 = OpString "#line 1 \"ps.hlsl\""
+      %void = OpTypeVoid
+         %5 = OpExtInst %void %1 DebugExpression
+         %6 = OpExtInst %void %1 DebugSource %2 %3
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+  Instruction* debug_expression = context->get_def_use_mgr()->GetDef(5);
+  EXPECT_EQ(debug_expression->GetShader100DebugOpcode(),
+            NonSemanticShaderDebugInfo100DebugExpression);
+  Instruction* debug_source = context->get_def_use_mgr()->GetDef(6);
+  EXPECT_EQ(debug_source->GetShader100DebugOpcode(),
+            NonSemanticShaderDebugInfo100DebugSource);
+
+  // Test that an opcode larger than the max will return Max.  This instruction
+  // cannot be in the assembly above because the assembler expects the string
+  // for the opcode, so we cannot use an arbitrary number.  However, a binary
+  // file could have an arbitrary number.
+  std::unique_ptr<Instruction> past_max(debug_expression->Clone(context.get()));
+  const uint32_t kExtInstOpcodeInIndex = 1;
+  uint32_t large_opcode = NonSemanticShaderDebugInfo100InstructionsMax + 2;
+  past_max->SetInOperand(kExtInstOpcodeInIndex, {large_opcode});
+  EXPECT_EQ(past_max->GetShader100DebugOpcode(),
+            NonSemanticShaderDebugInfo100InstructionsMax);
+
+  // Test that an opcode without a value in the enum, but less than Max returns
+  // the same value.
+  uint32_t opcode = NonSemanticShaderDebugInfo100InstructionsMax - 2;
+  past_max->SetInOperand(kExtInstOpcodeInIndex, {opcode});
+  EXPECT_EQ(past_max->GetShader100DebugOpcode(), opcode);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/interface_var_sroa_test.cpp b/test/opt/interface_var_sroa_test.cpp
new file mode 100644
index 0000000..7762458
--- /dev/null
+++ b/test/opt/interface_var_sroa_test.cpp
@@ -0,0 +1,410 @@
+// Copyright (c) 2022 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 <iostream>
+
+#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 InterfaceVariableScalarReplacementTest = PassTest<::testing::Test>;
+
+TEST_F(InterfaceVariableScalarReplacementTest,
+       ReplaceInterfaceVarsWithScalars) {
+  const std::string spirv = R"(
+               OpCapability Shader
+               OpCapability Tessellation
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint TessellationControl %func "shader" %x %y %z %w %u %v
+
+; CHECK:     OpName [[x:%\w+]] "x"
+; CHECK-NOT: OpName {{%\w+}} "x"
+; CHECK:     OpName [[y:%\w+]] "y"
+; CHECK-NOT: OpName {{%\w+}} "y"
+; CHECK:     OpName [[z0:%\w+]] "z"
+; CHECK:     OpName [[z1:%\w+]] "z"
+; CHECK:     OpName [[w0:%\w+]] "w"
+; CHECK:     OpName [[w1:%\w+]] "w"
+; CHECK:     OpName [[u0:%\w+]] "u"
+; CHECK:     OpName [[u1:%\w+]] "u"
+; CHECK:     OpName [[v0:%\w+]] "v"
+; CHECK:     OpName [[v1:%\w+]] "v"
+; CHECK:     OpName [[v2:%\w+]] "v"
+; CHECK:     OpName [[v3:%\w+]] "v"
+; CHECK:     OpName [[v4:%\w+]] "v"
+; CHECK:     OpName [[v5:%\w+]] "v"
+               OpName %x "x"
+               OpName %y "y"
+               OpName %z "z"
+               OpName %w "w"
+               OpName %u "u"
+               OpName %v "v"
+
+; CHECK-DAG: OpDecorate [[x]] Location 2
+; CHECK-DAG: OpDecorate [[y]] Location 0
+; CHECK-DAG: OpDecorate [[z0]] Location 0
+; CHECK-DAG: OpDecorate [[z0]] Component 0
+; CHECK-DAG: OpDecorate [[z1]] Location 1
+; CHECK-DAG: OpDecorate [[z1]] Component 0
+; CHECK-DAG: OpDecorate [[z0]] Patch
+; CHECK-DAG: OpDecorate [[z1]] Patch
+; CHECK-DAG: OpDecorate [[w0]] Location 2
+; CHECK-DAG: OpDecorate [[w0]] Component 0
+; CHECK-DAG: OpDecorate [[w1]] Location 3
+; CHECK-DAG: OpDecorate [[w1]] Component 0
+; CHECK-DAG: OpDecorate [[w0]] Patch
+; CHECK-DAG: OpDecorate [[w1]] Patch
+; CHECK-DAG: OpDecorate [[u0]] Location 3
+; CHECK-DAG: OpDecorate [[u0]] Component 2
+; CHECK-DAG: OpDecorate [[u1]] Location 4
+; CHECK-DAG: OpDecorate [[u1]] Component 2
+; CHECK-DAG: OpDecorate [[v0]] Location 3
+; CHECK-DAG: OpDecorate [[v0]] Component 3
+; CHECK-DAG: OpDecorate [[v1]] Location 4
+; CHECK-DAG: OpDecorate [[v1]] Component 3
+; CHECK-DAG: OpDecorate [[v2]] Location 5
+; CHECK-DAG: OpDecorate [[v2]] Component 3
+; CHECK-DAG: OpDecorate [[v3]] Location 6
+; CHECK-DAG: OpDecorate [[v3]] Component 3
+; CHECK-DAG: OpDecorate [[v4]] Location 7
+; CHECK-DAG: OpDecorate [[v4]] Component 3
+; CHECK-DAG: OpDecorate [[v5]] Location 8
+; CHECK-DAG: OpDecorate [[v5]] Component 3
+               OpDecorate %z Patch
+               OpDecorate %w Patch
+               OpDecorate %z Location 0
+               OpDecorate %x Location 2
+               OpDecorate %v Location 3
+               OpDecorate %v Component 3
+               OpDecorate %y Location 0
+               OpDecorate %w Location 2
+               OpDecorate %u Location 3
+               OpDecorate %u Component 2
+
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+     %uint_2 = OpConstant %uint 2
+     %uint_3 = OpConstant %uint 3
+     %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_2 = OpTypeArray %uint %uint_2
+%_ptr_Output__arr_uint_uint_2 = OpTypePointer Output %_arr_uint_uint_2
+%_ptr_Input__arr_uint_uint_2 = OpTypePointer Input %_arr_uint_uint_2
+%_ptr_Input_uint = OpTypePointer Input %uint
+%_ptr_Output_uint = OpTypePointer Output %uint
+%_arr_arr_uint_uint_2_3 = OpTypeArray %_arr_uint_uint_2 %uint_3
+%_ptr_Input__arr_arr_uint_uint_2_3 = OpTypePointer Input %_arr_arr_uint_uint_2_3
+%_arr_arr_arr_uint_uint_2_3_4 = OpTypeArray %_arr_arr_uint_uint_2_3 %uint_4
+%_ptr_Output__arr_arr_arr_uint_uint_2_3_4 = OpTypePointer Output %_arr_arr_arr_uint_uint_2_3_4
+%_ptr_Output__arr_arr_uint_uint_2_3 = OpTypePointer Output %_arr_arr_uint_uint_2_3
+          %z = OpVariable %_ptr_Output__arr_uint_uint_2 Output
+          %x = OpVariable %_ptr_Output__arr_uint_uint_2 Output
+          %y = OpVariable %_ptr_Input__arr_uint_uint_2 Input
+          %w = OpVariable %_ptr_Input__arr_uint_uint_2 Input
+          %u = OpVariable %_ptr_Input__arr_arr_uint_uint_2_3 Input
+          %v = OpVariable %_ptr_Output__arr_arr_arr_uint_uint_2_3_4 Output
+
+; CHECK-DAG:  [[x]] = OpVariable %_ptr_Output__arr_uint_uint_2 Output
+; CHECK-DAG:  [[y]] = OpVariable %_ptr_Input__arr_uint_uint_2 Input
+; CHECK-DAG: [[z0]] = OpVariable %_ptr_Output_uint Output
+; CHECK-DAG: [[z1]] = OpVariable %_ptr_Output_uint Output
+; CHECK-DAG: [[w0]] = OpVariable %_ptr_Input_uint Input
+; CHECK-DAG: [[w1]] = OpVariable %_ptr_Input_uint Input
+; CHECK-DAG: [[u0]] = OpVariable %_ptr_Input__arr_uint_uint_3 Input
+; CHECK-DAG: [[u1]] = OpVariable %_ptr_Input__arr_uint_uint_3 Input
+; CHECK-DAG: [[v0]] = OpVariable %_ptr_Output__arr_uint_uint_4 Output
+; CHECK-DAG: [[v1]] = OpVariable %_ptr_Output__arr_uint_uint_4 Output
+; CHECK-DAG: [[v2]] = OpVariable %_ptr_Output__arr_uint_uint_4 Output
+; CHECK-DAG: [[v3]] = OpVariable %_ptr_Output__arr_uint_uint_4 Output
+; CHECK-DAG: [[v4]] = OpVariable %_ptr_Output__arr_uint_uint_4 Output
+; CHECK-DAG: [[v5]] = OpVariable %_ptr_Output__arr_uint_uint_4 Output
+
+     %void   = OpTypeVoid
+     %void_f = OpTypeFunction %void
+     %func   = OpFunction %void None %void_f
+     %label  = OpLabel
+
+; CHECK: [[w0_value:%\w+]] = OpLoad %uint [[w0]]
+; CHECK: [[w1_value:%\w+]] = OpLoad %uint [[w1]]
+; CHECK:  [[w_value:%\w+]] = OpCompositeConstruct %_arr_uint_uint_2 [[w0_value]] [[w1_value]]
+; CHECK:       [[w0:%\w+]] = OpCompositeExtract %uint [[w_value]] 0
+; CHECK:                     OpStore [[z0]] [[w0]]
+; CHECK:       [[w1:%\w+]] = OpCompositeExtract %uint [[w_value]] 1
+; CHECK:                     OpStore [[z1]] [[w1]]
+    %w_value = OpLoad %_arr_uint_uint_2 %w
+               OpStore %z %w_value
+
+; CHECK: [[u00_ptr:%\w+]] = OpAccessChain %_ptr_Input_uint [[u0]] %uint_0
+; CHECK:     [[u00:%\w+]] = OpLoad %uint [[u00_ptr]]
+; CHECK: [[u10_ptr:%\w+]] = OpAccessChain %_ptr_Input_uint [[u1]] %uint_0
+; CHECK:     [[u10:%\w+]] = OpLoad %uint [[u10_ptr]]
+; CHECK: [[u01_ptr:%\w+]] = OpAccessChain %_ptr_Input_uint [[u0]] %uint_1
+; CHECK:     [[u01:%\w+]] = OpLoad %uint [[u01_ptr]]
+; CHECK: [[u11_ptr:%\w+]] = OpAccessChain %_ptr_Input_uint [[u1]] %uint_1
+; CHECK:     [[u11:%\w+]] = OpLoad %uint [[u11_ptr]]
+; CHECK: [[u02_ptr:%\w+]] = OpAccessChain %_ptr_Input_uint [[u0]] %uint_2
+; CHECK:     [[u02:%\w+]] = OpLoad %uint [[u02_ptr]]
+; CHECK: [[u12_ptr:%\w+]] = OpAccessChain %_ptr_Input_uint [[u1]] %uint_2
+; CHECK:     [[u12:%\w+]] = OpLoad %uint [[u12_ptr]]
+
+; CHECK-DAG: [[u0_val:%\w+]] = OpCompositeConstruct %_arr_uint_uint_2 [[u00]] [[u10]]
+; CHECK-DAG: [[u1_val:%\w+]] = OpCompositeConstruct %_arr_uint_uint_2 [[u01]] [[u11]]
+; CHECK-DAG: [[u2_val:%\w+]] = OpCompositeConstruct %_arr_uint_uint_2 [[u02]] [[u12]]
+
+; CHECK: [[u_val:%\w+]] = OpCompositeConstruct %_arr__arr_uint_uint_2_uint_3 [[u0_val]] [[u1_val]] [[u2_val]]
+
+; CHECK: [[ptr:%\w+]] = OpAccessChain %_ptr_Output_uint [[v0]] %uint_1
+; CHECK: [[val:%\w+]] = OpCompositeExtract %uint [[u_val]] 0 0
+; CHECK:                OpStore [[ptr]] [[val]]
+; CHECK: [[ptr:%\w+]] = OpAccessChain %_ptr_Output_uint [[v1]] %uint_1
+; CHECK: [[val:%\w+]] = OpCompositeExtract %uint [[u_val]] 0 1
+; CHECK:                OpStore [[ptr]] [[val]]
+; CHECK: [[ptr:%\w+]] = OpAccessChain %_ptr_Output_uint [[v2]] %uint_1
+; CHECK: [[val:%\w+]] = OpCompositeExtract %uint [[u_val]] 1 0
+; CHECK:                OpStore [[ptr]] [[val]]
+; CHECK: [[ptr:%\w+]] = OpAccessChain %_ptr_Output_uint [[v3]] %uint_1
+; CHECK: [[val:%\w+]] = OpCompositeExtract %uint [[u_val]] 1 1
+; CHECK:                OpStore [[ptr]] [[val]]
+; CHECK: [[ptr:%\w+]] = OpAccessChain %_ptr_Output_uint [[v4]] %uint_1
+; CHECK: [[val:%\w+]] = OpCompositeExtract %uint [[u_val]] 2 0
+; CHECK:                OpStore [[ptr]] [[val]]
+; CHECK: [[ptr:%\w+]] = OpAccessChain %_ptr_Output_uint [[v5]] %uint_1
+; CHECK: [[val:%\w+]] = OpCompositeExtract %uint [[u_val]] 2 1
+; CHECK:                OpStore [[ptr]] [[val]]
+     %v_ptr  = OpAccessChain %_ptr_Output__arr_arr_uint_uint_2_3 %v %uint_1
+     %u_val  = OpLoad %_arr_arr_uint_uint_2_3 %u
+               OpStore %v_ptr %u_val
+
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  SinglePassRunAndMatch<InterfaceVariableScalarReplacement>(spirv, true);
+}
+
+TEST_F(InterfaceVariableScalarReplacementTest,
+       CheckPatchDecorationPreservation) {
+  // Make sure scalars for the variables with the extra arrayness have the extra
+  // arrayness after running the pass while others do not have it.
+  // Only "y" does not have the extra arrayness in the following SPIR-V.
+  const std::string spirv = R"(
+               OpCapability Shader
+               OpCapability Tessellation
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint TessellationEvaluation %func "shader" %x %y %z %w
+               OpDecorate %z Patch
+               OpDecorate %w Patch
+               OpDecorate %z Location 0
+               OpDecorate %x Location 2
+               OpDecorate %y Location 0
+               OpDecorate %w Location 1
+               OpName %x "x"
+               OpName %y "y"
+               OpName %z "z"
+               OpName %w "w"
+
+  ; CHECK:     OpName [[y:%\w+]] "y"
+  ; CHECK-NOT: OpName {{%\w+}} "y"
+  ; CHECK-DAG: OpName [[z0:%\w+]] "z"
+  ; CHECK-DAG: OpName [[z1:%\w+]] "z"
+  ; CHECK-DAG: OpName [[w0:%\w+]] "w"
+  ; CHECK-DAG: OpName [[w1:%\w+]] "w"
+  ; CHECK-DAG: OpName [[x0:%\w+]] "x"
+  ; CHECK-DAG: OpName [[x1:%\w+]] "x"
+
+       %uint = OpTypeInt 32 0
+     %uint_2 = OpConstant %uint 2
+%_arr_uint_uint_2 = OpTypeArray %uint %uint_2
+%_ptr_Output__arr_uint_uint_2 = OpTypePointer Output %_arr_uint_uint_2
+%_ptr_Input__arr_uint_uint_2 = OpTypePointer Input %_arr_uint_uint_2
+          %z = OpVariable %_ptr_Output__arr_uint_uint_2 Output
+          %x = OpVariable %_ptr_Output__arr_uint_uint_2 Output
+          %y = OpVariable %_ptr_Input__arr_uint_uint_2 Input
+          %w = OpVariable %_ptr_Input__arr_uint_uint_2 Input
+
+  ; CHECK-DAG: [[y]] = OpVariable %_ptr_Input__arr_uint_uint_2 Input
+  ; CHECK-DAG: [[z0]] = OpVariable %_ptr_Output_uint Output
+  ; CHECK-DAG: [[z1]] = OpVariable %_ptr_Output_uint Output
+  ; CHECK-DAG: [[w0]] = OpVariable %_ptr_Input_uint Input
+  ; CHECK-DAG: [[w1]] = OpVariable %_ptr_Input_uint Input
+  ; CHECK-DAG: [[x0]] = OpVariable %_ptr_Output_uint Output
+  ; CHECK-DAG: [[x1]] = OpVariable %_ptr_Output_uint Output
+
+     %void   = OpTypeVoid
+     %void_f = OpTypeFunction %void
+     %func   = OpFunction %void None %void_f
+     %label  = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  SinglePassRunAndMatch<InterfaceVariableScalarReplacement>(spirv, true);
+}
+
+TEST_F(InterfaceVariableScalarReplacementTest,
+       CheckEntryPointInterfaceOperands) {
+  const std::string spirv = R"(
+               OpCapability Shader
+               OpCapability Tessellation
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint TessellationEvaluation %tess "tess" %x %y
+               OpEntryPoint Vertex %vert "vert" %w
+               OpDecorate %z Location 0
+               OpDecorate %x Location 2
+               OpDecorate %y Location 0
+               OpDecorate %w Location 1
+               OpName %x "x"
+               OpName %y "y"
+               OpName %z "z"
+               OpName %w "w"
+
+  ; CHECK:     OpName [[y:%\w+]] "y"
+  ; CHECK-NOT: OpName {{%\w+}} "y"
+  ; CHECK-DAG: OpName [[x0:%\w+]] "x"
+  ; CHECK-DAG: OpName [[x1:%\w+]] "x"
+  ; CHECK-DAG: OpName [[w0:%\w+]] "w"
+  ; CHECK-DAG: OpName [[w1:%\w+]] "w"
+  ; CHECK-DAG: OpName [[z:%\w+]] "z"
+  ; CHECK-NOT: OpName {{%\w+}} "z"
+
+       %uint = OpTypeInt 32 0
+     %uint_2 = OpConstant %uint 2
+%_arr_uint_uint_2 = OpTypeArray %uint %uint_2
+%_ptr_Output__arr_uint_uint_2 = OpTypePointer Output %_arr_uint_uint_2
+%_ptr_Input__arr_uint_uint_2 = OpTypePointer Input %_arr_uint_uint_2
+          %z = OpVariable %_ptr_Output__arr_uint_uint_2 Output
+          %x = OpVariable %_ptr_Output__arr_uint_uint_2 Output
+          %y = OpVariable %_ptr_Input__arr_uint_uint_2 Input
+          %w = OpVariable %_ptr_Input__arr_uint_uint_2 Input
+
+  ; CHECK-DAG: [[y]] = OpVariable %_ptr_Input__arr_uint_uint_2 Input
+  ; CHECK-DAG: [[z]] = OpVariable %_ptr_Output__arr_uint_uint_2 Output
+  ; CHECK-DAG: [[w0]] = OpVariable %_ptr_Input_uint Input
+  ; CHECK-DAG: [[w1]] = OpVariable %_ptr_Input_uint Input
+  ; CHECK-DAG: [[x0]] = OpVariable %_ptr_Output_uint Output
+  ; CHECK-DAG: [[x1]] = OpVariable %_ptr_Output_uint Output
+
+     %void   = OpTypeVoid
+     %void_f = OpTypeFunction %void
+     %tess   = OpFunction %void None %void_f
+     %bb0    = OpLabel
+               OpReturn
+               OpFunctionEnd
+     %vert   = OpFunction %void None %void_f
+     %bb1    = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  SinglePassRunAndMatch<InterfaceVariableScalarReplacement>(spirv, true);
+}
+
+class InterfaceVarSROAErrorTest : public PassTest<::testing::Test> {
+ public:
+  InterfaceVarSROAErrorTest()
+      : consumer_([this](spv_message_level_t level, const char*,
+                         const spv_position_t& position, const char* message) {
+          if (!error_message_.empty()) error_message_ += "\n";
+          switch (level) {
+            case SPV_MSG_FATAL:
+            case SPV_MSG_INTERNAL_ERROR:
+            case SPV_MSG_ERROR:
+              error_message_ += "ERROR";
+              break;
+            case SPV_MSG_WARNING:
+              error_message_ += "WARNING";
+              break;
+            case SPV_MSG_INFO:
+              error_message_ += "INFO";
+              break;
+            case SPV_MSG_DEBUG:
+              error_message_ += "DEBUG";
+              break;
+          }
+          error_message_ +=
+              ": " + std::to_string(position.index) + ": " + message;
+        }) {}
+
+  Pass::Status RunPass(const std::string& text) {
+    std::unique_ptr<IRContext> context_ =
+        spvtools::BuildModule(SPV_ENV_UNIVERSAL_1_2, consumer_, text);
+    if (!context_.get()) return Pass::Status::Failure;
+
+    PassManager manager;
+    manager.SetMessageConsumer(consumer_);
+    manager.AddPass<InterfaceVariableScalarReplacement>();
+
+    return manager.Run(context_.get());
+  }
+
+  std::string GetErrorMessage() const { return error_message_; }
+
+  void TearDown() override { error_message_.clear(); }
+
+ private:
+  spvtools::MessageConsumer consumer_;
+  std::string error_message_;
+};
+
+TEST_F(InterfaceVarSROAErrorTest, CheckConflictOfExtraArraynessBetweenEntries) {
+  const std::string spirv = R"(
+               OpCapability Shader
+               OpCapability Tessellation
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint TessellationControl %tess "tess" %x %y %z
+               OpEntryPoint Vertex %vert "vert" %z %w
+               OpDecorate %z Location 0
+               OpDecorate %x Location 2
+               OpDecorate %y Location 0
+               OpDecorate %w Location 1
+               OpName %x "x"
+               OpName %y "y"
+               OpName %z "z"
+               OpName %w "w"
+       %uint = OpTypeInt 32 0
+     %uint_2 = OpConstant %uint 2
+%_arr_uint_uint_2 = OpTypeArray %uint %uint_2
+%_ptr_Output__arr_uint_uint_2 = OpTypePointer Output %_arr_uint_uint_2
+%_ptr_Input__arr_uint_uint_2 = OpTypePointer Input %_arr_uint_uint_2
+          %z = OpVariable %_ptr_Output__arr_uint_uint_2 Output
+          %x = OpVariable %_ptr_Output__arr_uint_uint_2 Output
+          %y = OpVariable %_ptr_Input__arr_uint_uint_2 Input
+          %w = OpVariable %_ptr_Input__arr_uint_uint_2 Input
+     %void   = OpTypeVoid
+     %void_f = OpTypeFunction %void
+     %tess   = OpFunction %void None %void_f
+     %bb0    = OpLabel
+               OpReturn
+               OpFunctionEnd
+     %vert   = OpFunction %void None %void_f
+     %bb1    = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  EXPECT_EQ(RunPass(spirv), Pass::Status::Failure);
+  const char expected_error[] =
+      "ERROR: 0: A variable is arrayed for an entry point but it is not "
+      "arrayed for another entry point\n"
+      "  %z = OpVariable %_ptr_Output__arr_uint_uint_2 Output";
+  EXPECT_STREQ(GetErrorMessage().c_str(), expected_error);
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/test/opt/ir_context_test.cpp b/test/opt/ir_context_test.cpp
index b6866d0..dcae7cf 100644
--- a/test/opt/ir_context_test.cpp
+++ b/test/opt/ir_context_test.cpp
@@ -90,6 +90,21 @@
   }
 }
 
+TEST_F(IRContextTest, DontRebuildValidAnalysis) {
+  std::unique_ptr<Module> module(new Module());
+  IRContext localContext(SPV_ENV_UNIVERSAL_1_2, std::move(module),
+                         spvtools::MessageConsumer());
+
+  auto* oldCfg = localContext.cfg();
+  auto* oldDefUse = localContext.get_def_use_mgr();
+  localContext.BuildInvalidAnalyses(IRContext::kAnalysisCFG |
+                                    IRContext::kAnalysisDefUse);
+  auto* newCfg = localContext.cfg();
+  auto* newDefUse = localContext.get_def_use_mgr();
+  EXPECT_EQ(oldCfg, newCfg);
+  EXPECT_EQ(oldDefUse, newDefUse);
+}
+
 TEST_F(IRContextTest, AllValidAfterBuild) {
   std::unique_ptr<Module> module = MakeUnique<Module>();
   IRContext localContext(SPV_ENV_UNIVERSAL_1_2, std::move(module),
@@ -1132,40 +1147,6 @@
   dbg_decl = ctx->get_def_use_mgr()->GetDef(25);
   EXPECT_EQ(dbg_decl->GetSingleWordOperand(kDebugDeclareOperandVariableIndex),
             20);
-
-  // No DebugValue should be added because result id '26' is not used for
-  // DebugDeclare.
-  ctx->get_debug_info_mgr()->AddDebugValueIfVarDeclIsVisible(dbg_decl, 26, 22,
-                                                             dbg_decl, nullptr);
-  EXPECT_EQ(dbg_decl->NextNode()->opcode(), SpvOpReturn);
-
-  // DebugValue should be added because result id '20' is used for DebugDeclare.
-  ctx->get_debug_info_mgr()->AddDebugValueIfVarDeclIsVisible(dbg_decl, 20, 22,
-                                                             dbg_decl, nullptr);
-  EXPECT_EQ(dbg_decl->NextNode()->GetOpenCL100DebugOpcode(),
-            OpenCLDebugInfo100DebugValue);
-
-  // Replace all uses of result it '20' with '26'
-  EXPECT_EQ(dbg_decl->GetSingleWordOperand(kDebugDeclareOperandVariableIndex),
-            20);
-  EXPECT_TRUE(ctx->ReplaceAllUsesWith(20, 26));
-  EXPECT_EQ(dbg_decl->GetSingleWordOperand(kDebugDeclareOperandVariableIndex),
-            26);
-
-  // No DebugValue should be added because result id '20' is not used for
-  // DebugDeclare.
-  ctx->get_debug_info_mgr()->AddDebugValueIfVarDeclIsVisible(dbg_decl, 20, 7,
-                                                             dbg_decl, nullptr);
-  Instruction* dbg_value = dbg_decl->NextNode();
-  EXPECT_EQ(dbg_value->GetOpenCL100DebugOpcode(), OpenCLDebugInfo100DebugValue);
-  EXPECT_EQ(dbg_value->GetSingleWordOperand(kDebugValueOperandValueIndex), 22);
-
-  // DebugValue should be added because result id '26' is used for DebugDeclare.
-  ctx->get_debug_info_mgr()->AddDebugValueIfVarDeclIsVisible(dbg_decl, 26, 7,
-                                                             dbg_decl, nullptr);
-  dbg_value = dbg_decl->NextNode();
-  EXPECT_EQ(dbg_value->GetOpenCL100DebugOpcode(), OpenCLDebugInfo100DebugValue);
-  EXPECT_EQ(dbg_value->GetSingleWordOperand(kDebugValueOperandValueIndex), 7);
 }
 
 }  // namespace
diff --git a/test/opt/local_access_chain_convert_test.cpp b/test/opt/local_access_chain_convert_test.cpp
index 6fcf23f..07fb537 100644
--- a/test/opt/local_access_chain_convert_test.cpp
+++ b/test/opt/local_access_chain_convert_test.cpp
@@ -1156,6 +1156,197 @@
 
   SinglePassRunAndMatch<LocalAccessChainConvertPass>(before, true);
 }
+TEST_F(LocalAccessChainConvertTest, AccessChainWithLongIndex) {
+  // The access chain take a value that is larger than 32-bit.  The index cannot
+  // be encoded in an OpCompositeExtract, so nothing should be done.
+  const std::string before =
+      R"(OpCapability Shader
+OpCapability Int64
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main_0004f4d4_85b2f584"
+OpExecutionMode %2 OriginUpperLeft
+%ulong = OpTypeInt 64 0
+%ulong_8589934592 = OpConstant %ulong 8589934592
+%ulong_8589934591 = OpConstant %ulong 8589934591
+%_arr_ulong_ulong_8589934592 = OpTypeArray %ulong %ulong_8589934592
+%_ptr_Function__arr_ulong_ulong_8589934592 = OpTypePointer Function %_arr_ulong_ulong_8589934592
+%_ptr_Function_ulong = OpTypePointer Function %ulong
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%2 = OpFunction %void None %10
+%11 = OpLabel
+%12 = OpVariable %_ptr_Function__arr_ulong_ulong_8589934592 Function
+%13 = OpAccessChain %_ptr_Function_ulong %12 %ulong_8589934591
+%14 = OpLoad %ulong %13
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<LocalAccessChainConvertPass>(before, before, false,
+                                                     true);
+}
+
+TEST_F(LocalAccessChainConvertTest, AccessChainWith32BitIndexInLong) {
+  // The access chain has a value that is 32-bits, but it is stored in a 64-bit
+  // variable.  This access change can be converted to an extract.
+  const std::string before =
+      R"(
+; CHECK: OpFunction
+; CHECK: [[var:%\w+]] = OpVariable
+; CHECK: [[ld:%\w+]] = OpLoad {{%\w+}} [[var]]
+; CHECK: OpCompositeExtract %ulong [[ld]] 3
+               OpCapability Shader
+               OpCapability Int64
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main_0004f4d4_85b2f584"
+               OpExecutionMode %2 OriginUpperLeft
+      %ulong = OpTypeInt 64 0
+%ulong_8589934592 = OpConstant %ulong 8589934592
+%ulong_3 = OpConstant %ulong 3
+%_arr_ulong_ulong_8589934592 = OpTypeArray %ulong %ulong_8589934592
+%_ptr_Function__arr_ulong_ulong_8589934592 = OpTypePointer Function %_arr_ulong_ulong_8589934592
+%_ptr_Function_ulong = OpTypePointer Function %ulong
+       %void = OpTypeVoid
+         %10 = OpTypeFunction %void
+          %2 = OpFunction %void None %10
+         %11 = OpLabel
+         %12 = OpVariable %_ptr_Function__arr_ulong_ulong_8589934592 Function
+         %13 = OpAccessChain %_ptr_Function_ulong %12 %ulong_3
+         %14 = OpLoad %ulong %13
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<LocalAccessChainConvertPass>(before, true);
+}
+
+TEST_F(LocalAccessChainConvertTest, AccessChainWithVarIndex) {
+  // The access chain has a value that is not constant, so there should not be
+  // any changes.
+  const std::string before =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main_0004f4d4_85b2f584"
+OpExecutionMode %2 OriginUpperLeft
+%uint = OpTypeInt 32 0
+%uint_5 = OpConstant %uint 5
+%_arr_uint_uint_5 = OpTypeArray %uint %uint_5
+%_ptr_Function__arr_uint_uint_5 = OpTypePointer Function %_arr_uint_uint_5
+%_ptr_Function_uint = OpTypePointer Function %uint
+%8 = OpUndef %uint
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%2 = OpFunction %void None %10
+%11 = OpLabel
+%12 = OpVariable %_ptr_Function__arr_uint_uint_5 Function
+%13 = OpAccessChain %_ptr_Function_uint %12 %8
+%14 = OpLoad %uint %13
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<LocalAccessChainConvertPass>(before, before, false,
+                                                     true);
+}
+
+TEST_F(LocalAccessChainConvertTest, OutOfBoundsAccess) {
+  // The access chain indexes element 12 in an array of size 10.  Nothing should
+  // be done.
+  const std::string assembly =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main" %3
+OpExecutionMode %2 OriginUpperLeft
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%int_10 = OpConstant %int 10
+%_arr_int_int_10 = OpTypeArray %int %int_10
+%_ptr_Function_int = OpTypePointer Function %int
+%int_12 = OpConstant %int 12
+%_ptr_Output_int = OpTypePointer Output %int
+%3 = OpVariable %_ptr_Output_int Output
+%_ptr_Function__arr_int_int_10 = OpTypePointer Function %_arr_int_int_10
+%2 = OpFunction %void None %5
+%13 = OpLabel
+%14 = OpVariable %_ptr_Function__arr_int_int_10 Function
+%15 = OpAccessChain %_ptr_Function_int %14 %int_12
+%16 = OpLoad %int %15
+OpStore %3 %16
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<LocalAccessChainConvertPass>(assembly, assembly, false,
+                                                     true);
+}
+
+TEST_F(LocalAccessChainConvertTest, OutOfBoundsAccessAtBoundary) {
+  // The access chain indexes element 10 in an array of size 10.  Nothing should
+  // be done.
+  const std::string assembly =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main" %3
+OpExecutionMode %2 OriginUpperLeft
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%int_10 = OpConstant %int 10
+%_arr_int_int_10 = OpTypeArray %int %int_10
+%_ptr_Function_int = OpTypePointer Function %int
+%_ptr_Output_int = OpTypePointer Output %int
+%3 = OpVariable %_ptr_Output_int Output
+%_ptr_Function__arr_int_int_10 = OpTypePointer Function %_arr_int_int_10
+%2 = OpFunction %void None %5
+%12 = OpLabel
+%13 = OpVariable %_ptr_Function__arr_int_int_10 Function
+%14 = OpAccessChain %_ptr_Function_int %13 %int_10
+%15 = OpLoad %int %14
+OpStore %3 %15
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<LocalAccessChainConvertPass>(assembly, assembly, false,
+                                                     true);
+}
+
+TEST_F(LocalAccessChainConvertTest, NegativeIndex) {
+  // The access chain has a negative index and should not be converted because
+  // the extract instruction cannot hold a negative number.
+  const std::string assembly =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%uint = OpTypeInt 32 0
+%uint_3808428041 = OpConstant %uint 3808428041
+%_arr_int_uint_3808428041 = OpTypeArray %int %uint_3808428041
+%_ptr_Function__arr_int_uint_3808428041 = OpTypePointer Function %_arr_int_uint_3808428041
+%_ptr_Function_int = OpTypePointer Function %int
+%int_n1272971256 = OpConstant %int -1272971256
+%2 = OpFunction %void None %4
+%12 = OpLabel
+%13 = OpVariable %_ptr_Function__arr_int_uint_3808428041 Function
+%14 = OpAccessChain %_ptr_Function_int %13 %int_n1272971256
+%15 = OpLoad %int %14
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<LocalAccessChainConvertPass>(assembly, assembly, false,
+                                                     true);
+}
 
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
diff --git a/test/opt/local_single_store_elim_test.cpp b/test/opt/local_single_store_elim_test.cpp
index 5d910c4..8f43a11 100644
--- a/test/opt/local_single_store_elim_test.cpp
+++ b/test/opt/local_single_store_elim_test.cpp
@@ -1494,19 +1494,19 @@
          %56 = OpLoad %v4float %in_var_COLOR
 ;CHECK:      DebugNoScope
 ;CHECK-NOT:  OpLine
-;CHECK:      [[pos:%\w+]] = OpLoad %v4float %in_var_POSITION
-;CHECK:      [[color:%\w+]] = OpLoad %v4float %in_var_COLOR
-
                OpLine %7 7 23
                OpStore %param_var_color %56
                OpNoLine
          %93 = OpExtInst %void %1 DebugScope %48
          %73 = OpExtInst %void %1 DebugDeclare %53 %param_var_pos %52
          %74 = OpExtInst %void %1 DebugDeclare %51 %param_var_color %52
+;CHECK:      [[pos:%\w+]] = OpLoad %v4float %in_var_POSITION
 ;CHECK:      OpLine [[file:%\w+]] 6 23
-;CHECK-NEXT: {{%\w+}} = OpExtInst %void {{%\w+}} DebugValue [[dbg_pos]] [[pos]] [[empty_expr:%\w+]]
+;CHECK:      {{%\w+}} = OpExtInst %void {{%\w+}} DebugValue [[dbg_pos]] [[pos]] [[empty_expr:%\w+]]
+;CHECK:      [[color:%\w+]] = OpLoad %v4float %in_var_COLOR
 ;CHECK:      OpLine [[file]] 7 23
-;CHECK-NEXT: {{%\w+}} = OpExtInst %void {{%\w+}} DebugValue [[dbg_color]] [[color]] [[empty_expr]]
+;CHECK:      {{%\w+}} = OpExtInst %void {{%\w+}} DebugValue [[dbg_color]] [[color]] [[empty_expr]]
+;CHECK:      OpLine [[file]] 9 3
 
          %94 = OpExtInst %void %1 DebugScope %49
                OpLine %7 9 3
@@ -1529,6 +1529,227 @@
   SinglePassRunAndMatch<LocalSingleStoreElimPass>(text, false);
 }
 
+TEST_F(LocalSingleStoreElimTest, DebugValuesForAllLocalsAndParams) {
+  // Texture2D g_tColor;
+  //
+  // SamplerState g_sAniso;
+  //
+  // struct PS_INPUT
+  // {
+  //     float2 vTextureCoords : TEXCOORD2 ;
+  // } ;
+  //
+  // struct PS_OUTPUT
+  // {
+  //     float4 vColor : SV_Target0 ;
+  // } ;
+  //
+  // void do_sample ( in float2 tc, out float4 c ) {
+  //     c = g_tColor . Sample ( g_sAniso , tc ) ;
+  // }
+  //
+  // PS_OUTPUT MainPs ( PS_INPUT i )
+  // {
+  //     PS_OUTPUT ps_output ;
+  //     float4 color;
+  //
+  //     do_sample ( i . vTextureCoords . xy , color ) ;
+  //     ps_output . vColor = color;
+  //     return ps_output ;
+  // }
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "OpenCL.DebugInfo.100"
+;CHECK:      [[set:%\w+]] = OpExtInstImport "OpenCL.DebugInfo.100"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %MainPs "MainPs" %in_var_TEXCOORD2 %out_var_SV_Target0 %g_tColor %g_sAniso
+               OpExecutionMode %MainPs OriginUpperLeft
+          %7 = OpString "foo2.frag"
+         %21 = OpString "float"
+         %27 = OpString "PS_INPUT"
+         %31 = OpString "vTextureCoords"
+         %34 = OpString "PS_OUTPUT"
+         %38 = OpString "vColor"
+         %40 = OpString "do_sample"
+         %41 = OpString ""
+         %45 = OpString "c"
+         %47 = OpString "tc"
+         %50 = OpString "MainPs"
+         %54 = OpString "color"
+         %56 = OpString "ps_output"
+         %59 = OpString "i"
+         %62 = OpString "@type.sampler"
+         %63 = OpString "type.sampler"
+         %65 = OpString "g_sAniso"
+         %67 = OpString "@type.2d.image"
+         %68 = OpString "type.2d.image"
+         %70 = OpString "TemplateParam"
+         %73 = OpString "g_tColor"
+;CHECK:      [[str_c:%\w+]] = OpString "c"
+;CHECK:      [[str_tc:%\w+]] = OpString "tc"
+;CHECK:      [[str_color:%\w+]] = OpString "color"
+;CHECK:      [[str_ps_output:%\w+]] = OpString "ps_output"
+;CHECK:      [[str_i:%\w+]] = OpString "i"
+               OpName %type_2d_image "type.2d.image"
+               OpName %g_tColor "g_tColor"
+               OpName %type_sampler "type.sampler"
+               OpName %g_sAniso "g_sAniso"
+               OpName %in_var_TEXCOORD2 "in.var.TEXCOORD2"
+               OpName %out_var_SV_Target0 "out.var.SV_Target0"
+               OpName %MainPs "MainPs"
+               OpName %PS_INPUT "PS_INPUT"
+               OpMemberName %PS_INPUT 0 "vTextureCoords"
+               OpName %PS_OUTPUT "PS_OUTPUT"
+               OpMemberName %PS_OUTPUT 0 "vColor"
+               OpName %type_sampled_image "type.sampled.image"
+               OpDecorate %in_var_TEXCOORD2 Location 0
+               OpDecorate %out_var_SV_Target0 Location 0
+               OpDecorate %g_tColor DescriptorSet 0
+               OpDecorate %g_tColor Binding 0
+               OpDecorate %g_sAniso DescriptorSet 0
+               OpDecorate %g_sAniso Binding 1
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %uint = OpTypeInt 32 0
+    %uint_32 = OpConstant %uint 32
+      %float = OpTypeFloat 32
+%type_2d_image = OpTypeImage %float 2D 2 0 0 1 Unknown
+%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
+%type_sampler = OpTypeSampler
+%_ptr_UniformConstant_type_sampler = OpTypePointer UniformConstant %type_sampler
+    %v2float = OpTypeVector %float 2
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+    %uint_64 = OpConstant %uint 64
+     %uint_0 = OpConstant %uint 0
+   %uint_128 = OpConstant %uint 128
+         %75 = OpTypeFunction %void
+   %PS_INPUT = OpTypeStruct %v2float
+%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT
+  %PS_OUTPUT = OpTypeStruct %v4float
+         %85 = OpTypeFunction %PS_OUTPUT %_ptr_Function_PS_INPUT
+%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+        %105 = OpTypeFunction %void %_ptr_Function_v2float %_ptr_Function_v4float
+%type_sampled_image = OpTypeSampledImage %type_2d_image
+   %g_tColor = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+   %g_sAniso = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+%in_var_TEXCOORD2 = OpVariable %_ptr_Input_v2float Input
+%out_var_SV_Target0 = OpVariable %_ptr_Output_v4float Output
+        %145 = OpExtInst %void %1 DebugOperation Deref
+         %61 = OpExtInst %void %1 DebugInfoNone
+         %58 = OpExtInst %void %1 DebugExpression
+         %23 = OpExtInst %void %1 DebugTypeBasic %21 %uint_32 Float
+         %24 = OpExtInst %void %1 DebugTypeVector %23 2
+         %25 = OpExtInst %void %1 DebugSource %7
+         %26 = OpExtInst %void %1 DebugCompilationUnit 1 4 %25 HLSL
+         %29 = OpExtInst %void %1 DebugTypeComposite %27 Structure %25 5 8 %26 %27 %uint_64 FlagIsProtected|FlagIsPrivate %30
+         %30 = OpExtInst %void %1 DebugTypeMember %31 %24 %25 7 12 %29 %uint_0 %uint_64 FlagIsProtected|FlagIsPrivate
+         %33 = OpExtInst %void %1 DebugTypeVector %23 4
+         %36 = OpExtInst %void %1 DebugTypeComposite %34 Structure %25 10 8 %26 %34 %uint_128 FlagIsProtected|FlagIsPrivate %37
+         %37 = OpExtInst %void %1 DebugTypeMember %38 %33 %25 12 12 %36 %uint_0 %uint_128 FlagIsProtected|FlagIsPrivate
+         %39 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %void %24 %33
+         %42 = OpExtInst %void %1 DebugFunction %40 %39 %25 15 1 %26 %41 FlagIsProtected|FlagIsPrivate 15 %61
+         %44 = OpExtInst %void %1 DebugLexicalBlock %25 15 47 %42
+         %46 = OpExtInst %void %1 DebugLocalVariable %45 %33 %25 15 43 %42 FlagIsLocal 2
+         %48 = OpExtInst %void %1 DebugLocalVariable %47 %24 %25 15 28 %42 FlagIsLocal 1
+         %49 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %36 %29
+         %51 = OpExtInst %void %1 DebugFunction %50 %49 %25 19 1 %26 %41 FlagIsProtected|FlagIsPrivate 20 %61
+         %53 = OpExtInst %void %1 DebugLexicalBlock %25 20 1 %51
+         %55 = OpExtInst %void %1 DebugLocalVariable %54 %33 %25 22 12 %53 FlagIsLocal
+         %57 = OpExtInst %void %1 DebugLocalVariable %56 %36 %25 21 15 %53 FlagIsLocal
+         %60 = OpExtInst %void %1 DebugLocalVariable %59 %29 %25 19 29 %51 FlagIsLocal 1
+         %64 = OpExtInst %void %1 DebugTypeComposite %62 Structure %25 0 0 %26 %63 %61 FlagIsProtected|FlagIsPrivate
+         %66 = OpExtInst %void %1 DebugGlobalVariable %65 %64 %25 3 14 %26 %65 %g_sAniso FlagIsDefinition
+         %69 = OpExtInst %void %1 DebugTypeComposite %67 Class %25 0 0 %26 %68 %61 FlagIsProtected|FlagIsPrivate
+         %71 = OpExtInst %void %1 DebugTypeTemplateParameter %70 %33 %61 %25 0 0
+         %72 = OpExtInst %void %1 DebugTypeTemplate %69 %71
+         %74 = OpExtInst %void %1 DebugGlobalVariable %73 %72 %25 1 11 %26 %73 %g_tColor FlagIsDefinition
+        %142 = OpExtInst %void %1 DebugInlinedAt 24 %53
+        %144 = OpExtInst %void %1 DebugExpression %145
+        %155 = OpExtInst %void %1 DebugExpression %145
+;CHECK:      [[var_c:%\w+]] = OpExtInst %void [[set]] DebugLocalVariable [[str_c]]
+;CHECK:      [[var_tc:%\w+]] = OpExtInst %void [[set]] DebugLocalVariable [[str_tc]]
+;CHECK:      [[var_color:%\w+]] = OpExtInst %void [[set]] DebugLocalVariable [[str_color]]
+;CHECK:      [[var_ps_output:%\w+]] = OpExtInst %void [[set]] DebugLocalVariable [[str_ps_output]]
+;CHECK:      [[var_i:%\w+]] = OpExtInst %void [[set]] DebugLocalVariable [[str_i]]
+     %MainPs = OpFunction %void None %75
+         %76 = OpLabel
+        %153 = OpVariable %_ptr_Function_v2float Function
+        %149 = OpVariable %_ptr_Function_v4float Function
+        %157 = OpExtInst %void %1 DebugScope %53
+        %143 = OpVariable %_ptr_Function_v4float Function
+        %121 = OpVariable %_ptr_Function_v4float Function
+        %122 = OpVariable %_ptr_Function_v2float Function
+        %158 = OpExtInst %void %1 DebugScope %51
+               OpLine %7 19 29
+        %156 = OpExtInst %void %1 DebugValue %60 %153 %155 %int_0
+        %159 = OpExtInst %void %1 DebugScope %53
+               OpLine %7 21 15
+        %146 = OpExtInst %void %1 DebugValue %57 %143 %144 %int_0
+               OpNoLine
+        %160 = OpExtInst %void %1 DebugNoScope
+         %80 = OpLoad %v2float %in_var_TEXCOORD2
+         %81 = OpCompositeConstruct %PS_INPUT %80
+        %154 = OpCompositeExtract %v2float %81 0
+               OpStore %153 %154
+        %161 = OpExtInst %void %1 DebugScope %53
+               OpLine %7 22 12
+        %127 = OpExtInst %void %1 DebugDeclare %55 %121 %58
+               OpLine %7 24 17
+        %129 = OpLoad %v2float %153
+               OpStore %122 %129
+        %162 = OpExtInst %void %1 DebugScope %42 %142
+               OpLine %7 15 28
+        %135 = OpExtInst %void %1 DebugDeclare %48 %122 %58
+               OpLine %7 15 43
+        %136 = OpExtInst %void %1 DebugDeclare %46 %121 %58
+        %163 = OpExtInst %void %1 DebugScope %44 %142
+               OpLine %7 16 9
+        %137 = OpLoad %type_2d_image %g_tColor
+               OpLine %7 16 29
+        %138 = OpLoad %type_sampler %g_sAniso
+               OpLine %7 16 40
+        %139 = OpLoad %v2float %122
+               OpLine %7 16 9
+        %140 = OpSampledImage %type_sampled_image %137 %138
+        %141 = OpImageSampleImplicitLod %v4float %140 %139 None
+               OpLine %7 16 5
+               OpStore %121 %141
+        %164 = OpExtInst %void %1 DebugScope %53
+               OpLine %7 25 26
+        %131 = OpLoad %v4float %121
+               OpLine %7 25 5
+               OpStore %143 %131
+               OpLine %7 26 12
+        %147 = OpLoad %v4float %143
+        %148 = OpCompositeConstruct %PS_OUTPUT %147
+               OpLine %7 26 5
+        %150 = OpCompositeExtract %v4float %148 0
+               OpStore %149 %150
+               OpNoLine
+        %165 = OpExtInst %void %1 DebugNoScope
+        %151 = OpLoad %v4float %149
+        %152 = OpCompositeConstruct %PS_OUTPUT %151
+         %84 = OpCompositeExtract %v4float %152 0
+               OpStore %out_var_SV_Target0 %84
+               OpLine %7 27 1
+               OpReturn
+               OpFunctionEnd
+;CHECK:      {{%\w+}} = OpExtInst %void [[set]] DebugValue [[var_i]]
+;CHECK:      {{%\w+}} = OpExtInst %void [[set]] DebugValue [[var_tc]]
+;CHECK:      {{%\w+}} = OpExtInst %void [[set]] DebugValue [[var_c]]
+;CHECK:      {{%\w+}} = OpExtInst %void [[set]] DebugValue [[var_color]]
+;CHECK:      {{%\w+}} = OpExtInst %void [[set]] DebugValue [[var_ps_output]]
+  )";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  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 4b7542f..45006ca 100644
--- a/test/opt/local_ssa_elim_test.cpp
+++ b/test/opt/local_ssa_elim_test.cpp
@@ -2978,6 +2978,7 @@
 
 ; CHECK:      OpExtInst %void [[ext]] DebugScope [[dbg_main]]
 ; CHECK:      OpStore %f %float_0
+; CHECK-NEXT: OpExtInst %void [[ext]] DebugValue [[dbg_x]] %float_0
 ; CHECK-NEXT: OpExtInst %void [[ext]] DebugValue [[dbg_f]] %float_0
 ; CHECK-NEXT: OpStore %i %int_0
 ; CHECK-NEXT: OpExtInst %void [[ext]] DebugValue [[dbg_i]] %int_0
@@ -4251,6 +4252,1210 @@
   SinglePassRunAndCheck<SSARewritePass>(text, text, false);
 }
 
+TEST_F(LocalSSAElimTest, MissingDebugValue) {
+  // Make sure DebugValue for final fragcolor assignment is generated.
+
+  const std::string text =
+      R"(
+               OpCapability Shader
+               OpCapability ImageQuery
+               OpExtension "SPV_KHR_non_semantic_info"
+          %1 = OpExtInstImport "GLSL.std.450"
+          %2 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %in_var_TEXCOORD0 %out_var_SV_TARGET %textureposition %samplerposition %textureNormal %samplerNormal %textureAlbedo %samplerAlbedo %textureShadowMap %samplerShadowMap %ubo
+               OpExecutionMode %main OriginUpperLeft
+         %15 = OpString "d2.frag"
+         %55 = OpString "float"
+         %63 = OpString "// Copyright 2020 Google LLC
+
+Texture2D textureposition : register(t1);
+SamplerState samplerposition : register(s1);
+Texture2D textureNormal : register(t2);
+SamplerState samplerNormal : register(s2);
+Texture2D textureAlbedo : register(t3);
+SamplerState samplerAlbedo : register(s3);
+// Depth from the light's point of view
+//layout (binding = 5) uniform sampler2DShadow samplerShadowMap;
+Texture2DArray textureShadowMap : register(t5);
+SamplerState samplerShadowMap : register(s5);
+
+#define LIGHT_COUNT 3
+#define SHADOW_FACTOR 0.25
+#define AMBIENT_LIGHT 0.1
+#define USE_PCF
+
+struct Light
+{
+	float4 position;
+	float4 target;
+	float4 color;
+	float4x4 viewMatrix;
+};
+
+struct UBO
+{
+	float4 viewPos;
+	Light lights[LIGHT_COUNT];
+	int useShadows;
+	int displayDebugTarget;
+};
+
+cbuffer ubo : register(b4) { UBO ubo; }
+
+float textureProj(float4 P, float layer, float2 offset)
+{
+	float shadow = 1.0;
+	float4 shadowCoord = P / P.w;
+	shadowCoord.xy = shadowCoord.xy * 0.5 + 0.5;
+
+	if (shadowCoord.z > -1.0 && shadowCoord.z < 1.0)
+	{
+		float dist = textureShadowMap.Sample(samplerShadowMap, float3(shadowCoord.xy + offset, layer)).r;
+		if (shadowCoord.w > 0.0 && dist < shadowCoord.z)
+		{
+			shadow = SHADOW_FACTOR;
+		}
+	}
+	return shadow;
+}
+
+float filterPCF(float4 sc, float layer)
+{
+	int2 texDim; int elements; int levels;
+	textureShadowMap.GetDimensions(0, texDim.x, texDim.y, elements, levels);
+	float scale = 1.5;
+	float dx = scale * 1.0 / float(texDim.x);
+	float dy = scale * 1.0 / float(texDim.y);
+
+	float shadowFactor = 0.0;
+	int count = 0;
+	int range = 1;
+
+	for (int x = -range; x <= range; x++)
+	{
+		for (int y = -range; y <= range; y++)
+		{
+			shadowFactor += textureProj(sc, layer, float2(dx*x, dy*y));
+			count++;
+		}
+
+	}
+	return shadowFactor / count;
+}
+
+float3 shadow(float3 fragcolor, float3 fragPos) {
+	for (int i = 0; i < LIGHT_COUNT; ++i)
+	{
+		float4 shadowClip = mul(ubo.lights[i].viewMatrix, float4(fragPos.xyz, 1.0));
+
+		float shadowFactor;
+		#ifdef USE_PCF
+			shadowFactor= filterPCF(shadowClip, i);
+		#else
+			shadowFactor = textureProj(shadowClip, i, float2(0.0, 0.0));
+		#endif
+
+		fragcolor *= shadowFactor;
+	}
+	return fragcolor;
+}
+
+float4 main([[vk::location(0)]] float2 inUV : TEXCOORD0) : SV_TARGET
+{
+	// Get G-Buffer values
+	float3 fragPos = textureposition.Sample(samplerposition, inUV).rgb;
+	float3 normal = textureNormal.Sample(samplerNormal, inUV).rgb;
+	float4 albedo = textureAlbedo.Sample(samplerAlbedo, inUV);
+
+	// Ambient part
+	float3 fragcolor  = albedo.rgb * AMBIENT_LIGHT;
+
+	float3 N = normalize(normal);
+
+	for(int i = 0; i < LIGHT_COUNT; ++i)
+	{
+		// Vector to light
+		float3 L = ubo.lights[i].position.xyz - fragPos;
+		// Distance from light to fragment position
+		float dist = length(L);
+		L = normalize(L);
+
+		// Viewer to fragment
+		float3 V = ubo.viewPos.xyz - fragPos;
+		V = normalize(V);
+
+		float lightCosInnerAngle = cos(radians(15.0));
+		float lightCosOuterAngle = cos(radians(25.0));
+		float lightRange = 100.0;
+
+		// Direction vector from source to target
+		float3 dir = normalize(ubo.lights[i].position.xyz - ubo.lights[i].target.xyz);
+
+		// Dual cone spot light with smooth transition between inner and outer angle
+		float cosDir = dot(L, dir);
+		float spotEffect = smoothstep(lightCosOuterAngle, lightCosInnerAngle, cosDir);
+		float heightAttenuation = smoothstep(lightRange, 0.0f, dist);
+
+		// Diffuse lighting
+		float NdotL = max(0.0, dot(N, L));
+		float3 diff = NdotL.xxx;
+
+		// Specular lighting
+		float3 R = reflect(-L, N);
+		float NdotR = max(0.0, dot(R, V));
+		float3 spec = (pow(NdotR, 16.0) * albedo.a * 2.5).xxx;
+
+		fragcolor += float3((diff + spec) * spotEffect * heightAttenuation) * ubo.lights[i].color.rgb * albedo.rgb;
+	}
+
+	// Shadow calculations in a separate pass
+	if (ubo.useShadows > 0)
+	{
+		fragcolor = shadow(fragcolor, fragPos);
+	}
+
+	return float4(fragcolor, 1);
+}
+"
+         %68 = OpString "textureProj"
+         %69 = OpString ""
+         %78 = OpString "dist"
+         %82 = OpString "shadowCoord"
+         %85 = OpString "shadow"
+         %89 = OpString "offset"
+         %92 = OpString "layer"
+         %95 = OpString "P"
+         %99 = OpString "filterPCF"
+        %108 = OpString "int"
+        %110 = OpString "y"
+        %114 = OpString "x"
+        %118 = OpString "range"
+        %122 = OpString "count"
+        %125 = OpString "shadowFactor"
+        %128 = OpString "dy"
+        %131 = OpString "dx"
+        %134 = OpString "scale"
+        %137 = OpString "levels"
+        %141 = OpString "elements"
+        %145 = OpString "texDim"
+        %150 = OpString "sc"
+        %162 = OpString "shadowClip"
+        %166 = OpString "i"
+        %169 = OpString "fragPos"
+        %171 = OpString "fragcolor"
+        %175 = OpString "main"
+        %184 = OpString "spec"
+        %187 = OpString "NdotR"
+        %190 = OpString "R"
+        %193 = OpString "diff"
+        %196 = OpString "NdotL"
+        %199 = OpString "heightAttenuation"
+        %202 = OpString "spotEffect"
+        %205 = OpString "cosDir"
+        %208 = OpString "dir"
+        %211 = OpString "lightRange"
+        %214 = OpString "lightCosOuterAngle"
+        %217 = OpString "lightCosInnerAngle"
+        %220 = OpString "V"
+        %225 = OpString "L"
+        %230 = OpString "N"
+        %235 = OpString "albedo"
+        %238 = OpString "normal"
+        %244 = OpString "inUV"
+        %246 = OpString "viewPos"
+        %249 = OpString "position"
+        %252 = OpString "target"
+        %254 = OpString "color"
+        %259 = OpString "viewMatrix"
+        %263 = OpString "Light"
+        %267 = OpString "lights"
+        %271 = OpString "useShadows"
+        %275 = OpString "displayDebugTarget"
+        %278 = OpString "UBO"
+        %282 = OpString "ubo"
+        %285 = OpString "type.ubo"
+        %289 = OpString "@type.sampler"
+        %290 = OpString "type.sampler"
+        %292 = OpString "samplerShadowMap"
+        %295 = OpString "@type.2d.image.array"
+        %296 = OpString "type.2d.image.array"
+        %298 = OpString "TemplateParam"
+        %301 = OpString "textureShadowMap"
+        %304 = OpString "samplerAlbedo"
+        %306 = OpString "@type.2d.image"
+        %307 = OpString "type.2d.image"
+        %311 = OpString "textureAlbedo"
+        %313 = OpString "samplerNormal"
+        %315 = OpString "textureNormal"
+        %317 = OpString "samplerposition"
+        %319 = OpString "textureposition"
+               OpName %type_2d_image "type.2d.image"
+               OpName %textureposition "textureposition"
+               OpName %type_sampler "type.sampler"
+               OpName %samplerposition "samplerposition"
+               OpName %textureNormal "textureNormal"
+               OpName %samplerNormal "samplerNormal"
+               OpName %textureAlbedo "textureAlbedo"
+               OpName %samplerAlbedo "samplerAlbedo"
+               OpName %type_2d_image_array "type.2d.image.array"
+               OpName %textureShadowMap "textureShadowMap"
+               OpName %samplerShadowMap "samplerShadowMap"
+               OpName %type_ubo "type.ubo"
+               OpMemberName %type_ubo 0 "ubo"
+               OpName %UBO "UBO"
+               OpMemberName %UBO 0 "viewPos"
+               OpMemberName %UBO 1 "lights"
+               OpMemberName %UBO 2 "useShadows"
+               OpMemberName %UBO 3 "displayDebugTarget"
+               OpName %Light "Light"
+               OpMemberName %Light 0 "position"
+               OpMemberName %Light 1 "target"
+               OpMemberName %Light 2 "color"
+               OpMemberName %Light 3 "viewMatrix"
+               OpName %ubo "ubo"
+               OpName %in_var_TEXCOORD0 "in.var.TEXCOORD0"
+               OpName %out_var_SV_TARGET "out.var.SV_TARGET"
+               OpName %main "main"
+               OpName %param_var_inUV "param.var.inUV"
+               OpName %type_sampled_image "type.sampled.image"
+               OpName %type_sampled_image_0 "type.sampled.image"
+               OpDecorate %in_var_TEXCOORD0 Location 0
+               OpDecorate %out_var_SV_TARGET Location 0
+               OpDecorate %textureposition DescriptorSet 0
+               OpDecorate %textureposition Binding 1
+               OpDecorate %samplerposition DescriptorSet 0
+               OpDecorate %samplerposition Binding 1
+               OpDecorate %textureNormal DescriptorSet 0
+               OpDecorate %textureNormal Binding 2
+               OpDecorate %samplerNormal DescriptorSet 0
+               OpDecorate %samplerNormal Binding 2
+               OpDecorate %textureAlbedo DescriptorSet 0
+               OpDecorate %textureAlbedo Binding 3
+               OpDecorate %samplerAlbedo DescriptorSet 0
+               OpDecorate %samplerAlbedo Binding 3
+               OpDecorate %textureShadowMap DescriptorSet 0
+               OpDecorate %textureShadowMap Binding 5
+               OpDecorate %samplerShadowMap DescriptorSet 0
+               OpDecorate %samplerShadowMap Binding 5
+               OpDecorate %ubo DescriptorSet 0
+               OpDecorate %ubo Binding 4
+               OpMemberDecorate %Light 0 Offset 0
+               OpMemberDecorate %Light 1 Offset 16
+               OpMemberDecorate %Light 2 Offset 32
+               OpMemberDecorate %Light 3 Offset 48
+               OpMemberDecorate %Light 3 MatrixStride 16
+               OpMemberDecorate %Light 3 RowMajor
+               OpDecorate %_arr_Light_uint_3 ArrayStride 112
+               OpMemberDecorate %UBO 0 Offset 0
+               OpMemberDecorate %UBO 1 Offset 16
+               OpMemberDecorate %UBO 2 Offset 352
+               OpMemberDecorate %UBO 3 Offset 356
+               OpMemberDecorate %type_ubo 0 Offset 0
+               OpDecorate %type_ubo Block
+)"
+      R"(   %float = OpTypeFloat 32
+%float_0_100000001 = OpConstant %float 0.100000001
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %int_3 = OpConstant %int 3
+      %int_1 = OpConstant %int 1
+   %float_15 = OpConstant %float 15
+   %float_25 = OpConstant %float 25
+  %float_100 = OpConstant %float 100
+    %float_0 = OpConstant %float 0
+   %float_16 = OpConstant %float 16
+  %float_2_5 = OpConstant %float 2.5
+      %int_2 = OpConstant %int 2
+    %float_1 = OpConstant %float 1
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+  %float_1_5 = OpConstant %float 1.5
+  %float_0_5 = OpConstant %float 0.5
+    %v2float = OpTypeVector %float 2
+         %35 = OpConstantComposite %v2float %float_0_5 %float_0_5
+   %float_n1 = OpConstant %float -1
+ %float_0_25 = OpConstant %float 0.25
+    %uint_32 = OpConstant %uint 32
+%type_2d_image = OpTypeImage %float 2D 2 0 0 1 Unknown
+%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
+%type_sampler = OpTypeSampler
+%_ptr_UniformConstant_type_sampler = OpTypePointer UniformConstant %type_sampler
+%type_2d_image_array = OpTypeImage %float 2D 2 1 0 1 Unknown
+%_ptr_UniformConstant_type_2d_image_array = OpTypePointer UniformConstant %type_2d_image_array
+    %v4float = OpTypeVector %float 4
+     %uint_3 = OpConstant %uint 3
+%mat4v4float = OpTypeMatrix %v4float 4
+      %Light = OpTypeStruct %v4float %v4float %v4float %mat4v4float
+%_arr_Light_uint_3 = OpTypeArray %Light %uint_3
+        %UBO = OpTypeStruct %v4float %_arr_Light_uint_3 %int %int
+   %type_ubo = OpTypeStruct %UBO
+%_ptr_Uniform_type_ubo = OpTypePointer Uniform %type_ubo
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+     %uint_4 = OpConstant %uint 4
+     %uint_2 = OpConstant %uint 2
+     %uint_1 = OpConstant %uint 1
+     %uint_5 = OpConstant %uint 5
+    %uint_37 = OpConstant %uint 37
+    %uint_38 = OpConstant %uint 38
+    %uint_44 = OpConstant %uint 44
+    %uint_47 = OpConstant %uint 47
+    %uint_45 = OpConstant %uint 45
+     %uint_9 = OpConstant %uint 9
+    %uint_40 = OpConstant %uint 40
+    %uint_39 = OpConstant %uint 39
+     %uint_8 = OpConstant %uint 8
+    %uint_49 = OpConstant %uint 49
+    %uint_35 = OpConstant %uint 35
+    %uint_26 = OpConstant %uint 26
+    %uint_54 = OpConstant %uint 54
+    %uint_55 = OpConstant %uint 55
+    %uint_67 = OpConstant %uint 67
+    %uint_69 = OpConstant %uint 69
+    %uint_68 = OpConstant %uint 68
+    %uint_12 = OpConstant %uint 12
+    %uint_66 = OpConstant %uint 66
+    %uint_11 = OpConstant %uint 11
+    %uint_64 = OpConstant %uint 64
+     %uint_6 = OpConstant %uint 6
+    %uint_63 = OpConstant %uint 63
+    %uint_62 = OpConstant %uint 62
+    %uint_60 = OpConstant %uint 60
+    %uint_59 = OpConstant %uint 59
+    %uint_58 = OpConstant %uint 58
+    %uint_56 = OpConstant %uint 56
+    %uint_33 = OpConstant %uint 33
+    %uint_19 = OpConstant %uint 19
+     %uint_7 = OpConstant %uint 7
+    %uint_34 = OpConstant %uint 34
+    %uint_24 = OpConstant %uint 24
+    %uint_78 = OpConstant %uint 78
+    %uint_80 = OpConstant %uint 80
+    %uint_83 = OpConstant %uint 83
+    %uint_81 = OpConstant %uint 81
+    %uint_10 = OpConstant %uint 10
+    %uint_79 = OpConstant %uint 79
+    %uint_22 = OpConstant %uint 22
+    %uint_95 = OpConstant %uint 95
+    %uint_96 = OpConstant %uint 96
+   %uint_145 = OpConstant %uint 145
+   %uint_108 = OpConstant %uint 108
+   %uint_138 = OpConstant %uint 138
+   %uint_137 = OpConstant %uint 137
+   %uint_136 = OpConstant %uint 136
+   %uint_133 = OpConstant %uint 133
+   %uint_132 = OpConstant %uint 132
+   %uint_129 = OpConstant %uint 129
+   %uint_128 = OpConstant %uint 128
+   %uint_127 = OpConstant %uint 127
+   %uint_124 = OpConstant %uint 124
+   %uint_121 = OpConstant %uint 121
+   %uint_120 = OpConstant %uint 120
+   %uint_119 = OpConstant %uint 119
+   %uint_116 = OpConstant %uint 116
+   %uint_112 = OpConstant %uint 112
+   %uint_110 = OpConstant %uint 110
+   %uint_107 = OpConstant %uint 107
+   %uint_105 = OpConstant %uint 105
+   %uint_103 = OpConstant %uint 103
+   %uint_100 = OpConstant %uint 100
+    %uint_99 = OpConstant %uint 99
+    %uint_98 = OpConstant %uint 98
+    %uint_29 = OpConstant %uint 29
+    %uint_21 = OpConstant %uint 21
+   %uint_256 = OpConstant %uint 256
+    %uint_23 = OpConstant %uint 23
+   %uint_384 = OpConstant %uint 384
+   %uint_512 = OpConstant %uint 512
+   %uint_896 = OpConstant %uint 896
+  %uint_2688 = OpConstant %uint 2688
+    %uint_30 = OpConstant %uint 30
+  %uint_2816 = OpConstant %uint 2816
+    %uint_31 = OpConstant %uint 31
+  %uint_2848 = OpConstant %uint 2848
+  %uint_2880 = OpConstant %uint 2880
+    %uint_27 = OpConstant %uint 27
+  %uint_2944 = OpConstant %uint 2944
+    %uint_14 = OpConstant %uint 14
+    %uint_16 = OpConstant %uint 16
+        %321 = OpTypeFunction %void
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+   %uint_150 = OpConstant %uint 150
+    %v3float = OpTypeVector %float 3
+%_ptr_Function_v3float = OpTypePointer Function %v3float
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Function_int = OpTypePointer Function %int
+%_ptr_Function_float = OpTypePointer Function %float
+    %uint_42 = OpConstant %uint 42
+%type_sampled_image = OpTypeSampledImage %type_2d_image
+    %uint_65 = OpConstant %uint 65
+    %uint_18 = OpConstant %uint 18
+    %uint_13 = OpConstant %uint 13
+    %uint_15 = OpConstant %uint 15
+    %uint_17 = OpConstant %uint 17
+       %bool = OpTypeBool
+    %uint_25 = OpConstant %uint 25
+%_ptr_Uniform_UBO = OpTypePointer Uniform %UBO
+%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
+    %uint_28 = OpConstant %uint 28
+    %uint_43 = OpConstant %uint 43
+   %uint_113 = OpConstant %uint 113
+   %uint_117 = OpConstant %uint 117
+    %uint_46 = OpConstant %uint 46
+    %uint_76 = OpConstant %uint 76
+    %uint_53 = OpConstant %uint 53
+    %uint_73 = OpConstant %uint 73
+    %uint_48 = OpConstant %uint 48
+   %uint_140 = OpConstant %uint 140
+    %uint_52 = OpConstant %uint 52
+    %uint_93 = OpConstant %uint 93
+    %uint_87 = OpConstant %uint 87
+   %uint_106 = OpConstant %uint 106
+    %uint_36 = OpConstant %uint 36
+   %uint_144 = OpConstant %uint 144
+%_ptr_Uniform_int = OpTypePointer Uniform %int
+   %uint_146 = OpConstant %uint 146
+   %uint_147 = OpConstant %uint 147
+   %uint_149 = OpConstant %uint 149
+    %uint_41 = OpConstant %uint 41
+%_ptr_Uniform_mat4v4float = OpTypePointer Uniform %mat4v4float
+    %uint_77 = OpConstant %uint 77
+    %uint_85 = OpConstant %uint 85
+    %uint_90 = OpConstant %uint 90
+    %uint_92 = OpConstant %uint 92
+      %v2int = OpTypeVector %int 2
+%_ptr_Function_v2int = OpTypePointer Function %v2int
+    %uint_57 = OpConstant %uint 57
+     %v3uint = OpTypeVector %uint 3
+    %uint_72 = OpConstant %uint 72
+    %uint_70 = OpConstant %uint 70
+    %uint_50 = OpConstant %uint 50
+    %uint_61 = OpConstant %uint 61
+    %uint_71 = OpConstant %uint 71
+    %uint_75 = OpConstant %uint 75
+    %uint_82 = OpConstant %uint 82
+%type_sampled_image_0 = OpTypeSampledImage %type_2d_image_array
+    %uint_51 = OpConstant %uint 51
+%textureposition = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+%samplerposition = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+%textureNormal = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+%samplerNormal = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+%textureAlbedo = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+%samplerAlbedo = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+%textureShadowMap = OpVariable %_ptr_UniformConstant_type_2d_image_array UniformConstant
+%samplerShadowMap = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+        %ubo = OpVariable %_ptr_Uniform_type_ubo Uniform
+%in_var_TEXCOORD0 = OpVariable %_ptr_Input_v2float Input
+%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output
+  %uint_1792 = OpConstant %uint 1792
+  %uint_1869 = OpConstant %uint 1869
+  %uint_2060 = OpConstant %uint 2060
+        %288 = OpExtInst %void %2 DebugInfoNone
+        %243 = OpExtInst %void %2 DebugExpression
+         %57 = OpExtInst %void %2 DebugTypeBasic %55 %uint_32 %uint_3 %uint_0
+         %58 = OpExtInst %void %2 DebugTypeVector %57 %uint_4
+         %60 = OpExtInst %void %2 DebugTypeVector %57 %uint_2
+         %62 = OpExtInst %void %2 DebugTypeFunction %uint_3 %57 %58 %57 %60
+         %64 = OpExtInst %void %2 DebugSource %15 %63
+         %65 = OpExtInst %void %2 DebugCompilationUnit %uint_1 %uint_4 %64 %uint_5
+         %70 = OpExtInst %void %2 DebugFunction %68 %62 %64 %uint_37 %uint_1 %65 %69 %uint_3 %uint_38
+         %73 = OpExtInst %void %2 DebugLexicalBlock %64 %uint_38 %uint_1 %70
+         %74 = OpExtInst %void %2 DebugLexicalBlock %64 %uint_44 %uint_2 %73
+         %76 = OpExtInst %void %2 DebugLexicalBlock %64 %uint_47 %uint_3 %74
+         %79 = OpExtInst %void %2 DebugLocalVariable %78 %57 %64 %uint_45 %uint_9 %74 %uint_4
+         %83 = OpExtInst %void %2 DebugLocalVariable %82 %58 %64 %uint_40 %uint_9 %73 %uint_4
+         %86 = OpExtInst %void %2 DebugLocalVariable %85 %57 %64 %uint_39 %uint_8 %73 %uint_4
+         %90 = OpExtInst %void %2 DebugLocalVariable %89 %60 %64 %uint_37 %uint_49 %70 %uint_4 %uint_3
+         %93 = OpExtInst %void %2 DebugLocalVariable %92 %57 %64 %uint_37 %uint_35 %70 %uint_4 %uint_2
+         %96 = OpExtInst %void %2 DebugLocalVariable %95 %58 %64 %uint_37 %uint_26 %70 %uint_4 %uint_1
+         %98 = OpExtInst %void %2 DebugTypeFunction %uint_3 %57 %58 %57
+        %100 = OpExtInst %void %2 DebugFunction %99 %98 %64 %uint_54 %uint_1 %65 %69 %uint_3 %uint_55
+        %103 = OpExtInst %void %2 DebugLexicalBlock %64 %uint_55 %uint_1 %100
+        %104 = OpExtInst %void %2 DebugLexicalBlock %64 %uint_67 %uint_2 %103
+        %106 = OpExtInst %void %2 DebugLexicalBlock %64 %uint_69 %uint_3 %104
+        %109 = OpExtInst %void %2 DebugTypeBasic %108 %uint_32 %uint_4 %uint_0
+        %111 = OpExtInst %void %2 DebugLocalVariable %110 %109 %64 %uint_68 %uint_12 %104 %uint_4
+        %115 = OpExtInst %void %2 DebugLocalVariable %114 %109 %64 %uint_66 %uint_11 %103 %uint_4
+        %119 = OpExtInst %void %2 DebugLocalVariable %118 %109 %64 %uint_64 %uint_6 %103 %uint_4
+        %123 = OpExtInst %void %2 DebugLocalVariable %122 %109 %64 %uint_63 %uint_6 %103 %uint_4
+        %126 = OpExtInst %void %2 DebugLocalVariable %125 %57 %64 %uint_62 %uint_8 %103 %uint_4
+        %129 = OpExtInst %void %2 DebugLocalVariable %128 %57 %64 %uint_60 %uint_8 %103 %uint_4
+        %132 = OpExtInst %void %2 DebugLocalVariable %131 %57 %64 %uint_59 %uint_8 %103 %uint_4
+        %135 = OpExtInst %void %2 DebugLocalVariable %134 %57 %64 %uint_58 %uint_8 %103 %uint_4
+        %138 = OpExtInst %void %2 DebugLocalVariable %137 %109 %64 %uint_56 %uint_33 %103 %uint_4
+        %142 = OpExtInst %void %2 DebugLocalVariable %141 %109 %64 %uint_56 %uint_19 %103 %uint_4
+        %144 = OpExtInst %void %2 DebugTypeVector %109 %uint_2
+        %146 = OpExtInst %void %2 DebugLocalVariable %145 %144 %64 %uint_56 %uint_7 %103 %uint_4
+        %148 = OpExtInst %void %2 DebugLocalVariable %92 %57 %64 %uint_54 %uint_34 %100 %uint_4 %uint_2
+        %151 = OpExtInst %void %2 DebugLocalVariable %150 %58 %64 %uint_54 %uint_24 %100 %uint_4 %uint_1
+        %153 = OpExtInst %void %2 DebugTypeVector %57 %uint_3
+        %154 = OpExtInst %void %2 DebugTypeFunction %uint_3 %153 %153 %153
+        %155 = OpExtInst %void %2 DebugFunction %85 %154 %64 %uint_78 %uint_1 %65 %69 %uint_3 %uint_78
+        %157 = OpExtInst %void %2 DebugLexicalBlock %64 %uint_78 %uint_49 %155
+        %158 = OpExtInst %void %2 DebugLexicalBlock %64 %uint_80 %uint_2 %157
+        %160 = OpExtInst %void %2 DebugLocalVariable %125 %57 %64 %uint_83 %uint_9 %158 %uint_4
+        %163 = OpExtInst %void %2 DebugLocalVariable %162 %58 %64 %uint_81 %uint_10 %158 %uint_4
+        %167 = OpExtInst %void %2 DebugLocalVariable %166 %109 %64 %uint_79 %uint_11 %157 %uint_4
+        %170 = OpExtInst %void %2 DebugLocalVariable %169 %153 %64 %uint_78 %uint_40 %155 %uint_4 %uint_2
+        %172 = OpExtInst %void %2 DebugLocalVariable %171 %153 %64 %uint_78 %uint_22 %155 %uint_4 %uint_1
+        %174 = OpExtInst %void %2 DebugTypeFunction %uint_3 %58 %60
+        %176 = OpExtInst %void %2 DebugFunction %175 %174 %64 %uint_95 %uint_1 %65 %69 %uint_3 %uint_96
+        %179 = OpExtInst %void %2 DebugLexicalBlock %64 %uint_96 %uint_1 %176
+        %180 = OpExtInst %void %2 DebugLexicalBlock %64 %uint_145 %uint_2 %179
+        %182 = OpExtInst %void %2 DebugLexicalBlock %64 %uint_108 %uint_2 %179
+        %185 = OpExtInst %void %2 DebugLocalVariable %184 %153 %64 %uint_138 %uint_10 %182 %uint_4
+        %188 = OpExtInst %void %2 DebugLocalVariable %187 %57 %64 %uint_137 %uint_9 %182 %uint_4
+        %191 = OpExtInst %void %2 DebugLocalVariable %190 %153 %64 %uint_136 %uint_10 %182 %uint_4
+        %194 = OpExtInst %void %2 DebugLocalVariable %193 %153 %64 %uint_133 %uint_10 %182 %uint_4
+        %197 = OpExtInst %void %2 DebugLocalVariable %196 %57 %64 %uint_132 %uint_9 %182 %uint_4
+        %200 = OpExtInst %void %2 DebugLocalVariable %199 %57 %64 %uint_129 %uint_9 %182 %uint_4
+        %203 = OpExtInst %void %2 DebugLocalVariable %202 %57 %64 %uint_128 %uint_9 %182 %uint_4
+        %206 = OpExtInst %void %2 DebugLocalVariable %205 %57 %64 %uint_127 %uint_9 %182 %uint_4
+        %209 = OpExtInst %void %2 DebugLocalVariable %208 %153 %64 %uint_124 %uint_10 %182 %uint_4
+        %212 = OpExtInst %void %2 DebugLocalVariable %211 %57 %64 %uint_121 %uint_9 %182 %uint_4
+        %215 = OpExtInst %void %2 DebugLocalVariable %214 %57 %64 %uint_120 %uint_9 %182 %uint_4
+        %218 = OpExtInst %void %2 DebugLocalVariable %217 %57 %64 %uint_119 %uint_9 %182 %uint_4
+        %221 = OpExtInst %void %2 DebugLocalVariable %220 %153 %64 %uint_116 %uint_10 %182 %uint_4
+        %223 = OpExtInst %void %2 DebugLocalVariable %78 %57 %64 %uint_112 %uint_9 %182 %uint_4
+        %226 = OpExtInst %void %2 DebugLocalVariable %225 %153 %64 %uint_110 %uint_10 %182 %uint_4
+        %228 = OpExtInst %void %2 DebugLocalVariable %166 %109 %64 %uint_107 %uint_10 %179 %uint_4
+        %231 = OpExtInst %void %2 DebugLocalVariable %230 %153 %64 %uint_105 %uint_9 %179 %uint_4
+        %233 = OpExtInst %void %2 DebugLocalVariable %171 %153 %64 %uint_103 %uint_9 %179 %uint_4
+        %236 = OpExtInst %void %2 DebugLocalVariable %235 %58 %64 %uint_100 %uint_9 %179 %uint_4
+        %239 = OpExtInst %void %2 DebugLocalVariable %238 %153 %64 %uint_99 %uint_9 %179 %uint_4
+        %241 = OpExtInst %void %2 DebugLocalVariable %169 %153 %64 %uint_98 %uint_9 %179 %uint_4
+        %245 = OpExtInst %void %2 DebugLocalVariable %244 %60 %64 %uint_95 %uint_40 %176 %uint_4 %uint_1
+)"
+      R"(     %247 = OpExtInst %void %2 DebugTypeMember %246 %58 %64 %uint_29 %uint_9 %uint_0 %uint_128 %uint_3
+        %250 = OpExtInst %void %2 DebugTypeMember %249 %58 %64 %uint_21 %uint_9 %uint_0 %uint_128 %uint_3
+        %253 = OpExtInst %void %2 DebugTypeMember %252 %58 %64 %uint_22 %uint_9 %uint_128 %uint_128 %uint_3
+        %256 = OpExtInst %void %2 DebugTypeMember %254 %58 %64 %uint_23 %uint_9 %uint_256 %uint_128 %uint_3
+        %258 = OpExtInst %void %2 DebugTypeArray %57 %uint_4 %uint_4
+        %262 = OpExtInst %void %2 DebugTypeMember %259 %258 %64 %uint_24 %uint_11 %uint_384 %uint_512 %uint_3
+        %265 = OpExtInst %void %2 DebugTypeComposite %263 %uint_1 %64 %uint_19 %uint_8 %65 %263 %uint_896 %uint_3 %250 %253 %256 %262
+        %266 = OpExtInst %void %2 DebugTypeArray %265 %uint_3
+        %269 = OpExtInst %void %2 DebugTypeMember %267 %266 %64 %uint_30 %uint_8 %uint_128 %uint_2688 %uint_3
+        %273 = OpExtInst %void %2 DebugTypeMember %271 %109 %64 %uint_31 %uint_6 %uint_2816 %uint_32 %uint_3
+        %277 = OpExtInst %void %2 DebugTypeMember %275 %109 %64 %uint_32 %uint_6 %uint_2848 %uint_32 %uint_3
+        %280 = OpExtInst %void %2 DebugTypeComposite %278 %uint_1 %64 %uint_27 %uint_8 %65 %278 %uint_2880 %uint_3 %247 %269 %273 %277
+        %284 = OpExtInst %void %2 DebugTypeMember %282 %280 %64 %uint_35 %uint_34 %uint_0 %uint_2944 %uint_3
+        %286 = OpExtInst %void %2 DebugTypeComposite %285 %uint_1 %64 %uint_35 %uint_9 %65 %285 %uint_2944 %uint_3 %284
+        %287 = OpExtInst %void %2 DebugGlobalVariable %282 %286 %64 %uint_35 %uint_9 %65 %282 %ubo %uint_8
+        %291 = OpExtInst %void %2 DebugTypeComposite %289 %uint_1 %64 %uint_0 %uint_0 %65 %290 %288 %uint_3
+        %293 = OpExtInst %void %2 DebugGlobalVariable %292 %291 %64 %uint_12 %uint_14 %65 %292 %samplerShadowMap %uint_8
+        %297 = OpExtInst %void %2 DebugTypeComposite %295 %uint_0 %64 %uint_0 %uint_0 %65 %296 %288 %uint_3
+        %299 = OpExtInst %void %2 DebugTypeTemplateParameter %298 %58 %288 %64 %uint_0 %uint_0
+        %300 = OpExtInst %void %2 DebugTypeTemplate %297 %299
+        %302 = OpExtInst %void %2 DebugGlobalVariable %301 %300 %64 %uint_11 %uint_16 %65 %301 %textureShadowMap %uint_8
+        %305 = OpExtInst %void %2 DebugGlobalVariable %304 %291 %64 %uint_8 %uint_14 %65 %304 %samplerAlbedo %uint_8
+        %308 = OpExtInst %void %2 DebugTypeComposite %306 %uint_0 %64 %uint_0 %uint_0 %65 %307 %288 %uint_3
+        %309 = OpExtInst %void %2 DebugTypeTemplateParameter %298 %58 %288 %64 %uint_0 %uint_0
+        %310 = OpExtInst %void %2 DebugTypeTemplate %308 %309
+        %312 = OpExtInst %void %2 DebugGlobalVariable %311 %310 %64 %uint_7 %uint_11 %65 %311 %textureAlbedo %uint_8
+        %314 = OpExtInst %void %2 DebugGlobalVariable %313 %291 %64 %uint_6 %uint_14 %65 %313 %samplerNormal %uint_8
+        %316 = OpExtInst %void %2 DebugGlobalVariable %315 %310 %64 %uint_5 %uint_11 %65 %315 %textureNormal %uint_8
+        %318 = OpExtInst %void %2 DebugGlobalVariable %317 %291 %64 %uint_4 %uint_14 %65 %317 %samplerposition %uint_8
+        %320 = OpExtInst %void %2 DebugGlobalVariable %319 %310 %64 %uint_3 %uint_11 %65 %319 %textureposition %uint_8
+       %1803 = OpExtInst %void %2 DebugInlinedAt %uint_1792 %180
+       %1885 = OpExtInst %void %2 DebugInlinedAt %uint_1869 %158 %1803
+       %2085 = OpExtInst %void %2 DebugInlinedAt %uint_2060 %106 %1885
+)"
+      R"(    %main = OpFunction %void None %321
+        %322 = OpLabel
+       %2083 = OpVariable %_ptr_Function_float Function
+       %2086 = OpVariable %_ptr_Function_v4float Function
+       %1883 = OpVariable %_ptr_Function_v2int Function
+       %1891 = OpVariable %_ptr_Function_float Function
+       %1892 = OpVariable %_ptr_Function_int Function
+       %1894 = OpVariable %_ptr_Function_int Function
+       %1895 = OpVariable %_ptr_Function_int Function
+       %1896 = OpVariable %_ptr_Function_v4float Function
+       %1801 = OpVariable %_ptr_Function_int Function
+       %1447 = OpVariable %_ptr_Function_v4float Function
+       %1448 = OpVariable %_ptr_Function_v3float Function
+       %1450 = OpVariable %_ptr_Function_int Function
+       %1451 = OpVariable %_ptr_Function_v3float Function
+       %1453 = OpVariable %_ptr_Function_v3float Function
+       %1466 = OpVariable %_ptr_Function_v3float Function
+%param_var_inUV = OpVariable %_ptr_Function_v2float Function
+        %325 = OpExtInst %void %2 DebugFunctionDefinition %176 %main
+        %326 = OpLoad %v2float %in_var_TEXCOORD0
+               OpStore %param_var_inUV %326
+       %2290 = OpExtInst %void %2 DebugScope %176
+       %1620 = OpExtInst %void %2 DebugLine %64 %uint_95 %uint_95 %uint_33 %uint_40
+       %1470 = OpExtInst %void %2 DebugDeclare %245 %param_var_inUV %243
+       %2291 = OpExtInst %void %2 DebugScope %179
+       %1621 = OpExtInst %void %2 DebugLine %64 %uint_98 %uint_98 %uint_19 %uint_19
+       %1471 = OpLoad %type_2d_image %textureposition
+       %1622 = OpExtInst %void %2 DebugLine %64 %uint_98 %uint_98 %uint_42 %uint_42
+       %1472 = OpLoad %type_sampler %samplerposition
+       %1624 = OpExtInst %void %2 DebugLine %64 %uint_98 %uint_98 %uint_19 %uint_63
+       %1474 = OpSampledImage %type_sampled_image %1471 %1472
+       %1475 = OpImageSampleImplicitLod %v4float %1474 %326 None
+       %1626 = OpExtInst %void %2 DebugLine %64 %uint_98 %uint_98 %uint_19 %uint_65
+       %1476 = OpVectorShuffle %v3float %1475 %1475 0 1 2
+       %2241 = OpExtInst %void %2 DebugLine %64 %uint_98 %uint_98 %uint_2 %uint_65
+       %2240 = OpExtInst %void %2 DebugValue %241 %1476 %243
+       %1629 = OpExtInst %void %2 DebugLine %64 %uint_99 %uint_99 %uint_18 %uint_18
+       %1478 = OpLoad %type_2d_image %textureNormal
+       %1630 = OpExtInst %void %2 DebugLine %64 %uint_99 %uint_99 %uint_39 %uint_39
+       %1479 = OpLoad %type_sampler %samplerNormal
+       %1632 = OpExtInst %void %2 DebugLine %64 %uint_99 %uint_99 %uint_18 %uint_58
+       %1481 = OpSampledImage %type_sampled_image %1478 %1479
+       %1482 = OpImageSampleImplicitLod %v4float %1481 %326 None
+       %1634 = OpExtInst %void %2 DebugLine %64 %uint_99 %uint_99 %uint_18 %uint_60
+       %1483 = OpVectorShuffle %v3float %1482 %1482 0 1 2
+       %2244 = OpExtInst %void %2 DebugLine %64 %uint_99 %uint_99 %uint_2 %uint_60
+       %2243 = OpExtInst %void %2 DebugValue %239 %1483 %243
+       %1637 = OpExtInst %void %2 DebugLine %64 %uint_100 %uint_100 %uint_18 %uint_18
+       %1485 = OpLoad %type_2d_image %textureAlbedo
+       %1638 = OpExtInst %void %2 DebugLine %64 %uint_100 %uint_100 %uint_39 %uint_39
+       %1486 = OpLoad %type_sampler %samplerAlbedo
+       %1640 = OpExtInst %void %2 DebugLine %64 %uint_100 %uint_100 %uint_18 %uint_58
+       %1488 = OpSampledImage %type_sampled_image %1485 %1486
+       %1489 = OpImageSampleImplicitLod %v4float %1488 %326 None
+       %1642 = OpExtInst %void %2 DebugLine %64 %uint_100 %uint_100 %uint_2 %uint_58
+               OpStore %1447 %1489
+       %1490 = OpExtInst %void %2 DebugDeclare %236 %1447 %243
+       %1645 = OpExtInst %void %2 DebugLine %64 %uint_103 %uint_103 %uint_22 %uint_29
+       %1492 = OpVectorShuffle %v3float %1489 %1489 0 1 2
+       %1646 = OpExtInst %void %2 DebugLine %64 %uint_103 %uint_103 %uint_22 %uint_35
+       %1493 = OpVectorTimesScalar %v3float %1492 %float_0_100000001
+       %1647 = OpExtInst %void %2 DebugLine %64 %uint_103 %uint_103 %uint_2 %uint_35
+               OpStore %1448 %1493
+       %1494 = OpExtInst %void %2 DebugDeclare %233 %1448 %243
+       %1650 = OpExtInst %void %2 DebugLine %64 %uint_105 %uint_105 %uint_13 %uint_29
+       %1496 = OpExtInst %v3float %1 Normalize %1483
+       %2247 = OpExtInst %void %2 DebugLine %64 %uint_105 %uint_105 %uint_2 %uint_29
+       %2246 = OpExtInst %void %2 DebugValue %231 %1496 %243
+       %1653 = OpExtInst %void %2 DebugLine %64 %uint_107 %uint_107 %uint_6 %uint_14
+               OpStore %1450 %int_0
+       %1498 = OpExtInst %void %2 DebugDeclare %228 %1450 %243
+       %1655 = OpExtInst %void %2 DebugLine %64 %uint_107 %uint_107 %uint_6 %uint_15
+               OpBranch %1499
+       %1499 = OpLabel
+       %2292 = OpExtInst %void %2 DebugScope %179
+       %1656 = OpExtInst %void %2 DebugLine %64 %uint_107 %uint_107 %uint_17 %uint_17
+       %1500 = OpLoad %int %1450
+       %1657 = OpExtInst %void %2 DebugLine %64 %uint_107 %uint_107 %uint_17 %uint_21
+       %1501 = OpSLessThan %bool %1500 %int_3
+       %2293 = OpExtInst %void %2 DebugNoScope
+               OpLoopMerge %1605 %1602 None
+               OpBranchConditional %1501 %1502 %1605
+       %1502 = OpLabel
+       %2294 = OpExtInst %void %2 DebugScope %182
+       %1660 = OpExtInst %void %2 DebugLine %64 %uint_110 %uint_110 %uint_25 %uint_25
+       %1503 = OpLoad %int %1450
+       %1661 = OpExtInst %void %2 DebugLine %64 %uint_110 %uint_110 %uint_14 %uint_37
+       %1504 = OpAccessChain %_ptr_Uniform_UBO %ubo %int_0
+       %1505 = OpAccessChain %_ptr_Uniform_v4float %1504 %int_1 %1503 %int_0
+       %1663 = OpExtInst %void %2 DebugLine %64 %uint_110 %uint_110 %uint_14 %uint_28
+       %1506 = OpLoad %v4float %1505
+       %1664 = OpExtInst %void %2 DebugLine %64 %uint_110 %uint_110 %uint_14 %uint_37
+       %1507 = OpVectorShuffle %v3float %1506 %1506 0 1 2
+       %1666 = OpExtInst %void %2 DebugLine %64 %uint_110 %uint_110 %uint_14 %uint_43
+       %1509 = OpFSub %v3float %1507 %1476
+       %1667 = OpExtInst %void %2 DebugLine %64 %uint_110 %uint_110 %uint_3 %uint_43
+               OpStore %1451 %1509
+       %1510 = OpExtInst %void %2 DebugDeclare %226 %1451 %243
+       %1670 = OpExtInst %void %2 DebugLine %64 %uint_112 %uint_112 %uint_16 %uint_24
+       %1512 = OpExtInst %float %1 Length %1509
+       %2250 = OpExtInst %void %2 DebugLine %64 %uint_112 %uint_112 %uint_3 %uint_24
+       %2249 = OpExtInst %void %2 DebugValue %223 %1512 %243
+       %1674 = OpExtInst %void %2 DebugLine %64 %uint_113 %uint_113 %uint_7 %uint_18
+       %1515 = OpExtInst %v3float %1 Normalize %1509
+       %1675 = OpExtInst %void %2 DebugLine %64 %uint_113 %uint_113 %uint_3 %uint_18
+               OpStore %1451 %1515
+       %1676 = OpExtInst %void %2 DebugLine %64 %uint_116 %uint_116 %uint_14 %uint_26
+       %1516 = OpAccessChain %_ptr_Uniform_UBO %ubo %int_0
+       %1517 = OpAccessChain %_ptr_Uniform_v4float %1516 %int_0
+       %1678 = OpExtInst %void %2 DebugLine %64 %uint_116 %uint_116 %uint_14 %uint_18
+       %1518 = OpLoad %v4float %1517
+       %1679 = OpExtInst %void %2 DebugLine %64 %uint_116 %uint_116 %uint_14 %uint_26
+       %1519 = OpVectorShuffle %v3float %1518 %1518 0 1 2
+       %1681 = OpExtInst %void %2 DebugLine %64 %uint_116 %uint_116 %uint_14 %uint_32
+       %1521 = OpFSub %v3float %1519 %1476
+       %1682 = OpExtInst %void %2 DebugLine %64 %uint_116 %uint_116 %uint_3 %uint_32
+               OpStore %1453 %1521
+       %1522 = OpExtInst %void %2 DebugDeclare %221 %1453 %243
+       %1685 = OpExtInst %void %2 DebugLine %64 %uint_117 %uint_117 %uint_7 %uint_18
+       %1524 = OpExtInst %v3float %1 Normalize %1521
+       %1686 = OpExtInst %void %2 DebugLine %64 %uint_117 %uint_117 %uint_3 %uint_18
+               OpStore %1453 %1524
+       %1687 = OpExtInst %void %2 DebugLine %64 %uint_119 %uint_119 %uint_34 %uint_46
+       %1525 = OpExtInst %float %1 Radians %float_15
+       %1688 = OpExtInst %void %2 DebugLine %64 %uint_119 %uint_119 %uint_30 %uint_47
+       %1526 = OpExtInst %float %1 Cos %1525
+       %2253 = OpExtInst %void %2 DebugLine %64 %uint_119 %uint_119 %uint_3 %uint_47
+       %2252 = OpExtInst %void %2 DebugValue %218 %1526 %243
+       %1691 = OpExtInst %void %2 DebugLine %64 %uint_120 %uint_120 %uint_34 %uint_46
+       %1528 = OpExtInst %float %1 Radians %float_25
+       %1692 = OpExtInst %void %2 DebugLine %64 %uint_120 %uint_120 %uint_30 %uint_47
+       %1529 = OpExtInst %float %1 Cos %1528
+       %2256 = OpExtInst %void %2 DebugLine %64 %uint_120 %uint_120 %uint_3 %uint_47
+       %2255 = OpExtInst %void %2 DebugValue %215 %1529 %243
+       %2259 = OpExtInst %void %2 DebugLine %64 %uint_121 %uint_121 %uint_3 %uint_22
+       %2258 = OpExtInst %void %2 DebugValue %212 %float_100 %243
+       %1698 = OpExtInst %void %2 DebugLine %64 %uint_124 %uint_124 %uint_26 %uint_49
+       %1533 = OpAccessChain %_ptr_Uniform_UBO %ubo %int_0
+       %1534 = OpAccessChain %_ptr_Uniform_v4float %1533 %int_1 %1503 %int_0
+       %1700 = OpExtInst %void %2 DebugLine %64 %uint_124 %uint_124 %uint_26 %uint_40
+       %1535 = OpLoad %v4float %1534
+       %1701 = OpExtInst %void %2 DebugLine %64 %uint_124 %uint_124 %uint_26 %uint_49
+       %1536 = OpVectorShuffle %v3float %1535 %1535 0 1 2
+       %1703 = OpExtInst %void %2 DebugLine %64 %uint_124 %uint_124 %uint_55 %uint_76
+       %1538 = OpAccessChain %_ptr_Uniform_UBO %ubo %int_0
+       %1539 = OpAccessChain %_ptr_Uniform_v4float %1538 %int_1 %1503 %int_1
+       %1705 = OpExtInst %void %2 DebugLine %64 %uint_124 %uint_124 %uint_55 %uint_69
+       %1540 = OpLoad %v4float %1539
+       %1706 = OpExtInst %void %2 DebugLine %64 %uint_124 %uint_124 %uint_55 %uint_76
+       %1541 = OpVectorShuffle %v3float %1540 %1540 0 1 2
+       %1707 = OpExtInst %void %2 DebugLine %64 %uint_124 %uint_124 %uint_26 %uint_76
+       %1542 = OpFSub %v3float %1536 %1541
+       %1708 = OpExtInst %void %2 DebugLine %64 %uint_124 %uint_124 %uint_16 %uint_79
+       %1543 = OpExtInst %v3float %1 Normalize %1542
+       %2262 = OpExtInst %void %2 DebugLine %64 %uint_124 %uint_124 %uint_3 %uint_79
+       %2261 = OpExtInst %void %2 DebugValue %209 %1543 %243
+       %1713 = OpExtInst %void %2 DebugLine %64 %uint_127 %uint_127 %uint_18 %uint_28
+       %1547 = OpDot %float %1515 %1543
+       %2265 = OpExtInst %void %2 DebugLine %64 %uint_127 %uint_127 %uint_3 %uint_28
+       %2264 = OpExtInst %void %2 DebugValue %206 %1547 %243
+       %1719 = OpExtInst %void %2 DebugLine %64 %uint_128 %uint_128 %uint_22 %uint_79
+       %1552 = OpExtInst %float %1 SmoothStep %1529 %1526 %1547
+       %2268 = OpExtInst %void %2 DebugLine %64 %uint_128 %uint_128 %uint_3 %uint_79
+       %2267 = OpExtInst %void %2 DebugValue %203 %1552 %243
+       %1724 = OpExtInst %void %2 DebugLine %64 %uint_129 %uint_129 %uint_29 %uint_62
+       %1556 = OpExtInst %float %1 SmoothStep %float_100 %float_0 %1512
+       %2271 = OpExtInst %void %2 DebugLine %64 %uint_129 %uint_129 %uint_3 %uint_62
+       %2270 = OpExtInst %void %2 DebugValue %200 %1556 %243
+       %1729 = OpExtInst %void %2 DebugLine %64 %uint_132 %uint_132 %uint_26 %uint_34
+       %1560 = OpDot %float %1496 %1515
+       %1730 = OpExtInst %void %2 DebugLine %64 %uint_132 %uint_132 %uint_17 %uint_35
+       %1561 = OpExtInst %float %1 FMax %float_0 %1560
+       %2274 = OpExtInst %void %2 DebugLine %64 %uint_132 %uint_132 %uint_3 %uint_35
+       %2273 = OpExtInst %void %2 DebugValue %197 %1561 %243
+       %1734 = OpExtInst %void %2 DebugLine %64 %uint_133 %uint_133 %uint_17 %uint_23
+       %1564 = OpCompositeConstruct %v3float %1561 %1561 %1561
+       %2277 = OpExtInst %void %2 DebugLine %64 %uint_133 %uint_133 %uint_3 %uint_23
+       %2276 = OpExtInst %void %2 DebugValue %194 %1564 %243
+       %1738 = OpExtInst %void %2 DebugLine %64 %uint_136 %uint_136 %uint_22 %uint_23
+       %1567 = OpFNegate %v3float %1515
+       %1740 = OpExtInst %void %2 DebugLine %64 %uint_136 %uint_136 %uint_14 %uint_27
+       %1569 = OpExtInst %v3float %1 Reflect %1567 %1496
+       %2280 = OpExtInst %void %2 DebugLine %64 %uint_136 %uint_136 %uint_3 %uint_27
+       %2279 = OpExtInst %void %2 DebugValue %191 %1569 %243
+       %1745 = OpExtInst %void %2 DebugLine %64 %uint_137 %uint_137 %uint_26 %uint_34
+       %1573 = OpDot %float %1569 %1524
+       %1746 = OpExtInst %void %2 DebugLine %64 %uint_137 %uint_137 %uint_17 %uint_35
+       %1574 = OpExtInst %float %1 FMax %float_0 %1573
+       %2283 = OpExtInst %void %2 DebugLine %64 %uint_137 %uint_137 %uint_3 %uint_35
+       %2282 = OpExtInst %void %2 DebugValue %188 %1574 %243
+       %1750 = OpExtInst %void %2 DebugLine %64 %uint_138 %uint_138 %uint_18 %uint_33
+       %1577 = OpExtInst %float %1 Pow %1574 %float_16
+       %1751 = OpExtInst %void %2 DebugLine %64 %uint_138 %uint_138 %uint_37 %uint_44
+       %1578 = OpAccessChain %_ptr_Function_float %1447 %int_3
+       %1579 = OpLoad %float %1578
+       %1753 = OpExtInst %void %2 DebugLine %64 %uint_138 %uint_138 %uint_18 %uint_44
+       %1580 = OpFMul %float %1577 %1579
+       %1754 = OpExtInst %void %2 DebugLine %64 %uint_138 %uint_138 %uint_18 %uint_48
+       %1581 = OpFMul %float %1580 %float_2_5
+       %1755 = OpExtInst %void %2 DebugLine %64 %uint_138 %uint_138 %uint_17 %uint_53
+       %1582 = OpCompositeConstruct %v3float %1581 %1581 %1581
+       %2286 = OpExtInst %void %2 DebugLine %64 %uint_138 %uint_138 %uint_3 %uint_53
+       %2285 = OpExtInst %void %2 DebugValue %185 %1582 %243
+       %1760 = OpExtInst %void %2 DebugLine %64 %uint_140 %uint_140 %uint_24 %uint_31
+       %1586 = OpFAdd %v3float %1564 %1582
+       %1762 = OpExtInst %void %2 DebugLine %64 %uint_140 %uint_140 %uint_23 %uint_39
+       %1588 = OpVectorTimesScalar %v3float %1586 %1552
+       %1764 = OpExtInst %void %2 DebugLine %64 %uint_140 %uint_140 %uint_23 %uint_52
+       %1590 = OpVectorTimesScalar %v3float %1588 %1556
+       %1766 = OpExtInst %void %2 DebugLine %64 %uint_140 %uint_140 %uint_73 %uint_93
+       %1592 = OpAccessChain %_ptr_Uniform_UBO %ubo %int_0
+       %1593 = OpAccessChain %_ptr_Uniform_v4float %1592 %int_1 %1503 %int_2
+       %1768 = OpExtInst %void %2 DebugLine %64 %uint_140 %uint_140 %uint_73 %uint_87
+       %1594 = OpLoad %v4float %1593
+       %1769 = OpExtInst %void %2 DebugLine %64 %uint_140 %uint_140 %uint_73 %uint_93
+       %1595 = OpVectorShuffle %v3float %1594 %1594 0 1 2
+       %1770 = OpExtInst %void %2 DebugLine %64 %uint_140 %uint_140 %uint_16 %uint_93
+       %1596 = OpFMul %v3float %1590 %1595
+       %1772 = OpExtInst %void %2 DebugLine %64 %uint_140 %uint_140 %uint_99 %uint_106
+       %1598 = OpVectorShuffle %v3float %1489 %1489 0 1 2
+       %1773 = OpExtInst %void %2 DebugLine %64 %uint_140 %uint_140 %uint_16 %uint_106
+       %1599 = OpFMul %v3float %1596 %1598
+       %1774 = OpExtInst %void %2 DebugLine %64 %uint_140 %uint_140 %uint_3 %uint_3
+       %1600 = OpLoad %v3float %1448
+       %1775 = OpExtInst %void %2 DebugLine %64 %uint_140 %uint_140 %uint_3 %uint_106
+       %1601 = OpFAdd %v3float %1600 %1599
+               OpStore %1448 %1601
+       %2295 = OpExtInst %void %2 DebugScope %179
+       %1777 = OpExtInst %void %2 DebugLine %64 %uint_107 %uint_107 %uint_34 %uint_36
+               OpBranch %1602
+       %1602 = OpLabel
+       %2296 = OpExtInst %void %2 DebugScope %179
+       %1778 = OpExtInst %void %2 DebugLine %64 %uint_107 %uint_107 %uint_34 %uint_36
+       %1603 = OpLoad %int %1450
+       %1604 = OpIAdd %int %1603 %int_1
+               OpStore %1450 %1604
+               OpBranch %1499
+       %1605 = OpLabel
+       %2297 = OpExtInst %void %2 DebugScope %179
+       %1782 = OpExtInst %void %2 DebugLine %64 %uint_144 %uint_144 %uint_6 %uint_10
+       %1606 = OpAccessChain %_ptr_Uniform_UBO %ubo %int_0
+       %1607 = OpAccessChain %_ptr_Uniform_int %1606 %int_2
+       %1608 = OpLoad %int %1607
+       %1785 = OpExtInst %void %2 DebugLine %64 %uint_144 %uint_144 %uint_6 %uint_23
+       %1609 = OpSGreaterThan %bool %1608 %int_0
+       %2298 = OpExtInst %void %2 DebugNoScope
+               OpSelectionMerge %1614 None
+               OpBranchConditional %1609 %1610 %1614
+)"
+      R"(    %1610 = OpLabel
+       %2299 = OpExtInst %void %2 DebugScope %180
+       %1788 = OpExtInst %void %2 DebugLine %64 %uint_146 %uint_146 %uint_22 %uint_22
+       %1611 = OpLoad %v3float %1448
+               OpStore %1466 %1611
+       %2300 = OpExtInst %void %2 DebugScope %155 %1803
+       %1842 = OpExtInst %void %2 DebugLine %64 %uint_78 %uint_78 %uint_15 %uint_22
+       %1810 = OpExtInst %void %2 DebugDeclare %172 %1466 %243
+       %2301 = OpExtInst %void %2 DebugScope %180
+       %2289 = OpExtInst %void %2 DebugLine %64 %uint_146 %uint_146 %uint_33 %uint_33
+       %2288 = OpExtInst %void %2 DebugValue %170 %1476 %243
+       %2302 = OpExtInst %void %2 DebugScope %157 %1803
+       %1844 = OpExtInst %void %2 DebugLine %64 %uint_79 %uint_79 %uint_7 %uint_15
+               OpStore %1801 %int_0
+       %1813 = OpExtInst %void %2 DebugDeclare %167 %1801 %243
+       %1846 = OpExtInst %void %2 DebugLine %64 %uint_79 %uint_79 %uint_7 %uint_16
+               OpBranch %1814
+       %1814 = OpLabel
+       %2303 = OpExtInst %void %2 DebugScope %157 %1803
+       %1847 = OpExtInst %void %2 DebugLine %64 %uint_79 %uint_79 %uint_18 %uint_18
+       %1815 = OpLoad %int %1801
+       %1848 = OpExtInst %void %2 DebugLine %64 %uint_79 %uint_79 %uint_18 %uint_22
+       %1816 = OpSLessThan %bool %1815 %int_3
+       %2304 = OpExtInst %void %2 DebugNoScope
+               OpLoopMerge %1840 %1837 None
+               OpBranchConditional %1816 %1817 %1840
+       %1817 = OpLabel
+       %2305 = OpExtInst %void %2 DebugScope %158 %1803
+       %1851 = OpExtInst %void %2 DebugLine %64 %uint_81 %uint_81 %uint_38 %uint_38
+       %1818 = OpLoad %int %1801
+       %1852 = OpExtInst %void %2 DebugLine %64 %uint_81 %uint_81 %uint_27 %uint_41
+       %1819 = OpAccessChain %_ptr_Uniform_UBO %ubo %int_0
+       %1820 = OpAccessChain %_ptr_Uniform_mat4v4float %1819 %int_1 %1818 %int_3
+       %1821 = OpLoad %mat4v4float %1820
+       %1856 = OpExtInst %void %2 DebugLine %64 %uint_81 %uint_81 %uint_60 %uint_68
+       %1823 = OpCompositeExtract %float %1476 0
+       %1824 = OpCompositeExtract %float %1476 1
+       %1825 = OpCompositeExtract %float %1476 2
+       %1859 = OpExtInst %void %2 DebugLine %64 %uint_81 %uint_81 %uint_53 %uint_76
+       %1826 = OpCompositeConstruct %v4float %1823 %1824 %1825 %float_1
+       %1860 = OpExtInst %void %2 DebugLine %64 %uint_81 %uint_81 %uint_23 %uint_77
+       %1827 = OpVectorTimesMatrix %v4float %1826 %1821
+       %2229 = OpExtInst %void %2 DebugLine %64 %uint_81 %uint_81 %uint_3 %uint_77
+       %2228 = OpExtInst %void %2 DebugValue %163 %1827 %243
+       %1867 = OpExtInst %void %2 DebugLine %64 %uint_85 %uint_85 %uint_40 %uint_40
+       %1832 = OpConvertSToF %float %1818
+       %2235 = OpExtInst %void %2 DebugLine %64 %uint_85 %uint_85 %uint_28 %uint_28
+       %2234 = OpExtInst %void %2 DebugValue %151 %1827 %243
+       %2238 = OpExtInst %void %2 DebugLine %64 %uint_85 %uint_85 %uint_40 %uint_40
+       %2237 = OpExtInst %void %2 DebugValue %148 %1832 %243
+       %2306 = OpExtInst %void %2 DebugScope %103 %1885
+       %1983 = OpExtInst %void %2 DebugLine %64 %uint_56 %uint_56 %uint_2 %uint_7
+       %1904 = OpExtInst %void %2 DebugDeclare %146 %1883 %243
+       %1986 = OpExtInst %void %2 DebugLine %64 %uint_57 %uint_57 %uint_2 %uint_2
+       %1907 = OpLoad %type_2d_image_array %textureShadowMap
+)"
+      R"(    %1987 = OpExtInst %void %2 DebugLine %64 %uint_57 %uint_57 %uint_2 %uint_72
+       %1908 = OpImageQuerySizeLod %v3uint %1907 %uint_0
+       %1909 = OpCompositeExtract %uint %1908 0
+       %1910 = OpBitcast %int %1909
+       %1911 = OpAccessChain %_ptr_Function_int %1883 %int_0
+               OpStore %1911 %1910
+       %1912 = OpCompositeExtract %uint %1908 1
+       %1913 = OpBitcast %int %1912
+       %1914 = OpAccessChain %_ptr_Function_int %1883 %int_1
+               OpStore %1914 %1913
+       %1915 = OpCompositeExtract %uint %1908 2
+       %1916 = OpBitcast %int %1915
+       %2204 = OpExtInst %void %2 DebugValue %142 %1916 %243
+       %1999 = OpExtInst %void %2 DebugLine %64 %uint_57 %uint_57 %uint_19 %uint_19
+       %1917 = OpImageQueryLevels %uint %1907
+       %2000 = OpExtInst %void %2 DebugLine %64 %uint_57 %uint_57 %uint_2 %uint_72
+       %1918 = OpBitcast %int %1917
+       %2207 = OpExtInst %void %2 DebugValue %138 %1918 %243
+       %2211 = OpExtInst %void %2 DebugLine %64 %uint_58 %uint_58 %uint_2 %uint_16
+       %2210 = OpExtInst %void %2 DebugValue %135 %float_1_5 %243
+       %2005 = OpExtInst %void %2 DebugLine %64 %uint_59 %uint_59 %uint_13 %uint_21
+       %1921 = OpFMul %float %float_1_5 %float_1
+       %2006 = OpExtInst %void %2 DebugLine %64 %uint_59 %uint_59 %uint_33 %uint_40
+       %1922 = OpAccessChain %_ptr_Function_int %1883 %int_0
+       %1923 = OpLoad %int %1922
+       %1924 = OpConvertSToF %float %1923
+       %2009 = OpExtInst %void %2 DebugLine %64 %uint_59 %uint_59 %uint_13 %uint_41
+       %1925 = OpFDiv %float %1921 %1924
+       %2214 = OpExtInst %void %2 DebugLine %64 %uint_59 %uint_59 %uint_2 %uint_41
+       %2213 = OpExtInst %void %2 DebugValue %132 %1925 %243
+       %2013 = OpExtInst %void %2 DebugLine %64 %uint_60 %uint_60 %uint_13 %uint_21
+       %1928 = OpFMul %float %float_1_5 %float_1
+       %2014 = OpExtInst %void %2 DebugLine %64 %uint_60 %uint_60 %uint_33 %uint_40
+       %1929 = OpAccessChain %_ptr_Function_int %1883 %int_1
+       %1930 = OpLoad %int %1929
+       %1931 = OpConvertSToF %float %1930
+       %2017 = OpExtInst %void %2 DebugLine %64 %uint_60 %uint_60 %uint_13 %uint_41
+       %1932 = OpFDiv %float %1928 %1931
+       %2217 = OpExtInst %void %2 DebugLine %64 %uint_60 %uint_60 %uint_2 %uint_41
+       %2216 = OpExtInst %void %2 DebugValue %129 %1932 %243
+       %2020 = OpExtInst %void %2 DebugLine %64 %uint_62 %uint_62 %uint_2 %uint_23
+               OpStore %1891 %float_0
+       %1934 = OpExtInst %void %2 DebugDeclare %126 %1891 %243
+       %2022 = OpExtInst %void %2 DebugLine %64 %uint_63 %uint_63 %uint_2 %uint_14
+               OpStore %1892 %int_0
+       %1935 = OpExtInst %void %2 DebugDeclare %123 %1892 %243
+       %2220 = OpExtInst %void %2 DebugLine %64 %uint_64 %uint_64 %uint_2 %uint_14
+       %2219 = OpExtInst %void %2 DebugValue %119 %int_1 %243
+       %2027 = OpExtInst %void %2 DebugLine %64 %uint_66 %uint_66 %uint_15 %uint_16
+       %1938 = OpSNegate %int %int_1
+       %2028 = OpExtInst %void %2 DebugLine %64 %uint_66 %uint_66 %uint_7 %uint_16
+               OpStore %1894 %1938
+       %1939 = OpExtInst %void %2 DebugDeclare %115 %1894 %243
+       %2030 = OpExtInst %void %2 DebugLine %64 %uint_66 %uint_66 %uint_7 %uint_21
+               OpBranch %1940
+       %1940 = OpLabel
+       %2307 = OpExtInst %void %2 DebugScope %103 %1885
+       %2031 = OpExtInst %void %2 DebugLine %64 %uint_66 %uint_66 %uint_23 %uint_23
+       %1941 = OpLoad %int %1894
+       %2033 = OpExtInst %void %2 DebugLine %64 %uint_66 %uint_66 %uint_23 %uint_28
+       %1943 = OpSLessThanEqual %bool %1941 %int_1
+       %2308 = OpExtInst %void %2 DebugNoScope
+               OpLoopMerge %1976 %1973 None
+               OpBranchConditional %1943 %1944 %1976
+       %1944 = OpLabel
+       %2309 = OpExtInst %void %2 DebugScope %104 %1885
+       %2037 = OpExtInst %void %2 DebugLine %64 %uint_68 %uint_68 %uint_16 %uint_17
+       %1946 = OpSNegate %int %int_1
+       %2038 = OpExtInst %void %2 DebugLine %64 %uint_68 %uint_68 %uint_8 %uint_17
+               OpStore %1895 %1946
+       %1947 = OpExtInst %void %2 DebugDeclare %111 %1895 %243
+       %2040 = OpExtInst %void %2 DebugLine %64 %uint_68 %uint_68 %uint_8 %uint_22
+               OpBranch %1948
+       %1948 = OpLabel
+       %2310 = OpExtInst %void %2 DebugScope %104 %1885
+       %2041 = OpExtInst %void %2 DebugLine %64 %uint_68 %uint_68 %uint_24 %uint_24
+       %1949 = OpLoad %int %1895
+       %2043 = OpExtInst %void %2 DebugLine %64 %uint_68 %uint_68 %uint_24 %uint_29
+       %1951 = OpSLessThanEqual %bool %1949 %int_1
+       %2311 = OpExtInst %void %2 DebugNoScope
+               OpLoopMerge %1972 %1969 None
+               OpBranchConditional %1951 %1952 %1972
+       %1952 = OpLabel
+       %2312 = OpExtInst %void %2 DebugScope %106 %1885
+       %2047 = OpExtInst %void %2 DebugLine %64 %uint_70 %uint_70 %uint_32 %uint_32
+               OpStore %1896 %1827
+       %2051 = OpExtInst %void %2 DebugLine %64 %uint_70 %uint_70 %uint_53 %uint_53
+       %1956 = OpLoad %int %1894
+       %1957 = OpConvertSToF %float %1956
+       %2053 = OpExtInst %void %2 DebugLine %64 %uint_70 %uint_70 %uint_50 %uint_53
+       %1958 = OpFMul %float %1925 %1957
+       %2055 = OpExtInst %void %2 DebugLine %64 %uint_70 %uint_70 %uint_59 %uint_59
+       %1960 = OpLoad %int %1895
+       %1961 = OpConvertSToF %float %1960
+       %2057 = OpExtInst %void %2 DebugLine %64 %uint_70 %uint_70 %uint_56 %uint_59
+       %1962 = OpFMul %float %1932 %1961
+       %2058 = OpExtInst %void %2 DebugLine %64 %uint_70 %uint_70 %uint_43 %uint_60
+       %1963 = OpCompositeConstruct %v2float %1958 %1962
+       %2313 = OpExtInst %void %2 DebugScope %70 %2085
+       %2141 = OpExtInst %void %2 DebugLine %64 %uint_37 %uint_37 %uint_19 %uint_26
+       %2090 = OpExtInst %void %2 DebugDeclare %96 %1896 %243
+       %2314 = OpExtInst %void %2 DebugScope %106 %1885
+       %2223 = OpExtInst %void %2 DebugLine %64 %uint_70 %uint_70 %uint_36 %uint_36
+       %2222 = OpExtInst %void %2 DebugValue %93 %1832 %243
+       %2226 = OpExtInst %void %2 DebugLine %64 %uint_70 %uint_70 %uint_43 %uint_60
+       %2225 = OpExtInst %void %2 DebugValue %90 %1963 %243
+       %2315 = OpExtInst %void %2 DebugScope %73 %2085
+       %2144 = OpExtInst %void %2 DebugLine %64 %uint_39 %uint_39 %uint_2 %uint_17
+               OpStore %2083 %float_1
+       %2094 = OpExtInst %void %2 DebugDeclare %86 %2083 %243
+       %2147 = OpExtInst %void %2 DebugLine %64 %uint_40 %uint_40 %uint_27 %uint_29
+       %2096 = OpAccessChain %_ptr_Function_float %1896 %int_3
+       %2097 = OpLoad %float %2096
+       %2098 = OpCompositeConstruct %v4float %2097 %2097 %2097 %2097
+       %2150 = OpExtInst %void %2 DebugLine %64 %uint_40 %uint_40 %uint_23 %uint_29
+       %2099 = OpFDiv %v4float %1827 %2098
+       %2151 = OpExtInst %void %2 DebugLine %64 %uint_40 %uint_40 %uint_2 %uint_29
+               OpStore %2086 %2099
+       %2100 = OpExtInst %void %2 DebugDeclare %83 %2086 %243
+       %2154 = OpExtInst %void %2 DebugLine %64 %uint_41 %uint_41 %uint_19 %uint_31
+       %2102 = OpVectorShuffle %v2float %2099 %2099 0 1
+       %2155 = OpExtInst %void %2 DebugLine %64 %uint_41 %uint_41 %uint_19 %uint_36
+       %2103 = OpVectorTimesScalar %v2float %2102 %float_0_5
+       %2156 = OpExtInst %void %2 DebugLine %64 %uint_41 %uint_41 %uint_19 %uint_42
+       %2104 = OpFAdd %v2float %2103 %35
+       %2158 = OpExtInst %void %2 DebugLine %64 %uint_41 %uint_41 %uint_2 %uint_42
+       %2106 = OpVectorShuffle %v4float %2099 %2104 4 5 2 3
+               OpStore %2086 %2106
+       %2160 = OpExtInst %void %2 DebugLine %64 %uint_43 %uint_43 %uint_6 %uint_18
+       %2107 = OpAccessChain %_ptr_Function_float %2086 %int_2
+       %2108 = OpLoad %float %2107
+       %2162 = OpExtInst %void %2 DebugLine %64 %uint_43 %uint_43 %uint_6 %uint_23
+       %2109 = OpFOrdGreaterThan %bool %2108 %float_n1
+       %2163 = OpExtInst %void %2 DebugLine %64 %uint_43 %uint_43 %uint_30 %uint_42
+       %2110 = OpAccessChain %_ptr_Function_float %2086 %int_2
+       %2111 = OpLoad %float %2110
+       %2165 = OpExtInst %void %2 DebugLine %64 %uint_43 %uint_43 %uint_30 %uint_46
+       %2112 = OpFOrdLessThan %bool %2111 %float_1
+       %2166 = OpExtInst %void %2 DebugLine %64 %uint_43 %uint_43 %uint_6 %uint_46
+       %2113 = OpLogicalAnd %bool %2109 %2112
+       %2316 = OpExtInst %void %2 DebugNoScope
+               OpSelectionMerge %2139 None
+               OpBranchConditional %2113 %2114 %2139
+       %2114 = OpLabel
+       %2317 = OpExtInst %void %2 DebugScope %74 %2085
+       %2169 = OpExtInst %void %2 DebugLine %64 %uint_45 %uint_45 %uint_16 %uint_16
+       %2115 = OpLoad %type_2d_image_array %textureShadowMap
+       %2170 = OpExtInst %void %2 DebugLine %64 %uint_45 %uint_45 %uint_40 %uint_40
+       %2116 = OpLoad %type_sampler %samplerShadowMap
+       %2171 = OpExtInst %void %2 DebugLine %64 %uint_45 %uint_45 %uint_65 %uint_65
+       %2117 = OpLoad %v4float %2086
+       %2172 = OpExtInst %void %2 DebugLine %64 %uint_45 %uint_45 %uint_65 %uint_77
+       %2118 = OpVectorShuffle %v2float %2117 %2117 0 1
+       %2174 = OpExtInst %void %2 DebugLine %64 %uint_45 %uint_45 %uint_65 %uint_82
+       %2120 = OpFAdd %v2float %2118 %1963
+       %2122 = OpCompositeExtract %float %2120 0
+       %2123 = OpCompositeExtract %float %2120 1
+       %2178 = OpExtInst %void %2 DebugLine %64 %uint_45 %uint_45 %uint_58 %uint_95
+       %2124 = OpCompositeConstruct %v3float %2122 %2123 %1832
+       %2179 = OpExtInst %void %2 DebugLine %64 %uint_45 %uint_45 %uint_16 %uint_96
+       %2125 = OpSampledImage %type_sampled_image_0 %2115 %2116
+       %2126 = OpImageSampleImplicitLod %v4float %2125 %2124 None
+       %2181 = OpExtInst %void %2 DebugLine %64 %uint_45 %uint_45 %uint_16 %uint_98
+       %2127 = OpCompositeExtract %float %2126 0
+       %2202 = OpExtInst %void %2 DebugLine %64 %uint_45 %uint_45 %uint_3 %uint_98
+       %2201 = OpExtInst %void %2 DebugValue %79 %2127 %243
+       %2184 = OpExtInst %void %2 DebugLine %64 %uint_46 %uint_46 %uint_7 %uint_19
+       %2129 = OpAccessChain %_ptr_Function_float %2086 %int_3
+       %2130 = OpLoad %float %2129
+       %2186 = OpExtInst %void %2 DebugLine %64 %uint_46 %uint_46 %uint_7 %uint_23
+       %2131 = OpFOrdGreaterThan %bool %2130 %float_0
+       %2188 = OpExtInst %void %2 DebugLine %64 %uint_46 %uint_46 %uint_37 %uint_49
+       %2133 = OpAccessChain %_ptr_Function_float %2086 %int_2
+       %2134 = OpLoad %float %2133
+       %2190 = OpExtInst %void %2 DebugLine %64 %uint_46 %uint_46 %uint_30 %uint_49
+       %2135 = OpFOrdLessThan %bool %2127 %2134
+       %2191 = OpExtInst %void %2 DebugLine %64 %uint_46 %uint_46 %uint_7 %uint_49
+       %2136 = OpLogicalAnd %bool %2131 %2135
+       %2318 = OpExtInst %void %2 DebugNoScope
+               OpSelectionMerge %2138 None
+               OpBranchConditional %2136 %2137 %2138
+       %2137 = OpLabel
+       %2319 = OpExtInst %void %2 DebugScope %76 %2085
+       %2194 = OpExtInst %void %2 DebugLine %64 %uint_48 %uint_48 %uint_4 %uint_13
+               OpStore %2083 %float_0_25
+       %2320 = OpExtInst %void %2 DebugScope %74 %2085
+       %2195 = OpExtInst %void %2 DebugLine %64 %uint_49 %uint_49 %uint_3 %uint_3
+               OpBranch %2138
+       %2138 = OpLabel
+       %2321 = OpExtInst %void %2 DebugScope %73 %2085
+       %2196 = OpExtInst %void %2 DebugLine %64 %uint_50 %uint_50 %uint_2 %uint_2
+               OpBranch %2139
+       %2139 = OpLabel
+       %2322 = OpExtInst %void %2 DebugScope %73 %2085
+       %2197 = OpExtInst %void %2 DebugLine %64 %uint_51 %uint_51 %uint_9 %uint_9
+       %2140 = OpLoad %float %2083
+       %2323 = OpExtInst %void %2 DebugScope %106 %1885
+       %2061 = OpExtInst %void %2 DebugLine %64 %uint_70 %uint_70 %uint_4 %uint_4
+       %1965 = OpLoad %float %1891
+       %2062 = OpExtInst %void %2 DebugLine %64 %uint_70 %uint_70 %uint_4 %uint_61
+       %1966 = OpFAdd %float %1965 %2140
+               OpStore %1891 %1966
+       %2064 = OpExtInst %void %2 DebugLine %64 %uint_71 %uint_71 %uint_4 %uint_9
+       %1967 = OpLoad %int %1892
+       %1968 = OpIAdd %int %1967 %int_1
+               OpStore %1892 %1968
+       %2324 = OpExtInst %void %2 DebugScope %104 %1885
+       %2067 = OpExtInst %void %2 DebugLine %64 %uint_68 %uint_68 %uint_36 %uint_37
+               OpBranch %1969
+       %1969 = OpLabel
+       %2325 = OpExtInst %void %2 DebugScope %104 %1885
+       %2068 = OpExtInst %void %2 DebugLine %64 %uint_68 %uint_68 %uint_36 %uint_37
+       %1970 = OpLoad %int %1895
+       %1971 = OpIAdd %int %1970 %int_1
+               OpStore %1895 %1971
+               OpBranch %1948
+       %1972 = OpLabel
+       %2326 = OpExtInst %void %2 DebugScope %103 %1885
+       %2072 = OpExtInst %void %2 DebugLine %64 %uint_66 %uint_66 %uint_35 %uint_36
+               OpBranch %1973
+       %1973 = OpLabel
+       %2327 = OpExtInst %void %2 DebugScope %103 %1885
+       %2073 = OpExtInst %void %2 DebugLine %64 %uint_66 %uint_66 %uint_35 %uint_36
+       %1974 = OpLoad %int %1894
+       %1975 = OpIAdd %int %1974 %int_1
+               OpStore %1894 %1975
+               OpBranch %1940
+       %1976 = OpLabel
+       %2328 = OpExtInst %void %2 DebugScope %103 %1885
+       %2077 = OpExtInst %void %2 DebugLine %64 %uint_75 %uint_75 %uint_9 %uint_9
+       %1977 = OpLoad %float %1891
+       %2078 = OpExtInst %void %2 DebugLine %64 %uint_75 %uint_75 %uint_24 %uint_24
+       %1978 = OpLoad %int %1892
+       %1979 = OpConvertSToF %float %1978
+       %2080 = OpExtInst %void %2 DebugLine %64 %uint_75 %uint_75 %uint_9 %uint_24
+       %1980 = OpFDiv %float %1977 %1979
+       %2329 = OpExtInst %void %2 DebugScope %158 %1803
+       %2232 = OpExtInst %void %2 DebugLine %64 %uint_85 %uint_85 %uint_4 %uint_41
+       %2231 = OpExtInst %void %2 DebugValue %160 %1980 %243
+       %1872 = OpExtInst %void %2 DebugLine %64 %uint_90 %uint_90 %uint_3 %uint_3
+       %1835 = OpLoad %v3float %1466
+       %1873 = OpExtInst %void %2 DebugLine %64 %uint_90 %uint_90 %uint_3 %uint_16
+       %1836 = OpVectorTimesScalar %v3float %1835 %1980
+               OpStore %1466 %1836
+       %2330 = OpExtInst %void %2 DebugScope %157 %1803
+       %1875 = OpExtInst %void %2 DebugLine %64 %uint_79 %uint_79 %uint_35 %uint_37
+               OpBranch %1837
+       %1837 = OpLabel
+       %2331 = OpExtInst %void %2 DebugScope %157 %1803
+       %1876 = OpExtInst %void %2 DebugLine %64 %uint_79 %uint_79 %uint_35 %uint_37
+       %1838 = OpLoad %int %1801
+       %1839 = OpIAdd %int %1838 %int_1
+               OpStore %1801 %1839
+               OpBranch %1814
+       %1840 = OpLabel
+       %2332 = OpExtInst %void %2 DebugScope %157 %1803
+       %1880 = OpExtInst %void %2 DebugLine %64 %uint_92 %uint_92 %uint_9 %uint_9
+       %1841 = OpLoad %v3float %1466
+       %2333 = OpExtInst %void %2 DebugScope %180
+       %1793 = OpExtInst %void %2 DebugLine %64 %uint_146 %uint_146 %uint_3 %uint_40
+               OpStore %1448 %1841
+       %2334 = OpExtInst %void %2 DebugScope %179
+       %1794 = OpExtInst %void %2 DebugLine %64 %uint_147 %uint_147 %uint_2 %uint_2
+               OpBranch %1614
+       %1614 = OpLabel
+;CHECK:      %1614 = OpLabel
+;CHECK-NEXT: [[phi:%\w+]] = OpPhi 
+;CHECK-NEXT: {{%\w+}} = OpExtInst %void {{%\w+}} DebugValue %233
+       %2335 = OpExtInst %void %2 DebugScope %179
+       %1795 = OpExtInst %void %2 DebugLine %64 %uint_149 %uint_149 %uint_16 %uint_16
+       %1615 = OpLoad %v3float %1448
+       %1616 = OpCompositeExtract %float %1615 0
+       %1617 = OpCompositeExtract %float %1615 1
+       %1618 = OpCompositeExtract %float %1615 2
+       %1799 = OpExtInst %void %2 DebugLine %64 %uint_149 %uint_149 %uint_9 %uint_28
+       %1619 = OpCompositeConstruct %v4float %1616 %1617 %1618 %float_1
+       %2336 = OpExtInst %void %2 DebugNoLine
+       %2337 = OpExtInst %void %2 DebugNoScope
+               OpStore %out_var_SV_TARGET %1619
+        %329 = OpExtInst %void %2 DebugLine %64 %uint_150 %uint_150 %uint_1 %uint_1
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_2);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<SSARewritePass>(text, true);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    No optimization in the presence of
diff --git a/test/opt/loop_optimizations/loop_descriptions.cpp b/test/opt/loop_optimizations/loop_descriptions.cpp
index 4d2f989..b3f4f44 100644
--- a/test/opt/loop_optimizations/loop_descriptions.cpp
+++ b/test/opt/loop_optimizations/loop_descriptions.cpp
@@ -298,7 +298,7 @@
 
 /*
 Generated from following GLSL with latch block artificially inserted to be
-seperate from continue.
+separate from continue.
 #version 430
 void main(void) {
     float x[10];
diff --git a/test/opt/loop_optimizations/loop_fission.cpp b/test/opt/loop_optimizations/loop_fission.cpp
index 55b9c26..bc3ec39 100644
--- a/test/opt/loop_optimizations/loop_fission.cpp
+++ b/test/opt/loop_optimizations/loop_fission.cpp
@@ -692,7 +692,7 @@
 SinglePassRunAndCheck<LoopFissionPass>(source, expected, true);
 
 // By passing 1 as argument we are using the constructor which makes the
-// critera to split the loop be if the registers in the loop exceede 1. By
+// criteria to split the loop be if the registers in the loop exceede 1. By
 // using this constructor we are also enabling multiple passes (disabled by
 // default).
 SinglePassRunAndCheck<LoopFissionPass>(source, expected_multiple_passes, true,
diff --git a/test/opt/loop_optimizations/unroll_simple.cpp b/test/opt/loop_optimizations/unroll_simple.cpp
index ac0dfde..299fb2d 100644
--- a/test/opt/loop_optimizations/unroll_simple.cpp
+++ b/test/opt/loop_optimizations/unroll_simple.cpp
@@ -886,7 +886,7 @@
   LoopUnroller loop_unroller;
   SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
   // By unrolling by a factor that doesn't divide evenly into the number of loop
-  // iterations we perfom an additional transform when partially unrolling to
+  // iterations we perform an additional transform when partially unrolling to
   // account for the remainder.
   SinglePassRunAndCheck<PartialUnrollerTestPass<3>>(text, output, false);
 }
@@ -3118,7 +3118,7 @@
 
 /*
 Generated from following GLSL with latch block artificially inserted to be
-seperate from continue.
+separate from continue.
 #version 430
 void main(void) {
     float x[10];
@@ -3789,6 +3789,40 @@
   SinglePassRunAndMatch<PartialUnrollerTestPass<2>>(text, true);
 }
 
+TEST_F(PassClassTest, DontUnrollInfiteLoop) {
+  // This is an infinite loop that because the step is 0.  We want to make sure
+  // the unroller does not try to unroll it.
+  const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%int_50 = OpConstant %int 50
+%bool = OpTypeBool
+%int_0_0 = OpConstant %int 0
+%2 = OpFunction %void None %4
+%10 = OpLabel
+OpBranch %11
+%11 = OpLabel
+%12 = OpPhi %int %int_0 %10 %13 %14
+%15 = OpSLessThan %bool %12 %int_50
+OpLoopMerge %16 %14 Unroll
+OpBranchConditional %15 %14 %16
+%14 = OpLabel
+%13 = OpIAdd %int %12 %int_0_0
+OpBranch %11
+%16 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<LoopUnroller>(text, text, false);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/module_test.cpp b/test/opt/module_test.cpp
index a3c2eed..17a1365 100644
--- a/test/opt/module_test.cpp
+++ b/test/opt/module_test.cpp
@@ -52,7 +52,7 @@
 }
 
 TEST(ModuleTest, ComputeIdBound) {
-  // Emtpy module case.
+  // Empty module case.
   EXPECT_EQ(1u, BuildModule("")->module()->ComputeIdBound());
   // Sensitive to result id
   EXPECT_EQ(2u, BuildModule("%void = OpTypeVoid")->module()->ComputeIdBound());
diff --git a/test/opt/optimizer_test.cpp b/test/opt/optimizer_test.cpp
index a51638a..0171c09 100644
--- a/test/opt/optimizer_test.cpp
+++ b/test/opt/optimizer_test.cpp
@@ -147,7 +147,7 @@
 
   std::vector<std::string> pass_flags = {
       "--strip-debug",
-      "--strip-reflect",
+      "--strip-nonsemantic",
       "--set-spec-const-default-value=23:42 21:12",
       "--if-conversion",
       "--freeze-spec-const",
diff --git a/test/opt/pass_manager_test.cpp b/test/opt/pass_manager_test.cpp
index 22d5e22..4f36d5b 100644
--- a/test/opt/pass_manager_test.cpp
+++ b/test/opt/pass_manager_test.cpp
@@ -30,7 +30,7 @@
 using spvtest::GetIdBound;
 using ::testing::Eq;
 
-// A null pass whose construtors accept arguments
+// A null pass whose constructors accept arguments
 class NullPassWithArgs : public NullPass {
  public:
   NullPassWithArgs(uint32_t) {}
diff --git a/test/opt/pass_merge_return_test.cpp b/test/opt/pass_merge_return_test.cpp
index 21960d1..04bd5d9 100644
--- a/test/opt/pass_merge_return_test.cpp
+++ b/test/opt/pass_merge_return_test.cpp
@@ -2231,7 +2231,7 @@
 
 TEST_F(MergeReturnPassTest, UnreachableMergeAndContinue) {
   // Make sure that the pass can handle a single block that is both a merge and
-  // a continue.
+  // a continue. Note that this is invalid SPIR-V.
   const std::string text =
       R"(
                OpCapability Shader
@@ -2265,7 +2265,7 @@
 )";
 
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  auto result = SinglePassRunAndDisassemble<MergeReturnPass>(text, true, true);
+  auto result = SinglePassRunAndDisassemble<MergeReturnPass>(text, true, false);
 
   // Not looking for any particular output.  Other tests do that.
   // Just want to make sure the check for unreachable blocks does not emit an
diff --git a/test/opt/reduce_load_size_test.cpp b/test/opt/reduce_load_size_test.cpp
index abb5cde..4546750 100644
--- a/test/opt/reduce_load_size_test.cpp
+++ b/test/opt/reduce_load_size_test.cpp
@@ -498,6 +498,45 @@
   SinglePassRunAndMatch<ReduceLoadSize>(test, false, 1.1);
 }
 
+TEST_F(ReduceLoadSizeTest, replace_array_with_spec_constant_size) {
+  const std::string test =
+      R"(
+               OpCapability ClipDistance
+               OpExtension "   "
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %1 "       "
+               OpExecutionMode %1 OriginUpperLeft
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+       %uint = OpTypeInt 32 0
+          %6 = OpSpecConstant %uint 538976288
+ %_arr_int_6 = OpTypeArray %int %6
+  %_struct_8 = OpTypeStruct %_arr_int_6
+  %_struct_9 = OpTypeStruct %_struct_8
+%_ptr_Uniform__struct_9 = OpTypePointer Uniform %_struct_9
+; CHECK: [[var:%\w+]] = OpVariable %_ptr_Uniform__struct_9 Uniform
+         %11 = OpVariable %_ptr_Uniform__struct_9 Uniform
+      %int_0 = OpConstant %int 0
+%_ptr_Uniform__arr_int_6 = OpTypePointer Uniform %_arr_int_6
+          %1 = OpFunction %void None %3
+         %14 = OpLabel
+; CHECK: [[ac:%\w+]] = OpAccessChain %_ptr_Uniform__arr_int_6 [[var]] %int_0 %int_0
+; CHECK: [[new_ac:%\w+]] = OpAccessChain %_ptr_Uniform_int [[ac]] %uint_538976288
+; CHECK: [[ld:%\w+]] = OpLoad %int [[new_ac]]
+; CHECK: %18 = OpIAdd %int [[ld]] [[ld]]
+         %15 = OpAccessChain %_ptr_Uniform__arr_int_6 %11 %int_0 %int_0
+         %16 = OpLoad %_arr_int_6 %15
+         %17 = OpCompositeExtract %int %16 538976288
+         %18 = OpIAdd %int %17 %17
+               OpUnreachable
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<ReduceLoadSize>(test, false,
+                                        kDefaultLoadReductionThreshold);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/remove_dontinline_test.cpp b/test/opt/remove_dontinline_test.cpp
new file mode 100644
index 0000000..c5425e8
--- /dev/null
+++ b/test/opt/remove_dontinline_test.cpp
@@ -0,0 +1,127 @@
+// Copyright (c) 2017 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.
+
+#include <vector>
+
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using StrengthReductionBasicTest = PassTest<::testing::Test>;
+
+TEST_F(StrengthReductionBasicTest, ClearDontInline) {
+  const std::vector<const char*> text = {
+      // clang-format off
+               "OpCapability Shader",
+          "%1 = OpExtInstImport \"GLSL.std.450\"",
+               "OpMemoryModel Logical GLSL450",
+               "OpEntryPoint Vertex %main \"main\"",
+       "%void = OpTypeVoid",
+          "%4 = OpTypeFunction %void",
+"; CHECK: OpFunction %void None",
+       "%main = OpFunction %void DontInline %4",
+          "%8 = OpLabel",
+               "OpReturn",
+               "OpFunctionEnd"
+      // clang-format on
+  };
+
+  SinglePassRunAndMatch<RemoveDontInline>(JoinAllInsts(text), true);
+}
+
+TEST_F(StrengthReductionBasicTest, LeaveUnchanged1) {
+  const std::vector<const char*> text = {
+      // clang-format off
+      "OpCapability Shader",
+      "%1 = OpExtInstImport \"GLSL.std.450\"",
+      "OpMemoryModel Logical GLSL450",
+      "OpEntryPoint Vertex %main \"main\"",
+      "%void = OpTypeVoid",
+      "%4 = OpTypeFunction %void",
+      "%main = OpFunction %void None %4",
+      "%8 = OpLabel",
+      "OpReturn",
+      "OpFunctionEnd"
+      // clang-format on
+  };
+
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange,
+            std::get<1>(SinglePassRunAndDisassemble<RemoveDontInline>(
+                JoinAllInsts(text), false, true)));
+}
+
+TEST_F(StrengthReductionBasicTest, LeaveUnchanged2) {
+  const std::vector<const char*> text = {
+      // clang-format off
+      "OpCapability Shader",
+      "%1 = OpExtInstImport \"GLSL.std.450\"",
+      "OpMemoryModel Logical GLSL450",
+      "OpEntryPoint Vertex %main \"main\"",
+      "%void = OpTypeVoid",
+      "%4 = OpTypeFunction %void",
+      "%main = OpFunction %void Inline %4",
+      "%8 = OpLabel",
+      "OpReturn",
+      "OpFunctionEnd"
+      // clang-format on
+  };
+
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange,
+            std::get<1>(SinglePassRunAndDisassemble<RemoveDontInline>(
+                JoinAllInsts(text), false, true)));
+}
+
+TEST_F(StrengthReductionBasicTest, ClearMultipleDontInline) {
+  const std::vector<const char*> text = {
+      // clang-format off
+      "OpCapability Shader",
+      "%1 = OpExtInstImport \"GLSL.std.450\"",
+      "OpMemoryModel Logical GLSL450",
+      "OpEntryPoint Vertex %main1 \"main1\"",
+      "OpEntryPoint Vertex %main2 \"main2\"",
+      "OpEntryPoint Vertex %main3 \"main3\"",
+      "OpEntryPoint Vertex %main4 \"main4\"",
+      "%void = OpTypeVoid",
+      "%4 = OpTypeFunction %void",
+      "; CHECK: OpFunction %void None",
+      "%main1 = OpFunction %void DontInline %4",
+      "%8 = OpLabel",
+      "OpReturn",
+      "OpFunctionEnd",
+      "; CHECK: OpFunction %void Inline",
+      "%main2 = OpFunction %void Inline %4",
+      "%9 = OpLabel",
+      "OpReturn",
+      "OpFunctionEnd",
+      "; CHECK: OpFunction %void Pure",
+      "%main3 = OpFunction %void DontInline|Pure %4",
+      "%10 = OpLabel",
+      "OpReturn",
+      "OpFunctionEnd",
+      "; CHECK: OpFunction %void None",
+      "%main4 = OpFunction %void None %4",
+      "%11 = OpLabel",
+      "OpReturn",
+      "OpFunctionEnd"
+      // clang-format on
+  };
+
+  SinglePassRunAndMatch<RemoveDontInline>(JoinAllInsts(text), true);
+}
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/test/opt/replace_desc_array_access_using_var_index_test.cpp b/test/opt/replace_desc_array_access_using_var_index_test.cpp
index ca62581..9ab9eb1 100644
--- a/test/opt/replace_desc_array_access_using_var_index_test.cpp
+++ b/test/opt/replace_desc_array_access_using_var_index_test.cpp
@@ -406,6 +406,171 @@
   SinglePassRunAndMatch<ReplaceDescArrayAccessUsingVarIndex>(text, true);
 }
 
+TEST_F(ReplaceDescArrayAccessUsingVarIndexTest, ReplaceMultipleAccessChains) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %1 "TestFragment" %2
+               OpExecutionMode %1 OriginUpperLeft
+               OpName %11 "type.ConstantBuffer.TestStruct"
+               OpMemberName %11 0 "val1"
+               OpMemberName %11 1 "val2"
+               OpName %3 "TestResources"
+               OpName %13 "type.2d.image"
+               OpName %4 "OutBuffer"
+               OpName %2 "in.var.SV_INSTANCEID"
+               OpName %1 "TestFragment"
+               OpDecorate %2 Flat
+               OpDecorate %2 Location 0
+               OpDecorate %3 DescriptorSet 0
+               OpDecorate %3 Binding 0
+               OpDecorate %4 DescriptorSet 0
+               OpDecorate %4 Binding 1
+               OpMemberDecorate %11 0 Offset 0
+               OpMemberDecorate %11 1 Offset 4
+               OpDecorate %11 Block
+         %9  = OpTypeInt 32 0
+         %10 = OpConstant %9 2
+         %11 = OpTypeStruct %9 %9
+         %8  = OpTypeArray %11 %10
+         %7  = OpTypePointer Uniform %8
+         %13 = OpTypeImage %9 2D 2 0 0 2 R32ui
+         %12 = OpTypePointer UniformConstant %13
+         %14 = OpTypePointer Input %9
+         %15 = OpTypeVoid
+         %16 = OpTypeFunction %15
+         %40 = OpTypeVector %9 2
+         %3  = OpVariable %7 Uniform
+         %4  = OpVariable %12 UniformConstant
+         %2  = OpVariable %14 Input
+         %57 = OpTypePointer Uniform %11
+         %61 = OpTypePointer Uniform %9
+         %62 = OpConstant %9 0
+         %1  = OpFunction %15 None %16
+         %17 = OpLabel
+         %20 = OpLoad %9 %2
+         %47 = OpAccessChain %57 %3 %20
+         %63 = OpAccessChain %61 %47 %62
+         %64 = OpLoad %9 %63
+
+; CHECK: [[null_value:%\w+]] = OpConstantNull %uint
+
+; CHECK: [[var_index:%\w+]] = OpLoad %uint %in_var_SV_INSTANCEID
+; CHECK: OpSelectionMerge [[merge:%\w+]] None
+; CHECK: OpSwitch [[var_index]] [[default:%\w+]] 0 [[case0:%\w+]] 1 [[case1:%\w+]]
+; CHECK: [[case0]] = OpLabel
+; CHECK: OpAccessChain
+; CHECK: OpAccessChain
+; CHECK: [[result0:%\w+]] = OpLoad
+; CHECK: OpBranch [[merge]]
+; CHECK: [[case1]] = OpLabel
+; CHECK: OpAccessChain
+; CHECK: OpAccessChain
+; CHECK: [[result1:%\w+]] = OpLoad
+; CHECK: OpBranch [[merge]]
+; CHECK: [[default]] = OpLabel
+; CHECK: OpBranch [[merge]]
+; CHECK: [[merge]] = OpLabel
+; CHECK: OpPhi %uint [[result0]] [[case0]] [[result1]] [[case1]] [[null_value]] [[default]]
+
+         %55 = OpCompositeConstruct %40 %20 %20
+         %56 = OpLoad %13 %4
+               OpImageWrite %56 %55 %64 None
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  SinglePassRunAndMatch<ReplaceDescArrayAccessUsingVarIndex>(text, true);
+}
+
+TEST_F(ReplaceDescArrayAccessUsingVarIndexTest,
+       ReplaceAccessChainToTextureArrayWithNonUniformIndex) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpCapability ShaderNonUniform
+               OpCapability SampledImageArrayNonUniformIndexing
+               OpExtension "SPV_EXT_descriptor_indexing"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %PSMain "PSMain" %in_var_TEXCOORD0 %in_var_MATERIAL_ID %out_var_SV_TARGET
+               OpExecutionMode %PSMain OriginUpperLeft
+               OpSource HLSL 610
+               OpName %type_sampler "type.sampler"
+               OpName %sampler_ "sampler_"
+               OpName %type_2d_image "type.2d.image"
+               OpName %texture_2d "texture_2d"
+               OpName %in_var_TEXCOORD0 "in.var.TEXCOORD0"
+               OpName %in_var_MATERIAL_ID "in.var.MATERIAL_ID"
+               OpName %out_var_SV_TARGET "out.var.SV_TARGET"
+               OpName %PSMain "PSMain"
+               OpName %type_sampled_image "type.sampled.image"
+               OpDecorate %in_var_MATERIAL_ID Flat
+               OpDecorate %in_var_TEXCOORD0 Location 0
+               OpDecorate %in_var_MATERIAL_ID Location 1
+               OpDecorate %out_var_SV_TARGET Location 0
+               OpDecorate %sampler_ DescriptorSet 1
+               OpDecorate %sampler_ Binding 1
+               OpDecorate %texture_2d DescriptorSet 0
+               OpDecorate %texture_2d Binding 0
+
+; CHECK: OpDecorate [[v0:%\w+]] NonUniform
+; CHECK: OpDecorate [[v1:%\w+]] NonUniform
+; CHECK: OpDecorate [[v2:%\w+]] NonUniform
+; CHECK: OpDecorate [[v3:%\w+]] NonUniform
+
+               OpDecorate %10 NonUniform
+               OpDecorate %11 NonUniform
+               OpDecorate %12 NonUniform
+               OpDecorate %13 NonUniform
+%type_sampler = OpTypeSampler
+%_ptr_UniformConstant_type_sampler = OpTypePointer UniformConstant %type_sampler
+       %uint = OpTypeInt 32 0
+     %uint_4 = OpConstant %uint 4
+      %float = OpTypeFloat 32
+%type_2d_image = OpTypeImage %float 2D 2 0 0 1 Unknown
+%_arr_type_2d_image_uint_4 = OpTypeArray %type_2d_image %uint_4
+%_ptr_UniformConstant__arr_type_2d_image_uint_4 = OpTypePointer UniformConstant %_arr_type_2d_image_uint_4
+    %v2float = OpTypeVector %float 2
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%_ptr_Input_uint = OpTypePointer Input %uint
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %26 = OpTypeFunction %void
+%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
+%type_sampled_image = OpTypeSampledImage %type_2d_image
+   %sampler_ = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+ %texture_2d = OpVariable %_ptr_UniformConstant__arr_type_2d_image_uint_4 UniformConstant
+%in_var_TEXCOORD0 = OpVariable %_ptr_Input_v2float Input
+%in_var_MATERIAL_ID = OpVariable %_ptr_Input_uint Input
+%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output
+
+; CHECK: %uint_0 = OpConstant %uint 0
+; CHECK: %uint_1 = OpConstant %uint 1
+; CHECK: %uint_2 = OpConstant %uint 2
+; CHECK: %uint_3 = OpConstant %uint 3
+
+     %PSMain = OpFunction %void None %26
+         %28 = OpLabel
+         %29 = OpLoad %v2float %in_var_TEXCOORD0
+         %30 = OpLoad %uint %in_var_MATERIAL_ID
+; CHECK: [[v0]] = OpCopyObject %uint {{%\w+}}
+         %10 = OpCopyObject %uint %30
+; CHECK: [[v1]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %texture_2d [[v0]]
+         %11 = OpAccessChain %_ptr_UniformConstant_type_2d_image %texture_2d %10
+; CHECK: [[v2]] = OpLoad %type_2d_image [[v1]]
+         %12 = OpLoad %type_2d_image %11
+         %31 = OpLoad %type_sampler %sampler_
+; CHECK: [[v3]] = OpSampledImage %type_sampled_image [[v2]] {{%\w+}}
+         %13 = OpSampledImage %type_sampled_image %12 %31
+         %32 = OpImageSampleImplicitLod %v4float %13 %29 None
+               OpStore %out_var_SV_TARGET %32
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  SinglePassRunAndMatch<ReplaceDescArrayAccessUsingVarIndex>(text, true);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/scalar_analysis.cpp b/test/opt/scalar_analysis.cpp
index 598d8c7..df2aa8f 100644
--- a/test/opt/scalar_analysis.cpp
+++ b/test/opt/scalar_analysis.cpp
@@ -1202,7 +1202,6 @@
   EXPECT_EQ(phis.size(), 2u);
   SENode* phi_node_1 = analysis.AnalyzeInstruction(phis[0]);
   SENode* phi_node_2 = analysis.AnalyzeInstruction(phis[1]);
-  phi_node_1->DumpDot(std::cout, true);
   EXPECT_NE(phi_node_1, nullptr);
   EXPECT_NE(phi_node_2, nullptr);
 
diff --git a/test/opt/scalar_replacement_test.cpp b/test/opt/scalar_replacement_test.cpp
index 8cb888c..0c97c80 100644
--- a/test/opt/scalar_replacement_test.cpp
+++ b/test/opt/scalar_replacement_test.cpp
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "source/opt/scalar_replacement_pass.h"
+
 #include <string>
 
 #include "gmock/gmock.h"
@@ -23,6 +25,18 @@
 namespace opt {
 namespace {
 
+using ScalarReplacementPassName = ::testing::Test;
+
+TEST_F(ScalarReplacementPassName, Default) {
+  auto srp = ScalarReplacementPass();
+  EXPECT_STREQ(srp.name(), "scalar-replacement=100");
+}
+
+TEST_F(ScalarReplacementPassName, Large) {
+  auto srp = ScalarReplacementPass(0xffffffffu);
+  EXPECT_STREQ(srp.name(), "scalar-replacement=4294967295");
+}
+
 using ScalarReplacementTest = PassTest<::testing::Test>;
 
 TEST_F(ScalarReplacementTest, SimpleStruct) {
@@ -470,9 +484,9 @@
 ; CHECK: [[const_array:%\w+]] = OpConstantComposite [[array]]
 ; CHECK: [[const_matrix:%\w+]] = OpConstantNull [[matrix]]
 ; CHECK: [[const_struct1:%\w+]] = OpConstantComposite [[struct1]]
-; CHECK: OpConstantNull [[uint]]
-; CHECK: OpConstantNull [[vector]]
-; CHECK: OpConstantNull [[long]]
+; CHECK: OpUndef [[uint]]
+; CHECK: OpUndef [[vector]]
+; CHECK: OpUndef [[long]]
 ; CHECK: OpFunction
 ; CHECK-NOT: OpVariable [[struct2_ptr]] Function
 ; CHECK: OpVariable [[uint_ptr]] Function
@@ -654,11 +668,10 @@
 ; CHECK: [[uint:%\w+]] = OpTypeInt 32 0
 ; CHECK: [[struct1:%\w+]] = OpTypeStruct [[uint]] [[uint]]
 ; CHECK: [[uint_ptr:%\w+]] = OpTypePointer Function [[uint]]
-; CHECK: [[const:%\w+]] = OpConstant [[uint]] 0
-; CHECK: [[null:%\w+]] = OpConstantNull [[uint]]
+; CHECK: [[undef:%\w+]] = OpUndef [[uint]]
 ; CHECK: [[var0:%\w+]] = OpVariable [[uint_ptr]] Function
 ; CHECK: [[l0:%\w+]] = OpLoad [[uint]] [[var0]] Nontemporal
-; CHECK: OpCompositeConstruct [[struct1]] [[l0]] [[null]]
+; CHECK: OpCompositeConstruct [[struct1]] [[l0]] [[undef]]
 ;
 OpCapability Shader
 OpCapability Linkage
@@ -1267,16 +1280,16 @@
 ; CHECK: [[struct1:%\w+]] = OpTypeStruct [[uint]] [[uint]]
 ; CHECK: [[uint_ptr:%\w+]] = OpTypePointer Function [[uint]]
 ; CHECK: [[const:%\w+]] = OpConstant [[uint]] 0
-; CHECK: [[null:%\w+]] = OpConstantNull [[uint]]
+; CHECK: [[undef:%\w+]] = OpUndef [[uint]]
 ; CHECK: [[var0:%\w+]] = OpVariable [[uint_ptr]] Function
 ; CHECK: [[var1:%\w+]] = OpVariable [[uint_ptr]] Function
 ; CHECK-NOT: OpVariable
 ; CHECK: [[l0:%\w+]] = OpLoad [[uint]] [[var0]]
-; CHECK: [[c0:%\w+]] = OpCompositeConstruct [[struct1]] [[l0]] [[null]]
+; CHECK: [[c0:%\w+]] = OpCompositeConstruct [[struct1]] [[l0]] [[undef]]
 ; CHECK: [[e0:%\w+]] = OpCompositeExtract [[uint]] [[c0]] 0
 ; CHECK: OpStore [[var1]] [[e0]]
 ; CHECK: [[l1:%\w+]] = OpLoad [[uint]] [[var1]]
-; CHECK: [[c1:%\w+]] = OpCompositeConstruct [[struct1]] [[l1]] [[null]]
+; CHECK: [[c1:%\w+]] = OpCompositeConstruct [[struct1]] [[l1]] [[undef]]
 ; CHECK: [[e1:%\w+]] = OpCompositeExtract [[uint]] [[c1]] 0
 ;
 OpCapability Shader
@@ -1314,7 +1327,7 @@
 ; CHECK: [[struct1:%\w+]] = OpTypeStruct [[uint]] [[uint]]
 ; CHECK: [[uint_ptr:%\w+]] = OpTypePointer Function [[uint]]
 ; CHECK: [[const:%\w+]] = OpConstant [[uint]] 0
-; CHECK: [[null:%\w+]] = OpConstantNull [[uint]]
+; CHECK: [[undef:%\w+]] = OpUndef [[uint]]
 ; CHECK: [[var1:%\w+]] = OpVariable [[uint_ptr]] Function
 ; CHECK: [[var0a:%\w+]] = OpVariable [[uint_ptr]] Function
 ; CHECK: [[var0b:%\w+]] = OpVariable [[uint_ptr]] Function
@@ -1325,7 +1338,7 @@
 ; CHECK: [[e0:%\w+]] = OpCompositeExtract [[uint]] [[c0]] 0
 ; CHECK: OpStore [[var1]] [[e0]]
 ; CHECK: [[l1:%\w+]] = OpLoad [[uint]] [[var1]]
-; CHECK: [[c1:%\w+]] = OpCompositeConstruct [[struct1]] [[l1]] [[null]]
+; CHECK: [[c1:%\w+]] = OpCompositeConstruct [[struct1]] [[l1]] [[undef]]
 ; CHECK: [[e1:%\w+]] = OpCompositeExtract [[uint]] [[c1]] 0
 ;
 OpCapability Shader
@@ -1362,14 +1375,14 @@
 ; CHECK: [[struct1:%\w+]] = OpTypeStruct [[uint]] [[struct_member:%\w+]]
 ; CHECK: [[uint_ptr:%\w+]] = OpTypePointer Function [[uint]]
 ; CHECK: [[const:%\w+]] = OpConstant [[uint]] 0
-; CHECK: [[null:%\w+]] = OpConstantNull [[struct_member]]
+; CHECK: [[undef:%\w+]] = OpUndef [[struct_member]]
 ; CHECK: [[var0a:%\w+]] = OpVariable [[uint_ptr]] Function
 ; CHECK: [[var1:%\w+]] = OpVariable [[uint_ptr]] Function
 ; CHECK: [[var0b:%\w+]] = OpVariable [[uint_ptr]] Function
 ; CHECK-NOT: OpVariable
 ; CHECK: OpStore [[var1]]
 ; CHECK: [[l1:%\w+]] = OpLoad [[uint]] [[var1]]
-; CHECK: [[c1:%\w+]] = OpCompositeConstruct [[struct1]] [[l1]] [[null]]
+; CHECK: [[c1:%\w+]] = OpCompositeConstruct [[struct1]] [[l1]] [[undef]]
 ; CHECK: [[e1:%\w+]] = OpCompositeExtract [[uint]] [[c1]] 0
 ;
 OpCapability Shader
@@ -1444,13 +1457,13 @@
 ; CHECK: [[struct1:%\w+]] = OpTypeStruct [[uint]] [[struct_member:%\w+]]
 ; CHECK: [[uint_ptr:%\w+]] = OpTypePointer Function [[uint]]
 ; CHECK: [[const:%\w+]] = OpConstant [[uint]] 0
-; CHECK: [[null:%\w+]] = OpConstantNull [[struct_member]]
+; CHECK: [[undef:%\w+]] = OpUndef [[struct_member]]
 ; CHECK: [[var0a:%\w+]] = OpVariable [[uint_ptr]] Function
 ; CHECK: [[var1:%\w+]] = OpVariable [[uint_ptr]] Function
 ; CHECK: [[var0b:%\w+]] = OpVariable [[uint_ptr]] Function
 ; CHECK: OpStore [[var1]]
 ; CHECK: [[l1:%\w+]] = OpLoad [[uint]] [[var1]]
-; CHECK: [[c1:%\w+]] = OpCompositeConstruct [[struct1]] [[l1]] [[null]]
+; CHECK: [[c1:%\w+]] = OpCompositeConstruct [[struct1]] [[l1]] [[undef]]
 ; CHECK: [[e1:%\w+]] = OpCompositeExtract [[uint]] [[c1]] 0
 ;
 OpCapability Shader
@@ -2263,6 +2276,40 @@
   SinglePassRunAndCheck<ScalarReplacementPass>(text, text, false);
 }
 
+TEST_F(ScalarReplacementTest, UndefImageMember) {
+  // Test that scalar replacement creates an undef for a type that cannot have
+  // and OpConstantNull.
+  const std::string text = R"(
+; CHECK: [[image_type:%\w+]] = OpTypeSampledImage {{%\w+}}
+; CHECK: [[struct_type:%\w+]] = OpTypeStruct [[image_type]]
+; CHECK: [[undef:%\w+]] = OpUndef [[image_type]]
+; CHECK: {{%\w+}} = OpCompositeConstruct [[struct_type]] [[undef]]
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+       %void = OpTypeVoid
+          %4 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+          %6 = OpTypeImage %float 2D 0 0 0 1 Unknown
+          %7 = OpTypeSampledImage %6
+  %_struct_8 = OpTypeStruct %7
+          %9 = OpTypeFunction %_struct_8
+         %10 = OpUndef %_struct_8
+%_ptr_Function__struct_8 = OpTypePointer Function %_struct_8
+          %2 = OpFunction %void None %4
+         %11 = OpLabel
+         %16 = OpVariable %_ptr_Function__struct_8 Function
+               OpStore %16 %10
+         %12 = OpLoad %_struct_8 %16
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
 }  // 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 f1dd50e..10f805b 100644
--- a/test/opt/set_spec_const_default_value_test.cpp
+++ b/test/opt/set_spec_const_default_value_test.cpp
@@ -618,7 +618,7 @@
         {"", SpecIdToValueBitPatternMap{}, ""},
         // 1. Empty with non-empty values to set.
         {"", SpecIdToValueBitPatternMap{{1, {100}}, {2, {200}}}, ""},
-        // 2. Baisc bool type.
+        // 2. Basic bool type.
         {
             // code
             "OpDecorate %1 SpecId 100\n"
diff --git a/test/opt/spread_volatile_semantics_test.cpp b/test/opt/spread_volatile_semantics_test.cpp
new file mode 100644
index 0000000..dbb889c
--- /dev/null
+++ b/test/opt/spread_volatile_semantics_test.cpp
@@ -0,0 +1,1267 @@
+// Copyright (c) 2022 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/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+struct ExecutionModelAndBuiltIn {
+  const char* execution_model;
+  const char* built_in;
+  const bool use_v4uint;
+};
+
+using AddVolatileDecorationTest =
+    PassTest<::testing::TestWithParam<ExecutionModelAndBuiltIn>>;
+
+TEST_P(AddVolatileDecorationTest, InMain) {
+  const auto& tc = GetParam();
+  const std::string execution_model(tc.execution_model);
+  const std::string built_in(tc.built_in);
+  const std::string var_type =
+      tc.use_v4uint ? "%_ptr_Input_v4uint" : "%_ptr_Input_uint";
+  const std::string var_load_type = tc.use_v4uint ? "%v4uint" : "%uint";
+
+  const std::string text =
+      std::string(R"(OpCapability RuntimeDescriptorArray
+OpCapability RayTracingKHR
+OpCapability SubgroupBallotKHR
+OpExtension "SPV_EXT_descriptor_indexing"
+OpExtension "SPV_KHR_ray_tracing"
+OpExtension "SPV_KHR_shader_ballot"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint )") +
+      execution_model + std::string(R"( %main "main" %var
+OpSource GLSL 460
+OpSourceExtension "GL_EXT_nonuniform_qualifier"
+OpSourceExtension "GL_KHR_ray_tracing"
+OpName %main "main"
+OpName %fn "fn"
+OpName %StorageBuffer "StorageBuffer"
+OpMemberName %StorageBuffer 0 "index"
+OpMemberName %StorageBuffer 1 "red"
+OpName %sbo "sbo"
+OpName %images "images"
+OpMemberDecorate %StorageBuffer 0 Offset 0
+OpMemberDecorate %StorageBuffer 1 Offset 4
+OpDecorate %StorageBuffer BufferBlock
+OpDecorate %sbo DescriptorSet 0
+OpDecorate %sbo Binding 0
+OpDecorate %images DescriptorSet 0
+OpDecorate %images Binding 1
+OpDecorate %images NonWritable
+)") + std::string(R"(
+; CHECK: OpDecorate [[var:%\w+]] BuiltIn )") +
+      built_in + std::string(R"(
+; CHECK: OpDecorate [[var]] Volatile
+OpDecorate %var BuiltIn )") + built_in + std::string(R"(
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%float = OpTypeFloat 32
+%StorageBuffer = OpTypeStruct %uint %float
+%_ptr_Uniform_StorageBuffer = OpTypePointer Uniform %StorageBuffer
+%sbo = OpVariable %_ptr_Uniform_StorageBuffer Uniform
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%13 = OpTypeImage %float 2D 0 0 0 2 Rgba32f
+%_runtimearr_13 = OpTypeRuntimeArray %13
+%_ptr_UniformConstant__runtimearr_13 = OpTypePointer UniformConstant %_runtimearr_13
+%images = OpVariable %_ptr_UniformConstant__runtimearr_13 UniformConstant
+%_ptr_Input_uint = OpTypePointer Input %uint
+%v4uint = OpTypeVector %uint 4
+%_ptr_Input_v4uint = OpTypePointer Input %v4uint
+%var = OpVariable )") +
+      var_type + std::string(R"( Input
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
+%v2int = OpTypeVector %int 2
+%25 = OpConstantComposite %v2int %int_0 %int_0
+%v4float = OpTypeVector %float 4
+%uint_0 = OpConstant %uint 0
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%main = OpFunction %void None %3
+%5 = OpLabel
+%19 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
+%20 = OpLoad %uint %19
+%load = OpLoad )") + var_load_type + std::string(R"( %var
+%22 = OpAccessChain %_ptr_UniformConstant_13 %images %20
+%23 = OpLoad %13 %22
+%27 = OpImageRead %v4float %23 %25
+%29 = OpCompositeExtract %float %27 0
+%31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
+OpStore %31 %29
+%32 = OpFunctionCall %void %fn
+OpReturn
+OpFunctionEnd
+%fn = OpFunction %void None %3
+%33 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+
+  SinglePassRunAndMatch<SpreadVolatileSemantics>(text, true);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AddVolatileDecoration, AddVolatileDecorationTest,
+    ::testing::ValuesIn(std::vector<ExecutionModelAndBuiltIn>{
+        {"RayGenerationKHR", "SubgroupSize", false},
+        {"RayGenerationKHR", "SubgroupLocalInvocationId", false},
+        {"RayGenerationKHR", "SubgroupEqMask", true},
+        {"ClosestHitKHR", "SubgroupLocalInvocationId", true},
+        {"IntersectionKHR", "SubgroupEqMask", true},
+        {"MissKHR", "SubgroupGeMask", true},
+        {"CallableKHR", "SubgroupGtMask", true},
+        {"RayGenerationKHR", "SubgroupLeMask", true},
+    }));
+
+using SetLoadVolatileTest =
+    PassTest<::testing::TestWithParam<ExecutionModelAndBuiltIn>>;
+
+TEST_P(SetLoadVolatileTest, InMain) {
+  const auto& tc = GetParam();
+  const std::string execution_model(tc.execution_model);
+  const std::string built_in(tc.built_in);
+
+  const std::string var_type =
+      tc.use_v4uint ? "%_ptr_Input_v4uint" : "%_ptr_Input_uint";
+  const std::string var_value = tc.use_v4uint ? std::string(R"(
+; CHECK: [[ptr:%\w+]] = OpAccessChain %_ptr_Input_uint [[var]] %int_0
+; CHECK: OpLoad {{%\w+}} [[ptr]] Volatile
+%ptr = OpAccessChain %_ptr_Input_uint %var %int_0
+%var_value = OpLoad %uint %ptr)")
+                                              : std::string(R"(
+; CHECK: OpLoad {{%\w+}} [[var]] Volatile
+%var_value = OpLoad %uint %var)");
+
+  const std::string text = std::string(R"(OpCapability RuntimeDescriptorArray
+OpCapability RayTracingKHR
+OpCapability SubgroupBallotKHR
+OpCapability VulkanMemoryModel
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpExtension "SPV_EXT_descriptor_indexing"
+OpExtension "SPV_KHR_ray_tracing"
+OpExtension "SPV_KHR_shader_ballot"
+OpMemoryModel Logical Vulkan
+OpEntryPoint )") + execution_model +
+                           std::string(R"( %main "main" %var
+OpName %main "main"
+OpName %StorageBuffer "StorageBuffer"
+OpMemberName %StorageBuffer 0 "index"
+OpMemberName %StorageBuffer 1 "red"
+OpName %sbo "sbo"
+OpName %images "images"
+OpMemberDecorate %StorageBuffer 0 Offset 0
+OpMemberDecorate %StorageBuffer 1 Offset 4
+OpDecorate %StorageBuffer BufferBlock
+OpDecorate %sbo DescriptorSet 0
+OpDecorate %sbo Binding 0
+OpDecorate %images DescriptorSet 0
+OpDecorate %images Binding 1
+OpDecorate %images NonWritable
+)") + std::string(R"(
+; CHECK: OpDecorate [[var:%\w+]] BuiltIn )") +
+                           built_in + std::string(R"(
+OpDecorate %var BuiltIn )") + built_in +
+                           std::string(R"(
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%float = OpTypeFloat 32
+%StorageBuffer = OpTypeStruct %uint %float
+%_ptr_Uniform_StorageBuffer = OpTypePointer Uniform %StorageBuffer
+%sbo = OpVariable %_ptr_Uniform_StorageBuffer Uniform
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%13 = OpTypeImage %float 2D 0 0 0 2 Rgba32f
+%_runtimearr_13 = OpTypeRuntimeArray %13
+%_ptr_UniformConstant__runtimearr_13 = OpTypePointer UniformConstant %_runtimearr_13
+%images = OpVariable %_ptr_UniformConstant__runtimearr_13 UniformConstant
+%_ptr_Input_uint = OpTypePointer Input %uint
+%v4uint = OpTypeVector %uint 4
+%_ptr_Input_v4uint = OpTypePointer Input %v4uint
+%var = OpVariable )") + var_type +
+                           std::string(R"( Input
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
+%v2int = OpTypeVector %int 2
+%25 = OpConstantComposite %v2int %int_0 %int_0
+%v4float = OpTypeVector %float 4
+%uint_0 = OpConstant %uint 0
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%main = OpFunction %void None %3
+%5 = OpLabel
+%19 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
+%20 = OpLoad %uint %19
+)") + var_value + std::string(R"(
+%test = OpIAdd %uint %var_value %20
+%22 = OpAccessChain %_ptr_UniformConstant_13 %images %test
+%23 = OpLoad %13 %22
+%27 = OpImageRead %v4float %23 %25
+%29 = OpCompositeExtract %float %27 0
+%31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
+OpStore %31 %29
+OpReturn
+OpFunctionEnd
+)");
+
+  SinglePassRunAndMatch<SpreadVolatileSemantics>(text, true);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    SetLoadVolatile, SetLoadVolatileTest,
+    ::testing::ValuesIn(std::vector<ExecutionModelAndBuiltIn>{
+        {"RayGenerationKHR", "SubgroupSize", false},
+        {"RayGenerationKHR", "SubgroupLocalInvocationId", false},
+        {"RayGenerationKHR", "SubgroupEqMask", true},
+        {"ClosestHitKHR", "SubgroupLocalInvocationId", true},
+        {"IntersectionKHR", "SubgroupEqMask", true},
+        {"MissKHR", "SubgroupGeMask", true},
+        {"CallableKHR", "SubgroupGtMask", true},
+        {"RayGenerationKHR", "SubgroupLeMask", true},
+    }));
+
+using VolatileSpreadTest = PassTest<::testing::Test>;
+
+TEST_F(VolatileSpreadTest, SpreadVolatileForHelperInvocation) {
+  const std::string text =
+      R"(
+OpCapability Shader
+OpCapability DemoteToHelperInvocation
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var
+OpExecutionMode %main OriginUpperLeft
+
+; CHECK: OpDecorate [[var:%\w+]] BuiltIn HelperInvocation
+; CHECK: OpDecorate [[var]] Volatile
+OpDecorate %var BuiltIn HelperInvocation
+
+%bool = OpTypeBool
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%_ptr_Input_bool = OpTypePointer Input %bool
+%var = OpVariable %_ptr_Input_bool Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%load = OpLoad %bool %var
+OpDemoteToHelperInvocation
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_UNIVERSAL_1_6);
+  SinglePassRunAndMatch<SpreadVolatileSemantics>(text, true);
+}
+
+TEST_F(VolatileSpreadTest, MultipleExecutionModel) {
+  const std::string text =
+      R"(
+OpCapability RuntimeDescriptorArray
+OpCapability RayTracingKHR
+OpCapability SubgroupBallotKHR
+OpExtension "SPV_EXT_descriptor_indexing"
+OpExtension "SPV_KHR_ray_tracing"
+OpExtension "SPV_KHR_shader_ballot"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint RayGenerationKHR %RayGeneration "RayGeneration" %var
+OpEntryPoint GLCompute %compute "Compute" %gl_LocalInvocationIndex
+OpExecutionMode %compute LocalSize 16 16 1
+OpSource GLSL 460
+OpSourceExtension "GL_EXT_nonuniform_qualifier"
+OpSourceExtension "GL_KHR_ray_tracing"
+OpName %RayGeneration "RayGeneration"
+OpName %StorageBuffer "StorageBuffer"
+OpMemberName %StorageBuffer 0 "index"
+OpMemberName %StorageBuffer 1 "red"
+OpName %sbo "sbo"
+OpName %images "images"
+OpMemberDecorate %StorageBuffer 0 Offset 0
+OpMemberDecorate %StorageBuffer 1 Offset 4
+OpDecorate %gl_LocalInvocationIndex BuiltIn LocalInvocationIndex
+OpDecorate %StorageBuffer BufferBlock
+OpDecorate %sbo DescriptorSet 0
+OpDecorate %sbo Binding 0
+OpDecorate %images DescriptorSet 0
+OpDecorate %images Binding 1
+OpDecorate %images NonWritable
+
+; CHECK:     OpEntryPoint RayGenerationNV {{%\w+}} "RayGeneration" [[var:%\w+]]
+; CHECK:     OpDecorate [[var]] BuiltIn SubgroupSize
+; CHECK:     OpDecorate [[var]] Volatile
+; CHECK-NOT: OpDecorate {{%\w+}} Volatile
+OpDecorate %var BuiltIn SubgroupSize
+
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%float = OpTypeFloat 32
+%StorageBuffer = OpTypeStruct %uint %float
+%_ptr_Uniform_StorageBuffer = OpTypePointer Uniform %StorageBuffer
+%sbo = OpVariable %_ptr_Uniform_StorageBuffer Uniform
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%13 = OpTypeImage %float 2D 0 0 0 2 Rgba32f
+%_runtimearr_13 = OpTypeRuntimeArray %13
+%_ptr_UniformConstant__runtimearr_13 = OpTypePointer UniformConstant %_runtimearr_13
+%images = OpVariable %_ptr_UniformConstant__runtimearr_13 UniformConstant
+%_ptr_Input_uint = OpTypePointer Input %uint
+%var = OpVariable %_ptr_Input_uint Input
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
+%v2int = OpTypeVector %int 2
+%25 = OpConstantComposite %v2int %int_0 %int_0
+%v4float = OpTypeVector %float 4
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%gl_LocalInvocationIndex = OpVariable %_ptr_Input_uint Input
+%_ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%shared = OpVariable %_ptr_Workgroup_uint Workgroup
+
+%RayGeneration = OpFunction %void None %3
+%5 = OpLabel
+%19 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
+%20 = OpLoad %uint %var
+%22 = OpAccessChain %_ptr_UniformConstant_13 %images %20
+%23 = OpLoad %13 %22
+%27 = OpImageRead %v4float %23 %25
+%29 = OpCompositeExtract %float %27 0
+%31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
+OpStore %31 %29
+OpReturn
+OpFunctionEnd
+
+%compute = OpFunction %void None %3
+%66 = OpLabel
+%62 = OpLoad %uint %gl_LocalInvocationIndex
+%61 = OpAtomicIAdd %uint %shared %uint_1 %uint_0 %62
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<SpreadVolatileSemantics>(text, true);
+}
+
+TEST_F(VolatileSpreadTest, VarUsedInMultipleEntryPoints) {
+  const std::string text =
+      R"(
+OpCapability RuntimeDescriptorArray
+OpCapability RayTracingKHR
+OpCapability SubgroupBallotKHR
+OpExtension "SPV_EXT_descriptor_indexing"
+OpExtension "SPV_KHR_ray_tracing"
+OpExtension "SPV_KHR_shader_ballot"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint RayGenerationKHR %RayGeneration "RayGeneration" %var
+OpEntryPoint ClosestHitKHR %ClosestHit "ClosestHit" %var
+OpSource GLSL 460
+OpSourceExtension "GL_EXT_nonuniform_qualifier"
+OpSourceExtension "GL_KHR_ray_tracing"
+OpName %RayGeneration "RayGeneration"
+OpName %ClosestHit "ClosestHit"
+OpName %StorageBuffer "StorageBuffer"
+OpMemberName %StorageBuffer 0 "index"
+OpMemberName %StorageBuffer 1 "red"
+OpName %sbo "sbo"
+OpName %images "images"
+OpMemberDecorate %StorageBuffer 0 Offset 0
+OpMemberDecorate %StorageBuffer 1 Offset 4
+OpDecorate %gl_LocalInvocationIndex BuiltIn LocalInvocationIndex
+OpDecorate %StorageBuffer BufferBlock
+OpDecorate %sbo DescriptorSet 0
+OpDecorate %sbo Binding 0
+OpDecorate %images DescriptorSet 0
+OpDecorate %images Binding 1
+OpDecorate %images NonWritable
+
+; CHECK:     OpEntryPoint RayGenerationNV {{%\w+}} "RayGeneration" [[var:%\w+]]
+; CHECK:     OpEntryPoint ClosestHitNV {{%\w+}} "ClosestHit" [[var]]
+; CHECK:     OpDecorate [[var]] BuiltIn SubgroupSize
+; CHECK:     OpDecorate [[var]] Volatile
+; CHECK-NOT: OpDecorate {{%\w+}} Volatile
+OpDecorate %var BuiltIn SubgroupSize
+
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%float = OpTypeFloat 32
+%StorageBuffer = OpTypeStruct %uint %float
+%_ptr_Uniform_StorageBuffer = OpTypePointer Uniform %StorageBuffer
+%sbo = OpVariable %_ptr_Uniform_StorageBuffer Uniform
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%13 = OpTypeImage %float 2D 0 0 0 2 Rgba32f
+%_runtimearr_13 = OpTypeRuntimeArray %13
+%_ptr_UniformConstant__runtimearr_13 = OpTypePointer UniformConstant %_runtimearr_13
+%images = OpVariable %_ptr_UniformConstant__runtimearr_13 UniformConstant
+%_ptr_Input_uint = OpTypePointer Input %uint
+%var = OpVariable %_ptr_Input_uint Input
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
+%v2int = OpTypeVector %int 2
+%25 = OpConstantComposite %v2int %int_0 %int_0
+%v4float = OpTypeVector %float 4
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%gl_LocalInvocationIndex = OpVariable %_ptr_Input_uint Input
+%_ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%shared = OpVariable %_ptr_Workgroup_uint Workgroup
+
+%RayGeneration = OpFunction %void None %3
+%5 = OpLabel
+%19 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
+%20 = OpLoad %uint %var
+%22 = OpAccessChain %_ptr_UniformConstant_13 %images %20
+%23 = OpLoad %13 %22
+%27 = OpImageRead %v4float %23 %25
+%29 = OpCompositeExtract %float %27 0
+%31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
+OpStore %31 %29
+OpReturn
+OpFunctionEnd
+
+%ClosestHit = OpFunction %void None %3
+%45 = OpLabel
+%49 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
+%40 = OpLoad %uint %var
+%42 = OpAccessChain %_ptr_UniformConstant_13 %images %40
+%43 = OpLoad %13 %42
+%47 = OpImageRead %v4float %43 %25
+%59 = OpCompositeExtract %float %47 0
+%51 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
+OpStore %51 %59
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<SpreadVolatileSemantics>(text, true);
+}
+
+class VolatileSpreadErrorTest : public PassTest<::testing::Test> {
+ public:
+  VolatileSpreadErrorTest()
+      : consumer_([this](spv_message_level_t level, const char*,
+                         const spv_position_t& position, const char* message) {
+          if (!error_message_.empty()) error_message_ += "\n";
+          switch (level) {
+            case SPV_MSG_FATAL:
+            case SPV_MSG_INTERNAL_ERROR:
+            case SPV_MSG_ERROR:
+              error_message_ += "ERROR";
+              break;
+            case SPV_MSG_WARNING:
+              error_message_ += "WARNING";
+              break;
+            case SPV_MSG_INFO:
+              error_message_ += "INFO";
+              break;
+            case SPV_MSG_DEBUG:
+              error_message_ += "DEBUG";
+              break;
+          }
+          error_message_ +=
+              ": " + std::to_string(position.index) + ": " + message;
+        }) {}
+
+  Pass::Status RunPass(const std::string& text) {
+    std::unique_ptr<IRContext> context_ =
+        spvtools::BuildModule(SPV_ENV_UNIVERSAL_1_2, consumer_, text);
+    if (!context_.get()) return Pass::Status::Failure;
+
+    PassManager manager;
+    manager.SetMessageConsumer(consumer_);
+    manager.AddPass<SpreadVolatileSemantics>();
+
+    return manager.Run(context_.get());
+  }
+
+  std::string GetErrorMessage() const { return error_message_; }
+
+  void TearDown() override { error_message_.clear(); }
+
+ private:
+  spvtools::MessageConsumer consumer_;
+  std::string error_message_;
+};
+
+TEST_F(VolatileSpreadErrorTest, VarUsedInMultipleExecutionModelError) {
+  const std::string text =
+      R"(
+OpCapability RuntimeDescriptorArray
+OpCapability RayTracingKHR
+OpCapability SubgroupBallotKHR
+OpExtension "SPV_EXT_descriptor_indexing"
+OpExtension "SPV_KHR_ray_tracing"
+OpExtension "SPV_KHR_shader_ballot"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint RayGenerationKHR %RayGeneration "RayGeneration" %var
+OpEntryPoint GLCompute %compute "Compute" %gl_LocalInvocationIndex %var
+OpExecutionMode %compute LocalSize 16 16 1
+OpSource GLSL 460
+OpSourceExtension "GL_EXT_nonuniform_qualifier"
+OpSourceExtension "GL_KHR_ray_tracing"
+OpName %RayGeneration "RayGeneration"
+OpName %StorageBuffer "StorageBuffer"
+OpMemberName %StorageBuffer 0 "index"
+OpMemberName %StorageBuffer 1 "red"
+OpName %sbo "sbo"
+OpName %images "images"
+OpMemberDecorate %StorageBuffer 0 Offset 0
+OpMemberDecorate %StorageBuffer 1 Offset 4
+OpDecorate %gl_LocalInvocationIndex BuiltIn LocalInvocationIndex
+OpDecorate %StorageBuffer BufferBlock
+OpDecorate %sbo DescriptorSet 0
+OpDecorate %sbo Binding 0
+OpDecorate %images DescriptorSet 0
+OpDecorate %images Binding 1
+OpDecorate %images NonWritable
+OpDecorate %var BuiltIn SubgroupSize
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%float = OpTypeFloat 32
+%StorageBuffer = OpTypeStruct %uint %float
+%_ptr_Uniform_StorageBuffer = OpTypePointer Uniform %StorageBuffer
+%sbo = OpVariable %_ptr_Uniform_StorageBuffer Uniform
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%13 = OpTypeImage %float 2D 0 0 0 2 Rgba32f
+%_runtimearr_13 = OpTypeRuntimeArray %13
+%_ptr_UniformConstant__runtimearr_13 = OpTypePointer UniformConstant %_runtimearr_13
+%images = OpVariable %_ptr_UniformConstant__runtimearr_13 UniformConstant
+%_ptr_Input_uint = OpTypePointer Input %uint
+%var = OpVariable %_ptr_Input_uint Input
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
+%v2int = OpTypeVector %int 2
+%25 = OpConstantComposite %v2int %int_0 %int_0
+%v4float = OpTypeVector %float 4
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%gl_LocalInvocationIndex = OpVariable %_ptr_Input_uint Input
+%_ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%shared = OpVariable %_ptr_Workgroup_uint Workgroup
+
+%RayGeneration = OpFunction %void None %3
+%5 = OpLabel
+%19 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
+%20 = OpLoad %uint %var
+%22 = OpAccessChain %_ptr_UniformConstant_13 %images %20
+%23 = OpLoad %13 %22
+%27 = OpImageRead %v4float %23 %25
+%29 = OpCompositeExtract %float %27 0
+%31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
+OpStore %31 %29
+OpReturn
+OpFunctionEnd
+
+%compute = OpFunction %void None %3
+%66 = OpLabel
+%62 = OpLoad %uint %gl_LocalInvocationIndex
+%63 = OpLoad %uint %var
+%64 = OpIAdd %uint %62 %63
+%61 = OpAtomicIAdd %uint %shared %uint_1 %uint_0 %64
+OpReturn
+OpFunctionEnd
+)";
+
+  EXPECT_EQ(RunPass(text), Pass::Status::Failure);
+  const char expected_error[] =
+      "ERROR: 0: Variable is a target for Volatile semantics for an entry "
+      "point, but it is not for another entry point";
+  EXPECT_STREQ(GetErrorMessage().substr(0, sizeof(expected_error) - 1).c_str(),
+               expected_error);
+}
+
+TEST_F(VolatileSpreadErrorTest,
+       VarUsedInMultipleReverseOrderExecutionModelError) {
+  const std::string text =
+      R"(
+OpCapability RuntimeDescriptorArray
+OpCapability RayTracingKHR
+OpCapability SubgroupBallotKHR
+OpExtension "SPV_EXT_descriptor_indexing"
+OpExtension "SPV_KHR_ray_tracing"
+OpExtension "SPV_KHR_shader_ballot"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %compute "Compute" %gl_LocalInvocationIndex %var
+OpEntryPoint RayGenerationKHR %RayGeneration "RayGeneration" %var
+OpExecutionMode %compute LocalSize 16 16 1
+OpSource GLSL 460
+OpSourceExtension "GL_EXT_nonuniform_qualifier"
+OpSourceExtension "GL_KHR_ray_tracing"
+OpName %RayGeneration "RayGeneration"
+OpName %StorageBuffer "StorageBuffer"
+OpMemberName %StorageBuffer 0 "index"
+OpMemberName %StorageBuffer 1 "red"
+OpName %sbo "sbo"
+OpName %images "images"
+OpMemberDecorate %StorageBuffer 0 Offset 0
+OpMemberDecorate %StorageBuffer 1 Offset 4
+OpDecorate %gl_LocalInvocationIndex BuiltIn LocalInvocationIndex
+OpDecorate %StorageBuffer BufferBlock
+OpDecorate %sbo DescriptorSet 0
+OpDecorate %sbo Binding 0
+OpDecorate %images DescriptorSet 0
+OpDecorate %images Binding 1
+OpDecorate %images NonWritable
+OpDecorate %var BuiltIn SubgroupSize
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%float = OpTypeFloat 32
+%StorageBuffer = OpTypeStruct %uint %float
+%_ptr_Uniform_StorageBuffer = OpTypePointer Uniform %StorageBuffer
+%sbo = OpVariable %_ptr_Uniform_StorageBuffer Uniform
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%13 = OpTypeImage %float 2D 0 0 0 2 Rgba32f
+%_runtimearr_13 = OpTypeRuntimeArray %13
+%_ptr_UniformConstant__runtimearr_13 = OpTypePointer UniformConstant %_runtimearr_13
+%images = OpVariable %_ptr_UniformConstant__runtimearr_13 UniformConstant
+%_ptr_Input_uint = OpTypePointer Input %uint
+%var = OpVariable %_ptr_Input_uint Input
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
+%v2int = OpTypeVector %int 2
+%25 = OpConstantComposite %v2int %int_0 %int_0
+%v4float = OpTypeVector %float 4
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%gl_LocalInvocationIndex = OpVariable %_ptr_Input_uint Input
+%_ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%shared = OpVariable %_ptr_Workgroup_uint Workgroup
+
+%RayGeneration = OpFunction %void None %3
+%5 = OpLabel
+%19 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
+%20 = OpLoad %uint %var
+%22 = OpAccessChain %_ptr_UniformConstant_13 %images %20
+%23 = OpLoad %13 %22
+%27 = OpImageRead %v4float %23 %25
+%29 = OpCompositeExtract %float %27 0
+%31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
+OpStore %31 %29
+OpReturn
+OpFunctionEnd
+
+%compute = OpFunction %void None %3
+%66 = OpLabel
+%62 = OpLoad %uint %gl_LocalInvocationIndex
+%63 = OpLoad %uint %var
+%64 = OpIAdd %uint %62 %63
+%61 = OpAtomicIAdd %uint %shared %uint_1 %uint_0 %64
+OpReturn
+OpFunctionEnd
+)";
+
+  EXPECT_EQ(RunPass(text), Pass::Status::Failure);
+  const char expected_error[] =
+      "ERROR: 0: Variable is a target for Volatile semantics for an entry "
+      "point, but it is not for another entry point";
+  EXPECT_STREQ(GetErrorMessage().substr(0, sizeof(expected_error) - 1).c_str(),
+               expected_error);
+}
+
+TEST_F(VolatileSpreadErrorTest, FunctionNotInlined) {
+  const std::string text =
+      R"(
+OpCapability RuntimeDescriptorArray
+OpCapability RayTracingKHR
+OpCapability SubgroupBallotKHR
+OpExtension "SPV_EXT_descriptor_indexing"
+OpExtension "SPV_KHR_ray_tracing"
+OpExtension "SPV_KHR_shader_ballot"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint RayGenerationKHR %RayGeneration "RayGeneration" %var
+OpEntryPoint ClosestHitKHR %ClosestHit "ClosestHit" %var
+OpSource GLSL 460
+OpSourceExtension "GL_EXT_nonuniform_qualifier"
+OpSourceExtension "GL_KHR_ray_tracing"
+OpName %RayGeneration "RayGeneration"
+OpName %ClosestHit "ClosestHit"
+OpName %StorageBuffer "StorageBuffer"
+OpMemberName %StorageBuffer 0 "index"
+OpMemberName %StorageBuffer 1 "red"
+OpName %sbo "sbo"
+OpName %images "images"
+OpMemberDecorate %StorageBuffer 0 Offset 0
+OpMemberDecorate %StorageBuffer 1 Offset 4
+OpDecorate %gl_LocalInvocationIndex BuiltIn LocalInvocationIndex
+OpDecorate %StorageBuffer BufferBlock
+OpDecorate %sbo DescriptorSet 0
+OpDecorate %sbo Binding 0
+OpDecorate %images DescriptorSet 0
+OpDecorate %images Binding 1
+OpDecorate %images NonWritable
+OpDecorate %var BuiltIn SubgroupSize
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%float = OpTypeFloat 32
+%StorageBuffer = OpTypeStruct %uint %float
+%_ptr_Uniform_StorageBuffer = OpTypePointer Uniform %StorageBuffer
+%sbo = OpVariable %_ptr_Uniform_StorageBuffer Uniform
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%13 = OpTypeImage %float 2D 0 0 0 2 Rgba32f
+%_runtimearr_13 = OpTypeRuntimeArray %13
+%_ptr_UniformConstant__runtimearr_13 = OpTypePointer UniformConstant %_runtimearr_13
+%images = OpVariable %_ptr_UniformConstant__runtimearr_13 UniformConstant
+%_ptr_Input_uint = OpTypePointer Input %uint
+%var = OpVariable %_ptr_Input_uint Input
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
+%v2int = OpTypeVector %int 2
+%25 = OpConstantComposite %v2int %int_0 %int_0
+%v4float = OpTypeVector %float 4
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%gl_LocalInvocationIndex = OpVariable %_ptr_Input_uint Input
+%_ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%shared = OpVariable %_ptr_Workgroup_uint Workgroup
+
+%RayGeneration = OpFunction %void None %3
+%5 = OpLabel
+%19 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
+%20 = OpLoad %uint %19
+%22 = OpAccessChain %_ptr_UniformConstant_13 %images %20
+%23 = OpLoad %13 %22
+%27 = OpImageRead %v4float %23 %25
+%29 = OpCompositeExtract %float %27 0
+%31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
+OpStore %31 %29
+OpReturn
+OpFunctionEnd
+
+%NotInlined = OpFunction %void None %3
+%32 = OpLabel
+OpReturn
+OpFunctionEnd
+
+%ClosestHit = OpFunction %void None %3
+%45 = OpLabel
+%49 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
+%40 = OpLoad %uint %49
+%42 = OpAccessChain %_ptr_UniformConstant_13 %images %40
+%43 = OpLoad %13 %42
+%47 = OpImageRead %v4float %43 %25
+%59 = OpCompositeExtract %float %47 0
+%51 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
+OpStore %51 %59
+OpReturn
+OpFunctionEnd
+)";
+
+  EXPECT_EQ(RunPass(text), Pass::Status::SuccessWithoutChange);
+}
+
+TEST_F(VolatileSpreadErrorTest, VarNotUsedInEntryPointForVolatile) {
+  const std::string text =
+      R"(
+OpCapability RuntimeDescriptorArray
+OpCapability RayTracingKHR
+OpCapability SubgroupBallotKHR
+OpExtension "SPV_EXT_descriptor_indexing"
+OpExtension "SPV_KHR_ray_tracing"
+OpExtension "SPV_KHR_shader_ballot"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint RayGenerationKHR %RayGeneration "RayGeneration" %var
+OpEntryPoint GLCompute %compute "Compute" %gl_LocalInvocationIndex %var
+OpExecutionMode %compute LocalSize 16 16 1
+OpSource GLSL 460
+OpSourceExtension "GL_EXT_nonuniform_qualifier"
+OpSourceExtension "GL_KHR_ray_tracing"
+OpName %RayGeneration "RayGeneration"
+OpName %StorageBuffer "StorageBuffer"
+OpMemberName %StorageBuffer 0 "index"
+OpMemberName %StorageBuffer 1 "red"
+OpName %sbo "sbo"
+OpName %images "images"
+OpMemberDecorate %StorageBuffer 0 Offset 0
+OpMemberDecorate %StorageBuffer 1 Offset 4
+OpDecorate %gl_LocalInvocationIndex BuiltIn LocalInvocationIndex
+OpDecorate %StorageBuffer BufferBlock
+OpDecorate %sbo DescriptorSet 0
+OpDecorate %sbo Binding 0
+OpDecorate %images DescriptorSet 0
+OpDecorate %images Binding 1
+OpDecorate %images NonWritable
+
+; CHECK-NOT: OpDecorate {{%\w+}} Volatile
+
+OpDecorate %var BuiltIn SubgroupSize
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%float = OpTypeFloat 32
+%StorageBuffer = OpTypeStruct %uint %float
+%_ptr_Uniform_StorageBuffer = OpTypePointer Uniform %StorageBuffer
+%sbo = OpVariable %_ptr_Uniform_StorageBuffer Uniform
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%13 = OpTypeImage %float 2D 0 0 0 2 Rgba32f
+%_runtimearr_13 = OpTypeRuntimeArray %13
+%_ptr_UniformConstant__runtimearr_13 = OpTypePointer UniformConstant %_runtimearr_13
+%images = OpVariable %_ptr_UniformConstant__runtimearr_13 UniformConstant
+%_ptr_Input_uint = OpTypePointer Input %uint
+%var = OpVariable %_ptr_Input_uint Input
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
+%v2int = OpTypeVector %int 2
+%25 = OpConstantComposite %v2int %int_0 %int_0
+%v4float = OpTypeVector %float 4
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%gl_LocalInvocationIndex = OpVariable %_ptr_Input_uint Input
+%_ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%shared = OpVariable %_ptr_Workgroup_uint Workgroup
+
+%RayGeneration = OpFunction %void None %3
+%5 = OpLabel
+%19 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
+%20 = OpLoad %uint %19
+%22 = OpAccessChain %_ptr_UniformConstant_13 %images %20
+%23 = OpLoad %13 %22
+%27 = OpImageRead %v4float %23 %25
+%29 = OpCompositeExtract %float %27 0
+%31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
+OpStore %31 %29
+OpReturn
+OpFunctionEnd
+
+%compute = OpFunction %void None %3
+%66 = OpLabel
+%62 = OpLoad %uint %gl_LocalInvocationIndex
+%63 = OpLoad %uint %var
+%64 = OpIAdd %uint %62 %63
+%61 = OpAtomicIAdd %uint %shared %uint_1 %uint_0 %64
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<SpreadVolatileSemantics>(text, true);
+}
+
+TEST_F(VolatileSpreadTest, RecursivelySpreadVolatile) {
+  const std::string text =
+      R"(
+OpCapability RuntimeDescriptorArray
+OpCapability RayTracingKHR
+OpCapability SubgroupBallotKHR
+OpCapability VulkanMemoryModel
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpExtension "SPV_EXT_descriptor_indexing"
+OpExtension "SPV_KHR_ray_tracing"
+OpExtension "SPV_KHR_shader_ballot"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical Vulkan
+OpEntryPoint RayGenerationKHR %RayGeneration "RayGeneration" %var0 %var1
+OpSource GLSL 460
+OpSourceExtension "GL_EXT_nonuniform_qualifier"
+OpSourceExtension "GL_KHR_ray_tracing"
+OpName %RayGeneration "RayGeneration"
+OpName %StorageBuffer "StorageBuffer"
+OpMemberName %StorageBuffer 0 "index"
+OpMemberName %StorageBuffer 1 "red"
+OpName %sbo "sbo"
+OpName %images "images"
+OpMemberDecorate %StorageBuffer 0 Offset 0
+OpMemberDecorate %StorageBuffer 1 Offset 4
+OpDecorate %StorageBuffer BufferBlock
+OpDecorate %sbo DescriptorSet 0
+OpDecorate %sbo Binding 0
+OpDecorate %images DescriptorSet 0
+OpDecorate %images Binding 1
+OpDecorate %images NonWritable
+
+; CHECK: OpDecorate [[var0:%\w+]] BuiltIn SubgroupEqMask
+; CHECK: OpDecorate [[var1:%\w+]] BuiltIn SubgroupGeMask
+OpDecorate %var0 BuiltIn SubgroupEqMask
+OpDecorate %var1 BuiltIn SubgroupGeMask
+
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%float = OpTypeFloat 32
+%StorageBuffer = OpTypeStruct %uint %float
+%_ptr_Uniform_StorageBuffer = OpTypePointer Uniform %StorageBuffer
+%sbo = OpVariable %_ptr_Uniform_StorageBuffer Uniform
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%13 = OpTypeImage %float 2D 0 0 0 2 Rgba32f
+%_runtimearr_13 = OpTypeRuntimeArray %13
+%_ptr_UniformConstant__runtimearr_13 = OpTypePointer UniformConstant %_runtimearr_13
+%images = OpVariable %_ptr_UniformConstant__runtimearr_13 UniformConstant
+%v4uint = OpTypeVector %uint 4
+%_ptr_Input_v4uint = OpTypePointer Input %v4uint
+%_ptr_Input_uint = OpTypePointer Input %uint
+%var0 = OpVariable %_ptr_Input_v4uint Input
+%var1 = OpVariable %_ptr_Input_v4uint Input
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
+%v2int = OpTypeVector %int 2
+%25 = OpConstantComposite %v2int %int_0 %int_0
+%v4float = OpTypeVector %float 4
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+
+%RayGeneration = OpFunction %void None %3
+%5 = OpLabel
+
+; CHECK: [[ptr0:%\w+]] = OpAccessChain %_ptr_Input_uint [[var0]] %int_0
+; CHECK: OpLoad {{%\w+}} [[ptr0]] Volatile
+%19 = OpAccessChain %_ptr_Input_uint %var0 %int_0
+%20 = OpLoad %uint %19
+
+%22 = OpAccessChain %_ptr_UniformConstant_13 %images %20
+%23 = OpLoad %13 %22
+%27 = OpImageRead %v4float %23 %25
+%29 = OpCompositeExtract %float %27 0
+%31 = OpAccessChain %_ptr_Uniform_float %sbo %uint_1
+
+; CHECK: OpLoad {{%\w+}} [[ptr0]] Volatile
+%24 = OpLoad %uint %19
+
+; CHECK: [[var2:%\w+]] = OpCopyObject %_ptr_Input_v4uint [[var0]]
+; CHECK: [[ptr2:%\w+]] = OpAccessChain %_ptr_Input_uint [[var2]] %int_1
+; CHECK: OpLoad {{%\w+}} [[ptr2]] Volatile
+%18 = OpCopyObject %_ptr_Input_v4uint %var0
+%21 = OpAccessChain %_ptr_Input_uint %18 %int_1
+%26 = OpLoad %uint %21
+
+%28 = OpIAdd %uint %24 %26
+%30 = OpConvertUToF %float %28
+
+; CHECK: [[ptr1:%\w+]] = OpAccessChain %_ptr_Input_uint [[var1]] %int_1
+; CHECK: OpLoad {{%\w+}} [[ptr1]] Volatile
+%32 = OpAccessChain %_ptr_Input_uint %var1 %int_1
+%33 = OpLoad %uint %32
+
+%34 = OpConvertUToF %float %33
+%35 = OpFAdd %float %34 %30
+%36 = OpFAdd %float %35 %29
+OpStore %31 %36
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<SpreadVolatileSemantics>(text, true);
+}
+
+TEST_F(VolatileSpreadTest, SpreadVolatileOnlyForTargetEntryPoints) {
+  const std::string text =
+      R"(
+OpCapability RuntimeDescriptorArray
+OpCapability RayTracingKHR
+OpCapability SubgroupBallotKHR
+OpCapability VulkanMemoryModel
+OpCapability VulkanMemoryModelDeviceScopeKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpExtension "SPV_EXT_descriptor_indexing"
+OpExtension "SPV_KHR_ray_tracing"
+OpExtension "SPV_KHR_shader_ballot"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical Vulkan
+OpEntryPoint RayGenerationKHR %RayGeneration "RayGeneration" %var0 %var1
+OpEntryPoint GLCompute %compute "Compute" %var0 %var1
+OpExecutionMode %compute LocalSize 16 16 1
+OpSource GLSL 460
+OpSourceExtension "GL_EXT_nonuniform_qualifier"
+OpSourceExtension "GL_KHR_ray_tracing"
+OpName %RayGeneration "RayGeneration"
+OpName %StorageBuffer "StorageBuffer"
+OpMemberName %StorageBuffer 0 "index"
+OpMemberName %StorageBuffer 1 "red"
+OpName %sbo "sbo"
+OpName %images "images"
+OpMemberDecorate %StorageBuffer 0 Offset 0
+OpMemberDecorate %StorageBuffer 1 Offset 4
+OpDecorate %StorageBuffer BufferBlock
+OpDecorate %sbo DescriptorSet 0
+OpDecorate %sbo Binding 0
+OpDecorate %images DescriptorSet 0
+OpDecorate %images Binding 1
+OpDecorate %images NonWritable
+
+; CHECK: OpDecorate [[var0:%\w+]] BuiltIn SubgroupEqMask
+; CHECK: OpDecorate [[var1:%\w+]] BuiltIn SubgroupGeMask
+OpDecorate %var0 BuiltIn SubgroupEqMask
+OpDecorate %var1 BuiltIn SubgroupGeMask
+
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%float = OpTypeFloat 32
+%StorageBuffer = OpTypeStruct %uint %float
+%_ptr_Uniform_StorageBuffer = OpTypePointer Uniform %StorageBuffer
+%sbo = OpVariable %_ptr_Uniform_StorageBuffer Uniform
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%13 = OpTypeImage %float 2D 0 0 0 2 Rgba32f
+%_runtimearr_13 = OpTypeRuntimeArray %13
+%_ptr_UniformConstant__runtimearr_13 = OpTypePointer UniformConstant %_runtimearr_13
+%images = OpVariable %_ptr_UniformConstant__runtimearr_13 UniformConstant
+%v4uint = OpTypeVector %uint 4
+%_ptr_Input_v4uint = OpTypePointer Input %v4uint
+%_ptr_Input_uint = OpTypePointer Input %uint
+%var0 = OpVariable %_ptr_Input_v4uint Input
+%var1 = OpVariable %_ptr_Input_v4uint Input
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
+%v2int = OpTypeVector %int 2
+%25 = OpConstantComposite %v2int %int_0 %int_0
+%v4float = OpTypeVector %float 4
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%_ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%shared = OpVariable %_ptr_Workgroup_uint Workgroup
+
+%RayGeneration = OpFunction %void None %3
+%5 = OpLabel
+
+; CHECK: [[ptr0:%\w+]] = OpAccessChain %_ptr_Input_uint [[var0]] %int_0
+; CHECK: OpLoad {{%\w+}} [[ptr0]] Volatile
+%19 = OpAccessChain %_ptr_Input_uint %var0 %int_0
+%20 = OpLoad %uint %19
+
+%22 = OpAccessChain %_ptr_UniformConstant_13 %images %20
+%23 = OpLoad %13 %22
+%27 = OpImageRead %v4float %23 %25
+%29 = OpCompositeExtract %float %27 0
+%31 = OpAccessChain %_ptr_Uniform_float %sbo %uint_1
+
+; CHECK: OpLoad {{%\w+}} [[ptr0]] Volatile
+%24 = OpLoad %uint %19
+
+; CHECK: [[var2:%\w+]] = OpCopyObject %_ptr_Input_v4uint [[var0]]
+; CHECK: [[ptr2:%\w+]] = OpAccessChain %_ptr_Input_uint [[var2]] %int_1
+; CHECK: OpLoad {{%\w+}} [[ptr2]] Volatile
+%18 = OpCopyObject %_ptr_Input_v4uint %var0
+%21 = OpAccessChain %_ptr_Input_uint %18 %int_1
+%26 = OpLoad %uint %21
+
+%28 = OpIAdd %uint %24 %26
+%30 = OpConvertUToF %float %28
+
+; CHECK: [[ptr1:%\w+]] = OpAccessChain %_ptr_Input_uint [[var1]] %int_1
+; CHECK: OpLoad {{%\w+}} [[ptr1]] Volatile
+%32 = OpAccessChain %_ptr_Input_uint %var1 %int_1
+%33 = OpLoad %uint %32
+
+%34 = OpConvertUToF %float %33
+%35 = OpFAdd %float %34 %30
+%36 = OpFAdd %float %35 %29
+OpStore %31 %36
+OpReturn
+OpFunctionEnd
+
+%compute = OpFunction %void None %3
+%66 = OpLabel
+
+; CHECK-NOT: OpLoad {{%\w+}} {{%\w+}} Volatile
+%62 = OpLoad %v4uint %var0
+%63 = OpLoad %v4uint %var1
+%64 = OpIAdd %v4uint %62 %63
+%65 = OpCompositeExtract %uint %64 0
+%61 = OpAtomicIAdd %uint %shared %uint_1 %uint_0 %65
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<SpreadVolatileSemantics>(text, true);
+}
+
+TEST_F(VolatileSpreadTest, SkipIfItHasNoExecutionModel) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%4 = OpFunction %2 None %3
+%5 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  Pass::Status status;
+  std::tie(std::ignore, status) =
+      SinglePassRunToBinary<SpreadVolatileSemantics>(text,
+                                                     /* skip_nop = */ false);
+  EXPECT_EQ(status, Pass::Status::SuccessWithoutChange);
+}
+
+TEST_F(VolatileSpreadTest, NoInlinedfuncCalls) {
+  const std::string text = R"(
+OpCapability RayTracingNV
+OpCapability VulkanMemoryModel
+OpCapability GroupNonUniform
+OpExtension "SPV_NV_ray_tracing"
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical Vulkan
+OpEntryPoint RayGenerationNV %main "main" %SubgroupSize
+OpSource HLSL 630
+OpName %main "main"
+OpName %src_main "src.main"
+OpName %bb_entry "bb.entry"
+OpName %func0 "func0"
+OpName %bb_entry_0 "bb.entry"
+OpName %func2 "func2"
+OpName %bb_entry_1 "bb.entry"
+OpName %param_var_count "param.var.count"
+OpName %func1 "func1"
+OpName %bb_entry_2 "bb.entry"
+OpName %func3 "func3"
+OpName %count "count"
+OpName %bb_entry_3 "bb.entry"
+OpDecorate %SubgroupSize BuiltIn SubgroupSize
+%uint = OpTypeInt 32 0
+%_ptr_Input_uint = OpTypePointer Input %uint
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%_ptr_Function_uint = OpTypePointer Function %uint
+%25 = OpTypeFunction %void %_ptr_Function_uint
+%SubgroupSize = OpVariable %_ptr_Input_uint Input
+%main = OpFunction %void None %6
+%7 = OpLabel
+%8 = OpFunctionCall %void %src_main
+OpReturn
+OpFunctionEnd
+%src_main = OpFunction %void None %6
+%bb_entry = OpLabel
+%11 = OpFunctionCall %void %func0
+OpReturn
+OpFunctionEnd
+%func0 = OpFunction %void DontInline %6
+%bb_entry_0 = OpLabel
+%14 = OpFunctionCall %void %func2
+%16 = OpFunctionCall %void %func1
+OpReturn
+OpFunctionEnd
+%func2 = OpFunction %void DontInline %6
+%bb_entry_1 = OpLabel
+%param_var_count = OpVariable %_ptr_Function_uint Function
+; CHECK: {{%\w+}} = OpLoad %uint %SubgroupSize Volatile
+%21 = OpLoad %uint %SubgroupSize
+OpStore %param_var_count %21
+%22 = OpFunctionCall %void %func3 %param_var_count
+OpReturn
+OpFunctionEnd
+%func1 = OpFunction %void DontInline %6
+%bb_entry_2 = OpLabel
+OpReturn
+OpFunctionEnd
+%func3 = OpFunction %void DontInline %25
+%count = OpFunctionParameter %_ptr_Function_uint
+%bb_entry_3 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+  SinglePassRunAndMatch<SpreadVolatileSemantics>(text, true);
+}
+
+TEST_F(VolatileSpreadErrorTest, NoInlinedMultiEntryfuncCalls) {
+  const std::string text = R"(
+OpCapability RayTracingNV
+OpCapability SubgroupBallotKHR
+OpExtension "SPV_NV_ray_tracing"
+OpExtension "SPV_KHR_shader_ballot"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint RayGenerationNV %main "main" %SubgroupSize
+OpEntryPoint GLCompute %main2 "main2" %gl_LocalInvocationIndex %SubgroupSize
+OpSource HLSL 630
+OpName %main "main"
+OpName %bb_entry "bb.entry"
+OpName %main2 "main2"
+OpName %bb_entry_0 "bb.entry"
+OpName %func "func"
+OpName %count "count"
+OpName %bb_entry_1 "bb.entry"
+OpDecorate %gl_LocalInvocationIndex BuiltIn LocalInvocationIndex
+OpDecorate %SubgroupSize BuiltIn SubgroupSize
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%_ptr_Input_uint = OpTypePointer Input %uint
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%void = OpTypeVoid
+%12 = OpTypeFunction %void
+%_ptr_Function_uint = OpTypePointer Function %uint
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%29 = OpTypeFunction %void %_ptr_Function_v4float
+%34 = OpTypeFunction %void %_ptr_Function_uint
+%SubgroupSize = OpVariable %_ptr_Input_uint Input
+%gl_LocalInvocationIndex = OpVariable %_ptr_Input_uint Input
+%main = OpFunction %void None %12
+%bb_entry = OpLabel
+%20 = OpFunctionCall %void %func
+OpReturn
+OpFunctionEnd
+%main2 = OpFunction %void None %12
+%bb_entry_0 = OpLabel
+%33 = OpFunctionCall %void %func
+OpReturn
+OpFunctionEnd
+%func = OpFunction %void DontInline %12
+%bb_entry_1 = OpLabel
+%count = OpVariable %_ptr_Function_uint Function
+%35 = OpLoad %uint %SubgroupSize
+OpStore %count %35
+OpReturn
+OpFunctionEnd
+)";
+  EXPECT_EQ(RunPass(text), Pass::Status::Failure);
+  const char expected_error[] =
+      "ERROR: 0: Variable is a target for Volatile semantics for an entry "
+      "point, but it is not for another entry point";
+  EXPECT_STREQ(GetErrorMessage().substr(0, sizeof(expected_error) - 1).c_str(),
+               expected_error);
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/test/opt/strip_reflect_info_test.cpp b/test/opt/strip_nonsemantic_info_test.cpp
similarity index 64%
rename from test/opt/strip_reflect_info_test.cpp
rename to test/opt/strip_nonsemantic_info_test.cpp
index f3fc115..3aacffa 100644
--- a/test/opt/strip_reflect_info_test.cpp
+++ b/test/opt/strip_nonsemantic_info_test.cpp
@@ -13,10 +13,9 @@
 // limitations under the License.
 
 #include <string>
+
 #include "gmock/gmock.h"
-
 #include "spirv-tools/optimizer.hpp"
-
 #include "test/opt/pass_fixture.h"
 #include "test/opt/pass_utils.h"
 
@@ -24,7 +23,6 @@
 namespace opt {
 namespace {
 
-using StripLineReflectInfoTest = PassTest<::testing::Test>;
 using StripNonSemanticInfoTest = PassTest<::testing::Test>;
 
 // This test acts as an end-to-end code example on how to strip
@@ -33,7 +31,7 @@
 // option -fhlsl_functionality1 to insert reflection information,
 // but then want to filter out the extra instructions before sending
 // it to a driver that does not implement VK_GOOGLE_hlsl_functionality1.
-TEST_F(StripLineReflectInfoTest, StripReflectEnd2EndExample) {
+TEST_F(StripNonSemanticInfoTest, StripReflectEnd2EndExample) {
   // This is a non-sensical example, but exercises the instructions.
   std::string before = R"(OpCapability Shader
 OpCapability Linkage
@@ -49,11 +47,11 @@
   std::vector<uint32_t> binary_in;
   tools.Assemble(before, &binary_in);
 
-  // Instantiate the optimizer, and run the strip-reflection-info
+  // Instantiate the optimizer, and run the strip-nonsemantic-info
   // pass over the |binary_in| module, and place the modified module
   // into |binary_out|.
   spvtools::Optimizer optimizer(SPV_ENV_UNIVERSAL_1_1);
-  optimizer.RegisterPass(spvtools::CreateStripReflectInfoPass());
+  optimizer.RegisterPass(spvtools::CreateStripNonSemanticInfoPass());
   std::vector<uint32_t> binary_out;
   optimizer.Run(binary_in.data(), binary_in.size(), &binary_out);
 
@@ -71,7 +69,7 @@
 
 // This test is functionally the same as the end-to-end test above,
 // but uses the test SinglePassRunAndCheck test fixture instead.
-TEST_F(StripLineReflectInfoTest, StripHlslSemantic) {
+TEST_F(StripNonSemanticInfoTest, StripHlslSemantic) {
   // This is a non-sensical example, but exercises the instructions.
   std::string before = R"(OpCapability Shader
 OpCapability Linkage
@@ -90,10 +88,10 @@
 %float = OpTypeFloat 32
 )";
 
-  SinglePassRunAndCheck<StripReflectInfoPass>(before, after, false);
+  SinglePassRunAndCheck<StripNonSemanticInfoPass>(before, after, false);
 }
 
-TEST_F(StripLineReflectInfoTest, StripHlslCounterBuffer) {
+TEST_F(StripNonSemanticInfoTest, StripHlslCounterBuffer) {
   std::string before = R"(OpCapability Shader
 OpCapability Linkage
 OpExtension "SPV_GOOGLE_hlsl_functionality1"
@@ -109,10 +107,10 @@
 %float = OpTypeFloat 32
 )";
 
-  SinglePassRunAndCheck<StripReflectInfoPass>(before, after, false);
+  SinglePassRunAndCheck<StripNonSemanticInfoPass>(before, after, false);
 }
 
-TEST_F(StripLineReflectInfoTest, StripHlslSemanticOnMember) {
+TEST_F(StripNonSemanticInfoTest, StripHlslSemanticOnMember) {
   // This is a non-sensical example, but exercises the instructions.
   std::string before = R"(OpCapability Shader
 OpCapability Linkage
@@ -130,7 +128,7 @@
 %_struct_3 = OpTypeStruct %float
 )";
 
-  SinglePassRunAndCheck<StripReflectInfoPass>(before, after, false);
+  SinglePassRunAndCheck<StripNonSemanticInfoPass>(before, after, false);
 }
 
 TEST_F(StripNonSemanticInfoTest, StripNonSemanticImport) {
@@ -144,7 +142,7 @@
 OpMemoryModel Logical GLSL450
 )";
 
-  SinglePassRunAndMatch<StripReflectInfoPass>(text, true);
+  SinglePassRunAndMatch<StripNonSemanticInfoPass>(text, true);
 }
 
 TEST_F(StripNonSemanticInfoTest, StripNonSemanticGlobal) {
@@ -159,7 +157,7 @@
 %1 = OpExtInst %void %ext 1
 )";
 
-  SinglePassRunAndMatch<StripReflectInfoPass>(text, true);
+  SinglePassRunAndMatch<StripNonSemanticInfoPass>(text, true);
 }
 
 TEST_F(StripNonSemanticInfoTest, StripNonSemanticInFunction) {
@@ -179,7 +177,7 @@
 OpFunctionEnd
 )";
 
-  SinglePassRunAndMatch<StripReflectInfoPass>(text, true);
+  SinglePassRunAndMatch<StripNonSemanticInfoPass>(text, true);
 }
 
 TEST_F(StripNonSemanticInfoTest, StripNonSemanticAfterFunction) {
@@ -199,7 +197,7 @@
 %1 = OpExtInst %void %ext 1 %foo
 )";
 
-  SinglePassRunAndMatch<StripReflectInfoPass>(text, true);
+  SinglePassRunAndMatch<StripNonSemanticInfoPass>(text, true);
 }
 
 TEST_F(StripNonSemanticInfoTest, StripNonSemanticBetweenFunctions) {
@@ -223,7 +221,74 @@
 OpFunctionEnd
 )";
 
-  SinglePassRunAndMatch<StripReflectInfoPass>(text, true);
+  SinglePassRunAndMatch<StripNonSemanticInfoPass>(text, true);
+}
+
+// Make sure that strip reflect does not remove the debug info (OpString and
+// OpLine).
+TEST_F(StripNonSemanticInfoTest, DontStripDebug) {
+  std::string text = R"(OpCapability Shader
+OpMemoryModel Logical Simple
+OpEntryPoint Fragment %1 "main"
+OpExecutionMode %1 OriginUpperLeft
+%2 = OpString "file"
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%1 = OpFunction %void None %4
+%5 = OpLabel
+OpLine %2 1 1
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<StripNonSemanticInfoPass>(text, text, false);
+}
+
+TEST_F(StripNonSemanticInfoTest, RemovedNonSemanticDebugInfo) {
+  const std::string text = R"(
+;CHECK-NOT: OpExtension "SPV_KHR_non_semantic_info
+;CHECK-NOT: OpExtInstImport "NonSemantic.Shader.DebugInfo.100
+;CHECK-NOT: OpExtInst %void {{%\w+}} DebugSource
+;CHECK-NOT: OpExtInst %void {{%\w+}} DebugLine
+               OpCapability Shader
+               OpExtension "SPV_KHR_non_semantic_info"
+          %1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %PSMain "PSMain" %in_var_COLOR %out_var_SV_TARGET
+               OpExecutionMode %PSMain OriginUpperLeft
+          %5 = OpString "t.hlsl"
+          %6 = OpString "float"
+          %7 = OpString "color"
+          %8 = OpString "PSInput"
+          %9 = OpString "PSMain"
+         %10 = OpString ""
+         %11 = OpString "input"
+               OpName %in_var_COLOR "in.var.COLOR"
+               OpName %out_var_SV_TARGET "out.var.SV_TARGET"
+               OpName %PSMain "PSMain"
+               OpDecorate %in_var_COLOR Location 0
+               OpDecorate %out_var_SV_TARGET Location 0
+       %uint = OpTypeInt 32 0
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+     %uint_1 = OpConstant %uint 1
+     %uint_9 = OpConstant %uint 9
+         %21 = OpTypeFunction %void
+%in_var_COLOR = OpVariable %_ptr_Input_v4float Input
+%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output
+         %13 = OpExtInst %void %1 DebugSource %5
+     %PSMain = OpFunction %void None %21
+         %22 = OpLabel
+         %23 = OpLoad %v4float %in_var_COLOR
+               OpStore %out_var_SV_TARGET %23
+         %24 = OpExtInst %void %1 DebugLine %13 %uint_9 %uint_9 %uint_1 %uint_1
+               OpReturn
+               OpFunctionEnd
+)";
+  SinglePassRunAndMatch<StripNonSemanticInfoPass>(text, true);
 }
 
 }  // namespace
diff --git a/test/opt/type_manager_test.cpp b/test/opt/type_manager_test.cpp
index fdae2ef..df216bc 100644
--- a/test/opt/type_manager_test.cpp
+++ b/test/opt/type_manager_test.cpp
@@ -167,10 +167,25 @@
   types.emplace_back(new NamedBarrier());
   types.emplace_back(new AccelerationStructureNV());
   types.emplace_back(new CooperativeMatrixNV(f32, 24, 24, 24));
+  types.emplace_back(new RayQueryKHR());
 
   return types;
 }
 
+TEST(TypeManager, GenerateAllTypesGeneratesAllTypes) {
+  std::set<Type::Kind> generated_types;
+  for (auto& type : GenerateAllTypes()) {
+    generated_types.insert(type->kind());
+  }
+
+  std::vector<Type::Kind> all_types;
+  for (uint32_t kind = 0; kind != Type::Kind::kLast; ++kind) {
+    all_types.push_back(static_cast<Type::Kind>(kind));
+  }
+
+  EXPECT_THAT(generated_types, testing::UnorderedElementsAreArray(all_types));
+}
+
 TEST(TypeManager, TypeStrings) {
   const std::string text = R"(
     OpDecorate %spec_const_with_id SpecId 99
@@ -1065,6 +1080,7 @@
 ; CHECK: OpTypeNamedBarrier
 ; CHECK: OpTypeAccelerationStructureKHR
 ; CHECK: OpTypeCooperativeMatrixNV [[f32]] [[uint24]] [[uint24]] [[uint24]]
+; CHECK: OpTypeRayQueryKHR
 OpCapability Shader
 OpCapability Int64
 OpCapability Linkage
diff --git a/test/opt/types_test.cpp b/test/opt/types_test.cpp
index 82e4040..552ad97 100644
--- a/test/opt/types_test.cpp
+++ b/test/opt/types_test.cpp
@@ -266,6 +266,67 @@
   }
 }
 
+TEST(Types, TestNumberOfComponentsOnArrays) {
+  Float f32(32);
+  EXPECT_EQ(f32.NumberOfComponents(), 0);
+
+  Array array_size_42(
+      &f32, Array::LengthInfo{99u, {Array::LengthInfo::kConstant, 42u}});
+  EXPECT_EQ(array_size_42.NumberOfComponents(), 42);
+
+  Array array_size_0xDEADBEEF00C0FFEE(
+      &f32, Array::LengthInfo{
+                99u, {Array::LengthInfo::kConstant, 0xC0FFEE, 0xDEADBEEF}});
+  EXPECT_EQ(array_size_0xDEADBEEF00C0FFEE.NumberOfComponents(),
+            0xDEADBEEF00C0FFEEull);
+
+  Array array_size_unknown(
+      &f32,
+      Array::LengthInfo{99u, {Array::LengthInfo::kConstantWithSpecId, 10}});
+  EXPECT_EQ(array_size_unknown.NumberOfComponents(), UINT64_MAX);
+
+  RuntimeArray runtime_array(&f32);
+  EXPECT_EQ(runtime_array.NumberOfComponents(), UINT64_MAX);
+}
+
+TEST(Types, TestNumberOfComponentsOnVectors) {
+  Float f32(32);
+  EXPECT_EQ(f32.NumberOfComponents(), 0);
+
+  for (uint32_t vector_size = 1; vector_size < 4; ++vector_size) {
+    Vector vector(&f32, vector_size);
+    EXPECT_EQ(vector.NumberOfComponents(), vector_size);
+  }
+}
+
+TEST(Types, TestNumberOfComponentsOnMatrices) {
+  Float f32(32);
+  Vector vector(&f32, 2);
+
+  for (uint32_t number_of_columns = 1; number_of_columns < 4;
+       ++number_of_columns) {
+    Matrix matrix(&vector, number_of_columns);
+    EXPECT_EQ(matrix.NumberOfComponents(), number_of_columns);
+  }
+}
+
+TEST(Types, TestNumberOfComponentsOnStructs) {
+  Float f32(32);
+  Vector vector(&f32, 2);
+
+  Struct empty_struct({});
+  EXPECT_EQ(empty_struct.NumberOfComponents(), 0);
+
+  Struct struct_f32({&f32});
+  EXPECT_EQ(struct_f32.NumberOfComponents(), 1);
+
+  Struct struct_f32_vec({&f32, &vector});
+  EXPECT_EQ(struct_f32_vec.NumberOfComponents(), 2);
+
+  Struct struct_100xf32(std::vector<const Type*>(100, &f32));
+  EXPECT_EQ(struct_100xf32.NumberOfComponents(), 100);
+}
+
 TEST(Types, IntSignedness) {
   std::vector<bool> signednesses = {true, false, false, true};
   std::vector<std::unique_ptr<Integer>> types;
diff --git a/test/opt/unify_const_test.cpp b/test/opt/unify_const_test.cpp
index 6ed2173..0d7c30b 100644
--- a/test/opt/unify_const_test.cpp
+++ b/test/opt/unify_const_test.cpp
@@ -263,7 +263,7 @@
           // decorated flat struct
           "%flat_d = OpTypeStruct %int %float",
           "%_pf_flat_d = OpTypePointer Function %flat_d",
-          // perserved contants. %flat_1 and %flat_d has same members, but
+          // preserved constants. %flat_1 and %flat_d has same members, but
           // their type are different in decorations, so they should not be
           // used to replace each other.
           "%int_1 = OpConstant %int 1",
@@ -682,7 +682,7 @@
             // zero-valued composite constant built from zero-valued constant
             // component. inner_zero should not be replace by null_inner.
             "%inner_zero = OpConstantComposite %inner_struct %bool_zero %float_zero",
-            // zero-valued composite contant built from zero-valued constants
+            // zero-valued composite constant built from zero-valued constants
             // and null constants.
             "%outer_zero = OpConstantComposite %outer_struct %inner_zero %int_null %double_null",
             // outer_struct type null constant, it should not be replaced by
@@ -820,7 +820,7 @@
           {
             "%spec_signed_add_duplicate = OpSpecConstantOp %int IAdd %spec_signed_1 %spec_signed_2",
           },
-          // use duplicated contants in main
+          // use duplicated constants in main
           {
             "%int_var = OpVariable %_pf_int Function",
             "OpStore %int_var %spec_signed_add_duplicate",
diff --git a/test/reduce/structured_loop_to_selection_test.cpp b/test/reduce/structured_loop_to_selection_test.cpp
index 0cfcfdf..d203f3e 100644
--- a/test/reduce/structured_loop_to_selection_test.cpp
+++ b/test/reduce/structured_loop_to_selection_test.cpp
@@ -2957,7 +2957,7 @@
                OpLoopMerge %12 %13 None
                OpBranch %12
          %13 = OpLabel
-               OpBranchConditional %6 %9 %11
+               OpBranch %11
          %12 = OpLabel
                OpBranch %10
          %10 = OpLabel
@@ -2999,7 +2999,7 @@
                OpLoopMerge %12 %13 None
                OpBranch %12
          %13 = OpLabel
-               OpBranchConditional %6 %9 %11
+               OpBranch %11
          %12 = OpLabel
                OpBranch %9
          %10 = OpLabel
@@ -3036,7 +3036,7 @@
                OpSelectionMerge %12 None
                OpBranchConditional %6 %12 %12
          %13 = OpLabel
-               OpBranchConditional %6 %9 %11
+               OpBranch %11
          %12 = OpLabel
                OpBranch %9
          %10 = OpLabel
@@ -3050,8 +3050,7 @@
 
 TEST(StructuredLoopToSelectionReductionPassTest,
      UnreachableInnerLoopContinueBranchingToOuterLoopMerge2) {
-  // In this test, the branch to the outer loop merge from the inner loop's
-  // continue is part of a structured selection.
+  // In this test, the unreachable continue is composed of multiple blocks.
   std::string shader = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
@@ -3073,8 +3072,7 @@
                OpLoopMerge %12 %13 None
                OpBranch %12
          %13 = OpLabel
-               OpSelectionMerge %14 None
-               OpBranchConditional %6 %9 %14
+               OpBranch %14
          %14 = OpLabel
                OpBranch %11
          %12 = OpLabel
@@ -3118,8 +3116,7 @@
                OpLoopMerge %12 %13 None
                OpBranch %12
          %13 = OpLabel
-               OpSelectionMerge %14 None
-               OpBranchConditional %6 %9 %14
+               OpBranch %14
          %14 = OpLabel
                OpBranch %11
          %12 = OpLabel
@@ -3158,8 +3155,7 @@
                OpSelectionMerge %12 None
                OpBranchConditional %6 %12 %12
          %13 = OpLabel
-               OpSelectionMerge %14 None
-               OpBranchConditional %6 %9 %14
+               OpBranch %14
          %14 = OpLabel
                OpBranch %11
          %12 = OpLabel
diff --git a/test/target_env_test.cpp b/test/target_env_test.cpp
index 4acb8ff..7917cbf 100644
--- a/test/target_env_test.cpp
+++ b/test/target_env_test.cpp
@@ -135,7 +135,8 @@
         {VK(1, 0), SPV(1, 3), true, SPV_ENV_VULKAN_1_1},
         {VK(1, 0), SPV(1, 4), true, SPV_ENV_VULKAN_1_1_SPIRV_1_4},
         {VK(1, 0), SPV(1, 5), true, SPV_ENV_VULKAN_1_2},
-        {VK(1, 0), SPV(1, 6), false, SPV_ENV_UNIVERSAL_1_0},
+        {VK(1, 0), SPV(1, 6), true, SPV_ENV_VULKAN_1_3},
+        {VK(1, 0), SPV(1, 7), false, SPV_ENV_UNIVERSAL_1_0},
         // Vulkan 1.1 cases
         {VK(1, 1), SPV(1, 0), true, SPV_ENV_VULKAN_1_1},
         {VK(1, 1), SPV(1, 1), true, SPV_ENV_VULKAN_1_1},
@@ -143,7 +144,8 @@
         {VK(1, 1), SPV(1, 3), true, SPV_ENV_VULKAN_1_1},
         {VK(1, 1), SPV(1, 4), true, SPV_ENV_VULKAN_1_1_SPIRV_1_4},
         {VK(1, 1), SPV(1, 5), true, SPV_ENV_VULKAN_1_2},
-        {VK(1, 1), SPV(1, 6), false, SPV_ENV_UNIVERSAL_1_0},
+        {VK(1, 1), SPV(1, 6), true, SPV_ENV_VULKAN_1_3},
+        {VK(1, 1), SPV(1, 7), false, SPV_ENV_UNIVERSAL_1_0},
         // Vulkan 1.2 cases
         {VK(1, 2), SPV(1, 0), true, SPV_ENV_VULKAN_1_2},
         {VK(1, 2), SPV(1, 1), true, SPV_ENV_VULKAN_1_2},
@@ -151,9 +153,17 @@
         {VK(1, 2), SPV(1, 3), true, SPV_ENV_VULKAN_1_2},
         {VK(1, 2), SPV(1, 4), true, SPV_ENV_VULKAN_1_2},
         {VK(1, 2), SPV(1, 5), true, SPV_ENV_VULKAN_1_2},
-        {VK(1, 2), SPV(1, 6), false, SPV_ENV_UNIVERSAL_1_0},
+        {VK(1, 2), SPV(1, 6), true, SPV_ENV_VULKAN_1_3},
+        {VK(1, 2), SPV(1, 7), false, SPV_ENV_UNIVERSAL_1_0},
         // Vulkan 1.3 cases
-        {VK(1, 3), SPV(1, 0), false, SPV_ENV_UNIVERSAL_1_0},
+        {VK(1, 3), SPV(1, 0), true, SPV_ENV_VULKAN_1_3},
+        {VK(1, 3), SPV(1, 1), true, SPV_ENV_VULKAN_1_3},
+        {VK(1, 3), SPV(1, 2), true, SPV_ENV_VULKAN_1_3},
+        {VK(1, 3), SPV(1, 3), true, SPV_ENV_VULKAN_1_3},
+        {VK(1, 3), SPV(1, 4), true, SPV_ENV_VULKAN_1_3},
+        {VK(1, 3), SPV(1, 5), true, SPV_ENV_VULKAN_1_3},
+        {VK(1, 3), SPV(1, 6), true, SPV_ENV_VULKAN_1_3},
+        {VK(1, 3), SPV(1, 7), false, SPV_ENV_UNIVERSAL_1_0},
         // Vulkan 2.0 cases
         {VK(2, 0), SPV(1, 0), false, SPV_ENV_UNIVERSAL_1_0},
         // Vulkan 99.0 cases
diff --git a/test/test_fixture.h b/test/test_fixture.h
index 0c5bfc9..029fc85 100644
--- a/test/test_fixture.h
+++ b/test/test_fixture.h
@@ -15,6 +15,7 @@
 #ifndef TEST_TEST_FIXTURE_H_
 #define TEST_TEST_FIXTURE_H_
 
+#include <algorithm>
 #include <string>
 #include <vector>
 
@@ -91,12 +92,26 @@
     return diagnostic->error;
   }
 
+  // Potentially flip the words in the binary representation to the other
+  // endianness
+  template <class It>
+  void MaybeFlipWords(bool flip_words, It begin, It end) {
+    SCOPED_TRACE(flip_words ? "Flipped Endianness" : "Normal Endianness");
+    if (flip_words) {
+      std::transform(begin, end, begin, [](const uint32_t raw_word) {
+        return spvFixWord(raw_word, I32_ENDIAN_HOST == I32_ENDIAN_BIG
+                                        ? SPV_ENDIANNESS_LITTLE
+                                        : SPV_ENDIANNESS_BIG);
+      });
+    }
+  }
+
   // Encodes SPIR-V text into binary and then decodes the binary using
   // given options. Returns the decoded text.
   std::string EncodeAndDecodeSuccessfully(
       const std::string& txt,
       uint32_t disassemble_options = SPV_BINARY_TO_TEXT_OPTION_NONE,
-      spv_target_env env = SPV_ENV_UNIVERSAL_1_0) {
+      spv_target_env env = SPV_ENV_UNIVERSAL_1_0, bool flip_words = false) {
     DestroyBinary();
     DestroyDiagnostic();
     ScopedContext context(env);
@@ -110,6 +125,8 @@
     EXPECT_EQ(SPV_SUCCESS, error);
     if (!binary) return "";
 
+    MaybeFlipWords(flip_words, binary->code, binary->code + binary->wordCount);
+
     spv_text decoded_text;
     error = spvBinaryToText(context.context, binary->code, binary->wordCount,
                             disassemble_options, &decoded_text, &diagnostic);
diff --git a/test/text_advance_test.cpp b/test/text_advance_test.cpp
index 9de77a8..0d23ab1 100644
--- a/test/text_advance_test.cpp
+++ b/test/text_advance_test.cpp
@@ -130,5 +130,14 @@
   EXPECT_EQ(2u, pos.line);
   EXPECT_EQ(4u, pos.index);
 }
+
+TEST(TextAdvance, HandleLotsOfWhitespace) {
+  std::string lots_of_spaces(10000, ' ');
+  lots_of_spaces += "Word";
+  const auto pos = PositionAfterAdvance(lots_of_spaces.c_str());
+  EXPECT_EQ(10000u, pos.column);
+  EXPECT_EQ(0u, pos.line);
+  EXPECT_EQ(10000u, pos.index);
+}
 }  // namespace
 }  // namespace spvtools
diff --git a/test/text_to_binary.annotation_test.cpp b/test/text_to_binary.annotation_test.cpp
index 61bdf64..76776de 100644
--- a/test/text_to_binary.annotation_test.cpp
+++ b/test/text_to_binary.annotation_test.cpp
@@ -398,7 +398,8 @@
 
 TEST_F(TextToBinaryTest, GroupMemberDecorateMissingGroupId) {
   EXPECT_THAT(CompileFailure("OpGroupMemberDecorate"),
-              Eq("Expected operand, found end of stream."));
+              Eq("Expected operand for OpGroupMemberDecorate instruction, but "
+                 "found the end of the stream."));
 }
 
 TEST_F(TextToBinaryTest, GroupMemberDecorateInvalidGroupId) {
@@ -413,7 +414,8 @@
 
 TEST_F(TextToBinaryTest, GroupMemberDecorateMissingTargetMemberNumber) {
   EXPECT_THAT(CompileFailure("OpGroupMemberDecorate %group %id0"),
-              Eq("Expected operand, found end of stream."));
+              Eq("Expected operand for OpGroupMemberDecorate instruction, but "
+                 "found the end of the stream."));
 }
 
 TEST_F(TextToBinaryTest, GroupMemberDecorateInvalidTargetMemberNumber) {
@@ -428,7 +430,8 @@
 
 TEST_F(TextToBinaryTest, GroupMemberDecorateMissingSecondTargetMemberNumber) {
   EXPECT_THAT(CompileFailure("OpGroupMemberDecorate %group %id0 42 %id1"),
-              Eq("Expected operand, found end of stream."));
+              Eq("Expected operand for OpGroupMemberDecorate instruction, but "
+                 "found the end of the stream."));
 }
 
 TEST_F(TextToBinaryTest, GroupMemberDecorateInvalidSecondTargetMemberNumber) {
diff --git a/test/text_to_binary.barrier_test.cpp b/test/text_to_binary.barrier_test.cpp
index 545d26f..f1cb4fb 100644
--- a/test/text_to_binary.barrier_test.cpp
+++ b/test/text_to_binary.barrier_test.cpp
@@ -44,7 +44,8 @@
 TEST_F(OpMemoryBarrier, BadMissingScopeId) {
   const std::string input = "OpMemoryBarrier\n";
   EXPECT_THAT(CompileFailure(input),
-              Eq("Expected operand, found end of stream."));
+              Eq("Expected operand for OpMemoryBarrier instruction, but found "
+                 "the end of the stream."));
 }
 
 TEST_F(OpMemoryBarrier, BadInvalidScopeId) {
@@ -55,7 +56,8 @@
 TEST_F(OpMemoryBarrier, BadMissingMemorySemanticsId) {
   const std::string input = "OpMemoryBarrier %scope\n";
   EXPECT_THAT(CompileFailure(input),
-              Eq("Expected operand, found end of stream."));
+              Eq("Expected operand for OpMemoryBarrier instruction, but found "
+                 "the end of the stream."));
 }
 
 TEST_F(OpMemoryBarrier, BadInvalidMemorySemanticsId) {
@@ -92,13 +94,16 @@
 
 TEST_F(NamedMemoryBarrierTest, ArgumentCount) {
   EXPECT_THAT(CompileFailure("OpMemoryNamedBarrier", SPV_ENV_UNIVERSAL_1_1),
-              Eq("Expected operand, found end of stream."));
+              Eq("Expected operand for OpMemoryNamedBarrier instruction, but "
+                 "found the end of the stream."));
   EXPECT_THAT(
       CompileFailure("OpMemoryNamedBarrier %bar", SPV_ENV_UNIVERSAL_1_1),
-      Eq("Expected operand, found end of stream."));
+      Eq("Expected operand for OpMemoryNamedBarrier instruction, but found the "
+         "end of the stream."));
   EXPECT_THAT(
       CompileFailure("OpMemoryNamedBarrier %bar %scope", SPV_ENV_UNIVERSAL_1_1),
-      Eq("Expected operand, found end of stream."));
+      Eq("Expected operand for OpMemoryNamedBarrier instruction, but found the "
+         "end of the stream."));
   EXPECT_THAT(
       CompiledInstructions("OpMemoryNamedBarrier %bar %scope %semantics",
                            SPV_ENV_UNIVERSAL_1_1),
@@ -151,10 +156,12 @@
 TEST_F(NamedBarrierInitializeTest, ArgumentCount) {
   EXPECT_THAT(
       CompileFailure("%bar = OpNamedBarrierInitialize", SPV_ENV_UNIVERSAL_1_1),
-      Eq("Expected operand, found end of stream."));
+      Eq("Expected operand for OpNamedBarrierInitialize instruction, but found "
+         "the end of the stream."));
   EXPECT_THAT(CompileFailure("%bar = OpNamedBarrierInitialize %ype",
                              SPV_ENV_UNIVERSAL_1_1),
-              Eq("Expected operand, found end of stream."));
+              Eq("Expected operand for OpNamedBarrierInitialize instruction, "
+                 "but found the end of the stream."));
   EXPECT_THAT(
       CompiledInstructions("%bar = OpNamedBarrierInitialize %type %count",
                            SPV_ENV_UNIVERSAL_1_1),
diff --git a/test/text_to_binary.control_flow_test.cpp b/test/text_to_binary.control_flow_test.cpp
index 3e117b8..472cb6d 100644
--- a/test/text_to_binary.control_flow_test.cpp
+++ b/test/text_to_binary.control_flow_test.cpp
@@ -163,7 +163,8 @@
 
 TEST_F(TextToBinaryTest, SwitchBadMissingSelector) {
   EXPECT_THAT(CompileFailure("OpSwitch"),
-              Eq("Expected operand, found end of stream."));
+              Eq("Expected operand for OpSwitch instruction, but found the end "
+                 "of the stream."));
 }
 
 TEST_F(TextToBinaryTest, SwitchBadInvalidSelector) {
@@ -173,7 +174,8 @@
 
 TEST_F(TextToBinaryTest, SwitchBadMissingDefault) {
   EXPECT_THAT(CompileFailure("OpSwitch %selector"),
-              Eq("Expected operand, found end of stream."));
+              Eq("Expected operand for OpSwitch instruction, but found the end "
+                 "of the stream."));
 }
 
 TEST_F(TextToBinaryTest, SwitchBadInvalidDefault) {
@@ -195,7 +197,8 @@
   EXPECT_THAT(CompileFailure("%1 = OpTypeInt 32 0\n"
                              "%2 = OpConstant %1 52\n"
                              "OpSwitch %2 %default 12"),
-              Eq("Expected operand, found end of stream."));
+              Eq("Expected operand for OpSwitch instruction, but found the end "
+                 "of the stream."));
 }
 
 // A test case for an OpSwitch.
@@ -379,7 +382,7 @@
        "OpTypeQueue",
        "OpTypePipe ReadOnly",
 
-       // Skip OpTypeForwardPointer becasuse it doesn't even produce a result
+       // Skip OpTypeForwardPointer because it doesn't even produce a result
        // ID.
 
        // At least one thing that isn't a type at all
diff --git a/test/text_to_binary.device_side_enqueue_test.cpp b/test/text_to_binary.device_side_enqueue_test.cpp
index 03d7e74..2f4dd70 100644
--- a/test/text_to_binary.device_side_enqueue_test.cpp
+++ b/test/text_to_binary.device_side_enqueue_test.cpp
@@ -83,7 +83,8 @@
       CompileFailure(
           "%result = OpEnqueueKernel %type %queue %flags %NDRange %num_events"
           " %wait_events %ret_event %invoke %param %param_size"),
-      Eq("Expected operand, found end of stream."));
+      Eq("Expected operand for OpEnqueueKernel instruction, but found the end "
+         "of the stream."));
 }
 
 TEST_F(OpKernelEnqueueBad, InvalidLastOperand) {
diff --git a/test/text_to_binary.extension_test.cpp b/test/text_to_binary.extension_test.cpp
index 1324206..cf4919a 100644
--- a/test/text_to_binary.extension_test.cpp
+++ b/test/text_to_binary.extension_test.cpp
@@ -197,7 +197,7 @@
                                                  SpvBuiltInSubgroupLtMask})},
             })));
 
-// The old builtin names (with KHR suffix) still work in the assmebler, and
+// The old builtin names (with KHR suffix) still work in the assembler, and
 // map to the enums without the KHR.
 INSTANTIATE_TEST_SUITE_P(
     SPV_KHR_shader_ballot_vulkan_1_1_alias_check, ExtensionAssemblyTest,
@@ -957,61 +957,62 @@
 INSTANTIATE_TEST_SUITE_P(
     SPV_KHR_integer_dot_product, ExtensionRoundTripTest,
     Combine(
-        Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_5, SPV_ENV_VULKAN_1_0,
-               SPV_ENV_VULKAN_1_1, SPV_ENV_VULKAN_1_2),
+        Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_5,
+               SPV_ENV_UNIVERSAL_1_6, SPV_ENV_VULKAN_1_0, SPV_ENV_VULKAN_1_1,
+               SPV_ENV_VULKAN_1_2, SPV_ENV_VULKAN_1_3),
         ValuesIn(std::vector<AssemblyCase>{
             {"OpExtension \"SPV_KHR_integer_dot_product\"\n",
              MakeInstruction(SpvOpExtension,
                              MakeVector("SPV_KHR_integer_dot_product"))},
-            {"OpCapability DotProductInputAllKHR\n",
+            {"OpCapability DotProductInputAll\n",
              MakeInstruction(SpvOpCapability,
                              {SpvCapabilityDotProductInputAllKHR})},
-            {"OpCapability DotProductInput4x8BitKHR\n",
+            {"OpCapability DotProductInput4x8Bit\n",
              MakeInstruction(SpvOpCapability,
                              {SpvCapabilityDotProductInput4x8BitKHR})},
-            {"OpCapability DotProductInput4x8BitPackedKHR\n",
+            {"OpCapability DotProductInput4x8BitPacked\n",
              MakeInstruction(SpvOpCapability,
                              {SpvCapabilityDotProductInput4x8BitPackedKHR})},
-            {"OpCapability DotProductKHR\n",
+            {"OpCapability DotProduct\n",
              MakeInstruction(SpvOpCapability, {SpvCapabilityDotProductKHR})},
-            {"%2 = OpSDotKHR %1 %3 %4\n",
+            {"%2 = OpSDot %1 %3 %4\n",
              MakeInstruction(SpvOpSDotKHR, {1, 2, 3, 4})},
-            {"%2 = OpSDotKHR %1 %3 %4 PackedVectorFormat4x8BitKHR\n",
+            {"%2 = OpSDot %1 %3 %4 PackedVectorFormat4x8Bit\n",
              MakeInstruction(
                  SpvOpSDotKHR,
                  {1, 2, 3, 4,
                   SpvPackedVectorFormatPackedVectorFormat4x8BitKHR})},
-            {"%2 = OpUDotKHR %1 %3 %4\n",
+            {"%2 = OpUDot %1 %3 %4\n",
              MakeInstruction(SpvOpUDotKHR, {1, 2, 3, 4})},
-            {"%2 = OpUDotKHR %1 %3 %4 PackedVectorFormat4x8BitKHR\n",
+            {"%2 = OpUDot %1 %3 %4 PackedVectorFormat4x8Bit\n",
              MakeInstruction(
                  SpvOpUDotKHR,
                  {1, 2, 3, 4,
                   SpvPackedVectorFormatPackedVectorFormat4x8BitKHR})},
-            {"%2 = OpSUDotKHR %1 %3 %4\n",
+            {"%2 = OpSUDot %1 %3 %4\n",
              MakeInstruction(SpvOpSUDotKHR, {1, 2, 3, 4})},
-            {"%2 = OpSUDotKHR %1 %3 %4 PackedVectorFormat4x8BitKHR\n",
+            {"%2 = OpSUDot %1 %3 %4 PackedVectorFormat4x8Bit\n",
              MakeInstruction(
                  SpvOpSUDotKHR,
                  {1, 2, 3, 4,
                   SpvPackedVectorFormatPackedVectorFormat4x8BitKHR})},
-            {"%2 = OpSDotAccSatKHR %1 %3 %4 %5\n",
+            {"%2 = OpSDotAccSat %1 %3 %4 %5\n",
              MakeInstruction(SpvOpSDotAccSatKHR, {1, 2, 3, 4, 5})},
-            {"%2 = OpSDotAccSatKHR %1 %3 %4 %5 PackedVectorFormat4x8BitKHR\n",
+            {"%2 = OpSDotAccSat %1 %3 %4 %5 PackedVectorFormat4x8Bit\n",
              MakeInstruction(
                  SpvOpSDotAccSatKHR,
                  {1, 2, 3, 4, 5,
                   SpvPackedVectorFormatPackedVectorFormat4x8BitKHR})},
-            {"%2 = OpUDotAccSatKHR %1 %3 %4 %5\n",
+            {"%2 = OpUDotAccSat %1 %3 %4 %5\n",
              MakeInstruction(SpvOpUDotAccSatKHR, {1, 2, 3, 4, 5})},
-            {"%2 = OpUDotAccSatKHR %1 %3 %4 %5 PackedVectorFormat4x8BitKHR\n",
+            {"%2 = OpUDotAccSat %1 %3 %4 %5 PackedVectorFormat4x8Bit\n",
              MakeInstruction(
                  SpvOpUDotAccSatKHR,
                  {1, 2, 3, 4, 5,
                   SpvPackedVectorFormatPackedVectorFormat4x8BitKHR})},
-            {"%2 = OpSUDotAccSatKHR %1 %3 %4 %5\n",
+            {"%2 = OpSUDotAccSat %1 %3 %4 %5\n",
              MakeInstruction(SpvOpSUDotAccSatKHR, {1, 2, 3, 4, 5})},
-            {"%2 = OpSUDotAccSatKHR %1 %3 %4 %5 PackedVectorFormat4x8BitKHR\n",
+            {"%2 = OpSUDotAccSat %1 %3 %4 %5 PackedVectorFormat4x8Bit\n",
              MakeInstruction(
                  SpvOpSUDotAccSatKHR,
                  {1, 2, 3, 4, 5,
@@ -1033,5 +1034,68 @@
                                  {SpvCapabilityBitInstructions})},
             })));
 
+// SPV_KHR_uniform_group_instructions
+
+INSTANTIATE_TEST_SUITE_P(
+    SPV_KHR_uniform_group_instructions, ExtensionRoundTripTest,
+    Combine(
+        Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_5,
+               SPV_ENV_UNIVERSAL_1_6, SPV_ENV_VULKAN_1_0, SPV_ENV_VULKAN_1_1,
+               SPV_ENV_VULKAN_1_2, SPV_ENV_VULKAN_1_3),
+        ValuesIn(std::vector<AssemblyCase>{
+            {"OpExtension \"SPV_KHR_uniform_group_instructions\"\n",
+             MakeInstruction(SpvOpExtension,
+                             MakeVector("SPV_KHR_uniform_group_instructions"))},
+            {"OpCapability GroupUniformArithmeticKHR\n",
+             MakeInstruction(SpvOpCapability,
+                             {SpvCapabilityGroupUniformArithmeticKHR})},
+            {"%2 = OpGroupIMulKHR %1 %3 Reduce %4\n",
+             MakeInstruction(SpvOpGroupIMulKHR,
+                             {1, 2, 3, SpvGroupOperationReduce, 4})},
+            {"%2 = OpGroupFMulKHR %1 %3 Reduce %4\n",
+             MakeInstruction(SpvOpGroupFMulKHR,
+                             {1, 2, 3, SpvGroupOperationReduce, 4})},
+            {"%2 = OpGroupBitwiseAndKHR %1 %3 Reduce %4\n",
+             MakeInstruction(SpvOpGroupBitwiseAndKHR,
+                             {1, 2, 3, SpvGroupOperationReduce, 4})},
+            {"%2 = OpGroupBitwiseOrKHR %1 %3 Reduce %4\n",
+             MakeInstruction(SpvOpGroupBitwiseOrKHR,
+                             {1, 2, 3, SpvGroupOperationReduce, 4})},
+            {"%2 = OpGroupBitwiseXorKHR %1 %3 Reduce %4\n",
+             MakeInstruction(SpvOpGroupBitwiseXorKHR,
+                             {1, 2, 3, SpvGroupOperationReduce, 4})},
+            {"%2 = OpGroupLogicalAndKHR %1 %3 Reduce %4\n",
+             MakeInstruction(SpvOpGroupLogicalAndKHR,
+                             {1, 2, 3, SpvGroupOperationReduce, 4})},
+            {"%2 = OpGroupLogicalOrKHR %1 %3 Reduce %4\n",
+             MakeInstruction(SpvOpGroupLogicalOrKHR,
+                             {1, 2, 3, SpvGroupOperationReduce, 4})},
+            {"%2 = OpGroupLogicalXorKHR %1 %3 Reduce %4\n",
+             MakeInstruction(SpvOpGroupLogicalXorKHR,
+                             {1, 2, 3, SpvGroupOperationReduce, 4})},
+        })));
+
+// SPV_KHR_subgroup_rotate
+
+INSTANTIATE_TEST_SUITE_P(
+    SPV_KHR_subgroup_rotate, ExtensionRoundTripTest,
+    Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_6,
+                   SPV_ENV_VULKAN_1_0, SPV_ENV_VULKAN_1_1, SPV_ENV_VULKAN_1_2,
+                   SPV_ENV_VULKAN_1_3, SPV_ENV_OPENCL_2_1),
+            ValuesIn(std::vector<AssemblyCase>{
+                {"OpExtension \"SPV_KHR_subgroup_rotate\"\n",
+                 MakeInstruction(SpvOpExtension,
+                                 MakeVector("SPV_KHR_subgroup_rotate"))},
+                {"OpCapability GroupNonUniformRotateKHR\n",
+                 MakeInstruction(SpvOpCapability,
+                                 {SpvCapabilityGroupNonUniformRotateKHR})},
+                {"%2 = OpGroupNonUniformRotateKHR %1 %3 %4 %5\n",
+                 MakeInstruction(SpvOpGroupNonUniformRotateKHR,
+                                 {1, 2, 3, 4, 5})},
+                {"%2 = OpGroupNonUniformRotateKHR %1 %3 %4 %5 %6\n",
+                 MakeInstruction(SpvOpGroupNonUniformRotateKHR,
+                                 {1, 2, 3, 4, 5, 6})},
+            })));
+
 }  // namespace
 }  // namespace spvtools
diff --git a/test/text_to_binary.image_test.cpp b/test/text_to_binary.image_test.cpp
index d445369..8d8ff43 100644
--- a/test/text_to_binary.image_test.cpp
+++ b/test/text_to_binary.image_test.cpp
@@ -123,7 +123,8 @@
 
 TEST_F(OpImageTest, MissingSampledImageOperand) {
   EXPECT_THAT(CompileFailure("%2 = OpImage %1"),
-              Eq("Expected operand, found end of stream."));
+              Eq("Expected operand for OpImage instruction, but found the end "
+                 "of the stream."));
 }
 
 TEST_F(OpImageTest, InvalidSampledImageOperand) {
@@ -222,7 +223,8 @@
 
 TEST_F(OpImageSparseReadTest, MissingImageOperand) {
   EXPECT_THAT(CompileFailure("%2 = OpImageSparseRead %1"),
-              Eq("Expected operand, found end of stream."));
+              Eq("Expected operand for OpImageSparseRead instruction, but "
+                 "found the end of the stream."));
 }
 
 TEST_F(OpImageSparseReadTest, InvalidImageOperand) {
@@ -232,7 +234,8 @@
 
 TEST_F(OpImageSparseReadTest, MissingCoordinateOperand) {
   EXPECT_THAT(CompileFailure("%2 = OpImageSparseRead %1 %2"),
-              Eq("Expected operand, found end of stream."));
+              Eq("Expected operand for OpImageSparseRead instruction, but "
+                 "found the end of the stream."));
 }
 
 TEST_F(OpImageSparseReadTest, InvalidCoordinateOperand) {
diff --git a/test/text_to_binary.memory_test.cpp b/test/text_to_binary.memory_test.cpp
index 7b09ed5..f94c134 100644
--- a/test/text_to_binary.memory_test.cpp
+++ b/test/text_to_binary.memory_test.cpp
@@ -166,7 +166,8 @@
 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"));
+  EXPECT_THAT(err, HasSubstr("Expected operand for OpCopyMemory instruction, "
+                             "but found the end of the stream."));
 }
 
 TEST_F(MemoryRoundTripTest, OpCopyMemoryTooManyArgsBad) {
@@ -295,7 +296,8 @@
 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"));
+  EXPECT_THAT(err, HasSubstr("Expected operand for OpCopyMemorySized "
+                             "instruction, but found the end of the stream."));
 }
 
 TEST_F(MemoryRoundTripTest, OpCopyMemorySizedTooManyArgsBad) {
diff --git a/test/text_to_binary.mode_setting_test.cpp b/test/text_to_binary.mode_setting_test.cpp
index 647bb3d..7f15c8b 100644
--- a/test/text_to_binary.mode_setting_test.cpp
+++ b/test/text_to_binary.mode_setting_test.cpp
@@ -290,7 +290,8 @@
 
 TEST_F(TextToBinaryCapability, BadMissingCapability) {
   EXPECT_THAT(CompileFailure("OpCapability"),
-              Eq("Expected operand, found end of stream."));
+              Eq("Expected operand for OpCapability instruction, but found the "
+                 "end of the stream."));
 }
 
 TEST_F(TextToBinaryCapability, BadInvalidCapability) {
diff --git a/test/text_to_binary.pipe_storage_test.cpp b/test/text_to_binary.pipe_storage_test.cpp
index f74dbcf..955f5ef 100644
--- a/test/text_to_binary.pipe_storage_test.cpp
+++ b/test/text_to_binary.pipe_storage_test.cpp
@@ -59,10 +59,12 @@
          "'OpConstantPipeStorage'."));
   EXPECT_THAT(
       CompileFailure("%1 = OpConstantPipeStorage", SPV_ENV_UNIVERSAL_1_1),
-      Eq("Expected operand, found end of stream."));
+      Eq("Expected operand for OpConstantPipeStorage instruction, but found "
+         "the end of the stream."));
   EXPECT_THAT(CompileFailure("%1 = OpConstantPipeStorage %2 3 4",
                              SPV_ENV_UNIVERSAL_1_1),
-              Eq("Expected operand, found end of stream."));
+              Eq("Expected operand for OpConstantPipeStorage instruction, but "
+                 "found the end of the stream."));
   EXPECT_THAT(CompiledInstructions("%1 = OpConstantPipeStorage %2 3 4 5",
                                    SPV_ENV_UNIVERSAL_1_1),
               Eq(MakeInstruction(SpvOpConstantPipeStorage, {1, 2, 3, 4, 5})));
@@ -101,10 +103,12 @@
          "'OpCreatePipeFromPipeStorage'."));
   EXPECT_THAT(
       CompileFailure("%1 = OpCreatePipeFromPipeStorage", SPV_ENV_UNIVERSAL_1_1),
-      Eq("Expected operand, found end of stream."));
+      Eq("Expected operand for OpCreatePipeFromPipeStorage instruction, but "
+         "found the end of the stream."));
   EXPECT_THAT(CompileFailure("%1 = OpCreatePipeFromPipeStorage %2 OpNop",
                              SPV_ENV_UNIVERSAL_1_1),
-              Eq("Expected operand, found next instruction instead."));
+              Eq("Expected operand for OpCreatePipeFromPipeStorage "
+                 "instruction, but found the next instruction instead."));
   EXPECT_THAT(CompiledInstructions("%1 = OpCreatePipeFromPipeStorage %2 %3",
                                    SPV_ENV_UNIVERSAL_1_1),
               Eq(MakeInstruction(SpvOpCreatePipeFromPipeStorage, {1, 2, 3})));
diff --git a/test/text_to_binary.subgroup_dispatch_test.cpp b/test/text_to_binary.subgroup_dispatch_test.cpp
index 967e3c3..8c40445 100644
--- a/test/text_to_binary.subgroup_dispatch_test.cpp
+++ b/test/text_to_binary.subgroup_dispatch_test.cpp
@@ -46,11 +46,13 @@
                  "found 'OpGetKernelLocalSizeForSubgroupCount'."));
   EXPECT_THAT(CompileFailure("%res = OpGetKernelLocalSizeForSubgroupCount",
                              SPV_ENV_UNIVERSAL_1_1),
-              Eq("Expected operand, found end of stream."));
+              Eq("Expected operand for OpGetKernelLocalSizeForSubgroupCount "
+                 "instruction, but found the end of the stream."));
   EXPECT_THAT(
       CompileFailure("%1 = OpGetKernelLocalSizeForSubgroupCount %2 %3 %4 %5 %6",
                      SPV_ENV_UNIVERSAL_1_1),
-      Eq("Expected operand, found end of stream."));
+      Eq("Expected operand for OpGetKernelLocalSizeForSubgroupCount "
+         "instruction, but found the end of the stream."));
   EXPECT_THAT(
       CompiledInstructions("%res = OpGetKernelLocalSizeForSubgroupCount %type "
                            "%sgcount %invoke %param %param_size %param_align",
@@ -93,10 +95,12 @@
          "'OpGetKernelMaxNumSubgroups'."));
   EXPECT_THAT(CompileFailure("%res = OpGetKernelMaxNumSubgroups",
                              SPV_ENV_UNIVERSAL_1_1),
-              Eq("Expected operand, found end of stream."));
+              Eq("Expected operand for OpGetKernelMaxNumSubgroups instruction, "
+                 "but found the end of the stream."));
   EXPECT_THAT(CompileFailure("%1 = OpGetKernelMaxNumSubgroups %2 %3 %4 %5",
                              SPV_ENV_UNIVERSAL_1_1),
-              Eq("Expected operand, found end of stream."));
+              Eq("Expected operand for OpGetKernelMaxNumSubgroups instruction, "
+                 "but found the end of the stream."));
   EXPECT_THAT(
       CompiledInstructions("%res = OpGetKernelMaxNumSubgroups %type "
                            "%invoke %param %param_size %param_align",
diff --git a/test/text_to_binary.type_declaration_test.cpp b/test/text_to_binary.type_declaration_test.cpp
index 1589188..65a2355 100644
--- a/test/text_to_binary.type_declaration_test.cpp
+++ b/test/text_to_binary.type_declaration_test.cpp
@@ -223,12 +223,14 @@
 
 TEST_F(OpTypeForwardPointerTest, MissingType) {
   EXPECT_THAT(CompileFailure("OpTypeForwardPointer"),
-              Eq("Expected operand, found end of stream."));
+              Eq("Expected operand for OpTypeForwardPointer instruction, but "
+                 "found the end of the stream."));
 }
 
 TEST_F(OpTypeForwardPointerTest, MissingClass) {
   EXPECT_THAT(CompileFailure("OpTypeForwardPointer %pt"),
-              Eq("Expected operand, found end of stream."));
+              Eq("Expected operand for OpTypeForwardPointer instruction, but "
+                 "found the end of the stream."));
 }
 
 TEST_F(OpTypeForwardPointerTest, WrongClass) {
@@ -252,7 +254,8 @@
       Eq("Expected <result-id> at the beginning of an instruction, found "
          "'OpSizeOf'."));
   EXPECT_THAT(CompileFailure("%res = OpSizeOf OpNop", SPV_ENV_UNIVERSAL_1_1),
-              Eq("Expected operand, found next instruction instead."));
+              Eq("Expected operand for OpSizeOf instruction, but found the "
+                 "next instruction instead."));
   EXPECT_THAT(
       CompiledInstructions("%1 = OpSizeOf %2 %3", SPV_ENV_UNIVERSAL_1_1),
       Eq(MakeInstruction(SpvOpSizeOf, {1, 2, 3})));
diff --git a/test/text_to_binary_test.cpp b/test/text_to_binary_test.cpp
index 99d9ed6..0b348e8 100644
--- a/test/text_to_binary_test.cpp
+++ b/test/text_to_binary_test.cpp
@@ -65,7 +65,7 @@
         {SPV_OPERAND_TYPE_FP_FAST_MATH_MODE, 1, "NotNaN"},
         {SPV_OPERAND_TYPE_FP_FAST_MATH_MODE, 2, "NotInf"},
         {SPV_OPERAND_TYPE_FP_FAST_MATH_MODE, 3, "NotNaN|NotInf"},
-        // Mask experssions are symmetric.
+        // Mask expressions are symmetric.
         {SPV_OPERAND_TYPE_FP_FAST_MATH_MODE, 3, "NotInf|NotNaN"},
         // Repeating a value has no effect.
         {SPV_OPERAND_TYPE_FP_FAST_MATH_MODE, 3, "NotInf|NotNaN|NotInf"},
diff --git a/test/tools/expect.py b/test/tools/expect.py
index 0b51adc..7351c02 100755
--- a/test/tools/expect.py
+++ b/test/tools/expect.py
@@ -285,6 +285,21 @@
     return True, ''
 
 
+class ValidObjectFile1_6(ReturnCodeIsZero, CorrectObjectFilePreamble):
+  """Mixin class for checking that every input file generates a valid SPIR-V 1.6
+    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), 0x10600)
+      if not success:
+        return False, message
+    return True, ''
+
+
 class ValidObjectFileWithAssemblySubstr(SuccessfulReturn,
                                         CorrectObjectFilePreamble):
   """Mixin class for checking that every input file generates a valid object
diff --git a/test/tools/opt/flags.py b/test/tools/opt/flags.py
index c79f680..52a43c5 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_5):
+class TestAssemblyFileAsOnlyParameter(expect.ValidObjectFile1_6):
   """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_5,
+class TestValidPassFlags(expect.ValidObjectFile1_6,
                          expect.ExecutedListOfPasses):
   """Tests that spirv-opt accepts all valid optimization flags."""
 
@@ -72,7 +72,7 @@
       '--private-to-local', '--reduce-load-size', '--redundancy-elimination',
       '--remove-duplicates', '--replace-invalid-opcode', '--ssa-rewrite',
       '--scalar-replacement', '--scalar-replacement=42', '--strength-reduction',
-      '--strip-debug', '--strip-reflect', '--vector-dce', '--workaround-1209',
+      '--strip-debug', '--strip-nonsemantic', '--vector-dce', '--workaround-1209',
       '--unify-const', '--graphics-robust-access', '--wrap-opkill', '--amd-ext-to-khr'
   ]
   expected_passes = [
@@ -117,7 +117,7 @@
       'scalar-replacement=42',
       'strength-reduction',
       'strip-debug',
-      'strip-reflect',
+      'strip-nonsemantic',
       'vector-dce',
       'workaround-1209',
       'unify-const',
@@ -132,7 +132,7 @@
 
 
 @inside_spirv_testsuite('SpirvOptFlags')
-class TestPerformanceOptimizationPasses(expect.ValidObjectFile1_5,
+class TestPerformanceOptimizationPasses(expect.ValidObjectFile1_6,
                                         expect.ExecutedListOfPasses):
   """Tests that spirv-opt schedules all the passes triggered by -O."""
 
@@ -190,7 +190,7 @@
 
 
 @inside_spirv_testsuite('SpirvOptFlags')
-class TestSizeOptimizationPasses(expect.ValidObjectFile1_5,
+class TestSizeOptimizationPasses(expect.ValidObjectFile1_6,
                                  expect.ExecutedListOfPasses):
   """Tests that spirv-opt schedules all the passes triggered by -Os."""
 
@@ -237,7 +237,7 @@
 
 
 @inside_spirv_testsuite('SpirvOptFlags')
-class TestLegalizationPasses(expect.ValidObjectFile1_5,
+class TestLegalizationPasses(expect.ValidObjectFile1_6,
                              expect.ExecutedListOfPasses):
   """Tests that spirv-opt schedules all the passes triggered by --legalize-hlsl.
   """
diff --git a/test/util/CMakeLists.txt b/test/util/CMakeLists.txt
index 6679dba..20038f7 100644
--- a/test/util/CMakeLists.txt
+++ b/test/util/CMakeLists.txt
@@ -16,6 +16,7 @@
   SRCS ilist_test.cpp
        bit_vector_test.cpp
        bitutils_test.cpp
+       hash_combine_test.cpp
        small_vector_test.cpp
   LIBS SPIRV-Tools-opt
 )
diff --git a/test/util/hash_combine_test.cpp b/test/util/hash_combine_test.cpp
new file mode 100644
index 0000000..9cd1d87
--- /dev/null
+++ b/test/util/hash_combine_test.cpp
@@ -0,0 +1,43 @@
+// Copyright (c) 2022 The Khronos Group 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.
+
+#include <utility>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/util/hash_combine.h"
+
+namespace spvtools {
+namespace utils {
+namespace {
+
+using HashCombineTest = ::testing::Test;
+
+TEST(HashCombineTest, Identity) { EXPECT_EQ(hash_combine(0), 0); }
+
+TEST(HashCombineTest, Variadic) {
+  // Expect manual and variadic template versions be the same.
+  EXPECT_EQ(hash_combine(hash_combine(hash_combine(0, 1), 2), 3),
+            hash_combine(0, 1, 2, 3));
+}
+
+TEST(HashCombineTest, Vector) {
+  // Expect variadic and vector versions be the same.
+  EXPECT_EQ(hash_combine(0, std::vector<uint32_t>({1, 2, 3})),
+            hash_combine(0, 1, 2, 3));
+}
+
+}  // namespace
+}  // namespace utils
+}  // namespace spvtools
diff --git a/test/util/small_vector_test.cpp b/test/util/small_vector_test.cpp
index 01d7df1..b8f068e 100644
--- a/test/util/small_vector_test.cpp
+++ b/test/util/small_vector_test.cpp
@@ -56,6 +56,18 @@
   }
 }
 
+TEST(SmallVectorTest, Initialize_list3) {
+  std::vector<uint32_t> result = {0, 1, 2, 3};
+  SmallVector<uint32_t, 6> vec(result.begin(), result.end());
+
+  EXPECT_FALSE(vec.empty());
+  EXPECT_EQ(vec.size(), 4);
+
+  for (uint32_t i = 0; i < vec.size(); ++i) {
+    EXPECT_EQ(vec[i], result[i]);
+  }
+}
+
 TEST(SmallVectorTest, Initialize_copy1) {
   SmallVector<uint32_t, 6> vec1 = {0, 1, 2, 3};
   SmallVector<uint32_t, 6> vec2(vec1);
@@ -593,6 +605,68 @@
   EXPECT_EQ(vec, result);
 }
 
+TEST(SmallVectorTest, Pop_back) {
+  SmallVector<uint32_t, 8> vec = {0, 1, 2, 10, 10, 10};
+  SmallVector<uint32_t, 8> result = {0, 1, 2};
+
+  EXPECT_EQ(vec.size(), 6);
+  vec.pop_back();
+  vec.pop_back();
+  vec.pop_back();
+  EXPECT_EQ(vec.size(), 3);
+  EXPECT_EQ(vec, result);
+}
+
+TEST(SmallVectorTest, Pop_back_TestDestructor) {
+  // Tracks number of constructions and destructions to ensure they are called.
+  struct TracksDtor {
+    TracksDtor& operator=(TracksDtor&&) = delete;
+    TracksDtor& operator=(const TracksDtor&) = delete;
+
+    TracksDtor(int& num_ctors, int& num_dtors)
+        : num_ctors_(num_ctors), num_dtors_(num_dtors) {
+      num_ctors_++;
+    }
+    TracksDtor(const TracksDtor& that)
+        : TracksDtor(that.num_ctors_, that.num_dtors_) {}
+    TracksDtor(TracksDtor&& that)
+        : TracksDtor(that.num_ctors_, that.num_dtors_) {}
+    ~TracksDtor() { num_dtors_++; }
+
+    int& num_ctors_;
+    int& num_dtors_;
+  };
+
+  constexpr int capacity = 4;
+  SmallVector<TracksDtor, capacity> vec;
+
+  int num_ctors = 0;
+  int num_dtors = 0;
+
+  // Make sure it works when staying within the smallvector capacity
+  for (int i = 0; i < capacity; ++i) {
+    vec.emplace_back(num_ctors, num_dtors);
+  }
+
+  EXPECT_EQ(num_ctors, capacity);
+  while (!vec.empty()) {
+    vec.pop_back();
+  }
+
+  EXPECT_EQ(num_ctors, capacity);
+  EXPECT_EQ(num_dtors, num_ctors);
+
+  // And when larger than builtin capacity
+  for (int i = 0; i < capacity * 2; ++i) {
+    vec.emplace_back(num_ctors, num_dtors);
+  }
+
+  while (!vec.empty()) {
+    vec.pop_back();
+  }
+  EXPECT_EQ(num_dtors, num_ctors);
+}
+
 }  // namespace
 }  // namespace utils
 }  // namespace spvtools
diff --git a/test/val/CMakeLists.txt b/test/val/CMakeLists.txt
index 64eba44..de89b93 100644
--- a/test/val/CMakeLists.txt
+++ b/test/val/CMakeLists.txt
@@ -36,15 +36,16 @@
        val_data_test.cpp
        val_decoration_test.cpp
        val_derivatives_test.cpp
-       val_entry_point.cpp
+       val_entry_point_test.cpp
        val_explicit_reserved_test.cpp
        val_extensions_test.cpp
-       val_extension_spv_khr_expect_assume.cpp
-       val_extension_spv_khr_linkonce_odr.cpp
-       val_extension_spv_khr_subgroup_uniform_control_flow.cpp
-       val_extension_spv_khr_integer_dot_product.cpp
-       val_extension_spv_khr_bit_instructions.cpp
-       val_extension_spv_khr_terminate_invocation.cpp
+       val_extension_spv_khr_expect_assume_test.cpp
+       val_extension_spv_khr_linkonce_odr_test.cpp
+       val_extension_spv_khr_subgroup_uniform_control_flow_test.cpp
+       val_extension_spv_khr_integer_dot_product_test.cpp
+       val_extension_spv_khr_bit_instructions_test.cpp
+       val_extension_spv_khr_terminate_invocation_test.cpp
+       val_extension_spv_khr_subgroup_rotate_test.cpp
        val_ext_inst_test.cpp
        val_ext_inst_debug_test.cpp
        ${VAL_TEST_COMMON_SRCS}
@@ -76,6 +77,7 @@
        val_literals_test.cpp
        val_logicals_test.cpp
        val_memory_test.cpp
+       val_mesh_shading_test.cpp
        val_misc_test.cpp
        val_modes_test.cpp
        val_non_semantic_test.cpp
@@ -87,8 +89,10 @@
   PCH_FILE pch_test_val
 )
 
-add_spvtools_unittest(TARGET val_stuvw
+add_spvtools_unittest(TARGET val_rstuvw
   SRCS
+       val_ray_query_test.cpp
+       val_ray_tracing_test.cpp
        val_small_type_uses_test.cpp
        val_ssa_test.cpp
        val_state_test.cpp
diff --git a/test/val/val_adjacency_test.cpp b/test/val/val_adjacency_test.cpp
index 2959853..4649948 100644
--- a/test/val/val_adjacency_test.cpp
+++ b/test/val/val_adjacency_test.cpp
@@ -54,7 +54,7 @@
   CompileSuccessfully(module);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("ID 1[%bool] has not been defined"));
+              HasSubstr("ID '1[%bool]' has not been defined"));
 }
 
 TEST_F(ValidateAdjacency, OpLoopMergeEndsModuleFail) {
diff --git a/test/val/val_annotation_test.cpp b/test/val/val_annotation_test.cpp
index 889c76c..bb30de0 100644
--- a/test/val/val_annotation_test.cpp
+++ b/test/val/val_annotation_test.cpp
@@ -754,6 +754,8 @@
 
   CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Location-06672"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("decoration must not be applied to this storage class"));
@@ -794,6 +796,8 @@
   CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("VUID-StandaloneSpirv-DescriptorSet-06491"));
+  EXPECT_THAT(getDiagnosticString(),
               HasSubstr("must be in the StorageBuffer, Uniform, or "
                         "UniformConstant storage class"));
 }
diff --git a/test/val/val_arithmetics_test.cpp b/test/val/val_arithmetics_test.cpp
index 856ad02..631375e 100644
--- a/test/val/val_arithmetics_test.cpp
+++ b/test/val/val_arithmetics_test.cpp
@@ -606,8 +606,9 @@
 
   CompileSuccessfully(GenerateCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 6[%float] cannot be a "
-                                               "type"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Operand '6[%float]' cannot be a "
+                        "type"));
 }
 
 TEST_F(ValidateArithmetics, DotNotVectorTypeOperand2) {
@@ -656,7 +657,7 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
-          "Expected operands to have the same number of componenets: Dot"));
+          "Expected operands to have the same number of components: Dot"));
 }
 
 TEST_F(ValidateArithmetics, VectorTimesScalarSuccess) {
diff --git a/test/val/val_atomics_test.cpp b/test/val/val_atomics_test.cpp
index d1a030a..b266ad6 100644
--- a/test/val/val_atomics_test.cpp
+++ b/test/val/val_atomics_test.cpp
@@ -280,7 +280,7 @@
               AnyVUID("VUID-StandaloneSpirv-None-04645"));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("in Vulkan evironment, Workgroup Storage Class is limited to "
+      HasSubstr("in Vulkan environment, Workgroup Storage Class is limited to "
                 "MeshNV, TaskNV, and GLCompute execution model"));
 }
 
@@ -708,7 +708,7 @@
               AnyVUID("VUID-StandaloneSpirv-None-04645"));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("in Vulkan evironment, Workgroup Storage Class is limited to "
+      HasSubstr("in Vulkan environment, Workgroup Storage Class is limited to "
                 "MeshNV, TaskNV, and GLCompute execution model"));
 }
 
@@ -778,8 +778,8 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("AtomicStore: Vulkan spec only allows storage classes for "
-                "atomic to be: Uniform, Workgroup, Image, StorageBuffer, or "
-                "PhysicalStorageBuffer."));
+                "atomic to be: Uniform, Workgroup, Image, StorageBuffer, "
+                "PhysicalStorageBuffer or TaskPayloadWorkgroupEXT."));
 }
 
 TEST_F(ValidateAtomics, AtomicStoreFunctionPointerStorageType) {
@@ -1000,8 +1000,9 @@
 
   CompileSuccessfully(GenerateKernelCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Operand 27[%_ptr_Workgroup_float] cannot be a type"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Operand '27[%_ptr_Workgroup_float]' cannot be a type"));
 }
 
 TEST_F(ValidateAtomics, AtomicLoadWrongPointerDataType) {
@@ -1273,7 +1274,7 @@
   CompileSuccessfully(GenerateKernelCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Operand 33[%_ptr_Workgroup_v4float] cannot be a "
+              HasSubstr("Operand '33[%_ptr_Workgroup_v4float]' cannot be a "
                         "type"));
 }
 
@@ -1400,7 +1401,7 @@
   CompileSuccessfully(GenerateKernelCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Operand 33[%_ptr_Workgroup_v4float] cannot be a "
+              HasSubstr("Operand '33[%_ptr_Workgroup_v4float]' cannot be a "
                         "type"));
 }
 
diff --git a/test/val/val_barriers_test.cpp b/test/val/val_barriers_test.cpp
index 1178ca0..c86cdc1 100644
--- a/test/val/val_barriers_test.cpp
+++ b/test/val/val_barriers_test.cpp
@@ -254,8 +254,9 @@
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_2));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpControlBarrier requires one of the following Execution "
-                "Models: TessellationControl, GLCompute or Kernel"));
+      HasSubstr("OpControlBarrier requires one of the following "
+                "Execution Models: TessellationControl, GLCompute, Kernel, "
+                "MeshNV or TaskNV"));
 }
 
 TEST_F(ValidateBarriers, OpControlBarrierExecutionModelFragmentSpirv13) {
@@ -360,11 +361,26 @@
   CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
-              AnyVUID("VUID-StandaloneSpirv-None-04638"));
+              AnyVUID("VUID-StandaloneSpirv-SubgroupVoteKHR-06997"));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("ControlBarrier: in Vulkan 1.0 environment Memory Scope is "
-                "limited to Device, Workgroup and Invocation"));
+      HasSubstr(
+          "ControlBarrier: in Vulkan 1.0 environment Memory Scope is can not "
+          "be Subgroup without SubgroupBallotKHR or SubgroupVoteKHR declared"));
+}
+
+TEST_F(ValidateBarriers, OpControlBarrierVulkanMemoryScopeSubgroupVoteKHR) {
+  const std::string capabilities = R"(
+OpCapability SubgroupVoteKHR
+OpExtension "SPV_KHR_subgroup_vote"
+)";
+  const std::string body = R"(
+OpControlBarrier %subgroup %subgroup %none
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, capabilities),
+                      SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
 }
 
 TEST_F(ValidateBarriers, OpControlBarrierVulkan1p1MemoryScopeSubgroup) {
@@ -386,8 +402,9 @@
   EXPECT_THAT(getDiagnosticString(),
               AnyVUID("VUID-StandaloneSpirv-None-04638"));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("ControlBarrier: in Vulkan environment, Memory Scope "
-                        "cannot be CrossDevice"));
+              HasSubstr("ControlBarrier: in Vulkan environment Memory Scope is "
+                        "limited to Device, QueueFamily, Workgroup, "
+                        "ShaderCallKHR, Subgroup, or Invocation"));
 }
 
 TEST_F(ValidateBarriers,
@@ -399,10 +416,12 @@
   CompileSuccessfully(GenerateVulkanVertexShaderCode(body), SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
   EXPECT_THAT(getDiagnosticString(),
-              AnyVUID("VUID-StandaloneSpirv-None-04639"));
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Workgroup Memory Scope is limited to MeshNV, TaskNV, "
-                        "and GLCompute execution model"));
+              AnyVUID("VUID-StandaloneSpirv-None-07321"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Workgroup Memory Scope is limited to MeshNV, "
+                "TaskNV, MeshEXT, TaskEXT, TessellationControl, and GLCompute "
+                "execution model"));
 }
 
 TEST_F(ValidateBarriers,
@@ -417,8 +436,8 @@
               AnyVUID("VUID-StandaloneSpirv-None-04637"));
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("in Vulkan environment, Workgroup execution scope is "
-                        "only for TaskNV, MeshNV, TessellationControl, and "
-                        "GLCompute execution models"));
+                        "only for TaskNV, MeshNV, TaskEXT, MeshEXT, "
+                        "TessellationControl, and GLCompute execution models"));
 }
 
 TEST_F(ValidateBarriers, OpControlBarrierVulkan1p1WorkgroupComputeSuccess) {
@@ -528,10 +547,11 @@
   CompileSuccessfully(GenerateShaderCode(body, "", "Fragment"),
                       SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("OpControlBarrier requires one of the following Execution "
-                "Models: TessellationControl, GLCompute or Kernel"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpControlBarrier requires one of the following "
+                        "Execution "
+                        "Models: TessellationControl, GLCompute, Kernel, "
+                        "MeshNV or TaskNV"));
 }
 
 TEST_F(ValidateBarriers, OpControlBarrierSubgroupExecutionVertex1p1) {
@@ -572,8 +592,9 @@
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpControlBarrier requires one of the following Execution "
-                "Models: TessellationControl, GLCompute or Kernel"));
+      HasSubstr("OpControlBarrier requires one of the following "
+                "Execution Models: TessellationControl, GLCompute, Kernel, "
+                "MeshNV or TaskNV"));
 }
 
 TEST_F(ValidateBarriers, OpControlBarrierSubgroupExecutionGeometry1p1) {
@@ -615,10 +636,11 @@
       GenerateShaderCode(body, "OpCapability Geometry\n", "Geometry"),
       SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("OpControlBarrier requires one of the following Execution "
-                "Models: TessellationControl, GLCompute or Kernel"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpControlBarrier requires one of the following "
+                        "Execution "
+                        "Models: TessellationControl, GLCompute, Kernel, "
+                        "MeshNV or TaskNV"));
 }
 
 TEST_F(ValidateBarriers,
@@ -663,10 +685,11 @@
                                          "TessellationEvaluation"),
                       SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("OpControlBarrier requires one of the following Execution "
-                "Models: TessellationControl, GLCompute or Kernel"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpControlBarrier requires one of the following "
+                        "Execution "
+                        "Models: TessellationControl, GLCompute, Kernel, "
+                        "MeshNV or TaskNV"));
 }
 
 TEST_F(ValidateBarriers, OpMemoryBarrierSuccess) {
@@ -752,11 +775,12 @@
   CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
-              AnyVUID("VUID-StandaloneSpirv-None-04638"));
+              AnyVUID("VUID-StandaloneSpirv-SubgroupVoteKHR-06997"));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("MemoryBarrier: in Vulkan 1.0 environment Memory Scope is "
-                "limited to Device, Workgroup and Invocation"));
+      HasSubstr(
+          "MemoryBarrier: in Vulkan 1.0 environment Memory Scope is can not be "
+          "Subgroup without SubgroupBallotKHR or SubgroupVoteKHR declared"));
 }
 
 TEST_F(ValidateBarriers, OpMemoryBarrierVulkan1p1MemoryScopeSubgroup) {
@@ -947,7 +971,7 @@
 
   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 "
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand '5[%uint]' cannot be a "
                                                "type"));
 }
 
diff --git a/test/val/val_bitwise_test.cpp b/test/val/val_bitwise_test.cpp
index 1001def..bebaa84 100644
--- a/test/val/val_bitwise_test.cpp
+++ b/test/val/val_bitwise_test.cpp
@@ -340,6 +340,16 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateBitwise, OpBitFieldInsertVulkanSuccess) {
+  const std::string body = R"(
+%val1 = OpBitFieldInsert %u32 %u32_1 %u32_2 %s32_1 %s32_2
+%val2 = OpBitFieldInsert %s32vec2 %s32vec2_12 %s32vec2_12 %s32_1 %u32_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str(), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
 TEST_F(ValidateBitwise, OpBitFieldInsertWrongResultType) {
   const std::string body = R"(
 %val1 = OpBitFieldInsert %bool %u64_1 %u64_2 %s32_1 %s32_2
@@ -350,7 +360,7 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
-          "Expected int scalar or vector type as Result Type: BitFieldInsert"));
+          "Expected Base Type to be equal to Result Type: BitFieldInsert"));
 }
 
 TEST_F(ValidateBitwise, OpBitFieldInsertWrongBaseType) {
@@ -403,6 +413,20 @@
       HasSubstr("Expected Count Type to be int scalar: BitFieldInsert"));
 }
 
+TEST_F(ValidateBitwise, OpBitFieldInsertNot32Vulkan) {
+  const std::string body = R"(
+%val1 = OpBitFieldInsert %u64 %u64_1 %u64_2 %s32_1 %s32_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str(), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Base-04781"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Expected 32-bit int type for Base operand: BitFieldInsert"));
+}
+
 TEST_F(ValidateBitwise, OpBitFieldSExtractSuccess) {
   const std::string body = R"(
 %val1 = OpBitFieldSExtract %u64 %u64_1 %s32_1 %s32_2
@@ -413,6 +437,16 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateBitwise, OpBitFieldSExtractVulkanSuccess) {
+  const std::string body = R"(
+%val1 = OpBitFieldSExtract %u32 %u32_1 %s32_1 %s32_2
+%val2 = OpBitFieldSExtract %s32vec2 %s32vec2_12 %s32_1 %u32_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str(), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
 TEST_F(ValidateBitwise, OpBitFieldSExtractWrongResultType) {
   const std::string body = R"(
 %val1 = OpBitFieldSExtract %bool %u64_1 %s32_1 %s32_2
@@ -420,9 +454,10 @@
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Expected int scalar or vector type as Result Type: "
-                        "BitFieldSExtract"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Expected Base Type to be equal to Result Type: BitFieldSExtract"));
 }
 
 TEST_F(ValidateBitwise, OpBitFieldSExtractWrongBaseType) {
@@ -462,6 +497,20 @@
       HasSubstr("Expected Count Type to be int scalar: BitFieldSExtract"));
 }
 
+TEST_F(ValidateBitwise, OpBitFieldSExtractNot32Vulkan) {
+  const std::string body = R"(
+%val1 = OpBitFieldSExtract %u64 %u64_1 %s32_1 %s32_2
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str(), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Base-04781"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Expected 32-bit int type for Base operand: BitFieldSExtract"));
+}
+
 TEST_F(ValidateBitwise, OpBitReverseSuccess) {
   const std::string body = R"(
 %val1 = OpBitReverse %u64 %u64_1
@@ -472,6 +521,16 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateBitwise, OpBitReverseVulkanSuccess) {
+  const std::string body = R"(
+%val1 = OpBitReverse %u32 %u32_1
+%val2 = OpBitReverse %s32vec2 %s32vec2_12
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str(), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
 TEST_F(ValidateBitwise, OpBitReverseWrongResultType) {
   const std::string body = R"(
 %val1 = OpBitReverse %bool %u64_1
@@ -481,8 +540,7 @@
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr(
-          "Expected int scalar or vector type as Result Type: BitReverse"));
+      HasSubstr("Expected Base Type to be equal to Result Type: BitReverse"));
 }
 
 TEST_F(ValidateBitwise, OpBitReverseWrongBaseType) {
@@ -497,16 +555,41 @@
       HasSubstr("Expected Base Type to be equal to Result Type: BitReverse"));
 }
 
+TEST_F(ValidateBitwise, OpBitReverseNot32Vulkan) {
+  const std::string body = R"(
+%val1 = OpBitReverse %u64 %u64_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str(), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Base-04781"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Expected 32-bit int type for Base operand: BitReverse"));
+}
+
 TEST_F(ValidateBitwise, OpBitCountSuccess) {
   const std::string body = R"(
 %val1 = OpBitCount %s32 %u64_1
 %val2 = OpBitCount %u32vec2 %s32vec2_12
+%val3 = OpBitCount %s64 %s64_1
 )";
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateBitwise, OpBitCountVulkanSuccess) {
+  const std::string body = R"(
+%val1 = OpBitCount %s32 %u32_1
+%val2 = OpBitCount %u32vec2 %s32vec2_12
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str(), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
 TEST_F(ValidateBitwise, OpBitCountWrongResultType) {
   const std::string body = R"(
 %val1 = OpBitCount %bool %u64_1
@@ -524,11 +607,14 @@
 %val1 = OpBitCount %u32 %f64_1
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body).c_str());
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  CompileSuccessfully(GenerateShaderCode(body).c_str(), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Base-04781"));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Expected Base Type to be int scalar or vector: BitCount"));
+      HasSubstr(
+          "Expected int scalar or vector type for Base operand: BitCount"));
 }
 
 TEST_F(ValidateBitwise, OpBitCountBaseWrongDimension) {
@@ -544,6 +630,19 @@
                 "BitCount"));
 }
 
+TEST_F(ValidateBitwise, OpBitCountNot32Vulkan) {
+  const std::string body = R"(
+%val1 = OpBitCount %s64 %s64_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str(), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Base-04781"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected 32-bit int type for Base operand: BitCount"));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_builtins_test.cpp b/test/val/val_builtins_test.cpp
index dff9adf..4f9fc97 100644
--- a/test/val/val_builtins_test.cpp
+++ b/test/val/val_builtins_test.cpp
@@ -60,8 +60,9 @@
 using ValidateVulkanCombineBuiltInExecutionModelDataTypeResult =
     spvtest::ValidateBase<std::tuple<const char*, 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 ValidateVulkanCombineBuiltInArrayedVariable =
+    spvtest::ValidateBase<std::tuple<const char*, const char*, const char*,
+                                     const char*, const char*, TestResult>>;
 using ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult =
     spvtest::ValidateBase<
         std::tuple<const char*, const char*, const char*, const char*,
@@ -93,7 +94,8 @@
     generator.extensions_ += extensions;
   }
 
-  generator.before_types_ = "OpMemberDecorate %built_in_type 0 BuiltIn ";
+  generator.before_types_ = R"(OpDecorate %built_in_type Block
+                               OpMemberDecorate %built_in_type 0 BuiltIn )";
   generator.before_types_ += built_in;
   generator.before_types_ += "\n";
 
@@ -251,7 +253,8 @@
     generator.extensions_ += extensions;
   }
 
-  generator.before_types_ = "OpMemberDecorate %built_in_type 0 BuiltIn ";
+  generator.before_types_ = R"(OpDecorate %built_in_type Block
+                              OpMemberDecorate %built_in_type 0 BuiltIn )";
   generator.before_types_ += built_in;
   generator.before_types_ += "\n";
 
@@ -392,6 +395,11 @@
   generator.before_types_ = "OpDecorate %built_in_var BuiltIn ";
   generator.before_types_ += built_in;
   generator.before_types_ += "\n";
+  if ((0 == std::strcmp(storage_class, "Input")) &&
+      (0 == std::strcmp(execution_model, "Fragment"))) {
+    // ensure any needed input types that might require Flat
+    generator.before_types_ += "OpDecorate %built_in_var Flat\n";
+  }
 
   std::ostringstream after_types;
   if (InitializerRequired(storage_class)) {
@@ -777,8 +785,8 @@
                    "VUID-NumWorkgroups-NumWorkgroups-04296 "
                    "VUID-WorkgroupId-WorkgroupId-04422"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
-                              "to be used only with GLCompute, MeshNV, or "
-                              "TaskNV execution model"))));
+                              "to be used only with GLCompute, MeshNV, "
+                              "TaskNV, MeshEXT or TaskEXT execution model"))));
 
 INSTANTIATE_TEST_SUITE_P(
     ComputeShaderInputInt32Vec3NotInput,
@@ -999,7 +1007,7 @@
         Values("VUID-Layer-Layer-04274 VUID-ViewportIndex-ViewportIndex-04406"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
                           "Input storage class if execution model is Vertex, "
-                          "TessellationEvaluation, Geometry, or MeshNV",
+                          "TessellationEvaluation, Geometry, MeshNV or MeshEXT",
                           "which is called with execution model"))));
 
 INSTANTIATE_TEST_SUITE_P(
@@ -1304,14 +1312,14 @@
 INSTANTIATE_TEST_SUITE_P(
     PrimitiveIdInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
-    Combine(Values("PrimitiveId"), Values("Vertex", "GLCompute"),
-            Values("Input"), Values("%u32"),
-            Values("VUID-PrimitiveId-PrimitiveId-04330"),
-            Values(TestResult(
-                SPV_ERROR_INVALID_DATA,
-                "to be used only with Fragment, TessellationControl, "
-                "TessellationEvaluation, Geometry, MeshNV, IntersectionKHR, "
-                "AnyHitKHR, and ClosestHitKHR execution models"))));
+    Combine(
+        Values("PrimitiveId"), Values("Vertex", "GLCompute"), Values("Input"),
+        Values("%u32"), Values("VUID-PrimitiveId-PrimitiveId-04330"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "to be used only with Fragment, TessellationControl, "
+                          "TessellationEvaluation, Geometry, MeshNV, MeshEXT, "
+                          "IntersectionKHR, "
+                          "AnyHitKHR, and ClosestHitKHR execution models"))));
 
 INSTANTIATE_TEST_SUITE_P(
     PrimitiveIdFragmentNotInput,
@@ -1860,16 +1868,18 @@
 INSTANTIATE_TEST_SUITE_P(
     DrawIndexInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
-    Combine(Values("DrawIndex"),
-            Values("Fragment", "GLCompute", "Geometry", "TessellationControl",
-                   "TessellationEvaluation"),
-            Values("Input"), Values("%u32"),
-            Values("OpCapability DrawParameters\n"),
-            Values("OpExtension \"SPV_KHR_shader_draw_parameters\"\n"),
-            Values("VUID-DrawIndex-DrawIndex-04207"),
-            Values(TestResult(SPV_ERROR_INVALID_DATA,
-                              "to be used only with Vertex, MeshNV, or TaskNV "
-                              "execution model"))));
+    Combine(
+        Values("DrawIndex"),
+        Values("Fragment", "GLCompute", "Geometry", "TessellationControl",
+               "TessellationEvaluation"),
+        Values("Input"), Values("%u32"),
+        Values("OpCapability DrawParameters\n"),
+        Values("OpExtension \"SPV_KHR_shader_draw_parameters\"\n"),
+        Values("VUID-DrawIndex-DrawIndex-04207"),
+        Values(TestResult(
+            SPV_ERROR_INVALID_DATA,
+            "to be used only with Vertex, MeshNV, TaskNV , MeshEXT or TaskEXT "
+            "execution model"))));
 
 INSTANTIATE_TEST_SUITE_P(
     DrawIndexNotInput,
@@ -2157,17 +2167,17 @@
 INSTANTIATE_TEST_SUITE_P(
     PrimitiveIdRTNotExecutionMode,
     ValidateGenericCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
-    Combine(Values(SPV_ENV_VULKAN_1_2), Values("PrimitiveId"),
-            Values("RayGenerationKHR", "MissKHR", "CallableKHR"),
-            Values("Input"), Values("%u32"),
-            Values("OpCapability RayTracingKHR\n"),
-            Values("OpExtension \"SPV_KHR_ray_tracing\"\n"),
-            Values("VUID-PrimitiveId-PrimitiveId-04330"),
-            Values(TestResult(
-                SPV_ERROR_INVALID_DATA,
-                "to be used only with Fragment, TessellationControl, "
-                "TessellationEvaluation, Geometry, MeshNV, IntersectionKHR, "
-                "AnyHitKHR, and ClosestHitKHR execution models"))));
+    Combine(
+        Values(SPV_ENV_VULKAN_1_2), Values("PrimitiveId"),
+        Values("RayGenerationKHR", "MissKHR", "CallableKHR"), Values("Input"),
+        Values("%u32"), Values("OpCapability RayTracingKHR\n"),
+        Values("OpExtension \"SPV_KHR_ray_tracing\"\n"),
+        Values("VUID-PrimitiveId-PrimitiveId-04330"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "to be used only with Fragment, TessellationControl, "
+                          "TessellationEvaluation, Geometry, MeshNV, MeshEXT, "
+                          "IntersectionKHR, "
+                          "AnyHitKHR, and ClosestHitKHR execution models"))));
 
 INSTANTIATE_TEST_SUITE_P(
     PrimitiveIdRTNotInput,
@@ -2370,6 +2380,67 @@
                               "needs to be a 32-bit int scalar",
                               "is not an int scalar"))));
 
+// CullMaskKHR is valid
+// in IS, AH, CH, MS shaders as an input i32 scalar
+INSTANTIATE_TEST_SUITE_P(
+    CullMaskSuccess,
+    ValidateGenericCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values(SPV_ENV_VULKAN_1_2), Values("CullMaskKHR"),
+            Values("AnyHitKHR", "ClosestHitKHR", "IntersectionKHR", "MissKHR"),
+            Values("Input"), Values("%u32"),
+            Values("OpCapability RayTracingKHR\nOpCapability RayCullMaskKHR\n"),
+            Values("OpExtension \"SPV_KHR_ray_tracing\"\nOpExtension "
+                   "\"SPV_KHR_ray_cull_mask\"\n"),
+            Values(nullptr), Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    CullMaskNotExecutionMode,
+    ValidateGenericCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values(SPV_ENV_VULKAN_1_2), Values("CullMaskKHR"),
+            Values("Vertex", "Fragment", "TessellationControl",
+                   "TessellationEvaluation", "Geometry", "Fragment",
+                   "GLCompute", "RayGenerationKHR", "CallableKHR"),
+            Values("Input"), Values("%u32"),
+            Values("OpCapability RayTracingKHR\nOpCapability RayCullMaskKHR\n"),
+            Values("OpExtension \"SPV_KHR_ray_tracing\"\nOpExtension "
+                   "\"SPV_KHR_ray_cull_mask\"\n"),
+            Values("VUID-CullMaskKHR-CullMaskKHR-06735 "
+                   "VUID-RayTmaxKHR-RayTmaxKHR-04348 "
+                   "VUID-RayTminKHR-RayTminKHR-04351 "),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "Vulkan spec does not allow BuiltIn",
+                              "to be used with the execution model"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    ICullMaskNotInput,
+    ValidateGenericCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values(SPV_ENV_VULKAN_1_2), Values("CullMaskKHR"),
+            Values("AnyHitKHR", "ClosestHitKHR", "IntersectionKHR", "MissKHR"),
+            Values("Output"), Values("%u32"),
+            Values("OpCapability RayTracingKHR\nOpCapability RayCullMaskKHR\n"),
+            Values("OpExtension \"SPV_KHR_ray_tracing\"\nOpExtension "
+                   "\"SPV_KHR_ray_cull_mask\"\n"),
+            Values("VUID-CullMaskKHR-CullMaskKHR-06736 "
+                   "VUID-RayTmaxKHR-RayTmaxKHR-04349 "
+                   "VUID-RayTminKHR-RayTminKHR-04352 "),
+            Values(TestResult(SPV_ERROR_INVALID_DATA, "Vulkan spec allows",
+                              "used for variables with Input storage class"))));
+INSTANTIATE_TEST_SUITE_P(
+    CullMaskNotIntScalar,
+    ValidateGenericCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values(SPV_ENV_VULKAN_1_2), Values("CullMaskKHR"),
+            Values("AnyHitKHR", "ClosestHitKHR", "IntersectionKHR", "MissKHR"),
+            Values("Input"), Values("%f32", "%u32vec3"),
+            Values("OpCapability RayTracingKHR\nOpCapability RayCullMaskKHR\n"),
+            Values("OpExtension \"SPV_KHR_ray_tracing\"\nOpExtension "
+                   "\"SPV_KHR_ray_cull_mask\"\n"),
+            Values("VUID-CullMaskKHR-CullMaskKHR-06737 "
+                   "VUID-RayTmaxKHR-RayTmaxKHR-04350 "
+                   "VUID-RayTminKHR-RayTminKHR-04353 "),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 32-bit int scalar",
+                              "is not an int scalar"))));
+
 // RayTmaxKHR, RayTminKHR are all valid
 // in IS, AH, CH, MS shaders as input f32 scalars
 INSTANTIATE_TEST_SUITE_P(
@@ -2608,7 +2679,8 @@
   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());
+  const char* const vuid = std::get<4>(GetParam());
+  const TestResult& test_result = std::get<5>(GetParam());
 
   CodeGenerator generator = GetArrayedVariableCodeGenerator(
       built_in, execution_model, storage_class, data_type);
@@ -2622,18 +2694,20 @@
   if (test_result.error_str2) {
     EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2));
   }
+  if (vuid) {
+    EXPECT_THAT(getDiagnosticString(), AnyVUID(vuid));
+  }
 }
 
-INSTANTIATE_TEST_SUITE_P(PointSizeArrayedF32TessControl,
-                         ValidateVulkanCombineBuiltInArrayedVariable,
-                         Combine(Values("PointSize"),
-                                 Values("TessellationControl"), Values("Input"),
-                                 Values("%f32"), Values(TestResult())));
+INSTANTIATE_TEST_SUITE_P(
+    PointSizeArrayedF32TessControl, ValidateVulkanCombineBuiltInArrayedVariable,
+    Combine(Values("PointSize"), Values("TessellationControl"), Values("Input"),
+            Values("%f32"), Values(nullptr), Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     PointSizeArrayedF64TessControl, ValidateVulkanCombineBuiltInArrayedVariable,
     Combine(Values("PointSize"), Values("TessellationControl"), Values("Input"),
-            Values("%f64"),
+            Values("%f64"), Values("VUID-PointSize-PointSize-04317"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float scalar",
                               "has bit width 64"))));
@@ -2641,7 +2715,7 @@
 INSTANTIATE_TEST_SUITE_P(
     PointSizeArrayedF32Vertex, ValidateVulkanCombineBuiltInArrayedVariable,
     Combine(Values("PointSize"), Values("Vertex"), Values("Output"),
-            Values("%f32"),
+            Values("%f32"), Values("VUID-PointSize-PointSize-04317"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float scalar",
                               "is not a float scalar"))));
@@ -2650,13 +2724,14 @@
                          ValidateVulkanCombineBuiltInArrayedVariable,
                          Combine(Values("Position"),
                                  Values("TessellationControl"), Values("Input"),
-                                 Values("%f32vec4"), Values(TestResult())));
+                                 Values("%f32vec4"), Values(nullptr),
+                                 Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     PositionArrayedF32Vec3TessControl,
     ValidateVulkanCombineBuiltInArrayedVariable,
     Combine(Values("Position"), Values("TessellationControl"), Values("Input"),
-            Values("%f32vec3"),
+            Values("%f32vec3"), Values("VUID-Position-Position-04321"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float vector",
                               "has 3 components"))));
@@ -2664,7 +2739,7 @@
 INSTANTIATE_TEST_SUITE_P(
     PositionArrayedF32Vec4Vertex, ValidateVulkanCombineBuiltInArrayedVariable,
     Combine(Values("Position"), Values("Vertex"), Values("Output"),
-            Values("%f32vec4"),
+            Values("%f32vec4"), Values("VUID-Position-Position-04321"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float vector",
                               "is not a float vector"))));
@@ -2674,13 +2749,15 @@
     ValidateVulkanCombineBuiltInArrayedVariable,
     Combine(Values("ClipDistance", "CullDistance"),
             Values("Geometry", "TessellationControl", "TessellationEvaluation"),
-            Values("Output"), Values("%f32arr2", "%f32arr4"),
+            Values("Output"), Values("%f32arr2", "%f32arr4"), Values(nullptr),
             Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     ClipAndCullDistanceVertexInput, ValidateVulkanCombineBuiltInArrayedVariable,
     Combine(Values("ClipDistance", "CullDistance"), Values("Fragment"),
             Values("Input"), Values("%f32arr4"),
+            Values("VUID-ClipDistance-ClipDistance-04191 "
+                   "VUID-CullDistance-CullDistance-04200"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float array",
                               "components are not float scalar"))));
@@ -2690,6 +2767,8 @@
     Combine(Values("ClipDistance", "CullDistance"),
             Values("Geometry", "TessellationControl", "TessellationEvaluation"),
             Values("Input"), Values("%f32vec2", "%f32vec4"),
+            Values("VUID-ClipDistance-ClipDistance-04191 "
+                   "VUID-CullDistance-CullDistance-04200"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float array",
                               "components are not float scalar"))));
@@ -2772,6 +2851,61 @@
                               "needs to be a 32-bit int scalar",
                               "has bit width 64"))));
 
+INSTANTIATE_TEST_SUITE_P(
+    ArmCoreBuiltinsInputSuccess,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("CoreIDARM", "CoreCountARM", "CoreMaxIDARM", "WarpIDARM",
+                   "WarpMaxIDARM"),
+            Values("Vertex", "Fragment", "TessellationControl",
+                   "TessellationEvaluation", "Geometry", "GLCompute"),
+            Values("Input"), Values("%u32"),
+            Values("OpCapability CoreBuiltinsARM\n"),
+            Values("OpExtension \"SPV_ARM_core_builtins\"\n"), Values(nullptr),
+            Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    ArmCoreBuiltinsNotInput,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("CoreIDARM", "CoreCountARM", "CoreMaxIDARM", "WarpIDARM",
+                   "WarpMaxIDARM"),
+            Values("Vertex", "Fragment", "TessellationControl",
+                   "TessellationEvaluation", "Geometry", "GLCompute"),
+            Values("Output"), Values("%u32"),
+            Values("OpCapability CoreBuiltinsARM\n"),
+            Values("OpExtension \"SPV_ARM_core_builtins\"\n"), Values(nullptr),
+            Values(TestResult(
+                SPV_ERROR_INVALID_DATA,
+                "to be only used for variables with Input storage class",
+                "uses storage class Output"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    ArmCoreBuiltinsNotIntScalar,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("CoreIDARM", "CoreCountARM", "CoreMaxIDARM", "WarpIDARM",
+                   "WarpMaxIDARM"),
+            Values("Vertex", "Fragment", "TessellationControl",
+                   "TessellationEvaluation", "Geometry", "GLCompute"),
+            Values("Input"), Values("%f32", "%u32vec3"),
+            Values("OpCapability CoreBuiltinsARM\n"),
+            Values("OpExtension \"SPV_ARM_core_builtins\"\n"), Values(nullptr),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 32-bit int scalar",
+                              "is not an int scalar"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    ArmCoreBuiltinsNotInt32,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("CoreIDARM", "CoreCountARM", "CoreMaxIDARM", "WarpIDARM",
+                   "WarpMaxIDARM"),
+            Values("Vertex", "Fragment", "TessellationControl",
+                   "TessellationEvaluation", "Geometry", "GLCompute"),
+            Values("Input"), Values("%u64"),
+            Values("OpCapability CoreBuiltinsARM\n"),
+            Values("OpExtension \"SPV_ARM_core_builtins\"\n"), Values(nullptr),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 32-bit int scalar",
+                              "has bit width 64"))));
+
 CodeGenerator GetWorkgroupSizeSuccessGenerator() {
   CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
 
@@ -2828,10 +2962,10 @@
 
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("Vulkan spec allows BuiltIn WorkgroupSize to be used "
-                "only with GLCompute, MeshNV, or TaskNV execution model"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Vulkan spec allows BuiltIn WorkgroupSize to be used "
+                        "only with GLCompute, MeshNV, TaskNV, MeshEXT or "
+                        "TaskEXT execution model"));
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("is referencing ID <2> (OpConstantComposite) which is "
                         "decorated with BuiltIn WorkgroupSize in function <1> "
@@ -3098,6 +3232,8 @@
   CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
 
   generator.before_types_ = R"(
+OpDecorate %input_type Block
+OpDecorate %output_type Block
 OpMemberDecorate %input_type 0 BuiltIn FragCoord
 OpMemberDecorate %output_type 0 BuiltIn Position
 )";
@@ -3138,6 +3274,8 @@
   CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
 
   generator.before_types_ = R"(
+OpDecorate %input_type Block
+OpDecorate %output_type Block
 OpMemberDecorate %input_type 0 BuiltIn Position
 OpMemberDecorate %output_type 0 BuiltIn FragCoord
 )";
@@ -3201,6 +3339,7 @@
 TEST_F(ValidateBuiltIns, FragmentPositionTwoEntryPoints) {
   CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
   generator.before_types_ = R"(
+OpDecorate %output_type Block
 OpMemberDecorate %output_type 0 BuiltIn Position
 )";
 
@@ -3252,6 +3391,7 @@
   CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
 
   generator.before_types_ = R"(
+OpDecorate %output_type Block
 OpMemberDecorate %output_type 0 BuiltIn FragDepth
 )";
 
@@ -3282,7 +3422,7 @@
 OpFunctionEnd
 )";
 
-    generator.add_at_the_end_ = function_body;
+  generator.add_at_the_end_ = function_body;
 
   return generator;
 }
@@ -3303,6 +3443,7 @@
   CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
 
   generator.before_types_ = R"(
+OpDecorate %output_type Block
 OpMemberDecorate %output_type 0 BuiltIn FragDepth
 )";
 
@@ -3344,7 +3485,7 @@
 OpFunctionEnd
 )";
 
-    generator.add_at_the_end_ = function_body;
+  generator.add_at_the_end_ = function_body;
 
   return generator;
 }
@@ -3362,7 +3503,6 @@
               HasSubstr("VUID-FragDepth-FragDepth-04216"));
 }
 
-
 TEST_F(ValidateBuiltIns, AllowInstanceIdWithIntersectionShader) {
   CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
   generator.capabilities_ += R"(
@@ -3374,6 +3514,7 @@
 )";
 
   generator.before_types_ = R"(
+OpDecorate %input_type Block
 OpMemberDecorate %input_type 0 BuiltIn InstanceId
 )";
 
@@ -3609,6 +3750,7 @@
 OpMemoryModel Logical GLSL450
 OpEntryPoint GLCompute %foo "foo"
 OpExecutionMode %foo LocalSize 1 1 1
+OpDecorate %struct Block
 OpMemberDecorate %struct 0 BuiltIn SubgroupEqMask
 %void = OpTypeVoid
 %int = OpTypeInt 32 0
@@ -3663,6 +3805,7 @@
 OpMemoryModel Logical GLSL450
 OpEntryPoint GLCompute %foo "foo"
 OpExecutionMode %foo LocalSize 1 1 1
+OpDecorate %struct Block
 OpMemberDecorate %struct 0 BuiltIn SubgroupSize
 %void = OpTypeVoid
 %int = OpTypeInt 32 0
@@ -3688,8 +3831,8 @@
             Values("VUID-SubgroupId-SubgroupId-04367 "
                    "VUID-NumSubgroups-NumSubgroups-04293"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
-                              "to be used only with GLCompute, MeshNV, or "
-                              "TaskNV execution model"))));
+                              "to be used only with GLCompute, MeshNV, "
+                              "TaskNV, MeshEXT or TaskEXT execution model"))));
 
 INSTANTIATE_TEST_SUITE_P(
     SubgroupNumAndIdNotU32, ValidateVulkanSubgroupBuiltIns,
@@ -3723,6 +3866,7 @@
 OpMemoryModel Logical GLSL450
 OpEntryPoint GLCompute %foo "foo"
 OpExecutionMode %foo LocalSize 1 1 1
+OpDecorate %struct Block
 OpMemberDecorate %struct 0 BuiltIn SubgroupId
 %void = OpTypeVoid
 %int = OpTypeInt 32 0
@@ -4052,6 +4196,71 @@
                 "According to the Vulkan spec BuiltIn FullyCoveredEXT variable "
                 "needs to be a bool scalar."))));
 
+INSTANTIATE_TEST_SUITE_P(
+    BaryCoordNotFragment,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(
+        Values("BaryCoordKHR", "BaryCoordNoPerspKHR"), Values("Vertex"),
+        Values("Input"), Values("%f32vec3"),
+        Values("OpCapability FragmentBarycentricKHR\n"),
+        Values("OpExtension \"SPV_KHR_fragment_shader_barycentric\"\n"),
+        Values("VUID-BaryCoordKHR-BaryCoordKHR-04154 "
+               "VUID-BaryCoordNoPerspKHR-BaryCoordNoPerspKHR-04160 "),
+        Values(TestResult(SPV_ERROR_INVALID_DATA, "Vulkan spec allows BuiltIn",
+                          "to be used only with Fragment execution model"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    BaryCoordNotInput,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("BaryCoordKHR", "BaryCoordNoPerspKHR"), Values("Fragment"),
+            Values("Output"), Values("%f32vec3"),
+            Values("OpCapability FragmentBarycentricKHR\n"),
+            Values("OpExtension \"SPV_KHR_fragment_shader_barycentric\"\n"),
+            Values("VUID-BaryCoordKHR-BaryCoordKHR-04155 "
+                   "VUID-BaryCoordNoPerspKHR-BaryCoordNoPerspKHR-04161 "),
+            Values(TestResult(
+                SPV_ERROR_INVALID_DATA, "Vulkan spec allows BuiltIn",
+                "to be only used for variables with Input storage class"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    BaryCoordNotFloatVector,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(
+        Values("BaryCoordKHR", "BaryCoordNoPerspKHR"), Values("Fragment"),
+        Values("Output"), Values("%f32arr3", "%u32vec4"),
+        Values("OpCapability FragmentBarycentricKHR\n"),
+        Values("OpExtension \"SPV_KHR_fragment_shader_barycentric\"\n"),
+        Values("VUID-BaryCoordKHR-BaryCoordKHR-04156 "
+               "VUID-BaryCoordNoPerspKHR-BaryCoordNoPerspKHR-04162 "),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "needs to be a 3-component 32-bit float vector"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    BaryCoordNotFloatVec3,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(
+        Values("BaryCoordKHR", "BaryCoordNoPerspKHR"), Values("Fragment"),
+        Values("Output"), Values("%f32vec2"),
+        Values("OpCapability FragmentBarycentricKHR\n"),
+        Values("OpExtension \"SPV_KHR_fragment_shader_barycentric\"\n"),
+        Values("VUID-BaryCoordKHR-BaryCoordKHR-04156 "
+               "VUID-BaryCoordNoPerspKHR-BaryCoordNoPerspKHR-04162 "),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "needs to be a 3-component 32-bit float vector"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    BaryCoordNotF32Vec3,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(
+        Values("BaryCoordKHR", "BaryCoordNoPerspKHR"), Values("Fragment"),
+        Values("Output"), Values("%f64vec3"),
+        Values("OpCapability FragmentBarycentricKHR\n"),
+        Values("OpExtension \"SPV_KHR_fragment_shader_barycentric\"\n"),
+        Values("VUID-BaryCoordKHR-BaryCoordKHR-04156 "
+               "VUID-BaryCoordNoPerspKHR-BaryCoordNoPerspKHR-04162 "),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "needs to be a 3-component 32-bit float vector"))));
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_capability_test.cpp b/test/val/val_capability_test.cpp
index c432c3c..0d84caa 100644
--- a/test/val/val_capability_test.cpp
+++ b/test/val/val_capability_test.cpp
@@ -2918,8 +2918,8 @@
            "StorageTexelBufferArrayNonUniformIndexing",
            "SPV_EXT_descriptor_indexing"),
       // SPV_EXT_physical_storage_buffer
-      IN15("PhysicalStorageBufferAddressesEXT",
-           "PhysicalStorageBufferAddresses", "SPV_EXT_physical_storage_buffer"),
+      IN15("PhysicalStorageBufferAddresses", "PhysicalStorageBufferAddresses",
+           "SPV_EXT_physical_storage_buffer"),
       // SPV_KHR_vulkan_memory_model
       IN15("VulkanMemoryModelKHR", "VulkanMemoryModel",
            "SPV_KHR_vulkan_memory_model"),
diff --git a/test/val/val_cfg_test.cpp b/test/val/val_cfg_test.cpp
index 311cfa7..a4d1444 100644
--- a/test/val/val_cfg_test.cpp
+++ b/test/val/val_cfg_test.cpp
@@ -65,7 +65,7 @@
   /// Creates a Block with a given label
   ///
   /// @param[in]: label the label id of the block
-  /// @param[in]: type the branch instruciton that ends the block
+  /// @param[in]: type the branch instruction that ends the block
   explicit Block(std::string label, SpvOp type = SpvOpBranch)
       : label_(label), body_(), type_(type), successors_() {}
 
@@ -386,8 +386,8 @@
   CompileSuccessfully(str);
   ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              MatchesRegex("Block .\\[%cont\\] appears in the binary "
-                           "before its dominator .\\[%branch\\]\n"
+              MatchesRegex("Block '.\\[%cont\\]' appears in the binary "
+                           "before its dominator '.\\[%branch\\]'\n"
                            "  %branch = OpLabel\n"));
 }
 
@@ -419,7 +419,7 @@
   if (is_shader) {
     ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
     EXPECT_THAT(getDiagnosticString(),
-                MatchesRegex("Block .\\[%merge\\] is already a merge block "
+                MatchesRegex("Block '.\\[%merge\\]' is already a merge block "
                              "for another header\n"
                              "  %Main = OpFunction %void None %9\n"));
   } else {
@@ -455,7 +455,7 @@
   if (is_shader) {
     ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
     EXPECT_THAT(getDiagnosticString(),
-                MatchesRegex("Block .\\[%merge\\] is already a merge block "
+                MatchesRegex("Block '.\\[%merge\\]' is already a merge block "
                              "for another header\n"
                              "  %Main = OpFunction %void None %9\n"));
   } else {
@@ -480,8 +480,8 @@
   CompileSuccessfully(str);
   ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              MatchesRegex("First block .\\[%entry\\] of function "
-                           ".\\[%Main\\] is targeted by block .\\[%bad\\]\n"
+              MatchesRegex("First block '.\\[%entry\\]' of function "
+                           "'.\\[%Main\\]' is targeted by block '.\\[%bad\\]'\n"
                            "  %Main = OpFunction %void None %10\n"));
 }
 
@@ -529,10 +529,11 @@
 
   CompileSuccessfully(str);
   ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              MatchesRegex("First block .\\[%entry\\] of function .\\[%Main\\] "
-                           "is targeted by block .\\[%bad\\]\n"
-                           "  %Main = OpFunction %void None %10\n"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      MatchesRegex("First block '.\\[%entry\\]' of function '.\\[%Main\\]' "
+                   "is targeted by block '.\\[%bad\\]'\n"
+                   "  %Main = OpFunction %void None %10\n"));
 }
 
 TEST_P(ValidateCFG, BranchConditionalFalseTargetFirstBlockBad) {
@@ -558,10 +559,11 @@
 
   CompileSuccessfully(str);
   ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              MatchesRegex("First block .\\[%entry\\] of function .\\[%Main\\] "
-                           "is targeted by block .\\[%bad\\]\n"
-                           "  %Main = OpFunction %void None %10\n"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      MatchesRegex("First block '.\\[%entry\\]' of function '.\\[%Main\\]' "
+                   "is targeted by block '.\\[%bad\\]'\n"
+                   "  %Main = OpFunction %void None %10\n"));
 }
 
 TEST_P(ValidateCFG, SwitchTargetFirstBlockBad) {
@@ -594,10 +596,11 @@
 
   CompileSuccessfully(str);
   ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              MatchesRegex("First block .\\[%entry\\] of function .\\[%Main\\] "
-                           "is targeted by block .\\[%bad\\]\n"
-                           "  %Main = OpFunction %void None %10\n"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      MatchesRegex("First block '.\\[%entry\\]' of function '.\\[%Main\\]' "
+                   "is targeted by block '.\\[%bad\\]'\n"
+                   "  %Main = OpFunction %void None %10\n"));
 }
 
 TEST_P(ValidateCFG, BranchToBlockInOtherFunctionBad) {
@@ -630,46 +633,11 @@
 
   CompileSuccessfully(str);
   ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      MatchesRegex("Block\\(s\\) \\{.\\[%middle2\\]\\} are referenced but not "
-                   "defined in function .\\[%Main\\]\n"
-                   "  %Main = OpFunction %void None %9\n"));
-}
-
-TEST_P(ValidateCFG, HeaderDoesntDominatesMergeBad) {
-  bool is_shader = GetParam() == SpvCapabilityShader;
-  Block entry("entry");
-  Block head("head", SpvOpBranchConditional);
-  Block f("f");
-  Block merge("merge", SpvOpReturn);
-
-  head.SetBody("%cond = OpSLessThan %boolt %one %two\n");
-
-  if (is_shader) head.AppendBody("OpSelectionMerge %merge None\n");
-
-  std::string str = GetDefaultHeader(GetParam()) +
-                    nameOps("head", "merge", std::make_pair("func", "Main")) +
-                    types_consts() +
-                    "%func    = OpFunction %voidt None %funct\n";
-
-  str += entry >> merge;
-  str += head >> std::vector<Block>({merge, f});
-  str += f >> merge;
-  str += merge;
-  str += "OpFunctionEnd\n";
-
-  CompileSuccessfully(str);
-  if (is_shader) {
-    ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-    EXPECT_THAT(
-        getDiagnosticString(),
-        MatchesRegex("The selection construct with the selection header "
-                     ".\\[%head\\] does not dominate the merge block "
-                     ".\\[%merge\\]\n  %merge = OpLabel\n"));
-  } else {
-    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-  }
+  EXPECT_THAT(getDiagnosticString(),
+              MatchesRegex(
+                  "Block\\(s\\) \\{'.\\[%middle2\\]'\\} are referenced but not "
+                  "defined in function '.\\[%Main\\]'\n"
+                  "  %Main = OpFunction %void None %9\n"));
 }
 
 TEST_P(ValidateCFG, HeaderDoesntStrictlyDominateMergeBad) {
@@ -697,9 +665,11 @@
     ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
     EXPECT_THAT(
         getDiagnosticString(),
-        MatchesRegex("The selection construct with the selection header "
-                     ".\\[%head\\] does not strictly dominate the merge block "
-                     ".\\[%head\\]\n  %head = OpLabel\n"));
+        MatchesRegex(
+            "The selection construct with the selection header "
+            "'.\\[%head\\]' does not strictly structurally dominate the "
+            "merge block "
+            "'.\\[%head\\]'\n  %head = OpLabel\n"));
   } else {
     ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()) << str;
   }
@@ -907,16 +877,7 @@
 
 TEST_P(ValidateCFG, UnreachableContinueUnreachableLoopInst) {
   CompileSuccessfully(GetUnreachableContinueUnreachableLoopInst(GetParam()));
-  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());
-  }
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
 std::string GetUnreachableMergeWithComplexBody(SpvCapability cap) {
@@ -1070,12 +1031,10 @@
   std::string header = 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("%placeholder   = OpVariable %intptrt Function\n");
@@ -1092,7 +1051,6 @@
   str += branch >> std::vector<Block>({merge});
   str += merge;
   str += target;
-  str += foo;
   str += "OpFunctionEnd\n";
 
   return str;
@@ -1156,6 +1114,7 @@
   Block body("body", SpvOpBranchConditional);
   Block t("t", SpvOpReturn);
   Block f("f", SpvOpReturn);
+  Block pre_target("pre_target", SpvOpBranch);
 
   target >> branch;
   body.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
@@ -1163,10 +1122,10 @@
   std::string str = header;
   if (cap == SpvCapabilityShader) {
     branch.AppendBody("OpLoopMerge %merge %target None\n");
-    body.AppendBody("OpSelectionMerge %target None\n");
+    body.AppendBody("OpSelectionMerge %pre_target None\n");
   }
 
-  str += nameOps("branch", "merge", "target", "body", "t", "f",
+  str += nameOps("branch", "merge", "pre_target", "target", "body", "t", "f",
                  std::make_pair("func", "Main"));
   str += types_consts();
   str += "%func    = OpFunction %voidt None %funct\n";
@@ -1176,6 +1135,7 @@
   str += t;
   str += f;
   str += merge;
+  str += pre_target >> target;
   str += target;
   str += "OpFunctionEnd\n";
 
@@ -1296,9 +1256,10 @@
     loop2.SetBody("OpLoopMerge %loop2_merge %loop2 None\n");
   }
 
-  std::string str = GetDefaultHeader(GetParam()) +
-                    nameOps("loop2", "loop2_merge") + types_consts() +
-                    "%func    = OpFunction %voidt None %funct\n";
+  std::string str =
+      GetDefaultHeader(GetParam()) +
+      nameOps("loop1", "loop1_cont_break_block", "loop2", "loop2_merge") +
+      types_consts() + "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> loop1;
   str += loop1 >> loop1_cont_break_block;
@@ -1389,11 +1350,13 @@
   CompileSuccessfully(str);
   if (GetParam() == SpvCapabilityShader) {
     ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-    EXPECT_THAT(getDiagnosticString(),
-                MatchesRegex("The continue construct with the continue target "
-                             ".\\[%loop1_cont\\] is not post dominated by the "
-                             "back-edge block .\\[%be_block\\]\n"
-                             "  %be_block = OpLabel\n"));
+    EXPECT_THAT(
+        getDiagnosticString(),
+        MatchesRegex(
+            "The continue construct with the continue target "
+            "'.\\[%loop1_cont\\]' is not structurally post dominated by the "
+            "back-edge block '.\\[%be_block\\]'\n"
+            "  %be_block = OpLabel\n"));
   } else {
     ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
   }
@@ -1426,7 +1389,7 @@
     ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
     EXPECT_THAT(
         getDiagnosticString(),
-        MatchesRegex("Back-edges \\(.\\[%f\\] -> .\\[%split\\]\\) can only "
+        MatchesRegex("Back-edges \\('.\\[%f\\]' -> '.\\[%split\\]'\\) can only "
                      "be formed between a block and a loop header.\n"
                      "  %f = OpLabel\n"));
   } else {
@@ -1458,7 +1421,7 @@
     EXPECT_THAT(
         getDiagnosticString(),
         MatchesRegex(
-            "Back-edges \\(.\\[%split\\] -> .\\[%split\\]\\) can only be "
+            "Back-edges \\('.\\[%split\\]' -> '.\\[%split\\]'\\) can only be "
             "formed between a block and a loop header.\n  %split = OpLabel\n"));
   } else {
     ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
@@ -1493,7 +1456,7 @@
     EXPECT_THAT(
         getDiagnosticString(),
         MatchesRegex(
-            "Loop header .\\[%loop\\] is targeted by 2 back-edge blocks but "
+            "Loop header '.\\[%loop\\]' is targeted by 2 back-edge blocks but "
             "the standard requires exactly one\n  %loop = OpLabel\n"))
         << str;
   } else {
@@ -1528,11 +1491,13 @@
   CompileSuccessfully(str);
   if (is_shader) {
     ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-    EXPECT_THAT(getDiagnosticString(),
-                MatchesRegex("The continue construct with the continue target "
-                             ".\\[%cheader\\] is not post dominated by the "
-                             "back-edge block .\\[%be_block\\]\n"
-                             "  %be_block = OpLabel\n"));
+    EXPECT_THAT(
+        getDiagnosticString(),
+        MatchesRegex(
+            "The continue construct with the continue target "
+            "'.\\[%cheader\\]' is not structurally post dominated by the "
+            "back-edge block '.\\[%be_block\\]'\n"
+            "  %be_block = OpLabel\n"));
   } else {
     ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
   }
@@ -1561,11 +1526,12 @@
   CompileSuccessfully(str);
   if (is_shader) {
     ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-    EXPECT_THAT(getDiagnosticString(),
-                MatchesRegex("The continue construct with the continue target "
-                             ".\\[%loop\\] is not post dominated by the "
-                             "back-edge block .\\[%cont\\]\n"
-                             "  %cont = OpLabel\n"))
+    EXPECT_THAT(
+        getDiagnosticString(),
+        MatchesRegex("The continue construct with the continue target "
+                     "'.\\[%loop\\]' is not structurally post dominated by the "
+                     "back-edge block '.\\[%cont\\]'\n"
+                     "  %cont = OpLabel\n"))
         << str;
   } else {
     ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
@@ -1597,11 +1563,12 @@
   CompileSuccessfully(str);
   if (is_shader) {
     ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-    EXPECT_THAT(getDiagnosticString(),
-                MatchesRegex("The continue construct with the continue target "
-                             ".\\[%loop\\] is not post dominated by the "
-                             "back-edge block .\\[%cont\\]\n"
-                             "  %cont = OpLabel\n"));
+    EXPECT_THAT(
+        getDiagnosticString(),
+        MatchesRegex("The continue construct with the continue target "
+                     "'.\\[%loop\\]' is not structurally post dominated by the "
+                     "back-edge block '.\\[%cont\\]'\n"
+                     "  %cont = OpLabel\n"));
   } else {
     ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
   }
@@ -1674,7 +1641,7 @@
   ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      MatchesRegex("Loop header .\\[%loop\\] is targeted by "
+      MatchesRegex("Loop header '.\\[%loop\\]' is targeted by "
                    "0 back-edge blocks but the standard requires exactly "
                    "one\n  %loop = OpLabel\n"));
 }
@@ -1786,9 +1753,10 @@
     EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
     EXPECT_THAT(
         getDiagnosticString(),
-        HasSubstr("Header block 3[%if_head] is contained in the loop construct "
-                  "headed "
-                  "by 2[%loop], but its merge block 5[%if_merge] is not"));
+        HasSubstr(
+            "Header block '3[%if_head]' is contained in the loop construct "
+            "headed "
+            "by '2[%loop]', but its merge block '5[%if_merge]' is not"));
   } else {
     EXPECT_THAT(SPV_SUCCESS, ValidateInstructions());
   }
@@ -1824,40 +1792,6 @@
       << str << getDiagnosticString();
 }
 
-TEST_P(ValidateCFG, SingleLatchBlockHeaderContinueTargetIsItselfGood) {
-  // This test case ensures we don't count a Continue Target from a loop
-  // header to itself as a self-loop when computing back edges.
-  // Also, it detects that there is an edge from %latch to the pseudo-exit
-  // node, rather than from %loop.  In particular, it detects that we
-  // have used the *reverse* textual order of blocks when computing
-  // predecessor traversal roots.
-  bool is_shader = GetParam() == SpvCapabilityShader;
-  Block entry("entry");
-  Block loop("loop");
-  Block latch("latch");
-  Block merge("merge", SpvOpReturn);
-
-  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
-  if (is_shader) {
-    loop.SetBody("OpLoopMerge %merge %loop None\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;
-  str += latch >> loop;
-  str += merge;
-  str += "OpFunctionEnd";
-
-  CompileSuccessfully(str);
-  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions())
-      << str << getDiagnosticString();
-}
-
 // Unit test to check the case where a basic block is the entry block of 2
 // different constructs. In this case, the basic block is the entry block of a
 // continue construct as well as a selection construct. See issue# 517 for more
@@ -2027,8 +1961,9 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
-          "Case construct that targets 10[%10] has branches to multiple other "
-          "case construct targets 12[%12] and 11[%11]\n  %10 = OpLabel"));
+          "Case construct that targets '10[%10]' has branches to multiple "
+          "other "
+          "case construct targets '12[%12]' and '11[%11]'\n  %10 = OpLabel"));
 }
 
 TEST_F(ValidateCFG, MultipleFallThroughToDefault) {
@@ -2062,7 +1997,7 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("Multiple case constructs have branches to the case construct "
-                "that targets 10[%10]\n  %10 = OpLabel"));
+                "that targets '10[%10]'\n  %10 = OpLabel"));
 }
 
 TEST_F(ValidateCFG, MultipleFallThroughToNonDefault) {
@@ -2096,7 +2031,7 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("Multiple case constructs have branches to the case construct "
-                "that targets 12[%12]\n  %12 = OpLabel"));
+                "that targets '12[%12]'\n  %12 = OpLabel"));
 }
 
 TEST_F(ValidateCFG, DuplicateTargetWithFallThrough) {
@@ -2157,10 +2092,11 @@
   ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Case construct that targets 12[%12] has branches to the case "
-                "construct that targets 11[%11], but does not immediately "
-                "precede it in the OpSwitch's target list\n"
-                "  OpSwitch %uint_0 %10 0 %11 1 %12"));
+      HasSubstr(
+          "Case construct that targets '12[%12]' has branches to the case "
+          "construct that targets '11[%11]', but does not immediately "
+          "precede it in the OpSwitch's target list\n"
+          "  OpSwitch %uint_0 %10 0 %11 1 %12"));
 }
 
 TEST_F(ValidateCFG, WrongOperandListThroughDefault) {
@@ -2193,10 +2129,11 @@
   ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Case construct that targets 12[%12] has branches to the case "
-                "construct that targets 11[%11], but does not immediately "
-                "precede it in the OpSwitch's target list\n"
-                "  OpSwitch %uint_0 %10 0 %11 1 %12"));
+      HasSubstr(
+          "Case construct that targets '12[%12]' has branches to the case "
+          "construct that targets '11[%11]', but does not immediately "
+          "precede it in the OpSwitch's target list\n"
+          "  OpSwitch %uint_0 %10 0 %11 1 %12"));
 }
 
 TEST_F(ValidateCFG, WrongOperandListNotLast) {
@@ -2231,10 +2168,11 @@
   ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Case construct that targets 12[%12] has branches to the case "
-                "construct that targets 11[%11], but does not immediately "
-                "precede it in the OpSwitch's target list\n"
-                "  OpSwitch %uint_0 %10 0 %11 1 %12 2 %13"));
+      HasSubstr(
+          "Case construct that targets '12[%12]' has branches to the case "
+          "construct that targets '11[%11]', but does not immediately "
+          "precede it in the OpSwitch's target list\n"
+          "  OpSwitch %uint_0 %10 0 %11 1 %12 2 %13"));
 }
 
 TEST_F(ValidateCFG, GoodUnreachableSwitch) {
@@ -2298,11 +2236,12 @@
 
   CompileSuccessfully(text);
   ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Case construct that targets 8[%8] has invalid branch "
-                        "to block 10[%10] (not another case construct, "
-                        "corresponding merge, outer loop merge or outer loop "
-                        "continue)"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Case construct that targets '8[%8]' has invalid branch "
+                "to block '10[%10]' (not another case construct, "
+                "corresponding merge, outer loop merge or outer loop "
+                "continue)"));
 }
 
 TEST_F(ValidateCFG, GoodCaseExitsToOuterConstructs) {
@@ -2369,8 +2308,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Case construct that targets 1[%default] has branches to the "
-                "case construct that targets 2[%other], but does not "
+      HasSubstr("Case construct that targets '1[%default]' has branches to the "
+                "case construct that targets '2[%other]', but does not "
                 "immediately precede it in the OpSwitch's target list"));
 }
 
@@ -2402,8 +2341,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Case construct that targets 2[%other] has branches to the "
-                "case construct that targets 1[%default], but does not "
+      HasSubstr("Case construct that targets '2[%other]' has branches to the "
+                "case construct that targets '1[%default]', but does not "
                 "immediately precede it in the OpSwitch's target list"));
 }
 
@@ -2872,8 +2811,8 @@
   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"));
+              HasSubstr("Back-edges ('10[%10]' -> '9[%9]') can only be formed "
+                        "between a block and a loop header"));
 }
 
 TEST_F(ValidateCFG, LoopMergeMergeBlockNotLabel) {
@@ -2898,7 +2837,7 @@
   CompileSuccessfully(text);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Merge Block 1[%undef] must be an OpLabel"));
+              HasSubstr("Merge Block '1[%undef]' must be an OpLabel"));
 }
 
 TEST_F(ValidateCFG, LoopMergeContinueTargetNotLabel) {
@@ -2923,7 +2862,7 @@
   CompileSuccessfully(text);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Continue Target 1[%undef] must be an OpLabel"));
+              HasSubstr("Continue Target '1[%undef]' must be an OpLabel"));
 }
 
 TEST_F(ValidateCFG, LoopMergeMergeBlockContinueTargetSameLabel) {
@@ -3167,9 +3106,10 @@
 
   CompileSuccessfully(text);
   EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("block <ID> 10[%10] exits the selection headed by <ID> "
-                        "8[%8], but not via a structured exit"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("block <ID> '10[%10]' exits the selection headed by <ID> "
+                "'8[%8]', but not via a structured exit"));
 }
 
 TEST_F(ValidateCFG, InvalidLoopExit) {
@@ -3203,8 +3143,8 @@
   CompileSuccessfully(text);
   EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("block <ID> 11[%11] exits the loop headed by <ID> "
-                        "8[%8], but not via a structured exit"));
+              HasSubstr("block <ID> '11[%11]' exits the loop headed by <ID> "
+                        "'8[%8]', but not via a structured exit"));
 }
 
 TEST_F(ValidateCFG, InvalidContinueExit) {
@@ -3237,9 +3177,10 @@
 
   CompileSuccessfully(text);
   EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("block <ID> 11[%11] exits the continue headed by <ID> "
-                        "10[%10], but not via a structured exit"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("block <ID> '11[%11]' exits the continue headed by <ID> "
+                "'10[%10]', but not via a structured exit"));
 }
 
 TEST_F(ValidateCFG, InvalidSelectionExitBackedge) {
@@ -3275,9 +3216,11 @@
 
   CompileSuccessfully(text);
   EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("block <ID> 13[%13] exits the selection headed by <ID> "
-                        "9[%9], but not via a structured exit"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "The continue construct with the continue target '9[%9]' is not "
+          "structurally post dominated by the back-edge block '13[%13]'"));
 }
 
 TEST_F(ValidateCFG, BreakFromSwitch) {
@@ -3340,9 +3283,10 @@
 
   CompileSuccessfully(text);
   EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("block <ID> 12[%12] exits the selection headed by <ID> "
-                        "10[%10], but not via a structured exit"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("block <ID> '12[%12]' exits the selection headed by <ID> "
+                "'10[%10]', but not via a structured exit"));
 }
 
 TEST_F(ValidateCFG, BreakToOuterSwitch) {
@@ -3379,9 +3323,10 @@
 
   CompileSuccessfully(text);
   EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("block <ID> 14[%14] exits the selection headed by <ID> "
-                        "10[%10], but not via a structured exit"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("block <ID> '14[%14]' exits the selection headed by <ID> "
+                "'10[%10]', but not via a structured exit"));
 }
 
 TEST_F(ValidateCFG, BreakToOuterLoop) {
@@ -3423,8 +3368,8 @@
   CompileSuccessfully(text);
   EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("block <ID> 15[%15] exits the loop headed by <ID> "
-                        "10[%10], but not via a structured exit"));
+              HasSubstr("block <ID> '15[%15]' exits the loop headed by <ID> "
+                        "'10[%10]', but not via a structured exit"));
 }
 
 TEST_F(ValidateCFG, ContinueFromNestedSelection) {
@@ -3523,7 +3468,7 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
-          "OpSwitch must be preceeded by an OpSelectionMerge instruction"));
+          "OpSwitch must be preceded by an OpSelectionMerge instruction"));
 }
 
 TEST_F(ValidateCFG, MissingMergeSwitchBad2) {
@@ -3550,7 +3495,7 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
-          "OpSwitch must be preceeded by an OpSelectionMerge instruction"));
+          "OpSwitch must be preceded by an OpSelectionMerge instruction"));
 }
 
 TEST_F(ValidateCFG, MissingMergeOneBranchToMergeGood) {
@@ -3622,7 +3567,7 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
-          "OpSwitch must be preceeded by an OpSelectionMerge instruction"));
+          "OpSwitch must be preceded by an OpSelectionMerge instruction"));
 }
 
 TEST_F(ValidateCFG, MissingMergeOneUnseenTargetSwitchBad) {
@@ -3654,7 +3599,7 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
-          "OpSwitch must be preceeded by an OpSelectionMerge instruction"));
+          "OpSwitch must be preceded by an OpSelectionMerge instruction"));
 }
 
 TEST_F(ValidateCFG, MissingMergeLoopBreakGood) {
@@ -3871,9 +3816,9 @@
   EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr(
-          "Header block 3[%body] is contained in the loop construct headed by "
-          "1[%loop], but its merge block 2[%continue] is not"));
+      HasSubstr("Header block '3[%body]' is contained in the loop construct "
+                "headed by "
+                "'1[%loop]', but its merge block '2[%continue]' is not"));
 }
 
 TEST_F(ValidateCFG, ContinueCannotBeLoopMergeTarget) {
@@ -3908,9 +3853,9 @@
   EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr(
-          "Header block 3[%inner] is contained in the loop construct headed by "
-          "1[%loop], but its merge block 2[%continue] is not"));
+      HasSubstr("Header block '3[%inner]' is contained in the loop construct "
+                "headed by "
+                "'1[%loop]', but its merge block '2[%continue]' is not"));
 }
 
 TEST_F(ValidateCFG, ExitFromConstructWhoseHeaderIsAMerge) {
@@ -4287,7 +4232,8 @@
   EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("The selection construct with the selection header "
-                        "8[%8] does not dominate the merge block 10[%10]\n"));
+                        "'8[%8]' does not structurally dominate the merge "
+                        "block '10[%10]'\n"));
 }
 
 TEST_F(ValidateCFG, UnreachableIsStaticallyReachable) {
@@ -4455,9 +4401,10 @@
 
   CompileSuccessfully(text);
   EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("block <ID> 1[%BAD] exits the continue headed by <ID> "
-                        "1[%BAD], but not via a structured exit"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("block <ID> '1[%BAD]' exits the continue headed by <ID> "
+                "'1[%BAD]', but not via a structured exit"));
 }
 
 TEST_F(ValidateCFG, SwitchSelectorNotAnInt) {
@@ -4579,6 +4526,81 @@
   EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
 }
 
+TEST_F(ValidateCFG, BranchConditionalDifferentTargetsPre1p6) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%undef = OpUndef %bool
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpBranchConditional %undef %target %target
+%target = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_5);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
+}
+
+TEST_F(ValidateCFG, BranchConditionalDifferentTargetsPost1p6) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%bool = OpTypeBool
+%undef = OpUndef %bool
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%entry = OpLabel
+OpBranchConditional %undef %target %target
+%target = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_6);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_6));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("In SPIR-V 1.6 or later, True Label and False Label "
+                        "must be different labels"));
+}
+
+TEST_F(ValidateCFG, BadBackEdgeUnreachableContinue) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeFunction %1
+%3 = OpFunction %1 None %2
+%4 = OpLabel
+OpBranch %5
+%5 = OpLabel
+OpLoopMerge %6 %7 None
+OpBranch %8
+%8 = OpLabel
+OpBranch %5
+%7 = OpLabel
+OpUnreachable
+%6 = OpLabel
+OpUnreachable
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("The continue construct with the continue target '7[%7]' "
+                "does not structurally dominate the back-edge block '8[%8]'"));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_composites_test.cpp b/test/val/val_composites_test.cpp
index 507ee88..0fd1ed6 100644
--- a/test/val/val_composites_test.cpp
+++ b/test/val/val_composites_test.cpp
@@ -322,8 +322,9 @@
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 5[%float] cannot be a "
-                                               "type"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Operand '5[%float]' cannot be a "
+                        "type"));
 }
 
 TEST_F(ValidateComposites, CompositeConstructVectorWrongConsituent2) {
@@ -540,7 +541,7 @@
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("ID 19[%float_0] is not a type id"));
+              HasSubstr("ID '19[%float_0]' is not a type id"));
 }
 
 TEST_F(ValidateComposites, CopyObjectWrongOperandType) {
@@ -660,7 +661,7 @@
 
   CompileSuccessfully(GenerateShaderCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 11[%v4float] cannot "
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand '11[%v4float]' cannot "
                                                "be a type"));
 }
 
diff --git a/test/val/val_conversion_test.cpp b/test/val/val_conversion_test.cpp
index b9802ec..1f8c426 100644
--- a/test/val/val_conversion_test.cpp
+++ b/test/val/val_conversion_test.cpp
@@ -813,8 +813,9 @@
 
   CompileSuccessfully(GenerateKernelCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 4[%float] cannot be a "
-                                               "type"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Operand '4[%float]' cannot be a "
+                        "type"));
 }
 
 TEST_F(ValidateConversion, PtrCastToGenericWrongInputStorageClass) {
@@ -1258,8 +1259,9 @@
 
   CompileSuccessfully(GenerateKernelCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 4[%float] cannot be a "
-                                               "type"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Operand '4[%float]' cannot be a "
+                        "type"));
 }
 
 TEST_F(ValidateConversion, BitcastWrongResultType) {
@@ -1458,7 +1460,7 @@
 
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 1[%uint] cannot be a "
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand '1[%uint]' cannot be a "
                                                "type"));
 }
 
@@ -1557,7 +1559,7 @@
 OpMemoryModel PhysicalStorageBuffer64 GLSL450
 OpEntryPoint Fragment %main "main"
 OpExecutionMode %main OriginUpperLeft
-OpDecorate %val1 RestrictPointerEXT
+OpDecorate %val1 RestrictPointer
 %uint64 = OpTypeInt 64 0
 %u64_1 = OpConstant %uint64 1
 %ptr = OpTypePointer PhysicalStorageBuffer %uint64
@@ -1615,7 +1617,7 @@
 OpMemoryModel PhysicalStorageBuffer64 GLSL450
 OpEntryPoint Fragment %main "main"
 OpExecutionMode %main OriginUpperLeft
-OpDecorate %val1 RestrictPointerEXT
+OpDecorate %val1 RestrictPointer
 %uint32 = OpTypeInt 32 0
 %uint64 = OpTypeInt 64 0
 %ptr = OpTypePointer PhysicalStorageBuffer %uint64
@@ -1641,6 +1643,131 @@
                 "integer type to have a 64-bit width for Vulkan environment."));
 }
 
+TEST_F(ValidateConversion, ConvertUToAccelerationStructureU32Vec2) {
+  const std::string extensions = R"(
+OpCapability RayQueryKHR
+OpExtension "SPV_KHR_ray_query"
+)";
+  const std::string types = R"(
+%u32vec2ptr_func = OpTypePointer Function %u32vec2
+%typeAS = OpTypeAccelerationStructureKHR
+)";
+  const std::string body = R"(
+%asHandle = OpVariable %u32vec2ptr_func Function
+%load = OpLoad %u32vec2 %asHandle
+%val = OpConvertUToAccelerationStructureKHR %typeAS %load
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extensions, "", types).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateConversion, ConvertUToAccelerationStructureSuccessU64) {
+  const std::string extensions = R"(
+OpCapability RayQueryKHR
+OpExtension "SPV_KHR_ray_query"
+)";
+  const std::string types = R"(
+%u64_func = OpTypePointer Function %u64
+%typeAS = OpTypeAccelerationStructureKHR
+)";
+  const std::string body = R"(
+%asHandle = OpVariable %u64_func Function
+%load = OpLoad %u64 %asHandle
+%val = OpConvertUToAccelerationStructureKHR %typeAS %load
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extensions, "", types).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateConversion, ConvertUToAccelerationStructureResult) {
+  const std::string extensions = R"(
+OpCapability RayQueryKHR
+OpExtension "SPV_KHR_ray_query"
+)";
+  const std::string types = R"(
+%u32vec2ptr_func = OpTypePointer Function %u32vec2
+%typeRQ = OpTypeRayQueryKHR
+)";
+  const std::string body = R"(
+%asHandle = OpVariable %u32vec2ptr_func Function
+%load = OpLoad %u32vec2 %asHandle
+%val = OpConvertUToAccelerationStructureKHR %typeRQ %load
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extensions, "", types).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected Result Type to be a Acceleration Structure"));
+}
+
+TEST_F(ValidateConversion, ConvertUToAccelerationStructureU32) {
+  const std::string extensions = R"(
+OpCapability RayQueryKHR
+OpExtension "SPV_KHR_ray_query"
+)";
+  const std::string types = R"(
+%u32ptr_func = OpTypePointer Function %u32
+%typeAS = OpTypeAccelerationStructureKHR
+)";
+  const std::string body = R"(
+%asHandle = OpVariable %u32ptr_func Function
+%load = OpLoad %u32 %asHandle
+%val = OpConvertUToAccelerationStructureKHR %typeAS %load
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extensions, "", types).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected 64-bit uint scalar or 2-component 32-bit "
+                        "uint vector as input"));
+}
+
+TEST_F(ValidateConversion, ConvertUToAccelerationStructureS64) {
+  const std::string extensions = R"(
+OpCapability RayQueryKHR
+OpExtension "SPV_KHR_ray_query"
+)";
+  const std::string types = R"(
+%s64ptr_func = OpTypePointer Function %s64
+%typeAS = OpTypeAccelerationStructureKHR
+)";
+  const std::string body = R"(
+%asHandle = OpVariable %s64ptr_func Function
+%load = OpLoad %s64 %asHandle
+%val = OpConvertUToAccelerationStructureKHR %typeAS %load
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extensions, "", types).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected 64-bit uint scalar or 2-component 32-bit "
+                        "uint vector as input"));
+}
+
+TEST_F(ValidateConversion, ConvertUToAccelerationStructureS32Vec2) {
+  const std::string extensions = R"(
+OpCapability RayQueryKHR
+OpExtension "SPV_KHR_ray_query"
+)";
+  const std::string types = R"(
+%s32vec2ptr_func = OpTypePointer Function %s32vec2
+%typeAS = OpTypeAccelerationStructureKHR
+)";
+  const std::string body = R"(
+%asHandle = OpVariable %s32vec2ptr_func Function
+%load = OpLoad %s32vec2 %asHandle
+%val = OpConvertUToAccelerationStructureKHR %typeAS %load
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extensions, "", types).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected 64-bit uint scalar or 2-component 32-bit "
+                        "uint vector as input"));
+}
+
 using ValidateSmallConversions = spvtest::ValidateBase<std::string>;
 
 CodeGenerator GetSmallConversionsCodeGenerator() {
diff --git a/test/val/val_data_test.cpp b/test/val/val_data_test.cpp
index 1d4c0e0..6a7f243 100644
--- a/test/val/val_data_test.cpp
+++ b/test/val/val_data_test.cpp
@@ -387,7 +387,7 @@
   CompileSuccessfully(str.c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Operand 3[%3] requires a previous definition"));
+              HasSubstr("Operand '3[%3]' requires a previous definition"));
 }
 
 TEST_F(ValidateData, matrix_bad_column_type) {
@@ -549,7 +549,7 @@
   CompileSuccessfully(str.c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Operand 3[%3] requires a previous definition"));
+              HasSubstr("Operand '3[%3]' requires a previous definition"));
 }
 
 TEST_F(ValidateData, missing_forward_pointer_decl_self_reference) {
@@ -561,7 +561,7 @@
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Operand 2[%_struct_2] requires a previous definition"));
+      HasSubstr("Operand '2[%_struct_2]' requires a previous definition"));
 }
 
 TEST_F(ValidateData, forward_pointer_missing_definition) {
@@ -767,6 +767,8 @@
   CompileSuccessfully(str.c_str(), SPV_ENV_VULKAN_1_1);
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
   EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
+  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"));
@@ -822,7 +824,7 @@
   CompileSuccessfully(test, SPV_ENV_UNIVERSAL_1_5);
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Operand 3[%_ptr_PhysicalStorageBuffer__struct_1] "
+              HasSubstr("Operand '3[%_ptr_PhysicalStorageBuffer__struct_1]' "
                         "requires a previous definition"));
 }
 
diff --git a/test/val/val_decoration_test.cpp b/test/val/val_decoration_test.cpp
index f2953ed..28ee970 100644
--- a/test/val/val_decoration_test.cpp
+++ b/test/val/val_decoration_test.cpp
@@ -42,6 +42,7 @@
 };
 
 using ValidateDecorations = spvtest::ValidateBase<bool>;
+using ValidateDecorationString = spvtest::ValidateBase<std::string>;
 using ValidateVulkanCombineDecorationResult =
     spvtest::ValidateBase<std::tuple<const char*, const char*, TestResult>>;
 
@@ -50,20 +51,20 @@
     OpCapability Shader
     OpCapability Linkage
     OpMemoryModel Logical GLSL450
-    OpDecorate %1 ArrayStride 4
-    OpDecorate %1 RelaxedPrecision
+    OpDecorate %1 Location 4
+    OpDecorate %1 Centroid
     %2 = OpTypeFloat 32
-    %1 = OpTypeRuntimeArray %2
+    %3 = OpTypePointer Output %2
+    %1 = OpVariable %3 Output
     ; Since %1 is used first in Decoration, it gets id 1.
 )";
   const uint32_t id = 1;
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
   // Must have 2 decorations.
-  EXPECT_THAT(
-      vstate_->id_decorations(id),
-      Eq(std::vector<Decoration>{Decoration(SpvDecorationArrayStride, {4}),
-                                 Decoration(SpvDecorationRelaxedPrecision)}));
+  EXPECT_THAT(vstate_->id_decorations(id),
+              Eq(std::set<Decoration>{Decoration(SpvDecorationLocation, {4}),
+                                      Decoration(SpvDecorationCentroid)}));
 }
 
 TEST_F(ValidateDecorations, ValidateOpMemberDecorateRegistration) {
@@ -88,15 +89,15 @@
   const uint32_t arr_id = 1;
   EXPECT_THAT(
       vstate_->id_decorations(arr_id),
-      Eq(std::vector<Decoration>{Decoration(SpvDecorationArrayStride, {4})}));
+      Eq(std::set<Decoration>{Decoration(SpvDecorationArrayStride, {4})}));
 
   // The struct must have 3 decorations.
   const uint32_t struct_id = 2;
   EXPECT_THAT(
       vstate_->id_decorations(struct_id),
-      Eq(std::vector<Decoration>{Decoration(SpvDecorationNonReadable, {}, 2),
-                                 Decoration(SpvDecorationOffset, {2}, 2),
-                                 Decoration(SpvDecorationBufferBlock)}));
+      Eq(std::set<Decoration>{Decoration(SpvDecorationNonReadable, {}, 2),
+                              Decoration(SpvDecorationOffset, {2}, 2),
+                              Decoration(SpvDecorationBufferBlock)}));
 }
 
 TEST_F(ValidateDecorations, ValidateOpMemberDecorateOutOfBound) {
@@ -119,7 +120,7 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("Index 1 provided in OpMemberDecorate for struct <id> "
-                        "2[%_struct_2] is out of bounds. The structure has 1 "
+                        "'2[%_struct_2]' is out of bounds. The structure has 1 "
                         "members. Largest valid index is 0."));
 }
 
@@ -151,9 +152,9 @@
 
   // Decoration group has 3 decorations.
   auto expected_decorations =
-      std::vector<Decoration>{Decoration(SpvDecorationDescriptorSet, {0}),
-                              Decoration(SpvDecorationRelaxedPrecision),
-                              Decoration(SpvDecorationRestrict)};
+      std::set<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.
@@ -181,7 +182,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
   // Decoration group has 1 decoration.
   auto expected_decorations =
-      std::vector<Decoration>{Decoration(SpvDecorationOffset, {3}, 3)};
+      std::set<Decoration>{Decoration(SpvDecorationOffset, {3}, 3)};
 
   // Decoration group is applied to id 2, 3, and 4.
   EXPECT_THAT(vstate_->id_decorations(2), Eq(expected_decorations));
@@ -226,6 +227,7 @@
                OpCapability Shader
                OpCapability Linkage
                OpMemoryModel Logical GLSL450
+               OpDecorate %_struct_1 Block
                OpMemberDecorate %_struct_1 0 BuiltIn Position
                OpMemberDecorate %_struct_1 1 BuiltIn Position
                OpMemberDecorate %_struct_1 2 BuiltIn Position
@@ -243,6 +245,7 @@
                OpCapability Shader
                OpCapability Linkage
                OpMemoryModel Logical GLSL450
+               OpDecorate %_struct_1 Block
                OpMemberDecorate %_struct_1 0 BuiltIn Position
                OpMemberDecorate %_struct_1 1 BuiltIn Position
       %float = OpTypeFloat 32
@@ -265,6 +268,7 @@
                OpCapability Shader
                OpCapability Linkage
                OpMemoryModel Logical GLSL450
+               OpDecorate %_struct_1 Block
                OpMemberDecorate %_struct_1 0 BuiltIn Position
                OpMemberDecorate %_struct_1 1 BuiltIn Position
                OpMemberDecorate %_struct_1 2 BuiltIn Position
@@ -276,12 +280,13 @@
   )";
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Structure <id> 1[%_struct_1] contains members with "
-                        "BuiltIn decoration. Therefore this structure may not "
-                        "be contained as a member of another structure type. "
-                        "Structure <id> 4[%_struct_4] contains structure <id> "
-                        "1[%_struct_1]."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Structure <id> '1[%_struct_1]' contains members with "
+                "BuiltIn decoration. Therefore this structure may not "
+                "be contained as a member of another structure type. "
+                "Structure <id> '4[%_struct_4]' contains structure <id> "
+                "'1[%_struct_1]'."));
 }
 
 TEST_F(ValidateDecorations, StructContainsNonBuiltInStructGood) {
@@ -305,6 +310,8 @@
                OpEntryPoint Geometry %main "main" %in_1 %in_2
                OpExecutionMode %main InputPoints
                OpExecutionMode %main OutputPoints
+               OpDecorate %struct_1 Block
+               OpDecorate %struct_2 Block
                OpMemberDecorate %struct_1 0 BuiltIn InvocationId
                OpMemberDecorate %struct_2 0 BuiltIn Position
       %int = OpTypeInt 32 1
@@ -339,6 +346,8 @@
                OpEntryPoint Geometry %main "main" %in_1 %out_1
                OpExecutionMode %main InputPoints
                OpExecutionMode %main OutputPoints
+               OpDecorate %struct_1 Block
+               OpDecorate %struct_2 Block
                OpMemberDecorate %struct_1 0 BuiltIn InvocationId
                OpMemberDecorate %struct_2 0 BuiltIn Position
       %int = OpTypeInt 32 1
@@ -560,6 +569,8 @@
   CompileSuccessfully(spirv, env);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState(env));
   EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Location-04915"));
+  EXPECT_THAT(getDiagnosticString(),
               HasSubstr("A BuiltIn variable (id 2) cannot have any Location or "
                         "Component decorations"));
 }
@@ -594,10 +605,354 @@
   CompileSuccessfully(spirv, env);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState(env));
   EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Location-04915"));
+  EXPECT_THAT(getDiagnosticString(),
               HasSubstr("A BuiltIn variable (id 2) cannot have any Location or "
                         "Component decorations"));
 }
 
+TEST_F(ValidateDecorations, LocationDecorationOnNumericTypeBad) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %fragCoord
+               OpExecutionMode %main OriginUpperLeft
+               OpDecorate %fragCoord Location 0
+               OpDecorate %v4float Location 1
+       %void = OpTypeVoid
+      %voidfn = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%ptr_v4float = OpTypePointer Output %v4float
+  %fragCoord = OpVariable %ptr_v4float Output
+%non_interface = OpVariable %ptr_v4float Output
+       %main = OpFunction %void None %voidfn
+      %label = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState(env));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Location decoration on target <id> '3[%v4float]' must "
+                        "be a variable"));
+}
+
+TEST_F(ValidateDecorations, LocationDecorationOnStructBad) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %fragCoord
+               OpExecutionMode %main OriginUpperLeft
+               OpDecorate %fragCoord Location 0
+               OpDecorate %struct Location 1
+       %void = OpTypeVoid
+      %voidfn = OpTypeFunction %void
+      %float = OpTypeFloat 32
+     %struct = OpTypeStruct %float
+    %v4float = OpTypeVector %float 4
+%ptr_v4float = OpTypePointer Output %v4float
+  %fragCoord = OpVariable %ptr_v4float Output
+%non_interface = OpVariable %ptr_v4float Output
+       %main = OpFunction %void None %voidfn
+      %label = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState(env));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Location decoration on target <id> '3[%_struct_3]' "
+                        "must be a variable"));
+}
+
+TEST_F(ValidateDecorations,
+       LocationDecorationUnusedNonInterfaceVariableVulkan_Ignored) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %fragCoord
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpDecorate %fragCoord Location 0
+               OpDecorate %non_interface Location 1
+       %void = OpTypeVoid
+      %voidfn = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%ptr_v4float = OpTypePointer Output %v4float
+  %fragCoord = OpVariable %ptr_v4float Output
+%non_interface = OpVariable %ptr_v4float Output
+       %main = OpFunction %void None %voidfn
+      %label = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(env));
+  EXPECT_EQ(getDiagnosticString(), "");
+}
+
+TEST_F(ValidateDecorations,
+       LocationDecorationNonInterfaceStructVulkan_Ignored) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv = R"(
+              OpCapability Shader
+              OpMemoryModel Logical GLSL450
+              OpEntryPoint Fragment %main "main" %fragCoord
+              OpExecutionMode %main OriginUpperLeft
+              OpDecorate %fragCoord Location 0
+              OpMemberDecorate %block 0 Location 2
+              OpMemberDecorate %block 0 Component 1
+              OpDecorate %block Block
+      %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+     %float = OpTypeFloat 32
+      %vec3 = OpTypeVector %float 3
+%outvar_ptr = OpTypePointer Output %vec3
+ %fragCoord = OpVariable %outvar_ptr Output
+     %block = OpTypeStruct %vec3
+ %invar_ptr = OpTypePointer Input %block
+%non_interface = OpVariable %invar_ptr Input
+      %main = OpFunction %void None %voidfn
+     %label = OpLabel
+              OpReturn
+              OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(env));
+  EXPECT_EQ(getDiagnosticString(), "");
+}
+
+TEST_F(ValidateDecorations, LocationDecorationNonInterfaceStructVulkanGood) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv = R"(
+              OpCapability Shader
+              OpMemoryModel Logical GLSL450
+              OpEntryPoint Fragment %main "main" %fragCoord %interface
+              OpExecutionMode %main OriginUpperLeft
+              OpDecorate %fragCoord Location 0
+              OpMemberDecorate %block 0 Location 2
+              OpMemberDecorate %block 0 Component 1
+              OpDecorate %block Block
+      %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+     %float = OpTypeFloat 32
+      %vec3 = OpTypeVector %float 3
+%outvar_ptr = OpTypePointer Output %vec3
+ %fragCoord = OpVariable %outvar_ptr Output
+     %block = OpTypeStruct %vec3
+ %invar_ptr = OpTypePointer Input %block
+ %interface = OpVariable %invar_ptr Input ;; this variable is unused. Ignore it
+      %main = OpFunction %void None %voidfn
+     %label = OpLabel
+              OpReturn
+              OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(env));
+}
+
+TEST_F(ValidateDecorations, LocationDecorationVariableNonStructVulkanBad) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %fragCoord %nonblock_var
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpDecorate %fragCoord Location 0
+       %void = OpTypeVoid
+      %voidfn = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%ptr_v4float = OpTypePointer Output %v4float
+  %fragCoord = OpVariable %ptr_v4float Output
+%nonblock_var = OpVariable %ptr_v4float Output
+       %main = OpFunction %void None %voidfn
+       %label = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateAndRetrieveValidationState(env));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Location-04916"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Variable must be decorated with a location"));
+}
+
+TEST_F(ValidateDecorations, LocationDecorationVariableStructNoBlockVulkanBad) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %fragCoord %block_var
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpDecorate %fragCoord Location 0
+       %void = OpTypeVoid
+      %voidfn = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%ptr_v4float = OpTypePointer Output %v4float
+  %fragCoord = OpVariable %ptr_v4float Output
+      %block = OpTypeStruct %v4float
+  %block_ptr = OpTypePointer Output %block
+  %block_var = OpVariable %block_ptr Output
+       %main = OpFunction %void None %voidfn
+       %label = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateAndRetrieveValidationState(env));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Location-04917"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Variable must be decorated with a location"));
+}
+
+TEST_F(ValidateDecorations, LocationDecorationVariableNoBlockVulkanGood) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %fragCoord %block_var
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpDecorate %fragCoord Location 0
+               OpDecorate %block_var Location 1
+       %void = OpTypeVoid
+      %voidfn = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%ptr_v4float = OpTypePointer Output %v4float
+  %fragCoord = OpVariable %ptr_v4float Output
+      %block = OpTypeStruct %v4float
+  %block_ptr = OpTypePointer Output %block
+  %block_var = OpVariable %block_ptr Output
+       %main = OpFunction %void None %voidfn
+       %label = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
+}
+
+TEST_F(ValidateDecorations, LocationDecorationVariableExtraMemeberVulkan) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %fragCoord %block_var
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpDecorate %fragCoord Location 0
+               OpDecorate %block Block
+               OpDecorate %block_var Location 1
+               OpMemberDecorate %block 0 Location 1
+       %void = OpTypeVoid
+      %voidfn = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%ptr_v4float = OpTypePointer Output %v4float
+  %fragCoord = OpVariable %ptr_v4float Output
+      %block = OpTypeStruct %v4float
+  %block_ptr = OpTypePointer Output %block
+  %block_var = OpVariable %block_ptr Output
+       %main = OpFunction %void None %voidfn
+       %label = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+)";
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateAndRetrieveValidationState(env));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Location-04918"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Members cannot be assigned a location"));
+}
+
+TEST_F(ValidateDecorations, LocationDecorationVariableMissingMemeberVulkan) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %fragCoord %block_var
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpDecorate %fragCoord Location 0
+               OpDecorate %block Block
+               OpMemberDecorate %block 0 Location 1
+       %void = OpTypeVoid
+      %voidfn = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%ptr_v4float = OpTypePointer Output %v4float
+  %fragCoord = OpVariable %ptr_v4float Output
+      %block = OpTypeStruct %v4float %v4float
+  %block_ptr = OpTypePointer Output %block
+  %block_var = OpVariable %block_ptr Output
+       %main = OpFunction %void None %voidfn
+       %label = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateAndRetrieveValidationState(env));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Location-04919"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Member index 1 is missing a location assignment"));
+}
+
+TEST_F(ValidateDecorations, LocationDecorationVariableOnlyMemeberVulkanGood) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %fragCoord %block_var
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpDecorate %fragCoord Location 0
+               OpDecorate %block Block
+               OpMemberDecorate %block 0 Location 1
+               OpMemberDecorate %block 1 Location 4
+       %void = OpTypeVoid
+      %voidfn = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%ptr_v4float = OpTypePointer Output %v4float
+  %fragCoord = OpVariable %ptr_v4float Output
+      %block = OpTypeStruct %v4float %v4float
+  %block_ptr = OpTypePointer Output %block
+  %block_var = OpVariable %block_ptr Output
+       %main = OpFunction %void None %voidfn
+       %label = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
+}
+
 // #version 440
 // #extension GL_EXT_nonuniform_qualifier : enable
 // layout(binding = 1) uniform sampler2D s2d[];
@@ -2334,6 +2689,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID,
             ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
   EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-PushConstant-06675"));
+  EXPECT_THAT(getDiagnosticString(),
               HasSubstr("In Vulkan, BufferBlock is disallowed on variables in "
                         "the StorageBuffer storage class"));
 }
@@ -2528,8 +2885,10 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID,
             ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
   EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-PushConstant-06675"));
+  EXPECT_THAT(getDiagnosticString(),
               HasSubstr("PushConstant id '2' is missing Block decoration.\n"
-                        "From Vulkan spec, section 14.5.1:\n"
+                        "From Vulkan spec:\n"
                         "Such variables must be identified with a Block "
                         "decoration"));
 }
@@ -2680,11 +3039,13 @@
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID,
             ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpEntryPoint-06674"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
           "Entry point id '1' uses more than one PushConstant interface.\n"
-          "From Vulkan spec, section 14.5.1:\n"
+          "From Vulkan spec:\n"
           "There must be no more than one push constant block "
           "statically used per shader entry point."));
 }
@@ -2791,11 +3152,13 @@
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID,
             ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpEntryPoint-06674"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
           "Entry point id '1' uses more than one PushConstant interface.\n"
-          "From Vulkan spec, section 14.5.1:\n"
+          "From Vulkan spec:\n"
           "There must be no more than one push constant block "
           "statically used per shader entry point."));
 }
@@ -2833,8 +3196,10 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID,
             ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
   EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-UniformConstant-06677"));
+  EXPECT_THAT(getDiagnosticString(),
               HasSubstr("Uniform id '3' is missing DescriptorSet decoration.\n"
-                        "From Vulkan spec, section 14.5.2:\n"
+                        "From Vulkan spec:\n"
                         "These variables must have DescriptorSet and Binding "
                         "decorations specified"));
 }
@@ -2872,8 +3237,10 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID,
             ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
   EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-UniformConstant-06677"));
+  EXPECT_THAT(getDiagnosticString(),
               HasSubstr("Uniform id '3' is missing Binding decoration.\n"
-                        "From Vulkan spec, section 14.5.2:\n"
+                        "From Vulkan spec:\n"
                         "These variables must have DescriptorSet and Binding "
                         "decorations specified"));
 }
@@ -2903,10 +3270,12 @@
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID,
             ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-UniformConstant-06677"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("UniformConstant id '2' is missing DescriptorSet decoration.\n"
-                "From Vulkan spec, section 14.5.2:\n"
+                "From Vulkan spec:\n"
                 "These variables must have DescriptorSet and Binding "
                 "decorations specified"));
 }
@@ -2936,10 +3305,12 @@
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID,
             ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-UniformConstant-06677"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("UniformConstant id '2' is missing Binding decoration.\n"
-                "From Vulkan spec, section 14.5.2:\n"
+                "From Vulkan spec:\n"
                 "These variables must have DescriptorSet and Binding "
                 "decorations specified"));
 }
@@ -2976,10 +3347,12 @@
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID,
             ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-UniformConstant-06677"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("StorageBuffer id '3' is missing DescriptorSet decoration.\n"
-                "From Vulkan spec, section 14.5.2:\n"
+                "From Vulkan spec:\n"
                 "These variables must have DescriptorSet and Binding "
                 "decorations specified"));
 }
@@ -3017,8 +3390,10 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID,
             ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
   EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-UniformConstant-06677"));
+  EXPECT_THAT(getDiagnosticString(),
               HasSubstr("StorageBuffer id '3' is missing Binding decoration.\n"
-                        "From Vulkan spec, section 14.5.2:\n"
+                        "From Vulkan spec:\n"
                         "These variables must have DescriptorSet and Binding "
                         "decorations specified"));
 }
@@ -3061,10 +3436,12 @@
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID,
             ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-UniformConstant-06677"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("StorageBuffer id '3' is missing DescriptorSet decoration.\n"
-                "From Vulkan spec, section 14.5.2:\n"
+                "From Vulkan spec:\n"
                 "These variables must have DescriptorSet and Binding "
                 "decorations specified"));
 }
@@ -4311,7 +4688,7 @@
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Coherent decoration targeting 1[%1] is "
+              HasSubstr("Coherent decoration targeting '1[%1]' is "
                         "banned when using the Vulkan memory model."));
 }
 
@@ -4331,8 +4708,9 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Coherent decoration targeting 1[%_struct_1] (member index 0) "
-                "is banned when using the Vulkan memory model."));
+      HasSubstr(
+          "Coherent decoration targeting '1[%_struct_1]' (member index 0) "
+          "is banned when using the Vulkan memory model."));
 }
 
 TEST_F(ValidateDecorations, VulkanMemoryModelNoVolatile) {
@@ -4352,7 +4730,7 @@
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Volatile decoration targeting 1[%1] is banned when "
+              HasSubstr("Volatile decoration targeting '1[%1]' is banned when "
                         "using the Vulkan memory model."));
 }
 
@@ -4371,7 +4749,7 @@
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Volatile decoration targeting 1[%_struct_1] (member "
+              HasSubstr("Volatile decoration targeting '1[%_struct_1]' (member "
                         "index 1) is banned when using the Vulkan memory "
                         "model."));
 }
@@ -5590,16 +5968,16 @@
 
 TEST_F(ValidateDecorations, PSBAliasedRestrictPointerSuccess) {
   const std::string body = R"(
-OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability PhysicalStorageBufferAddresses
 OpCapability Int64
 OpCapability Shader
 OpExtension "SPV_EXT_physical_storage_buffer"
-OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpMemoryModel PhysicalStorageBuffer64 GLSL450
 OpEntryPoint Fragment %main "main"
 OpExecutionMode %main OriginUpperLeft
-OpDecorate %val1 RestrictPointerEXT
+OpDecorate %val1 RestrictPointer
 %uint64 = OpTypeInt 64 0
-%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%ptr = OpTypePointer PhysicalStorageBuffer %uint64
 %pptr_f = OpTypePointer Function %ptr
 %void = OpTypeVoid
 %voidfn = OpTypeFunction %void
@@ -5616,15 +5994,15 @@
 
 TEST_F(ValidateDecorations, PSBAliasedRestrictPointerMissing) {
   const std::string body = R"(
-OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability PhysicalStorageBufferAddresses
 OpCapability Int64
 OpCapability Shader
 OpExtension "SPV_EXT_physical_storage_buffer"
-OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpMemoryModel PhysicalStorageBuffer64 GLSL450
 OpEntryPoint Fragment %main "main"
 OpExecutionMode %main OriginUpperLeft
 %uint64 = OpTypeInt 64 0
-%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%ptr = OpTypePointer PhysicalStorageBuffer %uint64
 %pptr_f = OpTypePointer Function %ptr
 %void = OpTypeVoid
 %voidfn = OpTypeFunction %void
@@ -5638,23 +6016,23 @@
   CompileSuccessfully(body.c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("expected AliasedPointerEXT or RestrictPointerEXT for "
-                        "PhysicalStorageBufferEXT pointer"));
+              HasSubstr("expected AliasedPointer or RestrictPointer for "
+                        "PhysicalStorageBuffer pointer"));
 }
 
 TEST_F(ValidateDecorations, PSBAliasedRestrictPointerBoth) {
   const std::string body = R"(
-OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability PhysicalStorageBufferAddresses
 OpCapability Int64
 OpCapability Shader
 OpExtension "SPV_EXT_physical_storage_buffer"
-OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpMemoryModel PhysicalStorageBuffer64 GLSL450
 OpEntryPoint Fragment %main "main"
 OpExecutionMode %main OriginUpperLeft
-OpDecorate %val1 RestrictPointerEXT
-OpDecorate %val1 AliasedPointerEXT
+OpDecorate %val1 RestrictPointer
+OpDecorate %val1 AliasedPointer
 %uint64 = OpTypeInt 64 0
-%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%ptr = OpTypePointer PhysicalStorageBuffer %uint64
 %pptr_f = OpTypePointer Function %ptr
 %void = OpTypeVoid
 %voidfn = OpTypeFunction %void
@@ -5667,24 +6045,23 @@
 
   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"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("can't specify both AliasedPointer and RestrictPointer "
+                        "for PhysicalStorageBuffer pointer"));
 }
 
 TEST_F(ValidateDecorations, PSBAliasedRestrictFunctionParamSuccess) {
   const std::string body = R"(
-OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability PhysicalStorageBufferAddresses
 OpCapability Int64
 OpCapability Shader
 OpExtension "SPV_EXT_physical_storage_buffer"
-OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpMemoryModel PhysicalStorageBuffer64 GLSL450
 OpEntryPoint Fragment %main "main"
 OpExecutionMode %main OriginUpperLeft
 OpDecorate %fparam Restrict
 %uint64 = OpTypeInt 64 0
-%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%ptr = OpTypePointer PhysicalStorageBuffer %uint64
 %void = OpTypeVoid
 %voidfn = OpTypeFunction %void
 %fnptr = OpTypeFunction %void %ptr
@@ -5705,15 +6082,15 @@
 
 TEST_F(ValidateDecorations, PSBAliasedRestrictFunctionParamMissing) {
   const std::string body = R"(
-OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability PhysicalStorageBufferAddresses
 OpCapability Int64
 OpCapability Shader
 OpExtension "SPV_EXT_physical_storage_buffer"
-OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpMemoryModel PhysicalStorageBuffer64 GLSL450
 OpEntryPoint Fragment %main "main"
 OpExecutionMode %main OriginUpperLeft
 %uint64 = OpTypeInt 64 0
-%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%ptr = OpTypePointer PhysicalStorageBuffer %uint64
 %void = OpTypeVoid
 %voidfn = OpTypeFunction %void
 %fnptr = OpTypeFunction %void %ptr
@@ -5732,22 +6109,22 @@
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("expected Aliased or Restrict for "
-                        "PhysicalStorageBufferEXT pointer"));
+                        "PhysicalStorageBuffer pointer"));
 }
 
 TEST_F(ValidateDecorations, PSBAliasedRestrictFunctionParamBoth) {
   const std::string body = R"(
-OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability PhysicalStorageBufferAddresses
 OpCapability Int64
 OpCapability Shader
 OpExtension "SPV_EXT_physical_storage_buffer"
-OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpMemoryModel PhysicalStorageBuffer64 GLSL450
 OpEntryPoint Fragment %main "main"
 OpExecutionMode %main OriginUpperLeft
 OpDecorate %fparam Restrict
 OpDecorate %fparam Aliased
 %uint64 = OpTypeInt 64 0
-%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%ptr = OpTypePointer PhysicalStorageBuffer %uint64
 %void = OpTypeVoid
 %voidfn = OpTypeFunction %void
 %fnptr = OpTypeFunction %void %ptr
@@ -5766,12 +6143,12 @@
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("can't specify both Aliased and Restrict for "
-                        "PhysicalStorageBufferEXT pointer"));
+                        "PhysicalStorageBuffer pointer"));
 }
 
 TEST_F(ValidateDecorations, PSBFPRoundingModeSuccess) {
   std::string spirv = R"(
-OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability PhysicalStorageBufferAddresses
 OpCapability Shader
 OpCapability Linkage
 OpCapability StorageBuffer16BitAccess
@@ -5779,14 +6156,14 @@
 OpExtension "SPV_KHR_storage_buffer_storage_class"
 OpExtension "SPV_KHR_variable_pointers"
 OpExtension "SPV_KHR_16bit_storage"
-OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpMemoryModel PhysicalStorageBuffer64 GLSL450
 OpEntryPoint GLCompute %main "main"
 OpDecorate %_ FPRoundingMode RTE
-OpDecorate %half_ptr_var AliasedPointerEXT
+OpDecorate %half_ptr_var AliasedPointer
 %half = OpTypeFloat 16
 %float = OpTypeFloat 32
 %float_1_25 = OpConstant %float 1.25
-%half_ptr = OpTypePointer PhysicalStorageBufferEXT %half
+%half_ptr = OpTypePointer PhysicalStorageBuffer %half
 %half_pptr_f = OpTypePointer Function %half_ptr
 %void = OpTypeVoid
 %func = OpTypeFunction %void
@@ -6806,7 +7183,9 @@
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("From Vulkan spec, section 14.5.2:\nSuch variables "
+              AnyVUID("VUID-StandaloneSpirv-PushConstant-06675"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("From Vulkan spec:\nSuch variables "
                         "must be identified with a Block decoration"));
 }
 
@@ -6834,7 +7213,9 @@
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("From Vulkan spec, section 14.5.2:\nSuch variables "
+              AnyVUID("VUID-StandaloneSpirv-PushConstant-06675"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("From Vulkan spec:\nSuch variables "
                         "must be identified with a Block decoration"));
 }
 
@@ -6863,7 +7244,9 @@
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("From Vulkan spec, section 14.5.2:\nSuch variables "
+              AnyVUID("VUID-StandaloneSpirv-PushConstant-06675"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("From Vulkan spec:\nSuch variables "
                         "must be identified with a Block decoration"));
 }
 
@@ -6935,10 +7318,11 @@
 
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("From Vulkan spec, section 14.5.2:\nSuch variables must be "
-                "identified with a Block or BufferBlock decoration"));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Uniform-06676"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("From Vulkan spec:\nSuch variables must be "
+                        "identified with a Block or BufferBlock decoration"));
 }
 
 TEST_F(ValidateDecorations, VulkanUniformArrayMissingBlock) {
@@ -6963,10 +7347,11 @@
 
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("From Vulkan spec, section 14.5.2:\nSuch variables must be "
-                "identified with a Block or BufferBlock decoration"));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Uniform-06676"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("From Vulkan spec:\nSuch variables must be "
+                        "identified with a Block or BufferBlock decoration"));
 }
 
 TEST_F(ValidateDecorations, VulkanUniformRuntimeArrayMissingBlock) {
@@ -6992,10 +7377,11 @@
 
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("From Vulkan spec, section 14.5.2:\nSuch variables must be "
-                "identified with a Block or BufferBlock decoration"));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Uniform-06676"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("From Vulkan spec:\nSuch variables must be "
+                        "identified with a Block or BufferBlock decoration"));
 }
 
 TEST_F(ValidateDecorations, VulkanArrayStrideZero) {
@@ -7639,6 +8025,7 @@
 OpDecorate %block Block
 OpMemberDecorate %block 0 Offset 0
 OpMemberDecorate %block 0 MatrixStride 3
+OpMemberDecorate %block 0 ColMajor
 OpDecorate %var DescriptorSet 0
 OpDecorate %var Binding 0
 %void = OpTypeVoid
@@ -7675,6 +8062,7 @@
 OpDecorate %block Block
 OpMemberDecorate %block 0 Offset 0
 OpMemberDecorate %block 0 MatrixStride 3
+OpMemberDecorate %block 0 ColMajor
 OpDecorate %var DescriptorSet 0
 OpDecorate %var Binding 0
 %void = OpTypeVoid
@@ -7710,6 +8098,7 @@
 OpDecorate %block Block
 OpMemberDecorate %block 0 Offset 0
 OpMemberDecorate %block 0 MatrixStride 3
+OpMemberDecorate %block 0 ColMajor
 OpDecorate %var DescriptorSet 0
 OpDecorate %var Binding 0
 %void = OpTypeVoid
@@ -7746,6 +8135,7 @@
 OpDecorate %block Block
 OpMemberDecorate %block 0 Offset 0
 OpMemberDecorate %block 0 MatrixStride 3
+OpMemberDecorate %block 0 RowMajor
 OpDecorate %var DescriptorSet 0
 OpDecorate %var Binding 0
 %void = OpTypeVoid
@@ -7837,6 +8227,785 @@
                         "Offset decorations"));
 }
 
+TEST_F(ValidateDecorations, PerVertexVulkanGood) {
+  const std::string spirv = R"(
+               OpCapability Shader
+               OpCapability FragmentBarycentricKHR
+               OpExtension "SPV_KHR_fragment_shader_barycentric"
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %vertexIDs
+               OpExecutionMode %main OriginUpperLeft
+               OpDecorate %vertexIDs Location 0
+               OpDecorate %vertexIDs PerVertexKHR
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+      %float = OpTypeFloat 32
+       %uint = OpTypeInt 32 0
+%ptrFloat = OpTypePointer Input %float
+     %uint_3 = OpConstant %uint 3
+%floatArray = OpTypeArray %float %uint_3
+%ptrFloatArray = OpTypePointer Input %floatArray
+  %vertexIDs = OpVariable %ptrFloatArray Input
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %main = OpFunction %void None %func
+      %label = OpLabel
+     %access = OpAccessChain %ptrFloat %vertexIDs %int_0
+       %load = OpLoad %float %access
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateDecorations, PerVertexVulkanOutput) {
+  const std::string spirv = R"(
+               OpCapability Shader
+               OpCapability FragmentBarycentricKHR
+               OpExtension "SPV_KHR_fragment_shader_barycentric"
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %vertexIDs
+               OpExecutionMode %main OriginUpperLeft
+               OpDecorate %vertexIDs Location 0
+               OpDecorate %vertexIDs PerVertexKHR
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+      %float = OpTypeFloat 32
+       %uint = OpTypeInt 32 0
+%ptrFloat = OpTypePointer Output %float
+     %uint_3 = OpConstant %uint 3
+%floatArray = OpTypeArray %float %uint_3
+%ptrFloatArray = OpTypePointer Output %floatArray
+  %vertexIDs = OpVariable %ptrFloatArray Output
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %main = OpFunction %void None %func
+      %label = OpLabel
+     %access = OpAccessChain %ptrFloat %vertexIDs %int_0
+       %load = OpLoad %float %access
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-PerVertexKHR-06777"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("storage class must be Input"));
+}
+
+TEST_F(ValidateDecorations, PerVertexVulkanNonFragment) {
+  const std::string spirv = R"(
+               OpCapability Shader
+               OpCapability FragmentBarycentricKHR
+               OpExtension "SPV_KHR_fragment_shader_barycentric"
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %vertexIDs
+               OpDecorate %vertexIDs Location 0
+               OpDecorate %vertexIDs PerVertexKHR
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+      %float = OpTypeFloat 32
+       %uint = OpTypeInt 32 0
+%ptrFloat = OpTypePointer Input %float
+     %uint_3 = OpConstant %uint 3
+%floatArray = OpTypeArray %float %uint_3
+%ptrFloatArray = OpTypePointer Input %floatArray
+  %vertexIDs = OpVariable %ptrFloatArray Input
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %main = OpFunction %void None %func
+      %label = OpLabel
+     %access = OpAccessChain %ptrFloat %vertexIDs %int_0
+       %load = OpLoad %float %access
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-PerVertexKHR-06777"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "PerVertexKHR can only be applied to Fragment Execution Models"));
+}
+
+TEST_F(ValidateDecorations, PerVertexVulkanNonArray) {
+  const std::string spirv = R"(
+               OpCapability Shader
+               OpCapability FragmentBarycentricKHR
+               OpExtension "SPV_KHR_fragment_shader_barycentric"
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %vertexIDs
+               OpExecutionMode %main OriginUpperLeft
+               OpDecorate %vertexIDs Location 0
+               OpDecorate %vertexIDs PerVertexKHR
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+      %float = OpTypeFloat 32
+   %ptrFloat = OpTypePointer Input %float
+  %vertexIDs = OpVariable %ptrFloat Input
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %main = OpFunction %void None %func
+      %label = OpLabel
+       %load = OpLoad %float %vertexIDs
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Input-06778"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("PerVertexKHR must be declared as arrays"));
+}
+
+TEST_F(ValidateDecorations, RelaxedPrecisionDecorationOnNumericTypeBad) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main"
+               OpExecutionMode %main OriginUpperLeft
+               OpDecorate %float RelaxedPrecision
+       %void = OpTypeVoid
+      %voidfn = OpTypeFunction %void
+      %float = OpTypeFloat 32
+       %main = OpFunction %void None %voidfn
+      %label = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState(env));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("RelaxPrecision decoration cannot be applied to a type"));
+}
+
+TEST_F(ValidateDecorations, RelaxedPrecisionDecorationOnStructMember) {
+  const spv_target_env env = SPV_ENV_VULKAN_1_0;
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main"
+               OpExecutionMode %main OriginUpperLeft
+               OpMemberDecorate %struct 0 RelaxedPrecision
+       %void = OpTypeVoid
+     %voidfn = OpTypeFunction %void
+      %float = OpTypeFloat 32
+     %struct = OpTypeStruct %float
+       %main = OpFunction %void None %voidfn
+      %label = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, env);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(env));
+}
+
+TEST_F(ValidateDecorations, VulkanFlatMultipleInterfaceGood) {
+  std::string spirv = R"(
+               OpCapability Shader
+               OpCapability Geometry
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %layer %gl_Layer
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpDecorate %layer Location 0
+               OpDecorate %gl_Layer Flat
+               OpDecorate %gl_Layer BuiltIn Layer
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+%_ptr_Output_int = OpTypePointer Output %int
+      %layer = OpVariable %_ptr_Output_int Output
+%_ptr_Input_int = OpTypePointer Input %int
+   %gl_Layer = OpVariable %_ptr_Input_int Input
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %11 = OpLoad %int %gl_Layer
+               OpStore %layer %11
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_SUCCESS,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateDecorations, VulkanFlatMultipleInterfaceBad) {
+  std::string spirv = R"(
+               OpCapability Shader
+               OpCapability Geometry
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %layer %gl_Layer
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpDecorate %layer Location 0
+               OpDecorate %gl_Layer BuiltIn Layer
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+%_ptr_Output_int = OpTypePointer Output %int
+      %layer = OpVariable %_ptr_Output_int Output
+%_ptr_Input_int = OpTypePointer Input %int
+   %gl_Layer = OpVariable %_ptr_Input_int Input
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %11 = OpLoad %int %gl_Layer
+               OpStore %layer %11
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Flat-04744"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Fragment OpEntryPoint operand 4 with Input interfaces with integer "
+          "or float type must have a Flat decoration for Entry Point id 2."));
+}
+
+TEST_F(ValidateDecorations, VulkanNoFlatFloat32) {
+  std::string spirv = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %in
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpDecorate %in Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Input_float = OpTypePointer Input %float
+         %in = OpVariable %_ptr_Input_float Input
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+          %b = OpVariable %_ptr_Function_float Function
+         %11 = OpLoad %float %in
+               OpStore %b %11
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_SUCCESS,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateDecorations, VulkanNoFlatFloat64) {
+  std::string spirv = R"(
+               OpCapability Shader
+               OpCapability Float64
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %in
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpDecorate %in Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+     %double = OpTypeFloat 64
+%_ptr_Function_double = OpTypePointer Function %double
+%_ptr_Input_double = OpTypePointer Input %double
+         %in = OpVariable %_ptr_Input_double Input
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+          %b = OpVariable %_ptr_Function_double Function
+         %11 = OpLoad %double %in
+               OpStore %b %11
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Flat-04744"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Fragment OpEntryPoint operand 3 with Input interfaces with integer "
+          "or float type must have a Flat decoration for Entry Point id 2."));
+}
+
+TEST_F(ValidateDecorations, VulkanNoFlatVectorFloat64) {
+  std::string spirv = R"(
+               OpCapability Shader
+               OpCapability Float64
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %in
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpDecorate %in Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+     %double = OpTypeFloat 64
+   %v2double = OpTypeVector %double 2
+%_ptr_Function_v2double = OpTypePointer Function %v2double
+%_ptr_Input_v2double = OpTypePointer Input %v2double
+         %in = OpVariable %_ptr_Input_v2double Input
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+          %b = OpVariable %_ptr_Function_v2double Function
+         %11 = OpLoad %v2double %in
+               OpStore %b %11
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_SUCCESS,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateDecorations, VulkanNoFlatIntVector) {
+  std::string spirv = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %in
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpDecorate %in Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+      %v2int = OpTypeVector %int 2
+%_ptr_Function_v2int = OpTypePointer Function %v2int
+%_ptr_Input_v2int = OpTypePointer Input %v2int
+         %in = OpVariable %_ptr_Input_v2int Input
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+          %b = OpVariable %_ptr_Function_v2int Function
+         %12 = OpLoad %v2int %in
+               OpStore %b %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID,
+            ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Flat-04744"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Fragment OpEntryPoint operand 3 with Input interfaces with integer "
+          "or float type must have a Flat decoration for Entry Point id 2."));
+}
+
+TEST_P(ValidateDecorationString, VulkanOutputInvalidInterface) {
+  const std::string decoration = GetParam();
+  std::stringstream ss;
+  ss << R"(
+               OpCapability Shader
+               OpCapability SampleRateShading
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %out
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpDecorate %out )"
+     << decoration << R"(
+               OpDecorate %out Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+%_ptr_Output_int = OpTypePointer Output %int
+        %out = OpVariable %_ptr_Output_int Output
+      %int_1 = OpConstant %int 1
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpStore %out %int_1
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(ss.str(), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Flat-06201"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpEntryPoint interfaces variable must not be fragment execution "
+          "model with an output storage class for Entry Point id 2."));
+}
+
+TEST_P(ValidateDecorationString, VulkanVertexInputInvalidInterface) {
+  const std::string decoration = GetParam();
+  std::stringstream ss;
+  ss << R"(
+               OpCapability Shader
+               OpCapability SampleRateShading
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %out %in
+               OpSource GLSL 450
+               OpDecorate %in )"
+     << decoration << R"(
+               OpDecorate %out Location 0
+               OpDecorate %in Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+%_ptr_Output_int = OpTypePointer Output %int
+          %out = OpVariable %_ptr_Output_int Output
+%_ptr_Input_int = OpTypePointer Input %int
+          %in = OpVariable %_ptr_Input_int Input
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %11 = OpLoad %int %in
+               OpStore %out %11
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(ss.str(), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Flat-06202"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpEntryPoint interfaces variable must not be vertex execution "
+                "model with an input storage class for Entry Point id 2."));
+}
+
+INSTANTIATE_TEST_SUITE_P(FragmentInputInterface, ValidateDecorationString,
+                         ::testing::Values("Flat", "NoPerspective", "Sample",
+                                           "Centroid"));
+
+TEST_F(ValidateDecorations, NVBindlessSamplerArrayInBlock) {
+  const std::string spirv = R"(
+               OpCapability Shader
+               OpCapability BindlessTextureNV
+               OpExtension "SPV_NV_bindless_texture"
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpSamplerImageAddressingModeNV 64
+               OpEntryPoint Fragment %main "main"
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpName %main "main"
+               OpName %UBO "UBO"
+               OpMemberName %UBO 0 "uboSampler"
+               OpName %_ ""
+               OpDecorate %array ArrayStride 16
+               OpMemberDecorate %UBO 0 Offset 0
+               OpDecorate %UBO Block
+               OpDecorate %_ DescriptorSet 0
+               OpDecorate %_ Binding 2
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+          %7 = OpTypeImage %float 2D 0 0 0 1 Unknown
+          %8 = OpTypeSampledImage %7
+       %uint = OpTypeInt 32 0
+     %uint_3 = OpConstant %uint 3
+      %array = OpTypeArray %8 %uint_3
+        %UBO = OpTypeStruct %array
+    %pointer = OpTypePointer Uniform %UBO
+          %_ = OpVariable %pointer Uniform
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateDecorations, Std140ColMajorMat2x2) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %block Block
+OpMemberDecorate %block 0 Offset 0
+OpMemberDecorate %block 0 ColMajor
+OpMemberDecorate %block 0 MatrixStride 8
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%float2 = OpTypeVector %float 2
+%matrix = OpTypeMatrix %float2 2
+%block = OpTypeStruct %matrix
+%ptr_block = OpTypePointer Uniform %block
+%var = OpVariable %ptr_block Uniform
+%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(
+          "member 0 is a matrix with stride 8 not satisfying alignment to 16"));
+}
+
+TEST_F(ValidateDecorations, Std140RowMajorMat2x2) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %block Block
+OpMemberDecorate %block 0 Offset 0
+OpMemberDecorate %block 0 RowMajor
+OpMemberDecorate %block 0 MatrixStride 8
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%float2 = OpTypeVector %float 2
+%matrix = OpTypeMatrix %float2 2
+%block = OpTypeStruct %matrix
+%ptr_block = OpTypePointer Uniform %block
+%var = OpVariable %ptr_block Uniform
+%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(
+          "member 0 is a matrix with stride 8 not satisfying alignment to 16"));
+}
+
+TEST_F(ValidateDecorations, Std140ColMajorMat4x2) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %block Block
+OpMemberDecorate %block 0 Offset 0
+OpMemberDecorate %block 0 ColMajor
+OpMemberDecorate %block 0 MatrixStride 8
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%float2 = OpTypeVector %float 2
+%matrix = OpTypeMatrix %float2 4
+%block = OpTypeStruct %matrix
+%ptr_block = OpTypePointer Uniform %block
+%var = OpVariable %ptr_block Uniform
+%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(
+          "member 0 is a matrix with stride 8 not satisfying alignment to 16"));
+}
+
+TEST_F(ValidateDecorations, Std140ColMajorMat2x3) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %block Block
+OpMemberDecorate %block 0 Offset 0
+OpMemberDecorate %block 0 ColMajor
+OpMemberDecorate %block 0 MatrixStride 12
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%float3 = OpTypeVector %float 3
+%matrix = OpTypeMatrix %float3 2
+%block = OpTypeStruct %matrix
+%ptr_block = OpTypePointer Uniform %block
+%var = OpVariable %ptr_block Uniform
+%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("member 0 is a matrix with stride 12 not satisfying "
+                        "alignment to 16"));
+}
+
+TEST_F(ValidateDecorations, MatrixMissingMajornessUniform) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %block Block
+OpMemberDecorate %block 0 Offset 0
+OpMemberDecorate %block 0 MatrixStride 16
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%float2 = OpTypeVector %float 2
+%matrix = OpTypeMatrix %float2 2
+%block = OpTypeStruct %matrix
+%ptr_block = OpTypePointer Uniform %block
+%var = OpVariable %ptr_block Uniform
+%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(
+          "must be explicitly laid out with RowMajor or ColMajor decorations"));
+}
+
+TEST_F(ValidateDecorations, MatrixMissingMajornessStorageBuffer) {
+  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 %block Block
+OpMemberDecorate %block 0 Offset 0
+OpMemberDecorate %block 0 MatrixStride 16
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%float2 = OpTypeVector %float 2
+%matrix = OpTypeMatrix %float2 2
+%block = OpTypeStruct %matrix
+%ptr_block = OpTypePointer StorageBuffer %block
+%var = OpVariable %ptr_block StorageBuffer
+%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(
+          "must be explicitly laid out with RowMajor or ColMajor decorations"));
+}
+
+TEST_F(ValidateDecorations, MatrixMissingMajornessPushConstant) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %block Block
+OpMemberDecorate %block 0 Offset 0
+OpMemberDecorate %block 0 MatrixStride 16
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%float2 = OpTypeVector %float 2
+%matrix = OpTypeMatrix %float2 2
+%block = OpTypeStruct %matrix
+%ptr_block = OpTypePointer PushConstant %block
+%var = OpVariable %ptr_block PushConstant
+%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(
+          "must be explicitly laid out with RowMajor or ColMajor decorations"));
+}
+
+TEST_F(ValidateDecorations, StructWithRowAndColMajor) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %block Block
+OpMemberDecorate %block 0 Offset 0
+OpMemberDecorate %block 0 MatrixStride 16
+OpMemberDecorate %block 0 ColMajor
+OpMemberDecorate %block 1 Offset 32
+OpMemberDecorate %block 1 MatrixStride 16
+OpMemberDecorate %block 1 RowMajor
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%float2 = OpTypeVector %float 2
+%matrix = OpTypeMatrix %float2 2
+%block = OpTypeStruct %matrix %matrix
+%ptr_block = OpTypePointer PushConstant %block
+%var = OpVariable %ptr_block PushConstant
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_derivatives_test.cpp b/test/val/val_derivatives_test.cpp
index 0a84661..e605f3a 100644
--- a/test/val/val_derivatives_test.cpp
+++ b/test/val/val_derivatives_test.cpp
@@ -130,7 +130,7 @@
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 10[%v4float] cannot "
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand '10[%v4float]' cannot "
                                                "be a type"));
 }
 
diff --git a/test/val/val_entry_point.cpp b/test/val/val_entry_point_test.cpp
similarity index 100%
rename from test/val/val_entry_point.cpp
rename to test/val/val_entry_point_test.cpp
diff --git a/test/val/val_ext_inst_debug_test.cpp b/test/val/val_ext_inst_debug_test.cpp
index 307a800..554e78b 100644
--- a/test/val/val_ext_inst_debug_test.cpp
+++ b/test/val/val_ext_inst_debug_test.cpp
@@ -1547,7 +1547,7 @@
                         "integer scalar type"));
 }
 
-TEST_F(ValidateVulkan100DebugInfo, DebugTypeArrayFailComponentCountZero) {
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeArrayComponentCountZero) {
   const std::string src = R"(
 %src = OpString "simple.hlsl"
 %code = OpString "main() {}"
@@ -1574,12 +1574,7 @@
 
   CompileSuccessfully(GenerateShaderCodeForDebugInfo(
       src, constants, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Component Count must be OpConstant with a 32- or "
-                        "64-bits integer scalar type or DebugGlobalVariable or "
-                        "DebugLocalVariable with a 32- or 64-bits unsigned "
-                        "integer scalar type"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
 TEST_F(ValidateVulkan100DebugInfo, DebugTypeArrayFailVariableSizeTypeFloat) {
@@ -1869,6 +1864,178 @@
                         "integer less than or equal to 4"));
 }
 
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeMatrix) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+%true = OpConstantTrue %bool
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%vfloat_info = OpExtInst %void %DbgExt DebugTypeVector %float_info %u32_4
+%mfloat_info = OpExtInst %void %DbgExt DebugTypeMatrix %vfloat_info %u32_4 %true
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeMatrixFailVectorTypeType) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+%true = OpConstantTrue %bool
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%vfloat_info = OpExtInst %void %DbgExt DebugTypeVector %float_info %u32_4
+%mfloat_info = OpExtInst %void %DbgExt DebugTypeMatrix %dbg_src %u32_4 %true
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Vector Type must be a result id of "
+                        "DebugTypeVector"));
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeMatrixFailVectorCountType) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+%true = OpConstantTrue %bool
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%vfloat_info = OpExtInst %void %DbgExt DebugTypeVector %float_info %u32_4
+%mfloat_info = OpExtInst %void %DbgExt DebugTypeMatrix %vfloat_info %dbg_src %true
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Vector Count must be a result id of "
+                        "32-bit unsigned OpConstant"));
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeMatrixFailVectorCountZero) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+%true = OpConstantTrue %bool
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%vfloat_info = OpExtInst %void %DbgExt DebugTypeVector %float_info %u32_4
+%mfloat_info = OpExtInst %void %DbgExt DebugTypeMatrix %vfloat_info %u32_0 %true
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Vector Count must be positive "
+                        "integer less than or equal to 4"));
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeMatrixFailVectorCountFive) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+%true = OpConstantTrue %bool
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%vfloat_info = OpExtInst %void %DbgExt DebugTypeVector %float_info %u32_4
+%mfloat_info = OpExtInst %void %DbgExt DebugTypeMatrix %vfloat_info %u32_5 %true
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Vector Count must be positive "
+                        "integer less than or equal to 4"));
+}
+
 TEST_F(ValidateOpenCL100DebugInfo, DebugTypedef) {
   const std::string src = R"(
 %src = OpString "simple.hlsl"
diff --git a/test/val/val_ext_inst_test.cpp b/test/val/val_ext_inst_test.cpp
index 2b6df04..e685acd 100644
--- a/test/val/val_ext_inst_test.cpp
+++ b/test/val/val_ext_inst_test.cpp
@@ -4321,7 +4321,7 @@
   CompileSuccessfully(GenerateKernelCode(ss.str()));
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Operand 89[%_ptr_Workgroup_half] cannot be a type"));
+              HasSubstr("Operand '89[%_ptr_Workgroup_half]' cannot be a type"));
 }
 
 TEST_P(ValidateOpenCLStdVStoreHalfLike, ConstPointer) {
@@ -4493,7 +4493,7 @@
   CompileSuccessfully(GenerateKernelCode(ss.str()));
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Operand 89[%_ptr_Workgroup_half] cannot be a type"));
+              HasSubstr("Operand '89[%_ptr_Workgroup_half]' cannot be a type"));
 }
 
 TEST_P(ValidateOpenCLStdVLoadHalfLike, OffsetWrongStorageType) {
@@ -4664,9 +4664,10 @@
 
   CompileSuccessfully(GenerateKernelCode(ss.str()));
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Operand 120[%_ptr_UniformConstant_float] cannot be a "
-                        "type"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Operand '120[%_ptr_UniformConstant_float]' cannot be a "
+                "type"));
 }
 
 TEST_F(ValidateExtInst, VLoadNWrongStorageClass) {
@@ -4779,7 +4780,7 @@
   CompileSuccessfully(GenerateKernelCode(ss.str()));
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Operand 114[%_ptr_UniformConstant_half] cannot be a "
+              HasSubstr("Operand '114[%_ptr_UniformConstant_half]' cannot be a "
                         "type"));
 }
 
@@ -4933,7 +4934,7 @@
   CompileSuccessfully(GenerateKernelCode(ss.str()));
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Operand 127[%_ptr_Generic_float] cannot be a type"));
+              HasSubstr("Operand '127[%_ptr_Generic_float]' cannot be a type"));
 }
 
 TEST_F(ValidateExtInst, VStoreNWrongStorageClass) {
@@ -5248,9 +5249,10 @@
 
   CompileSuccessfully(GenerateKernelCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Operand 137[%_ptr_UniformConstant_uchar] cannot be a "
-                        "type"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Operand '137[%_ptr_UniformConstant_uchar]' cannot be a "
+                "type"));
 }
 
 TEST_F(ValidateExtInst, OpenCLStdPrintfFormatNotUniformConstStorageClass) {
@@ -5342,7 +5344,7 @@
   CompileSuccessfully(GenerateKernelCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Operand 99[%_ptr_CrossWorkgroup_uint] cannot be a "
+              HasSubstr("Operand '99[%_ptr_CrossWorkgroup_uint]' cannot be a "
                         "type"));
 }
 
@@ -6045,6 +6047,18 @@
                         "declared without SPV_KHR_non_semantic_info"));
 }
 
+TEST_F(ValidateClspvReflection, DoesNotRequiresNonSemanticExtensionPost1p6) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "NonSemantic.ClspvReflection.1"
+OpMemoryModel Logical GLSL450
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_6);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_6));
+}
+
 TEST_F(ValidateClspvReflection, MissingVersion) {
   const std::string text = R"(
 OpCapability Shader
diff --git a/test/val/val_extension_spv_khr_bit_instructions.cpp b/test/val/val_extension_spv_khr_bit_instructions_test.cpp
similarity index 100%
rename from test/val/val_extension_spv_khr_bit_instructions.cpp
rename to test/val/val_extension_spv_khr_bit_instructions_test.cpp
diff --git a/test/val/val_extension_spv_khr_expect_assume.cpp b/test/val/val_extension_spv_khr_expect_assume_test.cpp
similarity index 100%
rename from test/val/val_extension_spv_khr_expect_assume.cpp
rename to test/val/val_extension_spv_khr_expect_assume_test.cpp
diff --git a/test/val/val_extension_spv_khr_integer_dot_product.cpp b/test/val/val_extension_spv_khr_integer_dot_product_test.cpp
similarity index 100%
rename from test/val/val_extension_spv_khr_integer_dot_product.cpp
rename to test/val/val_extension_spv_khr_integer_dot_product_test.cpp
diff --git a/test/val/val_extension_spv_khr_linkonce_odr.cpp b/test/val/val_extension_spv_khr_linkonce_odr_test.cpp
similarity index 100%
rename from test/val/val_extension_spv_khr_linkonce_odr.cpp
rename to test/val/val_extension_spv_khr_linkonce_odr_test.cpp
diff --git a/test/val/val_extension_spv_khr_subgroup_rotate_test.cpp b/test/val/val_extension_spv_khr_subgroup_rotate_test.cpp
new file mode 100644
index 0000000..4f156e8
--- /dev/null
+++ b/test/val/val_extension_spv_khr_subgroup_rotate_test.cpp
@@ -0,0 +1,352 @@
+// Copyright (c) 2022 The Khronos Group 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.
+
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "test/val/val_fixtures.h"
+
+namespace spvtools {
+namespace val {
+namespace {
+
+using ::testing::HasSubstr;
+using ::testing::Values;
+using ::testing::ValuesIn;
+
+struct Case {
+  std::vector<std::string> caps;
+  bool shader;
+  std::string result_type;
+  std::string scope;
+  std::string delta;
+  std::string cluster_size;
+  std::string expected_error;  // empty for no error.
+};
+
+inline std::ostream& operator<<(std::ostream& out, Case c) {
+  out << "\nSPV_KHR_subgroup_rotate Case{{";
+  for (auto& cap : c.caps) {
+    out << cap;
+  }
+  out << "} ";
+  out << (c.shader ? "shader " : "kernel ");
+  out << c.result_type + " ";
+  out << c.scope + " ";
+  out << c.delta + " ";
+  out << c.cluster_size + " ";
+  out << "err'" << c.expected_error << "'";
+  out << "}";
+  return out;
+}
+
+std::string AssemblyForCase(const Case& c) {
+  std::ostringstream ss;
+
+  if (c.shader) {
+    ss << "OpCapability Shader\n";
+  } else {
+    ss << "OpCapability Kernel\n";
+    ss << "OpCapability Addresses\n";
+  }
+  for (auto& cap : c.caps) {
+    ss << "OpCapability " << cap << "\n";
+  }
+  ss << "OpExtension \"SPV_KHR_subgroup_rotate\"\n";
+
+  if (c.shader) {
+    ss << "OpMemoryModel Logical GLSL450\n";
+    ss << "OpEntryPoint GLCompute %main \"main\"\n";
+  } else {
+    ss << "OpMemoryModel Physical32 OpenCL\n";
+    ss << "OpEntryPoint Kernel %main \"main\"\n";
+  }
+
+  ss << R"(
+    %void    = OpTypeVoid
+    %void_fn = OpTypeFunction %void
+    %u32 = OpTypeInt 32 0
+    %float = OpTypeFloat 32
+    %ptr = OpTypePointer Function %u32
+  )";
+
+  if (c.shader) {
+    ss << "%i32 = OpTypeInt 32 1\n";
+  }
+
+  ss << R"(
+    %u32_0 = OpConstant %u32 0
+    %u32_1 = OpConstant %u32 1
+    %u32_15 = OpConstant %u32 15
+    %u32_16 = OpConstant %u32 16
+    %u32_undef = OpUndef %u32
+    %u32_spec_1 = OpSpecConstant %u32 1
+    %u32_spec_16 = OpSpecConstant %u32 16
+    %f32_1 = OpConstant %float 1.0
+    %subgroup = OpConstant %u32 3
+    %workgroup = OpConstant %u32 2
+    %invalid_scope = OpConstant %u32 1
+    %val = OpConstant %u32 42
+  )";
+
+  if (c.shader) {
+    ss << "%i32_1 = OpConstant %i32 1\n";
+  }
+
+  ss << R"(
+    %main = OpFunction %void None %void_fn
+    %entry = OpLabel
+  )";
+
+  ss << "%unused = OpGroupNonUniformRotateKHR ";
+  ss << c.result_type + " ";
+  ss << c.scope;
+  ss << " %val ";
+  ss << c.delta;
+  ss << " " + c.cluster_size;
+  ss << "\n";
+
+  ss << R"(
+    OpReturn
+    OpFunctionEnd
+  )";
+
+  return ss.str();
+}
+
+using ValidateSpvKHRSubgroupRotate = spvtest::ValidateBase<Case>;
+
+TEST_P(ValidateSpvKHRSubgroupRotate, Base) {
+  const auto& c = GetParam();
+  const auto& assembly = AssemblyForCase(c);
+  CompileSuccessfully(assembly);
+  if (c.expected_error.empty()) {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
+  } else {
+    EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(c.expected_error));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    Valid, ValidateSpvKHRSubgroupRotate,
+    ::testing::Values(
+        Case{
+            {"GroupNonUniformRotateKHR"}, false, "%u32", "%subgroup", "%u32_1"},
+        Case{{"GroupNonUniformRotateKHR"}, true, "%u32", "%subgroup", "%u32_1"},
+        Case{{"GroupNonUniformRotateKHR"},
+             false,
+             "%u32",
+             "%subgroup",
+             "%u32_1",
+             "%u32_16"},
+        Case{{"GroupNonUniformRotateKHR"},
+             true,
+             "%u32",
+             "%subgroup",
+             "%u32_1",
+             "%u32_16"},
+        Case{{"GroupNonUniformRotateKHR"},
+             false,
+             "%u32",
+             "%subgroup",
+             "%u32_spec_1",
+             "%u32_16"},
+        Case{{"GroupNonUniformRotateKHR"},
+             true,
+             "%u32",
+             "%subgroup",
+             "%u32_1",
+             "%u32_spec_16"},
+        Case{{"GroupNonUniformRotateKHR"},
+             false,
+             "%u32",
+             "%workgroup",
+             "%u32_1"},
+        Case{
+            {"GroupNonUniformRotateKHR"}, true, "%u32", "%workgroup", "%u32_1"},
+        Case{{"GroupNonUniformRotateKHR"},
+             false,
+             "%u32",
+             "%workgroup",
+             "%u32_spec_1"},
+        Case{{"GroupNonUniformRotateKHR"},
+             true,
+             "%u32",
+             "%workgroup",
+             "%u32_spec_1"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    RequiresCapability, ValidateSpvKHRSubgroupRotate,
+    ::testing::Values(Case{{},
+                           false,
+                           "%u32",
+                           "%subgroup",
+                           "%u32_1",
+                           "",
+                           "Opcode GroupNonUniformRotateKHR requires one of "
+                           "these capabilities: "
+                           "GroupNonUniformRotateKHR"},
+                      Case{{},
+                           true,
+                           "%u32",
+                           "%subgroup",
+                           "%u32_1",
+                           "",
+                           "Opcode GroupNonUniformRotateKHR requires one of "
+                           "these capabilities: "
+                           "GroupNonUniformRotateKHR"}));
+
+TEST_F(ValidateSpvKHRSubgroupRotate, RequiresExtension) {
+  const std::string str = R"(
+    OpCapability GroupNonUniformRotateKHR
+)";
+  CompileSuccessfully(str.c_str());
+  EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "1st operand of Capability: operand GroupNonUniformRotateKHR(6026) "
+          "requires one of these extensions: SPV_KHR_subgroup_rotate"));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    InvalidExecutionScope, ValidateSpvKHRSubgroupRotate,
+    ::testing::Values(
+        Case{{"GroupNonUniformRotateKHR"},
+             false,
+             "%u32",
+             "%invalid_scope",
+             "%u32_1",
+             "",
+             "Execution scope is limited to Subgroup or Workgroup"},
+        Case{{"GroupNonUniformRotateKHR"},
+             true,
+             "%u32",
+             "%invalid_scope",
+             "%u32_1",
+             "",
+             "Execution scope is limited to Subgroup or Workgroup"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    InvalidResultType, ValidateSpvKHRSubgroupRotate,
+    ::testing::Values(Case{{"GroupNonUniformRotateKHR"},
+                           false,
+                           "%ptr",
+                           "%subgroup",
+                           "%u32_1",
+                           "",
+                           "Expected Result Type to be a scalar or vector of "
+                           "floating-point, integer or boolean type"},
+                      Case{{"GroupNonUniformRotateKHR"},
+                           true,
+                           "%ptr",
+                           "%subgroup",
+                           "%u32_1",
+                           "",
+                           "Expected Result Type to be a scalar or vector of "
+                           "floating-point, integer or boolean type"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    MismatchedResultAndValueTypes, ValidateSpvKHRSubgroupRotate,
+    ::testing::Values(
+        Case{{"GroupNonUniformRotateKHR"},
+             false,
+             "%float",
+             "%subgroup",
+             "%u32_1",
+             "",
+             "Result Type must be the same as the type of Value"},
+        Case{{"GroupNonUniformRotateKHR"},
+             true,
+             "%float",
+             "%subgroup",
+             "%u32_1",
+             "",
+             "Result Type must be the same as the type of Value"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    InvalidDelta, ValidateSpvKHRSubgroupRotate,
+    ::testing::Values(Case{{"GroupNonUniformRotateKHR"},
+                           false,
+                           "%u32",
+                           "%subgroup",
+                           "%f32_1",
+                           "",
+                           "Delta must be a scalar of integer type, whose "
+                           "Signedness operand is 0"},
+                      Case{{"GroupNonUniformRotateKHR"},
+                           true,
+                           "%u32",
+                           "%subgroup",
+                           "%f32_1",
+                           "",
+                           "Delta must be a scalar of integer type, whose "
+                           "Signedness operand is 0"},
+                      Case{{"GroupNonUniformRotateKHR"},
+                           true,
+                           "%u32",
+                           "%subgroup",
+                           "%i32_1",
+                           "",
+                           "Delta must be a scalar of integer type, whose "
+                           "Signedness operand is 0"}));
+
+INSTANTIATE_TEST_SUITE_P(
+    InvalidClusterSize, ValidateSpvKHRSubgroupRotate,
+    ::testing::Values(
+        Case{{"GroupNonUniformRotateKHR"},
+             false,
+             "%u32",
+             "%subgroup",
+             "%u32_1",
+             "%f32_1",
+             "ClusterSize must be a scalar of integer type, whose Signedness "
+             "operand is 0"},
+        Case{{"GroupNonUniformRotateKHR"},
+             true,
+             "%u32",
+             "%subgroup",
+             "%u32_1",
+             "%i32_1",
+             "ClusterSize must be a scalar of integer type, whose Signedness "
+             "operand is 0"},
+        Case{{"GroupNonUniformRotateKHR"},
+             true,
+             "%u32",
+             "%subgroup",
+             "%u32_1",
+             "%u32_0",
+             "Behavior is undefined unless ClusterSize is at least 1 and a "
+             "power of 2"},
+        Case{{"GroupNonUniformRotateKHR"},
+             true,
+             "%u32",
+             "%subgroup",
+             "%u32_1",
+             "%u32_15",
+             "Behavior is undefined unless ClusterSize is at least 1 and a "
+             "power of 2"},
+        Case{{"GroupNonUniformRotateKHR"},
+             true,
+             "%u32",
+             "%subgroup",
+             "%u32_1",
+             "%u32_undef",
+             "ClusterSize must come from a constant instruction"}));
+
+}  // namespace
+}  // namespace val
+}  // namespace spvtools
diff --git a/test/val/val_extension_spv_khr_subgroup_uniform_control_flow.cpp b/test/val/val_extension_spv_khr_subgroup_uniform_control_flow_test.cpp
similarity index 100%
rename from test/val/val_extension_spv_khr_subgroup_uniform_control_flow.cpp
rename to test/val/val_extension_spv_khr_subgroup_uniform_control_flow_test.cpp
diff --git a/test/val/val_extension_spv_khr_terminate_invocation.cpp b/test/val/val_extension_spv_khr_terminate_invocation_test.cpp
similarity index 82%
rename from test/val/val_extension_spv_khr_terminate_invocation.cpp
rename to test/val/val_extension_spv_khr_terminate_invocation_test.cpp
index 4cabf9e..8d92414 100644
--- a/test/val/val_extension_spv_khr_terminate_invocation.cpp
+++ b/test/val/val_extension_spv_khr_terminate_invocation_test.cpp
@@ -55,7 +55,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateSpvKHRTerminateInvocation, RequiresExtension) {
+TEST_F(ValidateSpvKHRTerminateInvocation, RequiresExtensionPre1p6) {
   const std::string str = R"(
     OpCapability Shader
     OpMemoryModel Logical Simple
@@ -72,9 +72,30 @@
 )";
   CompileSuccessfully(str.c_str());
   EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("TerminateInvocation requires one of the following "
-                        "extensions: SPV_KHR_terminate_invocation"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "TerminateInvocation requires SPIR-V version 1.6 at minimum or one "
+          "of the following extensions: SPV_KHR_terminate_invocation"));
+}
+
+TEST_F(ValidateSpvKHRTerminateInvocation, RequiresNoExtensionPost1p6) {
+  const std::string str = R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint Fragment %main "main"
+    OpExecutionMode %main OriginUpperLeft
+    
+    %void    = OpTypeVoid
+    %void_fn = OpTypeFunction %void
+
+    %main = OpFunction %void None %void_fn
+    %entry = OpLabel
+    OpTerminateInvocation
+    OpFunctionEnd
+)";
+  CompileSuccessfully(str.c_str(), SPV_ENV_UNIVERSAL_1_6);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_6));
 }
 
 TEST_F(ValidateSpvKHRTerminateInvocation, RequiresShaderCapability) {
diff --git a/test/val/val_fixtures.h b/test/val/val_fixtures.h
index acbe0e5..98d8d32 100644
--- a/test/val/val_fixtures.h
+++ b/test/val/val_fixtures.h
@@ -40,8 +40,10 @@
 
   // Assembles the given SPIR-V text, checks that it fails to assemble,
   // and returns resulting diagnostic.  No internal state is updated.
+  // Setting the desired_result to SPV_SUCCESS is used to allow all results
   std::string CompileFailure(std::string code,
-                             spv_target_env env = SPV_ENV_UNIVERSAL_1_0);
+                             spv_target_env env = SPV_ENV_UNIVERSAL_1_0,
+                             spv_result_t desired_result = SPV_SUCCESS);
 
   // Checks that 'code' is valid SPIR-V text representation and stores the
   // binary version for further method calls.
@@ -108,11 +110,17 @@
 
 template <typename T>
 std::string ValidateBase<T>::CompileFailure(std::string code,
-                                            spv_target_env env) {
+                                            spv_target_env env,
+                                            spv_result_t desired_result) {
   spv_diagnostic diagnostic = nullptr;
-  EXPECT_NE(SPV_SUCCESS,
-            spvTextToBinary(ScopedContext(env).context, code.c_str(),
-                            code.size(), &binary_, &diagnostic));
+  spv_result_t actual_result =
+      spvTextToBinary(ScopedContext(env).context, code.c_str(), code.size(),
+                      &binary_, &diagnostic);
+  EXPECT_NE(SPV_SUCCESS, actual_result);
+  // optional check for exact result
+  if (desired_result != SPV_SUCCESS) {
+    EXPECT_EQ(actual_result, desired_result);
+  }
   std::string result(diagnostic->error);
   spvDiagnosticDestroy(diagnostic);
   return result;
diff --git a/test/val/val_function_test.cpp b/test/val/val_function_test.cpp
index af0199a..e7d5cd7 100644
--- a/test/val/val_function_test.cpp
+++ b/test/val/val_function_test.cpp
@@ -181,13 +181,14 @@
   } 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"));
+      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]"));
+          HasSubstr("Invalid storage class for pointer operand '1[%var]'"));
     }
   }
 }
@@ -211,8 +212,9 @@
     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]"));
+    EXPECT_THAT(
+        getDiagnosticString(),
+        HasSubstr("Invalid storage class for pointer operand '1[%var]'"));
   }
 }
 
@@ -235,8 +237,9 @@
     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]"));
+    EXPECT_THAT(
+        getDiagnosticString(),
+        HasSubstr("Invalid storage class for pointer operand '1[%var]'"));
   }
 }
 
@@ -258,11 +261,12 @@
     EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
     if (storage_class == "StorageBuffer") {
       EXPECT_THAT(getDiagnosticString(),
-                  HasSubstr("StorageBuffer pointer operand 1[%p] requires a "
+                  HasSubstr("StorageBuffer pointer operand '1[%p]' requires a "
                             "variable pointers capability"));
     } else {
-      EXPECT_THAT(getDiagnosticString(),
-                  HasSubstr("Invalid storage class for pointer operand 1[%p]"));
+      EXPECT_THAT(
+          getDiagnosticString(),
+          HasSubstr("Invalid storage class for pointer operand '1[%p]'"));
     }
   }
 }
@@ -287,7 +291,7 @@
   } else {
     EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
     EXPECT_THAT(getDiagnosticString(),
-                HasSubstr("Invalid storage class for pointer operand 1[%p]"));
+                HasSubstr("Invalid storage class for pointer operand '1[%p]'"));
   }
 }
 
@@ -311,7 +315,7 @@
   } else {
     EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
     EXPECT_THAT(getDiagnosticString(),
-                HasSubstr("Invalid storage class for pointer operand 1[%p]"));
+                HasSubstr("Invalid storage class for pointer operand '1[%p]'"));
   }
 }
 
@@ -334,16 +338,17 @@
     EXPECT_THAT(
         getDiagnosticString(),
         HasSubstr(
-            "Pointer operand 2[%gep] must be a memory object declaration"));
+            "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"));
+      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]"));
+          HasSubstr("Invalid storage class for pointer operand '2[%gep]'"));
     }
   }
 }
@@ -373,11 +378,11 @@
       EXPECT_THAT(
           getDiagnosticString(),
           HasSubstr(
-              "Pointer operand 2[%gep] must be a memory object declaration"));
+              "Pointer operand '2[%gep]' must be a memory object declaration"));
     } else {
       EXPECT_THAT(
           getDiagnosticString(),
-          HasSubstr("Invalid storage class for pointer operand 2[%gep]"));
+          HasSubstr("Invalid storage class for pointer operand '2[%gep]'"));
     }
   }
 }
@@ -407,11 +412,11 @@
       EXPECT_THAT(
           getDiagnosticString(),
           HasSubstr(
-              "Pointer operand 2[%gep] must be a memory object declaration"));
+              "Pointer operand '2[%gep]' must be a memory object declaration"));
     } else {
       EXPECT_THAT(
           getDiagnosticString(),
-          HasSubstr("Invalid storage class for pointer operand 2[%gep]"));
+          HasSubstr("Invalid storage class for pointer operand '2[%gep]'"));
     }
   }
 }
diff --git a/test/val/val_id_test.cpp b/test/val/val_id_test.cpp
index ac05749..dd040b3 100644
--- a/test/val/val_id_test.cpp
+++ b/test/val/val_id_test.cpp
@@ -35,7 +35,15 @@
 using ::testing::HasSubstr;
 using ::testing::ValuesIn;
 
-using ValidateIdWithMessage = spvtest::ValidateBase<bool>;
+class ValidateIdWithMessage : public spvtest::ValidateBase<bool> {
+ public:
+  ValidateIdWithMessage() {
+    const bool use_friendly_names = GetParam();
+    spvValidatorOptionsSetFriendlyNames(options_, use_friendly_names);
+  }
+
+  std::string make_message(const char* msg);
+};
 
 std::string kOpCapabilitySetupWithoutVector16 = R"(
      OpCapability Shader
@@ -177,9 +185,61 @@
                OpFunctionEnd
 )";
 
+// Transform an expected validation message to either use friendly names (as
+// provided in the message) or replace the friendly names by the corresponding
+// id.  The same flag used to configure the validator to output friendly names
+// or not is used here.
+std::string ValidateIdWithMessage::make_message(const char* msg) {
+  const bool use_friendly_names = GetParam();
+  if (use_friendly_names) {
+    return msg;
+  }
+
+  std::string message(msg);
+  std::ostringstream result;
+
+  size_t next = 0;
+  while (next < message.size()) {
+    // Parse 'num[%name]'
+    size_t open_quote = message.find('\'', next);
+
+    // Copy up to the first quote
+    result.write(msg + next, open_quote - next);
+    if (open_quote == std::string::npos) {
+      break;
+    }
+    // Handle apostrophes
+    if (!isdigit(message[open_quote + 1])) {
+      result << '\'';
+      next = open_quote + 1;
+      continue;
+    }
+
+    size_t open_bracket = message.find('[', open_quote + 1);
+    assert(open_bracket != std::string::npos);
+
+    size_t close_bracket = message.find(']', open_bracket + 1);
+    assert(close_bracket != std::string::npos);
+
+    size_t close_quote = close_bracket + 1;
+    assert(close_quote < message.size() && message[close_quote] == '\'');
+
+    // Change to 'num[%num]' because friendly names are not being used.
+    result.write(msg + open_quote, open_bracket - open_quote + 1);
+    result << '%';
+    result.write(msg + open_quote + 1, open_bracket - open_quote - 1);
+    result << "]'";
+
+    // Continue to the next id, or end of string.
+    next = close_quote + 1;
+  }
+
+  return result.str();
+}
+
 // TODO: OpUndef
 
-TEST_F(ValidateIdWithMessage, OpName) {
+TEST_P(ValidateIdWithMessage, OpName) {
   std::string spirv = kGLSL450MemoryModel + R"(
      OpName %2 "name"
 %1 = OpTypeInt 32 0
@@ -189,7 +249,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpMemberNameGood) {
+TEST_P(ValidateIdWithMessage, OpMemberNameGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
      OpMemberName %2 0 "foo"
 %1 = OpTypeInt 32 0
@@ -197,30 +257,30 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpMemberNameTypeBad) {
+TEST_P(ValidateIdWithMessage, OpMemberNameTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
      OpMemberName %1 0 "foo"
 %1 = OpTypeInt 32 0)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("OpMemberName Type <id> '1[%uint]' is not a struct type."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "OpMemberName Type <id> '1[%uint]' is not a struct type.")));
 }
-TEST_F(ValidateIdWithMessage, OpMemberNameMemberBad) {
+TEST_P(ValidateIdWithMessage, OpMemberNameMemberBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
      OpMemberName %1 1 "foo"
 %2 = OpTypeInt 32 0
 %1 = OpTypeStruct %2)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("OpMemberName Member <id> '1[%_struct_1]' index is larger "
-                "than Type <id> '1[%_struct_1]'s member count."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "OpMemberName Member <id> '1[%_struct_1]' index is larger "
+                  "than Type <id> '1[%_struct_1]'s member count.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpLineGood) {
+TEST_P(ValidateIdWithMessage, OpLineGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpString "/path/to/source.file"
      OpLine %1 0 0
@@ -231,7 +291,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpLineFileBad) {
+TEST_P(ValidateIdWithMessage, OpLineFileBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
   %1 = OpTypeInt 32 0
      OpLine %1 0 0
@@ -239,10 +299,11 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpLine Target <id> '1[%uint]' is not an OpString."));
+              HasSubstr(make_message(
+                  "OpLine Target <id> '1[%uint]' is not an OpString.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpDecorateGood) {
+TEST_P(ValidateIdWithMessage, OpDecorateGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
      OpDecorate %2 GLSLShared
 %1 = OpTypeInt 64 0
@@ -250,16 +311,17 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpDecorateBad) {
+TEST_P(ValidateIdWithMessage, OpDecorateBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 OpDecorate %1 GLSLShared)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("forward referenced IDs have not been defined"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("forward referenced IDs have not been defined")));
 }
 
-TEST_F(ValidateIdWithMessage, OpMemberDecorateGood) {
+TEST_P(ValidateIdWithMessage, OpMemberDecorateGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
      OpMemberDecorate %2 0 RelaxedPrecision
 %1 = OpTypeInt 32 0
@@ -267,17 +329,18 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpMemberDecorateBad) {
+TEST_P(ValidateIdWithMessage, OpMemberDecorateBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
      OpMemberDecorate %1 0 RelaxedPrecision
 %1 = OpTypeInt 32 0)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpMemberDecorate Structure type <id> '1[%uint]' is "
-                        "not a struct type."));
+              HasSubstr(make_message(
+                  "OpMemberDecorate Structure type <id> '1[%uint]' is "
+                  "not a struct type.")));
 }
-TEST_F(ValidateIdWithMessage, OpMemberDecorateMemberBad) {
+TEST_P(ValidateIdWithMessage, OpMemberDecorateMemberBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
      OpMemberDecorate %1 3 RelaxedPrecision
 %int = OpTypeInt 32 0
@@ -285,12 +348,13 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Index 3 provided in OpMemberDecorate for struct <id> "
-                        "1[%_struct_1] is out of bounds. The structure has 2 "
-                        "members. Largest valid index is 1."));
+              HasSubstr(make_message(
+                  "Index 3 provided in OpMemberDecorate for struct <id> "
+                  "'1[%_struct_1]' is out of bounds. The structure has 2 "
+                  "members. Largest valid index is 1.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpGroupDecorateGood) {
+TEST_P(ValidateIdWithMessage, OpGroupDecorateGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpDecorationGroup
      OpDecorate %1 RelaxedPrecision
@@ -302,7 +366,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpDecorationGroupBad) {
+TEST_P(ValidateIdWithMessage, OpDecorationGroupBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpDecorationGroup
      OpDecorate %1 RelaxedPrecision
@@ -312,11 +376,12 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Result id of OpDecorationGroup can only "
-                        "be targeted by OpName, OpGroupDecorate, "
-                        "OpDecorate, OpDecorateId, and OpGroupMemberDecorate"));
+              HasSubstr(make_message(
+                  "Result id of OpDecorationGroup can only "
+                  "be targeted by OpName, OpGroupDecorate, "
+                  "OpDecorate, OpDecorateId, and OpGroupMemberDecorate")));
 }
-TEST_F(ValidateIdWithMessage, OpGroupDecorateDecorationGroupBad) {
+TEST_P(ValidateIdWithMessage, OpGroupDecorateDecorationGroupBad) {
   std::string spirv = R"(
     OpCapability Shader
     OpCapability Linkage
@@ -328,10 +393,11 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpGroupDecorate Decoration group <id> '1[%1]' is not "
-                        "a decoration group."));
+              HasSubstr(make_message(
+                  "OpGroupDecorate Decoration group <id> '1[%1]' is not "
+                  "a decoration group.")));
 }
-TEST_F(ValidateIdWithMessage, OpGroupDecorateTargetBad) {
+TEST_P(ValidateIdWithMessage, OpGroupDecorateTargetBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpDecorationGroup
      OpDecorate %1 RelaxedPrecision
@@ -340,10 +406,11 @@
 %2 = OpTypeInt 32 0)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("forward referenced IDs have not been defined"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("forward referenced IDs have not been defined")));
 }
-TEST_F(ValidateIdWithMessage, OpGroupMemberDecorateDecorationGroupBad) {
+TEST_P(ValidateIdWithMessage, OpGroupMemberDecorateDecorationGroupBad) {
   std::string spirv = R"(
     OpCapability Shader
     OpCapability Linkage
@@ -354,10 +421,11 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpGroupMemberDecorate Decoration group <id> '1[%1]' "
-                        "is not a decoration group."));
+              HasSubstr(make_message(
+                  "OpGroupMemberDecorate Decoration group <id> '1[%1]' "
+                  "is not a decoration group.")));
 }
-TEST_F(ValidateIdWithMessage, OpGroupMemberDecorateIdNotStructBad) {
+TEST_P(ValidateIdWithMessage, OpGroupMemberDecorateIdNotStructBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
      %1 = OpDecorationGroup
      OpGroupMemberDecorate %1 %2 0
@@ -365,10 +433,11 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpGroupMemberDecorate Structure type <id> '2[%uint]' "
-                        "is not a struct type."));
+              HasSubstr(make_message(
+                  "OpGroupMemberDecorate Structure type <id> '2[%uint]' "
+                  "is not a struct type.")));
 }
-TEST_F(ValidateIdWithMessage, OpGroupMemberDecorateIndexOutOfBoundBad) {
+TEST_P(ValidateIdWithMessage, OpGroupMemberDecorateIndexOutOfBoundBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
   OpDecorate %1 Offset 0
   %1 = OpDecorationGroup
@@ -379,14 +448,15 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Index 3 provided in OpGroupMemberDecorate for struct "
-                        "<id> 2[%_struct_2] is out of bounds. The structure "
-                        "has 3 members. Largest valid index is 2."));
+              HasSubstr(make_message(
+                  "Index 3 provided in OpGroupMemberDecorate for struct "
+                  "<id> '2[%_struct_2]' is out of bounds. The structure "
+                  "has 3 members. Largest valid index is 2.")));
 }
 
 // TODO: OpExtInst
 
-TEST_F(ValidateIdWithMessage, OpEntryPointGood) {
+TEST_P(ValidateIdWithMessage, OpEntryPointGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
      OpEntryPoint GLCompute %3 ""
 %1 = OpTypeVoid
@@ -399,17 +469,18 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpEntryPointFunctionBad) {
+TEST_P(ValidateIdWithMessage, OpEntryPointFunctionBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
      OpEntryPoint GLCompute %1 ""
 %1 = OpTypeVoid)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpEntryPoint Entry Point <id> '1[%void]' is not a "
-                        "function."));
+              HasSubstr(make_message(
+                  "OpEntryPoint Entry Point <id> '1[%void]' is not a "
+                  "function.")));
 }
-TEST_F(ValidateIdWithMessage, OpEntryPointParameterCountBad) {
+TEST_P(ValidateIdWithMessage, OpEntryPointParameterCountBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
      OpEntryPoint GLCompute %1 ""
 %2 = OpTypeVoid
@@ -420,11 +491,12 @@
      OpFunctionEnd)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpEntryPoint Entry Point <id> '1[%1]'s function "
-                        "parameter count is not zero"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("OpEntryPoint Entry Point <id> '1[%1]'s function "
+                             "parameter count is not zero")));
 }
-TEST_F(ValidateIdWithMessage, OpEntryPointReturnTypeBad) {
+TEST_P(ValidateIdWithMessage, OpEntryPointReturnTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
      OpEntryPoint GLCompute %1 ""
 %2 = OpTypeInt 32 0
@@ -436,11 +508,12 @@
      OpFunctionEnd)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpEntryPoint Entry Point <id> '1[%1]'s function "
-                        "return type is not void."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("OpEntryPoint Entry Point <id> '1[%1]'s function "
+                             "return type is not void.")));
 }
-TEST_F(ValidateIdWithMessage, OpEntryPointParameterCountBadInVulkan) {
+TEST_P(ValidateIdWithMessage, OpEntryPointParameterCountBadInVulkan) {
   std::string spirv = R"(
      OpCapability Shader
      OpMemoryModel Logical GLSL450
@@ -455,11 +528,12 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
               AnyVUID("VUID-StandaloneSpirv-None-04633"));
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpEntryPoint Entry Point <id> '1[%1]'s function "
-                        "parameter count is not zero"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("OpEntryPoint Entry Point <id> '1[%1]'s function "
+                             "parameter count is not zero")));
 }
-TEST_F(ValidateIdWithMessage, OpEntryPointReturnTypeBadInVulkan) {
+TEST_P(ValidateIdWithMessage, OpEntryPointReturnTypeBadInVulkan) {
   std::string spirv = R"(
      OpCapability Shader
      OpMemoryModel Logical GLSL450
@@ -475,12 +549,13 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
               AnyVUID("VUID-StandaloneSpirv-None-04633"));
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpEntryPoint Entry Point <id> '1[%1]'s function "
-                        "return type is not void."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("OpEntryPoint Entry Point <id> '1[%1]'s function "
+                             "return type is not void.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpEntryPointInterfaceIsNotVariableTypeBad) {
+TEST_P(ValidateIdWithMessage, OpEntryPointInterfaceIsNotVariableTypeBad) {
   std::string spirv = R"(
                OpCapability Shader
                OpCapability Geometry
@@ -502,11 +577,12 @@
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Interfaces passed to OpEntryPoint must be of type "
-                        "OpTypeVariable. Found OpTypePointer."));
+              HasSubstr(make_message(
+                  "Interfaces passed to OpEntryPoint must be of type "
+                  "OpTypeVariable. Found OpTypePointer.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpEntryPointInterfaceStorageClassBad) {
+TEST_P(ValidateIdWithMessage, OpEntryPointInterfaceStorageClassBad) {
   std::string spirv = R"(
                OpCapability Shader
                OpCapability Geometry
@@ -529,12 +605,13 @@
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpEntryPoint interfaces must be OpVariables with "
-                        "Storage Class of Input(1) or Output(3). Found Storage "
-                        "Class 2 for Entry Point id 1."));
+              HasSubstr(make_message(
+                  "OpEntryPoint interfaces must be OpVariables with "
+                  "Storage Class of Input(1) or Output(3). Found Storage "
+                  "Class 2 for Entry Point id 1.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpExecutionModeGood) {
+TEST_P(ValidateIdWithMessage, OpExecutionModeGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
      OpEntryPoint GLCompute %3 ""
      OpExecutionMode %3 LocalSize 1 1 1
@@ -548,7 +625,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpExecutionModeEntryPointMissing) {
+TEST_P(ValidateIdWithMessage, OpExecutionModeEntryPointMissing) {
   std::string spirv = kGLSL450MemoryModel + R"(
      OpExecutionMode %3 LocalSize 1 1 1
 %1 = OpTypeVoid
@@ -560,11 +637,12 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpExecutionMode Entry Point <id> '1[%1]' is not the "
-                        "Entry Point operand of an OpEntryPoint."));
+              HasSubstr(make_message(
+                  "OpExecutionMode Entry Point <id> '1[%1]' is not the "
+                  "Entry Point operand of an OpEntryPoint.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpExecutionModeEntryPointBad) {
+TEST_P(ValidateIdWithMessage, OpExecutionModeEntryPointBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
      OpEntryPoint GLCompute %3 "" %a
      OpExecutionMode %a LocalSize 1 1 1
@@ -579,11 +657,12 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpExecutionMode Entry Point <id> '2[%2]' is not the "
-                        "Entry Point operand of an OpEntryPoint."));
+              HasSubstr(make_message(
+                  "OpExecutionMode Entry Point <id> '2[%2]' is not the "
+                  "Entry Point operand of an OpEntryPoint.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpTypeVectorFloat) {
+TEST_P(ValidateIdWithMessage, OpTypeVectorFloat) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeFloat 32
 %2 = OpTypeVector %1 4)";
@@ -591,7 +670,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpTypeVectorInt) {
+TEST_P(ValidateIdWithMessage, OpTypeVectorInt) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpTypeVector %1 4)";
@@ -599,7 +678,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpTypeVectorUInt) {
+TEST_P(ValidateIdWithMessage, OpTypeVectorUInt) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 64 0
 %2 = OpTypeVector %1 4)";
@@ -607,7 +686,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpTypeVectorBool) {
+TEST_P(ValidateIdWithMessage, OpTypeVectorBool) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeBool
 %2 = OpTypeVector %1 4)";
@@ -615,20 +694,20 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpTypeVectorComponentTypeBad) {
+TEST_P(ValidateIdWithMessage, OpTypeVectorComponentTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeFloat 32
 %2 = OpTypePointer UniformConstant %1
 %3 = OpTypeVector %2 4)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("OpTypeVector Component Type <id> "
-                "'2[%_ptr_UniformConstant_float]' is not a scalar type."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "OpTypeVector Component Type <id> "
+                  "'2[%_ptr_UniformConstant_float]' is not a scalar type.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpTypeVectorColumnCountLessThanTwoBad) {
+TEST_P(ValidateIdWithMessage, OpTypeVectorColumnCountLessThanTwoBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeFloat 32
 %2 = OpTypeVector %1 1)";
@@ -636,11 +715,12 @@
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Illegal number of components (1) for TypeVector\n  %v1float = "
-                "OpTypeVector %float 1\n"));
+      HasSubstr(make_message(
+          "Illegal number of components (1) for TypeVector\n  %v1float = "
+          "OpTypeVector %float 1\n")));
 }
 
-TEST_F(ValidateIdWithMessage, OpTypeVectorColumnCountGreaterThanFourBad) {
+TEST_P(ValidateIdWithMessage, OpTypeVectorColumnCountGreaterThanFourBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeFloat 32
 %2 = OpTypeVector %1 5)";
@@ -648,24 +728,25 @@
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Illegal number of components (5) for TypeVector\n  %v5float = "
-                "OpTypeVector %float 5\n"));
+      HasSubstr(make_message(
+          "Illegal number of components (5) for TypeVector\n  %v5float = "
+          "OpTypeVector %float 5\n")));
 }
 
-TEST_F(ValidateIdWithMessage, OpTypeVectorColumnCountEightWithoutVector16Bad) {
+TEST_P(ValidateIdWithMessage, OpTypeVectorColumnCountEightWithoutVector16Bad) {
   std::string spirv = kGLSL450MemoryModelWithoutVector16 + R"(
 %1 = OpTypeFloat 32
 %2 = OpTypeVector %1 8)";
 
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("Having 8 components for TypeVector requires the Vector16 "
-                "capability\n  %v8float = OpTypeVector %float 8\n"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "Having 8 components for TypeVector requires the Vector16 "
+                  "capability\n  %v8float = OpTypeVector %float 8\n")));
 }
 
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        OpTypeVectorColumnCountSixteenWithoutVector16Bad) {
   std::string spirv = kGLSL450MemoryModelWithoutVector16 + R"(
 %1 = OpTypeFloat 32
@@ -673,13 +754,13 @@
 
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("Having 16 components for TypeVector requires the Vector16 "
-                "capability\n  %v16float = OpTypeVector %float 16\n"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "Having 16 components for TypeVector requires the Vector16 "
+                  "capability\n  %v16float = OpTypeVector %float 16\n")));
 }
 
-TEST_F(ValidateIdWithMessage, OpTypeVectorColumnCountOfEightWithVector16Good) {
+TEST_P(ValidateIdWithMessage, OpTypeVectorColumnCountOfEightWithVector16Good) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeFloat 32
 %2 = OpTypeVector %1 8)";
@@ -687,7 +768,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        OpTypeVectorColumnCountOfSixteenWithVector16Good) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeFloat 32
@@ -696,7 +777,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpTypeMatrixGood) {
+TEST_P(ValidateIdWithMessage, OpTypeMatrixGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeFloat 32
 %2 = OpTypeVector %1 2
@@ -705,32 +786,32 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpTypeMatrixColumnTypeNonVectorBad) {
+TEST_P(ValidateIdWithMessage, OpTypeMatrixColumnTypeNonVectorBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeFloat 32
 %2 = OpTypeMatrix %1 3)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("olumns in a matrix must be of type vector.\n  %mat3float = "
-                "OpTypeMatrix %float 3\n"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "olumns in a matrix must be of type vector.\n  %mat3float = "
+                  "OpTypeMatrix %float 3\n")));
 }
 
-TEST_F(ValidateIdWithMessage, OpTypeMatrixVectorTypeNonFloatBad) {
+TEST_P(ValidateIdWithMessage, OpTypeMatrixVectorTypeNonFloatBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 16 0
 %2 = OpTypeVector %1 2
 %3 = OpTypeMatrix %2 2)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("Matrix types can only be parameterized with floating-point "
-                "types.\n  %mat2v2ushort = OpTypeMatrix %v2ushort 2\n"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "Matrix types can only be parameterized with floating-point "
+                  "types.\n  %mat2v2ushort = OpTypeMatrix %v2ushort 2\n")));
 }
 
-TEST_F(ValidateIdWithMessage, OpTypeMatrixColumnCountLessThanTwoBad) {
+TEST_P(ValidateIdWithMessage, OpTypeMatrixColumnCountLessThanTwoBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeFloat 32
 %2 = OpTypeVector %1 2
@@ -739,11 +820,12 @@
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Matrix types can only be parameterized as having only 2, 3, "
-                "or 4 columns.\n  %mat1v2float = OpTypeMatrix %v2float 1\n"));
+      HasSubstr(make_message(
+          "Matrix types can only be parameterized as having only 2, 3, "
+          "or 4 columns.\n  %mat1v2float = OpTypeMatrix %v2float 1\n")));
 }
 
-TEST_F(ValidateIdWithMessage, OpTypeMatrixColumnCountGreaterThanFourBad) {
+TEST_P(ValidateIdWithMessage, OpTypeMatrixColumnCountGreaterThanFourBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeFloat 32
 %2 = OpTypeVector %1 2
@@ -752,11 +834,12 @@
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Matrix types can only be parameterized as having only 2, 3, "
-                "or 4 columns.\n  %mat8v2float = OpTypeMatrix %v2float 8\n"));
+      HasSubstr(make_message(
+          "Matrix types can only be parameterized as having only 2, 3, "
+          "or 4 columns.\n  %mat8v2float = OpTypeMatrix %v2float 8\n")));
 }
 
-TEST_F(ValidateIdWithMessage, OpTypeSamplerGood) {
+TEST_P(ValidateIdWithMessage, OpTypeSamplerGood) {
   // In Rev31, OpTypeSampler takes no arguments.
   std::string spirv = kGLSL450MemoryModel + R"(
 %s = OpTypeSampler)";
@@ -764,7 +847,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpTypeArrayGood) {
+TEST_P(ValidateIdWithMessage, OpTypeArrayGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpConstant %1 1
@@ -773,7 +856,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpTypeArrayElementTypeBad) {
+TEST_P(ValidateIdWithMessage, OpTypeArrayElementTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpConstant %1 1
@@ -781,8 +864,9 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpTypeArray Element Type <id> '2[%uint_1]' is not a "
-                        "type."));
+              HasSubstr(make_message(
+                  "OpTypeArray Element Type <id> '2[%uint_1]' is not a "
+                  "type.")));
 }
 
 // Signed or unsigned.
@@ -962,20 +1046,19 @@
 INSTANTIATE_TEST_SUITE_P(Widths, OpTypeArrayLengthTest,
                          ValuesIn(std::vector<int>{16, 32, 64}));
 
-TEST_F(ValidateIdWithMessage, OpTypeArrayLengthNull) {
+TEST_P(ValidateIdWithMessage, OpTypeArrayLengthNull) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %i32 = OpTypeInt 32 0
 %len = OpConstantNull %i32
 %ary = OpTypeArray %i32 %len)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr(
-          "OpTypeArray Length <id> '2[%2]' default value must be at least 1."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message("OpTypeArray Length <id> '2[%2]' default "
+                                     "value must be at least 1.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpTypeArrayLengthSpecConst) {
+TEST_P(ValidateIdWithMessage, OpTypeArrayLengthSpecConst) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %i32 = OpTypeInt 32 0
 %len = OpSpecConstant %i32 2
@@ -984,7 +1067,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpTypeArrayLengthSpecConstOp) {
+TEST_P(ValidateIdWithMessage, OpTypeArrayLengthSpecConstOp) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %i32 = OpTypeInt 32 0
 %c1 = OpConstant %i32 1
@@ -995,29 +1078,29 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpTypeRuntimeArrayGood) {
+TEST_P(ValidateIdWithMessage, OpTypeRuntimeArrayGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpTypeRuntimeArray %1)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpTypeRuntimeArrayBad) {
+TEST_P(ValidateIdWithMessage, OpTypeRuntimeArrayBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpConstant %1 0
 %3 = OpTypeRuntimeArray %2)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("OpTypeRuntimeArray Element Type <id> '2[%uint_0]' is not a "
-                "type."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "OpTypeRuntimeArray Element Type <id> '2[%uint_0]' is not a "
+                  "type.")));
 }
 // TODO: Object of this type can only be created with OpVariable using the
-// Unifrom Storage Class
+// Uniform Storage Class
 
-TEST_F(ValidateIdWithMessage, OpTypeStructGood) {
+TEST_P(ValidateIdWithMessage, OpTypeStructGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpTypeFloat 64
@@ -1026,7 +1109,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpTypeStructMemberTypeBad) {
+TEST_P(ValidateIdWithMessage, OpTypeStructMemberTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpTypeFloat 64
@@ -1035,11 +1118,12 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpTypeStruct Member Type <id> '3[%double_0]' is not "
-                        "a type."));
+              HasSubstr(make_message(
+                  "OpTypeStruct Member Type <id> '3[%double_0]' is not "
+                  "a type.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpTypeStructOpaqueTypeBad) {
+TEST_P(ValidateIdWithMessage, OpTypeStructOpaqueTypeBad) {
   std::string spirv = R"(
                OpCapability Shader
                OpMemoryModel Logical GLSL450
@@ -1057,37 +1141,39 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
               AnyVUID("VUID-StandaloneSpirv-None-04667"));
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpTypeStruct must not contain an opaque type"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("OpTypeStruct must not contain an opaque type")));
 }
 
-TEST_F(ValidateIdWithMessage, OpTypePointerGood) {
+TEST_P(ValidateIdWithMessage, OpTypePointerGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpTypePointer Input %1)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpTypePointerBad) {
+TEST_P(ValidateIdWithMessage, OpTypePointerBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpConstant %1 0
 %3 = OpTypePointer Input %2)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpTypePointer Type <id> '2[%uint_0]' is not a "
-                        "type."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("OpTypePointer Type <id> '2[%uint_0]' is not a "
+                             "type.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpTypeFunctionGood) {
+TEST_P(ValidateIdWithMessage, OpTypeFunctionGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeFunction %1)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpTypeFunctionReturnTypeBad) {
+TEST_P(ValidateIdWithMessage, OpTypeFunctionReturnTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpConstant %1 0
@@ -1095,10 +1181,11 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpTypeFunction Return Type <id> '2[%uint_0]' is not "
-                        "a type."));
+              HasSubstr(make_message(
+                  "OpTypeFunction Return Type <id> '2[%uint_0]' is not "
+                  "a type.")));
 }
-TEST_F(ValidateIdWithMessage, OpTypeFunctionParameterBad) {
+TEST_P(ValidateIdWithMessage, OpTypeFunctionParameterBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -1106,13 +1193,13 @@
 %4 = OpTypeFunction %1 %2 %3)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("OpTypeFunction Parameter Type <id> '3[%uint_0]' is not a "
-                "type."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "OpTypeFunction Parameter Type <id> '3[%uint_0]' is not a "
+                  "type.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpTypeFunctionParameterTypeVoidBad) {
+TEST_P(ValidateIdWithMessage, OpTypeFunctionParameterTypeVoidBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -1120,11 +1207,12 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpTypeFunction Parameter Type <id> '1[%void]' cannot "
-                        "be OpTypeVoid."));
+              HasSubstr(make_message(
+                  "OpTypeFunction Parameter Type <id> '1[%void]' cannot "
+                  "be OpTypeVoid.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpTypePipeGood) {
+TEST_P(ValidateIdWithMessage, OpTypePipeGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeFloat 32
 %2 = OpTypeVector %1 16
@@ -1133,33 +1221,33 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpConstantTrueGood) {
+TEST_P(ValidateIdWithMessage, OpConstantTrueGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeBool
 %2 = OpConstantTrue %1)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpConstantTrueBad) {
+TEST_P(ValidateIdWithMessage, OpConstantTrueBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpConstantTrue %1)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("OpConstantTrue Result Type <id> '1[%void]' is not a boolean "
-                "type."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "OpConstantTrue Result Type <id> '1[%void]' is not a boolean "
+                  "type.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpConstantFalseGood) {
+TEST_P(ValidateIdWithMessage, OpConstantFalseGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeBool
 %2 = OpConstantTrue %1)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpConstantFalseBad) {
+TEST_P(ValidateIdWithMessage, OpConstantFalseBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpConstantFalse %1)";
@@ -1167,18 +1255,19 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpConstantFalse Result Type <id> '1[%void]' is not a boolean "
-                "type."));
+      HasSubstr(make_message(
+          "OpConstantFalse Result Type <id> '1[%void]' is not a boolean "
+          "type.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpConstantGood) {
+TEST_P(ValidateIdWithMessage, OpConstantGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpConstant %1 1)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpConstantBad) {
+TEST_P(ValidateIdWithMessage, OpConstantBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpConstant !1 !0)";
@@ -1189,7 +1278,7 @@
   EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpConstantCompositeVectorGood) {
+TEST_P(ValidateIdWithMessage, OpConstantCompositeVectorGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeFloat 32
 %2 = OpTypeVector %1 4
@@ -1198,7 +1287,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpConstantCompositeVectorWithUndefGood) {
+TEST_P(ValidateIdWithMessage, OpConstantCompositeVectorWithUndefGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeFloat 32
 %2 = OpTypeVector %1 4
@@ -1208,7 +1297,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpConstantCompositeVectorResultTypeBad) {
+TEST_P(ValidateIdWithMessage, OpConstantCompositeVectorResultTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeFloat 32
 %2 = OpTypeVector %1 4
@@ -1216,12 +1305,12 @@
 %4 = OpConstantComposite %1 %3 %3 %3 %3)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("OpConstantComposite Result Type <id> '1[%float]' is not a "
-                "composite type."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "OpConstantComposite Result Type <id> '1[%float]' is not a "
+                  "composite type.")));
 }
-TEST_F(ValidateIdWithMessage, OpConstantCompositeVectorConstituentTypeBad) {
+TEST_P(ValidateIdWithMessage, OpConstantCompositeVectorConstituentTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeFloat 32
 %2 = OpTypeVector %1 4
@@ -1231,13 +1320,13 @@
 %6 = OpConstantComposite %2 %3 %5 %3 %3)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("OpConstantComposite Constituent <id> '5[%uint_42]'s type "
-                "does not match Result Type <id> '2[%v4float]'s vector "
-                "element type."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "OpConstantComposite Constituent <id> '5[%uint_42]'s type "
+                  "does not match Result Type <id> '2[%v4float]'s vector "
+                  "element type.")));
 }
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        OpConstantCompositeVectorConstituentUndefTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeFloat 32
@@ -1250,10 +1339,11 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpConstantComposite Constituent <id> '5[%5]'s type does not "
-                "match Result Type <id> '2[%v4float]'s vector element type."));
+      HasSubstr(make_message(
+          "OpConstantComposite Constituent <id> '5[%5]'s type does not "
+          "match Result Type <id> '2[%v4float]'s vector element type.")));
 }
-TEST_F(ValidateIdWithMessage, OpConstantCompositeMatrixGood) {
+TEST_P(ValidateIdWithMessage, OpConstantCompositeMatrixGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
  %1 = OpTypeFloat 32
  %2 = OpTypeVector %1 4
@@ -1268,7 +1358,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpConstantCompositeMatrixUndefGood) {
+TEST_P(ValidateIdWithMessage, OpConstantCompositeMatrixUndefGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
  %1 = OpTypeFloat 32
  %2 = OpTypeVector %1 4
@@ -1283,7 +1373,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpConstantCompositeMatrixConstituentTypeBad) {
+TEST_P(ValidateIdWithMessage, OpConstantCompositeMatrixConstituentTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
  %1 = OpTypeFloat 32
  %2 = OpTypeVector %1 4
@@ -1299,11 +1389,12 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpConstantComposite Constituent <id> '10[%10]' vector "
-                        "component count does not match Result Type <id> "
-                        "'4[%mat4v4float]'s vector component count."));
+              HasSubstr(make_message(
+                  "OpConstantComposite Constituent <id> '10[%10]' vector "
+                  "component count does not match Result Type <id> "
+                  "'4[%mat4v4float]'s vector component count.")));
 }
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        OpConstantCompositeMatrixConstituentUndefTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
  %1 = OpTypeFloat 32
@@ -1320,11 +1411,12 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpConstantComposite Constituent <id> '10[%10]' vector "
-                        "component count does not match Result Type <id> "
-                        "'4[%mat4v4float]'s vector component count."));
+              HasSubstr(make_message(
+                  "OpConstantComposite Constituent <id> '10[%10]' vector "
+                  "component count does not match Result Type <id> "
+                  "'4[%mat4v4float]'s vector component count.")));
 }
-TEST_F(ValidateIdWithMessage, OpConstantCompositeArrayGood) {
+TEST_P(ValidateIdWithMessage, OpConstantCompositeArrayGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpConstant %1 4
@@ -1333,7 +1425,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpConstantCompositeArrayWithUndefGood) {
+TEST_P(ValidateIdWithMessage, OpConstantCompositeArrayWithUndefGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpConstant %1 4
@@ -1344,7 +1436,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpConstantCompositeArrayConstConstituentTypeBad) {
+TEST_P(ValidateIdWithMessage, OpConstantCompositeArrayConstConstituentTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpConstant %1 4
@@ -1352,10 +1444,11 @@
 %4 = OpConstantComposite %3 %2 %2 %2 %1)";  // Uses a type as operand
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 1[%uint] cannot be a "
-                                               "type"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message("Operand '1[%uint]' cannot be a "
+                                     "type")));
 }
-TEST_F(ValidateIdWithMessage, OpConstantCompositeArrayConstConstituentBad) {
+TEST_P(ValidateIdWithMessage, OpConstantCompositeArrayConstConstituentBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpConstant %1 4
@@ -1366,10 +1459,11 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpConstantComposite Constituent <id> '5[%5]' is not a "
-                        "constant or undef."));
+              HasSubstr(make_message(
+                  "OpConstantComposite Constituent <id> '5[%5]' is not a "
+                  "constant or undef.")));
 }
-TEST_F(ValidateIdWithMessage, OpConstantCompositeArrayConstituentTypeBad) {
+TEST_P(ValidateIdWithMessage, OpConstantCompositeArrayConstituentTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpConstant %1 4
@@ -1380,12 +1474,13 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpConstantComposite Constituent <id> "
-                        "'5[%float_3_1400001]'s type does not match Result "
-                        "Type <id> '3[%_arr_uint_uint_4]'s array element "
-                        "type."));
+              HasSubstr(make_message(
+                  "OpConstantComposite Constituent <id> "
+                  "'5[%float_3_1400001]'s type does not match Result "
+                  "Type <id> '3[%_arr_uint_uint_4]'s array element "
+                  "type.")));
 }
-TEST_F(ValidateIdWithMessage, OpConstantCompositeArrayConstituentUndefTypeBad) {
+TEST_P(ValidateIdWithMessage, OpConstantCompositeArrayConstituentUndefTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpConstant %1 4
@@ -1395,13 +1490,14 @@
 %4 = OpConstantComposite %3 %2 %2 %2 %6)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpConstantComposite Constituent <id> "
-                        "'5[%5]'s type does not match Result "
-                        "Type <id> '3[%_arr_uint_uint_4]'s array element "
-                        "type."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("OpConstantComposite Constituent <id> "
+                             "'5[%5]'s type does not match Result "
+                             "Type <id> '3[%_arr_uint_uint_4]'s array element "
+                             "type.")));
 }
-TEST_F(ValidateIdWithMessage, OpConstantCompositeStructGood) {
+TEST_P(ValidateIdWithMessage, OpConstantCompositeStructGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpTypeInt 64 0
@@ -1412,7 +1508,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpConstantCompositeStructUndefGood) {
+TEST_P(ValidateIdWithMessage, OpConstantCompositeStructUndefGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpTypeInt 64 0
@@ -1423,7 +1519,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpConstantCompositeStructMemberTypeBad) {
+TEST_P(ValidateIdWithMessage, OpConstantCompositeStructMemberTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpTypeInt 64 0
@@ -1434,12 +1530,13 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpConstantComposite Constituent <id> "
-                        "'5[%ulong_4300000000]' type does not match the "
-                        "Result Type <id> '3[%_struct_3]'s member type."));
+              HasSubstr(make_message(
+                  "OpConstantComposite Constituent <id> "
+                  "'5[%ulong_4300000000]' type does not match the "
+                  "Result Type <id> '3[%_struct_3]'s member type.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpConstantCompositeStructMemberUndefTypeBad) {
+TEST_P(ValidateIdWithMessage, OpConstantCompositeStructMemberUndefTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpTypeInt 64 0
@@ -1450,12 +1547,13 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpConstantComposite Constituent <id> '5[%5]' type "
-                        "does not match the Result Type <id> '3[%_struct_3]'s "
-                        "member type."));
+              HasSubstr(make_message(
+                  "OpConstantComposite Constituent <id> '5[%5]' type "
+                  "does not match the Result Type <id> '3[%_struct_3]'s "
+                  "member type.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpConstantSamplerGood) {
+TEST_P(ValidateIdWithMessage, OpConstantSamplerGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %float = OpTypeFloat 32
 %samplerType = OpTypeSampler
@@ -1463,7 +1561,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpConstantSamplerResultTypeBad) {
+TEST_P(ValidateIdWithMessage, OpConstantSamplerResultTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeFloat 32
 %2 = OpConstantSampler %1 Clamp 0 Nearest)";
@@ -1471,12 +1569,12 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr(
+      HasSubstr(make_message(
           "OpConstantSampler Result Type <id> '1[%float]' is not a sampler "
-          "type."));
+          "type.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpConstantNullGood) {
+TEST_P(ValidateIdWithMessage, OpConstantNullGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
  %1 = OpTypeBool
  %2 = OpConstantNull %1
@@ -1512,7 +1610,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpConstantNullBasicBad) {
+TEST_P(ValidateIdWithMessage, OpConstantNullBasicBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpConstantNull %1)";
@@ -1520,11 +1618,12 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpConstantNull Result Type <id> '1[%void]' cannot have a null "
-                "value."));
+      HasSubstr(make_message(
+          "OpConstantNull Result Type <id> '1[%void]' cannot have a null "
+          "value.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpConstantNullArrayBad) {
+TEST_P(ValidateIdWithMessage, OpConstantNullArrayBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %2 = OpTypeInt 32 0
 %3 = OpTypeSampler
@@ -1535,24 +1634,25 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr(
+      HasSubstr(make_message(
           "OpConstantNull Result Type <id> '4[%_arr_2_uint_4]' cannot have a "
-          "null value."));
+          "null value.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpConstantNullStructBad) {
+TEST_P(ValidateIdWithMessage, OpConstantNullStructBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %2 = OpTypeSampler
 %3 = OpTypeStruct %2 %2
 %4 = OpConstantNull %3)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpConstantNull Result Type <id> '2[%_struct_2]' "
-                        "cannot have a null value."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("OpConstantNull Result Type <id> '2[%_struct_2]' "
+                             "cannot have a null value.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpConstantNullRuntimeArrayBad) {
+TEST_P(ValidateIdWithMessage, OpConstantNullRuntimeArrayBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %bool = OpTypeBool
 %array = OpTypeRuntimeArray %bool
@@ -1561,56 +1661,57 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr(
+      HasSubstr(make_message(
           "OpConstantNull Result Type <id> '2[%_runtimearr_bool]' cannot have "
-          "a null value."));
+          "a null value.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpSpecConstantTrueGood) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantTrueGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeBool
 %2 = OpSpecConstantTrue %1)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpSpecConstantTrueBad) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantTrueBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpSpecConstantTrue %1)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantTrue Result Type <id> '1[%void]' is not "
-                        "a boolean type"));
+              HasSubstr(make_message(
+                  "OpSpecConstantTrue Result Type <id> '1[%void]' is not "
+                  "a boolean type")));
 }
 
-TEST_F(ValidateIdWithMessage, OpSpecConstantFalseGood) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantFalseGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeBool
 %2 = OpSpecConstantFalse %1)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpSpecConstantFalseBad) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantFalseBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpSpecConstantFalse %1)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("OpSpecConstantFalse Result Type <id> '1[%void]' is not "
-                "a boolean type"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "OpSpecConstantFalse Result Type <id> '1[%void]' is not "
+                  "a boolean type")));
 }
 
-TEST_F(ValidateIdWithMessage, OpSpecConstantGood) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeFloat 32
 %2 = OpSpecConstant %1 42)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpSpecConstantBad) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpSpecConstant !1 !4)";
@@ -1619,12 +1720,13 @@
   // change over time, but this must always fail.
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Type Id 1 is not a scalar numeric type"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("Type Id 1 is not a scalar numeric type")));
 }
 
 // Valid: SpecConstantComposite specializes to a vector.
-TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeVectorGood) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantCompositeVectorGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeFloat 32
 %2 = OpTypeVector %1 4
@@ -1636,7 +1738,7 @@
 }
 
 // Valid: Vector of floats and Undefs.
-TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeVectorWithUndefGood) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantCompositeVectorWithUndefGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeFloat 32
 %2 = OpTypeVector %1 4
@@ -1649,7 +1751,7 @@
 }
 
 // Invalid: result type is float.
-TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeVectorResultTypeBad) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantCompositeVectorResultTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeFloat 32
 %2 = OpTypeVector %1 4
@@ -1657,11 +1759,12 @@
 %4 = OpSpecConstantComposite %1 %3 %3 %3 %3)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("is not a composite type"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message("is not a composite type")));
 }
 
 // Invalid: Vector contains a mix of Int and Float.
-TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeVectorConstituentTypeBad) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantCompositeVectorConstituentTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeFloat 32
 %2 = OpTypeVector %1 4
@@ -1672,13 +1775,14 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> "
-                        "'5[%uint_42]'s type does not match Result Type <id> "
-                        "'2[%v4float]'s vector element type."));
+              HasSubstr(make_message(
+                  "OpSpecConstantComposite Constituent <id> "
+                  "'5[%uint_42]'s type does not match Result Type <id> "
+                  "'2[%v4float]'s vector element type.")));
 }
 
 // Invalid: Constituent is not a constant
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        OpSpecConstantCompositeVectorConstituentNotConstantBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeFloat 32
@@ -1691,12 +1795,13 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '6[%6]' is "
-                        "not a constant or undef."));
+              HasSubstr(make_message(
+                  "OpSpecConstantComposite Constituent <id> '6[%6]' is "
+                  "not a constant or undef.")));
 }
 
 // Invalid: Vector contains a mix of Undef-int and Float.
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        OpSpecConstantCompositeVectorConstituentUndefTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeFloat 32
@@ -1708,13 +1813,14 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '5[%5]'s "
-                        "type does not match Result Type <id> '2[%v4float]'s "
-                        "vector element type."));
+              HasSubstr(make_message(
+                  "OpSpecConstantComposite Constituent <id> '5[%5]'s "
+                  "type does not match Result Type <id> '2[%v4float]'s "
+                  "vector element type.")));
 }
 
 // Invalid: Vector expects 3 components, but 4 specified.
-TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeVectorNumComponentsBad) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantCompositeVectorNumComponentsBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeFloat 32
 %2 = OpTypeVector %1 3
@@ -1724,13 +1830,14 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> count does "
-                        "not match Result Type <id> '2[%v3float]'s vector "
-                        "component count."));
+              HasSubstr(make_message(
+                  "OpSpecConstantComposite Constituent <id> count does "
+                  "not match Result Type <id> '2[%v3float]'s vector "
+                  "component count.")));
 }
 
 // Valid: 4x4 matrix of floats
-TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeMatrixGood) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantCompositeMatrixGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
  %1 = OpTypeFloat 32
  %2 = OpTypeVector %1 4
@@ -1747,7 +1854,7 @@
 }
 
 // Valid: Matrix in which one column is Undef
-TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeMatrixUndefGood) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantCompositeMatrixUndefGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
  %1 = OpTypeFloat 32
  %2 = OpTypeVector %1 4
@@ -1764,7 +1871,7 @@
 }
 
 // Invalid: Matrix in which the sizes of column vectors are not equal.
-TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeMatrixConstituentTypeBad) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantCompositeMatrixConstituentTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
  %1 = OpTypeFloat 32
  %2 = OpTypeVector %1 4
@@ -1780,13 +1887,14 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '10[%10]' "
-                        "vector component count does not match Result Type "
-                        "<id> '4[%mat4v4float]'s vector component count."));
+              HasSubstr(make_message(
+                  "OpSpecConstantComposite Constituent <id> '10[%10]' "
+                  "vector component count does not match Result Type "
+                  "<id> '4[%mat4v4float]'s vector component count.")));
 }
 
 // Invalid: Matrix type expects 4 columns but only 3 specified.
-TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeMatrixNumColsBad) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantCompositeMatrixNumColsBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
  %1 = OpTypeFloat 32
  %2 = OpTypeVector %1 4
@@ -1799,15 +1907,15 @@
 %10 = OpSpecConstantComposite %3 %6 %7 %8)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("OpSpecConstantComposite Constituent <id> count does "
-                "not match Result Type <id> '3[%mat4v4float]'s matrix column "
-                "count."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "OpSpecConstantComposite Constituent <id> count does "
+                  "not match Result Type <id> '3[%mat4v4float]'s matrix column "
+                  "count.")));
 }
 
 // Invalid: Composite contains a non-const/undef component
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        OpSpecConstantCompositeMatrixConstituentNotConstBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
  %1 = OpTypeFloat 32
@@ -1821,12 +1929,13 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '7[%7]' is "
-                        "not a constant or undef."));
+              HasSubstr(make_message(
+                  "OpSpecConstantComposite Constituent <id> '7[%7]' is "
+                  "not a constant or undef.")));
 }
 
 // Invalid: Composite contains a column that is *not* a vector (it's an array)
-TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeMatrixColTypeBad) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantCompositeMatrixColTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
  %1 = OpTypeFloat 32
  %2 = OpTypeInt 32 0
@@ -1841,13 +1950,14 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '8[%8]' type "
-                        "does not match Result Type <id> '7[%mat4v4float]'s "
-                        "matrix column type."));
+              HasSubstr(make_message(
+                  "OpSpecConstantComposite Constituent <id> '8[%8]' type "
+                  "does not match Result Type <id> '7[%mat4v4float]'s "
+                  "matrix column type.")));
 }
 
 // Invalid: Matrix with an Undef column of the wrong size.
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        OpSpecConstantCompositeMatrixConstituentUndefTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
  %1 = OpTypeFloat 32
@@ -1864,13 +1974,14 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '10[%10]' "
-                        "vector component count does not match Result Type "
-                        "<id> '4[%mat4v4float]'s vector component count."));
+              HasSubstr(make_message(
+                  "OpSpecConstantComposite Constituent <id> '10[%10]' "
+                  "vector component count does not match Result Type "
+                  "<id> '4[%mat4v4float]'s vector component count.")));
 }
 
 // Invalid: Matrix in which some columns are Int and some are Float.
-TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeMatrixColumnTypeBad) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantCompositeMatrixColumnTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
  %1 = OpTypeInt 32 0
  %2 = OpTypeFloat 32
@@ -1885,13 +1996,14 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '8[%8]' "
-                        "component type does not match Result Type <id> "
-                        "'5[%mat2v2float]'s matrix column component type."));
+              HasSubstr(make_message(
+                  "OpSpecConstantComposite Constituent <id> '8[%8]' "
+                  "component type does not match Result Type <id> "
+                  "'5[%mat2v2float]'s matrix column component type.")));
 }
 
 // Valid: Array of integers
-TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeArrayGood) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantCompositeArrayGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpSpecConstant %1 4
@@ -1905,7 +2017,7 @@
 }
 
 // Invalid: Expecting an array of 4 components, but 3 specified.
-TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeArrayNumComponentsBad) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantCompositeArrayNumComponentsBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpConstant %1 4
@@ -1914,13 +2026,14 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent count does not "
-                        "match Result Type <id> '3[%_arr_uint_uint_4]'s array "
-                        "length."));
+              HasSubstr(make_message(
+                  "OpSpecConstantComposite Constituent count does not "
+                  "match Result Type <id> '3[%_arr_uint_uint_4]'s array "
+                  "length.")));
 }
 
 // Valid: Array of Integers and Undef-int
-TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeArrayWithUndefGood) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantCompositeArrayWithUndefGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpSpecConstant %1 4
@@ -1932,7 +2045,7 @@
 }
 
 // Invalid: Array uses a type as operand.
-TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeArrayConstConstituentBad) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantCompositeArrayConstConstituentBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpConstant %1 4
@@ -1943,12 +2056,13 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '5[%5]' is "
-                        "not a constant or undef."));
+              HasSubstr(make_message(
+                  "OpSpecConstantComposite Constituent <id> '5[%5]' is "
+                  "not a constant or undef.")));
 }
 
 // Invalid: Array has a mix of Int and Float components.
-TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeArrayConstituentTypeBad) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantCompositeArrayConstituentTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpConstant %1 4
@@ -1959,13 +2073,14 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '5[%5]'s "
-                        "type does not match Result Type <id> "
-                        "'3[%_arr_uint_uint_4]'s array element type."));
+              HasSubstr(make_message(
+                  "OpSpecConstantComposite Constituent <id> '5[%5]'s "
+                  "type does not match Result Type <id> "
+                  "'3[%_arr_uint_uint_4]'s array element type.")));
 }
 
 // Invalid: Array has a mix of Int and Undef-float.
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        OpSpecConstantCompositeArrayConstituentUndefTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
@@ -1977,13 +2092,14 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '5[%5]'s "
-                        "type does not match Result Type <id> "
-                        "'3[%_arr_uint_2]'s array element type."));
+              HasSubstr(make_message(
+                  "OpSpecConstantComposite Constituent <id> '5[%5]'s "
+                  "type does not match Result Type <id> "
+                  "'3[%_arr_uint_2]'s array element type.")));
 }
 
 // Valid: Struct of {Int32,Int32,Int64}.
-TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeStructGood) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantCompositeStructGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpTypeInt 64 0
@@ -1996,7 +2112,7 @@
 }
 
 // Invalid: missing one int32 struct member.
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        OpSpecConstantCompositeStructMissingComponentBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
@@ -2006,14 +2122,15 @@
 %6 = OpSpecConstantComposite %3 %4 %5)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> "
-                        "'2[%_struct_2]' count does not match Result Type "
-                        "<id> '2[%_struct_2]'s struct member count."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("OpSpecConstantComposite Constituent <id> "
+                             "'2[%_struct_2]' count does not match Result Type "
+                             "<id> '2[%_struct_2]'s struct member count.")));
 }
 
 // Valid: Struct uses Undef-int64.
-TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeStructUndefGood) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantCompositeStructUndefGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpTypeInt 64 0
@@ -2026,7 +2143,7 @@
 }
 
 // Invalid: Composite contains non-const/undef component.
-TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeStructNonConstBad) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantCompositeStructNonConstBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpTypeInt 64 0
@@ -2039,13 +2156,14 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '7[%7]' is "
-                        "not a constant or undef."));
+              HasSubstr(make_message(
+                  "OpSpecConstantComposite Constituent <id> '7[%7]' is "
+                  "not a constant or undef.")));
 }
 
 // Invalid: Struct component type does not match expected specialization type.
 // Second component was expected to be Int32, but got Int64.
-TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeStructMemberTypeBad) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantCompositeStructMemberTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpTypeInt 64 0
@@ -2056,13 +2174,14 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '5[%5]' type "
-                        "does not match the Result Type <id> '3[%_struct_3]'s "
-                        "member type."));
+              HasSubstr(make_message(
+                  "OpSpecConstantComposite Constituent <id> '5[%5]' type "
+                  "does not match the Result Type <id> '3[%_struct_3]'s "
+                  "member type.")));
 }
 
 // Invalid: Undef-int64 used when Int32 was expected.
-TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeStructMemberUndefTypeBad) {
+TEST_P(ValidateIdWithMessage, OpSpecConstantCompositeStructMemberUndefTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpTypeInt 64 0
@@ -2073,14 +2192,15 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpSpecConstantComposite Constituent <id> '5[%5]' type "
-                        "does not match the Result Type <id> '3[%_struct_3]'s "
-                        "member type."));
+              HasSubstr(make_message(
+                  "OpSpecConstantComposite Constituent <id> '5[%5]' type "
+                  "does not match the Result Type <id> '3[%_struct_3]'s "
+                  "member type.")));
 }
 
 // TODO: OpSpecConstantOp
 
-TEST_F(ValidateIdWithMessage, OpVariableGood) {
+TEST_P(ValidateIdWithMessage, OpVariableGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpTypePointer Input %1
@@ -2088,16 +2208,16 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpVariableInitializerConstantGood) {
+TEST_P(ValidateIdWithMessage, OpVariableInitializerConstantGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
-%2 = OpTypePointer Input %1
+%2 = OpTypePointer Output %1
 %3 = OpConstant %1 42
-%4 = OpVariable %2 Input %3)";
+%4 = OpVariable %2 Output %3)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpVariableInitializerGlobalVariableGood) {
+TEST_P(ValidateIdWithMessage, OpVariableInitializerGlobalVariableGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpTypePointer Uniform %1
@@ -2109,29 +2229,30 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 // TODO: Positive test OpVariable with OpConstantNull of OpTypePointer
-TEST_F(ValidateIdWithMessage, OpVariableResultTypeBad) {
+TEST_P(ValidateIdWithMessage, OpVariableResultTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpVariable %1 Input)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("OpVariable Result Type <id> '1[%uint]' is not a pointer "
-                "type."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "OpVariable Result Type <id> '1[%uint]' is not a pointer "
+                  "type.")));
 }
-TEST_F(ValidateIdWithMessage, OpVariableInitializerIsTypeBad) {
+TEST_P(ValidateIdWithMessage, OpVariableInitializerIsTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpTypePointer Input %1
 %3 = OpVariable %2 Input %2)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 2[%_ptr_Input_uint] "
-                                               "cannot be a type"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message("Operand '2[%_ptr_Input_uint]' "
+                                     "cannot be a type")));
 }
 
-TEST_F(ValidateIdWithMessage, OpVariableInitializerIsFunctionVarBad) {
+TEST_P(ValidateIdWithMessage, OpVariableInitializerIsFunctionVarBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %int = OpTypeInt 32 0
 %ptrint = OpTypePointer Function %int
@@ -2148,11 +2269,12 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpVariable Initializer <id> '8[%8]' is not a constant "
-                        "or module-scope variable"));
+              HasSubstr(make_message(
+                  "OpVariable Initializer <id> '8[%8]' is not a constant "
+                  "or module-scope variable")));
 }
 
-TEST_F(ValidateIdWithMessage, OpVariableInitializerIsModuleVarGood) {
+TEST_P(ValidateIdWithMessage, OpVariableInitializerIsModuleVarGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %int = OpTypeInt 32 0
 %ptrint = OpTypePointer Uniform %int
@@ -2170,7 +2292,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpVariableContainsBoolBad) {
+TEST_P(ValidateIdWithMessage, OpVariableContainsBoolBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %bool = OpTypeBool
 %int = OpTypeInt 32 0
@@ -2187,14 +2309,36 @@
 )";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("If OpTypeBool is stored in conjunction with OpVariable"
-                        ", it can only be used with non-externally visible "
-                        "shader Storage Classes: Workgroup, CrossWorkgroup, "
-                        "Private, and Function"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message(
+          "If OpTypeBool is stored in conjunction with OpVariable, it can only "
+          "be used with non-externally visible shader Storage Classes: "
+          "Workgroup, CrossWorkgroup, Private, Function, Input, Output, "
+          "RayPayloadKHR, IncomingRayPayloadKHR, HitAttributeKHR, "
+          "CallableDataKHR, or IncomingCallableDataKHR")));
 }
 
-TEST_F(ValidateIdWithMessage, OpVariableContainsBoolPointerGood) {
+TEST_P(ValidateIdWithMessage, OpVariableContainsBoolPrivateGood) {
+  std::string spirv = kGLSL450MemoryModel + R"(
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%block = OpTypeStruct %bool %int
+%_ptr_Private_block = OpTypePointer Private %block
+%var = OpVariable %_ptr_Private_block Private
+%void = OpTypeVoid
+%fnty = OpTypeFunction %void
+%main = OpFunction %void None %fnty
+%entry = OpLabel
+%load = OpLoad %block %var
+OpReturn
+OpFunctionEnd
+)";
+  CompileSuccessfully(spirv.c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateIdWithMessage, OpVariableContainsBoolPointerGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %bool = OpTypeBool
 %boolptr = OpTypePointer Uniform %bool
@@ -2214,7 +2358,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpVariableContainsBuiltinBoolGood) {
+TEST_P(ValidateIdWithMessage, OpVariableContainsBuiltinBoolGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 OpMemberDecorate %input 0 BuiltIn FrontFacing
 %bool = OpTypeBool
@@ -2233,7 +2377,59 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpVariableContainsRayPayloadBoolGood) {
+TEST_P(ValidateIdWithMessage, OpVariableContainsNoBuiltinBoolBad) {
+  std::string spirv = kGLSL450MemoryModel + R"(
+%bool = OpTypeBool
+%input = OpTypeStruct %bool
+%_ptr_input = OpTypePointer Input %input
+%var = OpVariable %_ptr_input Input
+%void = OpTypeVoid
+%fnty = OpTypeFunction %void
+%main = OpFunction %void None %fnty
+%entry = OpLabel
+%load = OpLoad %input %var
+OpReturn
+OpFunctionEnd
+)";
+  CompileSuccessfully(spirv.c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message(
+          "If OpTypeBool is stored in conjunction with OpVariable using Input "
+          "or Output Storage Classes it requires a BuiltIn decoration")));
+}
+
+TEST_P(ValidateIdWithMessage, OpVariableContainsNoBuiltinBoolBadVulkan) {
+  std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %var
+OpExecutionMode %main OriginUpperLeft
+%bool = OpTypeBool
+%input = OpTypeStruct %bool
+%_ptr_input = OpTypePointer Input %input
+%var = OpVariable %_ptr_input Input
+%void = OpTypeVoid
+%fnty = OpTypeFunction %void
+%main = OpFunction %void None %fnty
+%entry = OpLabel
+%load = OpLoad %input %var
+OpReturn
+OpFunctionEnd
+)";
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Input-07290"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message(
+          "If OpTypeBool is stored in conjunction with OpVariable using Input "
+          "or Output Storage Classes it requires a BuiltIn decoration")));
+}
+
+TEST_P(ValidateIdWithMessage, OpVariableContainsRayPayloadBoolGood) {
   std::string spirv = R"(
 OpCapability RayTracingNV
 OpCapability Shader
@@ -2256,7 +2452,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpVariablePointerNoVariablePointersBad) {
+TEST_P(ValidateIdWithMessage, OpVariablePointerNoVariablePointersBad) {
   const std::string spirv = R"(
 OpCapability Shader
 OpCapability Linkage
@@ -2277,11 +2473,11 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr(
-          "In Logical addressing, variables may not allocate a pointer type"));
+      HasSubstr(make_message(
+          "In Logical addressing, variables may not allocate a pointer type")));
 }
 
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        OpVariablePointerNoVariablePointersRelaxedLogicalGood) {
   const std::string spirv = R"(
 OpCapability Shader
@@ -2305,7 +2501,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpFunctionWithNonMemoryObject) {
+TEST_P(ValidateIdWithMessage, OpFunctionWithNonMemoryObject) {
   // DXC generates code that looks like when given something like:
   //   T t;
   //   t.s.fn_1();
@@ -2346,7 +2542,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        OpVariablePointerVariablePointersStorageBufferGood) {
   const std::string spirv = R"(
 OpCapability Shader
@@ -2370,7 +2566,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpVariablePointerVariablePointersGood) {
+TEST_P(ValidateIdWithMessage, OpVariablePointerVariablePointersGood) {
   const std::string spirv = R"(
 OpCapability Shader
 OpCapability Linkage
@@ -2393,7 +2589,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpVariablePointerVariablePointersBad) {
+TEST_P(ValidateIdWithMessage, OpVariablePointerVariablePointersBad) {
   const std::string spirv = R"(
 OpCapability Shader
 OpCapability VariablePointers
@@ -2409,12 +2605,13 @@
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("In Logical addressing with variable pointers, "
-                        "variables that allocate pointers must be in Function "
-                        "or Private storage classes"));
+              HasSubstr(make_message(
+                  "In Logical addressing with variable pointers, "
+                  "variables that allocate pointers must be in Function "
+                  "or Private storage classes")));
 }
 
-TEST_F(ValidateIdWithMessage, OpLoadGood) {
+TEST_P(ValidateIdWithMessage, OpLoadGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
  %1 = OpTypeVoid
  %2 = OpTypeInt 32 0
@@ -2487,7 +2684,7 @@
 
 // With the VariablePointer Capability, OpLoad should allow loading a
 // VaiablePointer. In this test the variable pointer is obtained by an OpSelect
-TEST_F(ValidateIdWithMessage, OpLoadVarPtrOpSelectGood) {
+TEST_P(ValidateIdWithMessage, OpLoadVarPtrOpSelectGood) {
   std::string result_strategy = R"(
     %isneg     = OpSLessThan %bool %i %zero
     %varptr    = OpSelect %f32ptr %isneg %ptr1 %ptr2
@@ -2506,7 +2703,7 @@
 // through a variable pointer.
 // Disabled since using OpSelect with pointers without VariablePointers will
 // fail LogicalsPass.
-TEST_F(ValidateIdWithMessage, DISABLED_OpLoadVarPtrOpSelectBad) {
+TEST_P(ValidateIdWithMessage, DISABLED_OpLoadVarPtrOpSelectBad) {
   std::string result_strategy = R"(
     %isneg     = OpSLessThan %bool %i %zero
     %varptr    = OpSelect %f32ptr %isneg %ptr1 %ptr2
@@ -2519,12 +2716,13 @@
                                     false /* Use Helper Function? */);
   CompileSuccessfully(spirv.str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("is not a logical pointer."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message("is not a logical pointer.")));
 }
 
 // With the VariablePointer Capability, OpLoad should allow loading a
 // VaiablePointer. In this test the variable pointer is obtained by an OpPhi
-TEST_F(ValidateIdWithMessage, OpLoadVarPtrOpPhiGood) {
+TEST_P(ValidateIdWithMessage, OpLoadVarPtrOpPhiGood) {
   std::string result_strategy = R"(
     %is_neg      = OpSLessThan %bool %i %zero
     OpSelectionMerge %end_label None
@@ -2548,7 +2746,7 @@
 
 // Without the VariablePointers Capability, OpPhi can have a pointer result
 // type.
-TEST_F(ValidateIdWithMessage, OpPhiBad) {
+TEST_P(ValidateIdWithMessage, OpPhiBad) {
   std::string result_strategy = R"(
     %is_neg      = OpSLessThan %bool %i %zero
     OpSelectionMerge %end_label None
@@ -2569,14 +2767,15 @@
   CompileSuccessfully(spirv.str());
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Using pointers with OpPhi requires capability "
-                        "VariablePointers or VariablePointersStorageBuffer"));
+              HasSubstr(make_message(
+                  "Using pointers with OpPhi requires capability "
+                  "VariablePointers or VariablePointersStorageBuffer")));
 }
 
 // With the VariablePointer Capability, OpLoad should allow loading through a
 // VaiablePointer. In this test the variable pointer is obtained from an
 // OpFunctionCall (return value from a function)
-TEST_F(ValidateIdWithMessage, OpLoadVarPtrOpFunctionCallGood) {
+TEST_P(ValidateIdWithMessage, OpLoadVarPtrOpFunctionCallGood) {
   std::ostringstream spirv;
   std::string result_strategy = R"(
     %isneg     = OpSLessThan %bool %i %zero
@@ -2591,7 +2790,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpLoadResultTypeBad) {
+TEST_P(ValidateIdWithMessage, OpLoadResultTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -2606,13 +2805,14 @@
 )";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpLoad Result Type <id> "
-                        "'3[%_ptr_UniformConstant_uint]' does not match "
-                        "Pointer <id> '5[%5]'s type."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("OpLoad Result Type <id> "
+                             "'3[%_ptr_UniformConstant_uint]' does not match "
+                             "Pointer <id> '5[%5]'s type.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpLoadPointerBad) {
+TEST_P(ValidateIdWithMessage, OpLoadPointerBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -2628,12 +2828,13 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   // Prove that SSA checks trigger for a bad Id value.
   // The next test case show the not-a-logical-pointer case.
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 8[%8] has not been "
-                                               "defined"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message("ID '8[%8]' has not been "
+                                     "defined")));
 }
 
 // Disabled as bitcasting type to object is now not valid.
-TEST_F(ValidateIdWithMessage, DISABLED_OpLoadLogicalPointerBad) {
+TEST_P(ValidateIdWithMessage, DISABLED_OpLoadLogicalPointerBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -2655,10 +2856,11 @@
   // I don't know if it's possible to generate a bad case
   // if/when the validator is complete.
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpLoad Pointer <id> '9' is not a logical pointer."));
+              HasSubstr(make_message(
+                  "OpLoad Pointer <id> '9' is not a logical pointer.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpStoreGood) {
+TEST_P(ValidateIdWithMessage, OpStoreGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -2674,7 +2876,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpStorePointerBad) {
+TEST_P(ValidateIdWithMessage, OpStorePointerBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -2691,12 +2893,13 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpStore Pointer <id> '7[%uint_0]' is not a logical "
-                        "pointer."));
+              HasSubstr(make_message(
+                  "OpStore Pointer <id> '7[%uint_0]' is not a logical "
+                  "pointer.")));
 }
 
 // Disabled as bitcasting type to object is now not valid.
-TEST_F(ValidateIdWithMessage, DISABLED_OpStoreLogicalPointerBad) {
+TEST_P(ValidateIdWithMessage, DISABLED_OpStoreLogicalPointerBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -2715,14 +2918,15 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpStore Pointer <id> '10' is not a logical pointer."));
+              HasSubstr(make_message(
+                  "OpStore Pointer <id> '10' is not a logical pointer.")));
 }
 
 // Without the VariablePointer Capability, OpStore should may not store
 // through a variable pointer.
 // Disabled since using OpSelect with pointers without VariablePointers will
 // fail LogicalsPass.
-TEST_F(ValidateIdWithMessage, DISABLED_OpStoreVarPtrBad) {
+TEST_P(ValidateIdWithMessage, DISABLED_OpStoreVarPtrBad) {
   std::string result_strategy = R"(
     %isneg     = OpSLessThan %bool %i %zero
     %varptr    = OpSelect %f32ptr %isneg %ptr1 %ptr2
@@ -2735,12 +2939,13 @@
       false /* Use Helper Function? */);
   CompileSuccessfully(spirv.str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("is not a logical pointer."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message("is not a logical pointer.")));
 }
 
 // With the VariablePointer Capability, OpStore should allow storing through a
 // variable pointer.
-TEST_F(ValidateIdWithMessage, OpStoreVarPtrGood) {
+TEST_P(ValidateIdWithMessage, OpStoreVarPtrGood) {
   std::string result_strategy = R"(
     %isneg     = OpSLessThan %bool %i %zero
     %varptr    = OpSelect %f32ptr %isneg %ptr1 %ptr2
@@ -2755,7 +2960,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpStoreObjectGood) {
+TEST_P(ValidateIdWithMessage, OpStoreObjectGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -2775,10 +2980,11 @@
      OpFunctionEnd)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpStore Object <id> '9[%9]'s type is void."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("OpStore Object <id> '9[%9]'s type is void.")));
 }
-TEST_F(ValidateIdWithMessage, OpStoreTypeBad) {
+TEST_P(ValidateIdWithMessage, OpStoreTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -2795,8 +3001,9 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpStore Pointer <id> '7[%7]'s type does not match "
-                        "Object <id> '6[%float_3_1400001]'s type."));
+              HasSubstr(make_message(
+                  "OpStore Pointer <id> '7[%7]'s type does not match "
+                  "Object <id> '6[%float_3_1400001]'s type.")));
 }
 
 // The next series of test check test a relaxation of the rules for stores to
@@ -2805,7 +3012,7 @@
 // TODO: Add tests for layout compatible arrays and matricies when the validator
 //       relaxes the rules for them as well.  Also need test to check for layout
 //       decorations specific to those types.
-TEST_F(ValidateIdWithMessage, OpStoreTypeBadStruct) {
+TEST_P(ValidateIdWithMessage, OpStoreTypeBadStruct) {
   std::string spirv = kGLSL450MemoryModel + R"(
      OpMemberDecorate %1 0 Offset 0
      OpMemberDecorate %1 1 Offset 4
@@ -2828,13 +3035,14 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpStore Pointer <id> '8[%8]'s type does not match "
-                        "Object <id> '11[%11]'s type."));
+              HasSubstr(make_message(
+                  "OpStore Pointer <id> '8[%8]'s type does not match "
+                  "Object <id> '11[%11]'s type.")));
 }
 
 // Same code as the last test.  The difference is that we relax the rule.
 // Because the structs %3 and %5 are defined the same way.
-TEST_F(ValidateIdWithMessage, OpStoreTypeRelaxedStruct) {
+TEST_P(ValidateIdWithMessage, OpStoreTypeRelaxedStruct) {
   std::string spirv = kGLSL450MemoryModel + R"(
      OpMemberDecorate %1 0 Offset 0
      OpMemberDecorate %1 1 Offset 4
@@ -2859,9 +3067,9 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-// Same code as the last test excect for an extra decoration on one of the
+// Same code as the last test except for an extra decoration on one of the
 // members. With the relaxed rules, the code is still valid.
-TEST_F(ValidateIdWithMessage, OpStoreTypeRelaxedStructWithExtraDecoration) {
+TEST_P(ValidateIdWithMessage, OpStoreTypeRelaxedStructWithExtraDecoration) {
   std::string spirv = kGLSL450MemoryModel + R"(
      OpMemberDecorate %1 0 Offset 0
      OpMemberDecorate %1 1 Offset 4
@@ -2889,7 +3097,7 @@
 
 // This test check that we recursively traverse the struct to check if they are
 // interchangable.
-TEST_F(ValidateIdWithMessage, OpStoreTypeRelaxedNestedStruct) {
+TEST_P(ValidateIdWithMessage, OpStoreTypeRelaxedNestedStruct) {
   std::string spirv = kGLSL450MemoryModel + R"(
      OpMemberDecorate %1 0 Offset 0
      OpMemberDecorate %1 1 Offset 4
@@ -2925,7 +3133,7 @@
 
 // This test check that the even with the relaxed rules an error is identified
 // if the members of the struct are in a different order.
-TEST_F(ValidateIdWithMessage, OpStoreTypeBadRelaxedStruct1) {
+TEST_P(ValidateIdWithMessage, OpStoreTypeBadRelaxedStruct1) {
   std::string spirv = kGLSL450MemoryModel + R"(
      OpMemberDecorate %1 0 Offset 0
      OpMemberDecorate %1 1 Offset 4
@@ -2959,13 +3167,14 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpStore Pointer <id> '13[%13]'s layout does not match Object "
-                "<id> '16[%16]'s layout."));
+      HasSubstr(make_message(
+          "OpStore Pointer <id> '13[%13]'s layout does not match Object "
+          "<id> '16[%16]'s layout.")));
 }
 
 // This test check that the even with the relaxed rules an error is identified
 // if the members of the struct are at different offsets.
-TEST_F(ValidateIdWithMessage, OpStoreTypeBadRelaxedStruct2) {
+TEST_P(ValidateIdWithMessage, OpStoreTypeBadRelaxedStruct2) {
   std::string spirv = kGLSL450MemoryModel + R"(
      OpMemberDecorate %1 0 Offset 4
      OpMemberDecorate %1 1 Offset 0
@@ -2999,11 +3208,12 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpStore Pointer <id> '13[%13]'s layout does not match Object "
-                "<id> '16[%16]'s layout."));
+      HasSubstr(make_message(
+          "OpStore Pointer <id> '13[%13]'s layout does not match Object "
+          "<id> '16[%16]'s layout.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpStoreTypeRelaxedLogicalPointerReturnPointer) {
+TEST_P(ValidateIdWithMessage, OpStoreTypeRelaxedLogicalPointerReturnPointer) {
   const std::string spirv = R"(
      OpCapability Shader
      OpCapability Linkage
@@ -3022,7 +3232,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpStoreTypeRelaxedLogicalPointerAllocPointer) {
+TEST_P(ValidateIdWithMessage, OpStoreTypeRelaxedLogicalPointerAllocPointer) {
   const std::string spirv = R"(
       OpCapability Shader
       OpCapability Linkage
@@ -3045,7 +3255,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpStoreVoid) {
+TEST_P(ValidateIdWithMessage, OpStoreVoid) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -3060,11 +3270,12 @@
      OpFunctionEnd)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpStore Object <id> '8[%8]'s type is void."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("OpStore Object <id> '8[%8]'s type is void.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpStoreLabel) {
+TEST_P(ValidateIdWithMessage, OpStoreLabel) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -3079,12 +3290,12 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Operand 7[%7] requires a type"));
+              HasSubstr(make_message("Operand '7[%7]' requires a type")));
 }
 
 // TODO: enable when this bug is fixed:
 // https://cvs.khronos.org/bugzilla/show_bug.cgi?id=15404
-TEST_F(ValidateIdWithMessage, DISABLED_OpStoreFunction) {
+TEST_P(ValidateIdWithMessage, DISABLED_OpStoreFunction) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %2 = OpTypeInt 32 0
 %3 = OpTypePointer UniformConstant %2
@@ -3100,7 +3311,7 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpStoreBuiltin) {
+TEST_P(ValidateIdWithMessage, OpStoreBuiltin) {
   std::string spirv = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
@@ -3135,10 +3346,11 @@
 
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("storage class is read-only"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message("storage class is read-only")));
 }
 
-TEST_F(ValidateIdWithMessage, OpCopyMemoryGood) {
+TEST_P(ValidateIdWithMessage, OpCopyMemoryGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
  %1 = OpTypeVoid
  %2 = OpTypeInt 32 0
@@ -3158,7 +3370,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpCopyMemoryNonPointerTarget) {
+TEST_P(ValidateIdWithMessage, OpCopyMemoryNonPointerTarget) {
   const std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -3175,11 +3387,12 @@
 
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Target operand <id> '6[%6]' is not a pointer."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("Target operand <id> '6[%6]' is not a pointer.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpCopyMemoryNonPointerSource) {
+TEST_P(ValidateIdWithMessage, OpCopyMemoryNonPointerSource) {
   const std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -3196,11 +3409,12 @@
 
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Source operand <id> '6[%6]' is not a pointer."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("Source operand <id> '6[%6]' is not a pointer.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpCopyMemoryBad) {
+TEST_P(ValidateIdWithMessage, OpCopyMemoryBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
  %1 = OpTypeVoid
  %2 = OpTypeInt 32 0
@@ -3220,11 +3434,11 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Target <id> '5[%5]'s type does not match "
-                        "Source <id> '2[%uint]'s type."));
+              HasSubstr(make_message("Target <id> '5[%5]'s type does not match "
+                                     "Source <id> '2[%uint]'s type.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpCopyMemoryVoidTarget) {
+TEST_P(ValidateIdWithMessage, OpCopyMemoryVoidTarget) {
   const std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -3242,12 +3456,13 @@
 
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Target operand <id> '7[%7]' cannot be a void "
-                        "pointer."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("Target operand <id> '7[%7]' cannot be a void "
+                             "pointer.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpCopyMemoryVoidSource) {
+TEST_P(ValidateIdWithMessage, OpCopyMemoryVoidSource) {
   const std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -3265,12 +3480,13 @@
 
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Source operand <id> '7[%7]' cannot be a void "
-                        "pointer."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("Source operand <id> '7[%7]' cannot be a void "
+                             "pointer.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpCopyMemorySizedGood) {
+TEST_P(ValidateIdWithMessage, OpCopyMemorySizedGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
  %1 = OpTypeVoid
  %2 = OpTypeInt 32 0
@@ -3288,7 +3504,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpCopyMemorySizedTargetBad) {
+TEST_P(ValidateIdWithMessage, OpCopyMemorySizedTargetBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -3305,9 +3521,10 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Target operand <id> '5[%uint_4]' is not a pointer."));
+              HasSubstr(make_message(
+                  "Target operand <id> '5[%uint_4]' is not a pointer.")));
 }
-TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSourceBad) {
+TEST_P(ValidateIdWithMessage, OpCopyMemorySizedSourceBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -3324,9 +3541,10 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Source operand <id> '5[%uint_4]' is not a pointer."));
+              HasSubstr(make_message(
+                  "Source operand <id> '5[%uint_4]' is not a pointer.")));
 }
-TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeBad) {
+TEST_P(ValidateIdWithMessage, OpCopyMemorySizedSizeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
  %1 = OpTypeVoid
  %2 = OpTypeInt 32 0
@@ -3343,11 +3561,11 @@
       OpFunctionEnd)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("Size operand <id> '6[%6]' must be a scalar integer type."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "Size operand <id> '6[%6]' must be a scalar integer type.")));
 }
-TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeTypeBad) {
+TEST_P(ValidateIdWithMessage, OpCopyMemorySizedSizeTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
  %1 = OpTypeVoid
  %2 = OpTypeInt 32 0
@@ -3366,13 +3584,13 @@
       OpFunctionEnd)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("Size operand <id> '9[%float_1]' must be a scalar integer "
-                "type."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "Size operand <id> '9[%float_1]' must be a scalar integer "
+                  "type.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeConstantNull) {
+TEST_P(ValidateIdWithMessage, OpCopyMemorySizedSizeConstantNull) {
   const std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -3392,12 +3610,13 @@
 
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Size operand <id> '3[%3]' cannot be a constant "
-                        "zero."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("Size operand <id> '3[%3]' cannot be a constant "
+                             "zero.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeConstantZero) {
+TEST_P(ValidateIdWithMessage, OpCopyMemorySizedSizeConstantZero) {
   const std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -3418,11 +3637,12 @@
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Size operand <id> '3[%uint_0]' cannot be a constant "
-                        "zero."));
+              HasSubstr(make_message(
+                  "Size operand <id> '3[%uint_0]' cannot be a constant "
+                  "zero.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeConstantZero64) {
+TEST_P(ValidateIdWithMessage, OpCopyMemorySizedSizeConstantZero64) {
   const std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 64 0
@@ -3443,11 +3663,12 @@
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Size operand <id> '3[%ulong_0]' cannot be a constant "
-                        "zero."));
+              HasSubstr(make_message(
+                  "Size operand <id> '3[%ulong_0]' cannot be a constant "
+                  "zero.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeConstantNegative) {
+TEST_P(ValidateIdWithMessage, OpCopyMemorySizedSizeConstantNegative) {
   const std::string spirv = kNoKernelGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 1
@@ -3467,13 +3688,13 @@
 
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("Size operand <id> '3[%int_n1]' cannot have the sign bit set "
-                "to 1."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "Size operand <id> '3[%int_n1]' cannot have the sign bit set "
+                  "to 1.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeConstantNegative64) {
+TEST_P(ValidateIdWithMessage, OpCopyMemorySizedSizeConstantNegative64) {
   const std::string spirv = kNoKernelGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 64 1
@@ -3495,11 +3716,12 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Size operand <id> '3[%long_n1]' cannot have the sign bit set "
-                "to 1."));
+      HasSubstr(make_message(
+          "Size operand <id> '3[%long_n1]' cannot have the sign bit set "
+          "to 1.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeUnsignedNegative) {
+TEST_P(ValidateIdWithMessage, OpCopyMemorySizedSizeUnsignedNegative) {
   const std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -3521,7 +3743,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeUnsignedNegative64) {
+TEST_P(ValidateIdWithMessage, OpCopyMemorySizedSizeUnsignedNegative64) {
   const std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 64 0
@@ -3656,7 +3878,7 @@
   )";
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 1[%void] cannot be a "
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand '1[%void]' cannot be a "
                                                "type"));
 }
 
@@ -3675,7 +3897,7 @@
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Operand 8[%_ptr_Private_float] cannot be a type"));
+              HasSubstr("Operand '8[%_ptr_Private_float]' cannot be a type"));
 }
 
 // Invalid: The storage class of Base and Result do not match.
@@ -4100,7 +4322,7 @@
 // TODO: OpImagePointer
 // TODO: OpGenericPtrMemSemantics
 
-TEST_F(ValidateIdWithMessage, OpFunctionGood) {
+TEST_P(ValidateIdWithMessage, OpFunctionGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -4112,7 +4334,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpFunctionResultTypeBad) {
+TEST_P(ValidateIdWithMessage, OpFunctionResultTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -4124,12 +4346,13 @@
      OpFunctionEnd)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpFunction Result Type <id> '2[%uint]' does not "
-                        "match the Function Type's return type <id> "
-                        "'1[%void]'."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("OpFunction Result Type <id> '2[%uint]' does not "
+                             "match the Function Type's return type <id> "
+                             "'1[%void]'.")));
 }
-TEST_F(ValidateIdWithMessage, OpReturnValueTypeBad) {
+TEST_P(ValidateIdWithMessage, OpReturnValueTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeInt 32 0
 %2 = OpTypeFloat 32
@@ -4142,10 +4365,11 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpReturnValue Value <id> '3[%float_0]'s type does "
-                        "not match OpFunction's return type."));
+              HasSubstr(make_message(
+                  "OpReturnValue Value <id> '3[%float_0]'s type does "
+                  "not match OpFunction's return type.")));
 }
-TEST_F(ValidateIdWithMessage, OpFunctionFunctionTypeBad) {
+TEST_P(ValidateIdWithMessage, OpFunctionFunctionTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -4155,13 +4379,13 @@
 OpFunctionEnd)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("OpFunction Function Type <id> '2[%uint]' is not a function "
-                "type."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "OpFunction Function Type <id> '2[%uint]' is not a function "
+                  "type.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpFunctionUseBad) {
+TEST_P(ValidateIdWithMessage, OpFunctionUseBad) {
   const std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeFloat 32
 %2 = OpTypeFunction %1
@@ -4173,11 +4397,12 @@
 
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Invalid use of function result id 3[%3]."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("Invalid use of function result id '3[%3]'.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpFunctionParameterGood) {
+TEST_P(ValidateIdWithMessage, OpFunctionParameterGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -4190,7 +4415,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpFunctionParameterMultipleGood) {
+TEST_P(ValidateIdWithMessage, OpFunctionParameterMultipleGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -4204,7 +4429,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpFunctionParameterResultTypeBad) {
+TEST_P(ValidateIdWithMessage, OpFunctionParameterResultTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -4218,11 +4443,12 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("OpFunctionParameter Result Type <id> '1[%void]' does not "
-                "match the OpTypeFunction parameter type of the same index."));
+      HasSubstr(make_message(
+          "OpFunctionParameter Result Type <id> '1[%void]' does not "
+          "match the OpTypeFunction parameter type of the same index.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpFunctionCallGood) {
+TEST_P(ValidateIdWithMessage, OpFunctionCallGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -4244,7 +4470,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpFunctionCallResultTypeBad) {
+TEST_P(ValidateIdWithMessage, OpFunctionCallResultTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -4266,12 +4492,13 @@
       OpFunctionEnd)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpFunctionCall Result Type <id> '1[%void]'s type "
-                        "does not match Function <id> '2[%uint]'s return "
-                        "type."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("OpFunctionCall Result Type <id> '1[%void]'s type "
+                             "does not match Function <id> '2[%uint]'s return "
+                             "type.")));
 }
-TEST_F(ValidateIdWithMessage, OpFunctionCallFunctionBad) {
+TEST_P(ValidateIdWithMessage, OpFunctionCallFunctionBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -4287,10 +4514,11 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpFunctionCall Function <id> '5[%uint_42]' is not a "
-                        "function."));
+              HasSubstr(make_message(
+                  "OpFunctionCall Function <id> '5[%uint_42]' is not a "
+                  "function.")));
 }
-TEST_F(ValidateIdWithMessage, OpFunctionCallArgumentTypeBad) {
+TEST_P(ValidateIdWithMessage, OpFunctionCallArgumentTypeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -4316,14 +4544,15 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpFunctionCall Argument <id> '7[%float_3_1400001]'s "
-                        "type does not match Function <id> '2[%uint]'s "
-                        "parameter type."));
+              HasSubstr(make_message(
+                  "OpFunctionCall Argument <id> '7[%float_3_1400001]'s "
+                  "type does not match Function <id> '2[%uint]'s "
+                  "parameter type.")));
 }
 
 // Valid: OpSampledImage result <id> is used in the same block by
 // OpImageSampleImplictLod
-TEST_F(ValidateIdWithMessage, OpSampledImageGood) {
+TEST_P(ValidateIdWithMessage, OpSampledImageGood) {
   std::string spirv = kGLSL450MemoryModel + sampledImageSetup + R"(
 %smpld_img = OpSampledImage %sampled_image_type %image_inst %sampler_inst
 %si_lod    = OpImageSampleImplicitLod %v4float %smpld_img %const_vec_1_1
@@ -4335,7 +4564,7 @@
 
 // Invalid: OpSampledImage result <id> is defined in one block and used in a
 // different block.
-TEST_F(ValidateIdWithMessage, OpSampledImageUsedInDifferentBlockBad) {
+TEST_P(ValidateIdWithMessage, OpSampledImageUsedInDifferentBlockBad) {
   std::string spirv = kGLSL450MemoryModel + sampledImageSetup + R"(
 %smpld_img = OpSampledImage %sampled_image_type %image_inst %sampler_inst
 OpBranch %label_2
@@ -4347,10 +4576,11 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("All OpSampledImage instructions must be in the same block in "
-                "which their Result <id> are consumed. OpSampledImage Result "
-                "Type <id> '23[%23]' has a consumer in a different basic "
-                "block. The consumer instruction <id> is '25[%25]'."));
+      HasSubstr(make_message(
+          "All OpSampledImage instructions must be in the same block in "
+          "which their Result <id> are consumed. OpSampledImage Result "
+          "Type <id> '23[%23]' has a consumer in a different basic "
+          "block. The consumer instruction <id> is '25[%25]'.")));
 }
 
 // Invalid: OpSampledImage result <id> is used by OpSelect
@@ -4361,7 +4591,7 @@
 // updated, the error message for this test may change.
 //
 // Disabled since OpSelect catches this now.
-TEST_F(ValidateIdWithMessage, DISABLED_OpSampledImageUsedInOpSelectBad) {
+TEST_P(ValidateIdWithMessage, DISABLED_OpSampledImageUsedInOpSelectBad) {
   std::string spirv = kGLSL450MemoryModel + sampledImageSetup + R"(
 %smpld_img  = OpSampledImage %sampled_image_type %image_inst %sampler_inst
 %select_img = OpSelect %sampled_image_type %spec_true %smpld_img %smpld_img
@@ -4370,12 +4600,13 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Result <id> from OpSampledImage instruction must not "
-                        "appear as operands of OpSelect. Found result <id> "
-                        "'23' as an operand of <id> '24'."));
+              HasSubstr(make_message(
+                  "Result <id> from OpSampledImage instruction must not "
+                  "appear as operands of OpSelect. Found result <id> "
+                  "'23' as an operand of <id> '24'.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpCopyObjectSampledImageGood) {
+TEST_P(ValidateIdWithMessage, OpCopyObjectSampledImageGood) {
   std::string spirv = kGLSL450MemoryModel + sampledImageSetup + R"(
 %smpld_img  = OpSampledImage %sampled_image_type %image_inst %sampler_inst
 %smpld_img2 = OpCopyObject %sampled_image_type %smpld_img
@@ -4388,7 +4619,7 @@
 
 // Valid: Get a float in a matrix using CompositeExtract.
 // Valid: Insert float into a matrix using CompositeInsert.
-TEST_F(ValidateIdWithMessage, CompositeExtractInsertGood) {
+TEST_P(ValidateIdWithMessage, CompositeExtractInsertGood) {
   std::ostringstream spirv;
   spirv << kGLSL450MemoryModel << kDeeplyNestedStructureSetup << std::endl;
   spirv << "%matrix = OpLoad %mat4x3 %my_matrix" << std::endl;
@@ -4404,7 +4635,7 @@
 }
 
 #if 0
-TEST_F(ValidateIdWithMessage, OpFunctionCallArgumentCountBar) {
+TEST_P(ValidateIdWithMessage, OpFunctionCallArgumentCountBar) {
   const char *spirv = R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -4471,7 +4702,7 @@
 // TODO: OpVectorExtractDynamic
 // TODO: OpVectorInsertDynamic
 
-TEST_F(ValidateIdWithMessage, OpVectorShuffleIntGood) {
+TEST_P(ValidateIdWithMessage, OpVectorShuffleIntGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %int = OpTypeInt 32 0
 %ivec3 = OpTypeVector %int 3
@@ -4494,7 +4725,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpVectorShuffleFloatGood) {
+TEST_P(ValidateIdWithMessage, OpVectorShuffleFloatGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %float = OpTypeFloat 32
 %vec2 = OpTypeVector %float 2
@@ -4520,7 +4751,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpVectorShuffleScalarResultType) {
+TEST_P(ValidateIdWithMessage, OpVectorShuffleScalarResultType) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %float = OpTypeFloat 32
 %vec2 = OpTypeVector %float 2
@@ -4538,12 +4769,12 @@
      OpFunctionEnd)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("Result Type of OpVectorShuffle must be OpTypeVector."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "Result Type of OpVectorShuffle must be OpTypeVector.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpVectorShuffleComponentCount) {
+TEST_P(ValidateIdWithMessage, OpVectorShuffleComponentCount) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %int = OpTypeInt 32 0
 %ivec3 = OpTypeVector %int 3
@@ -4562,13 +4793,13 @@
      OpFunctionEnd)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("OpVectorShuffle component literals count does not match "
-                "Result Type <id> '2[%v3uint]'s vector component count."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "OpVectorShuffle component literals count does not match "
+                  "Result Type <id> '2[%v3uint]'s vector component count.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpVectorShuffleVector1Type) {
+TEST_P(ValidateIdWithMessage, OpVectorShuffleVector1Type) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %int = OpTypeInt 32 0
 %ivec2 = OpTypeVector %int 2
@@ -4585,11 +4816,12 @@
      OpFunctionEnd)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("The type of Vector 1 must be OpTypeVector."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("The type of Vector 1 must be OpTypeVector.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpVectorShuffleVector2Type) {
+TEST_P(ValidateIdWithMessage, OpVectorShuffleVector2Type) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %int = OpTypeInt 32 0
 %ivec2 = OpTypeVector %int 2
@@ -4607,11 +4839,12 @@
      OpFunctionEnd)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("The type of Vector 2 must be OpTypeVector."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("The type of Vector 2 must be OpTypeVector.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpVectorShuffleVector1ComponentType) {
+TEST_P(ValidateIdWithMessage, OpVectorShuffleVector1ComponentType) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %int = OpTypeInt 32 0
 %ivec3 = OpTypeVector %int 3
@@ -4640,11 +4873,12 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("The Component Type of Vector 1 must be the same as "
-                        "ResultType."));
+              HasSubstr(make_message(
+                  "The Component Type of Vector 1 must be the same as "
+                  "ResultType.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpVectorShuffleVector2ComponentType) {
+TEST_P(ValidateIdWithMessage, OpVectorShuffleVector2ComponentType) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %int = OpTypeInt 32 0
 %ivec3 = OpTypeVector %int 3
@@ -4673,11 +4907,12 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("The Component Type of Vector 2 must be the same as "
-                        "ResultType."));
+              HasSubstr(make_message(
+                  "The Component Type of Vector 2 must be the same as "
+                  "ResultType.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpVectorShuffleLiterals) {
+TEST_P(ValidateIdWithMessage, OpVectorShuffleLiterals) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %float = OpTypeFloat 32
 %vec2 = OpTypeVector %float 2
@@ -4703,9 +4938,9 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr(
+      HasSubstr(make_message(
           "Component index 8 is out of bounds for combined (Vector1 + Vector2) "
-          "size of 5."));
+          "size of 5.")));
 }
 
 // TODO: OpCompositeConstruct
@@ -4792,7 +5027,7 @@
 // TODO: OpSelectionMerge
 // TODO: OpBranch
 
-TEST_F(ValidateIdWithMessage, OpPhiNotAType) {
+TEST_P(ValidateIdWithMessage, OpPhiNotAType) {
   std::string spirv = kOpenCLMemoryModel32 + R"(
 %2 = OpTypeBool
 %3 = OpConstantTrue %2
@@ -4809,11 +5044,12 @@
 
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 3[%true] is not a type "
-                                               "id"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message("ID '3[%true]' is not a type "
+                                     "id")));
 }
 
-TEST_F(ValidateIdWithMessage, OpPhiSamePredecessor) {
+TEST_P(ValidateIdWithMessage, OpPhiSamePredecessor) {
   std::string spirv = kOpenCLMemoryModel32 + R"(
 %2 = OpTypeBool
 %3 = OpConstantTrue %2
@@ -4832,7 +5068,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpPhiOddArgumentNumber) {
+TEST_P(ValidateIdWithMessage, OpPhiOddArgumentNumber) {
   std::string spirv = kOpenCLMemoryModel32 + R"(
 %2 = OpTypeBool
 %3 = OpConstantTrue %2
@@ -4849,12 +5085,13 @@
 
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpPhi does not have an equal number of incoming "
-                        "values and basic blocks."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("OpPhi does not have an equal number of incoming "
+                             "values and basic blocks.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpPhiTooFewPredecessors) {
+TEST_P(ValidateIdWithMessage, OpPhiTooFewPredecessors) {
   std::string spirv = kOpenCLMemoryModel32 + R"(
 %2 = OpTypeBool
 %3 = OpConstantTrue %2
@@ -4872,11 +5109,12 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpPhi's number of incoming blocks (0) does not match "
-                        "block's predecessor count (1)."));
+              HasSubstr(make_message(
+                  "OpPhi's number of incoming blocks (0) does not match "
+                  "block's predecessor count (1).")));
 }
 
-TEST_F(ValidateIdWithMessage, OpPhiTooManyPredecessors) {
+TEST_P(ValidateIdWithMessage, OpPhiTooManyPredecessors) {
   std::string spirv = kOpenCLMemoryModel32 + R"(
 %2 = OpTypeBool
 %3 = OpConstantTrue %2
@@ -4896,11 +5134,12 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpPhi's number of incoming blocks (2) does not match "
-                        "block's predecessor count (1)."));
+              HasSubstr(make_message(
+                  "OpPhi's number of incoming blocks (2) does not match "
+                  "block's predecessor count (1).")));
 }
 
-TEST_F(ValidateIdWithMessage, OpPhiMismatchedTypes) {
+TEST_P(ValidateIdWithMessage, OpPhiMismatchedTypes) {
   std::string spirv = kOpenCLMemoryModel32 + R"(
 %2 = OpTypeBool
 %3 = OpConstantTrue %2
@@ -4922,12 +5161,13 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpPhi's result type <id> 2[%bool] does not match "
-                        "incoming value <id> 6[%uint_0] type <id> "
-                        "5[%uint]."));
+              HasSubstr(make_message(
+                  "OpPhi's result type <id> '2[%bool]' does not match "
+                  "incoming value <id> '6[%uint_0]' type <id> "
+                  "'5[%uint]'.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpPhiPredecessorNotABlock) {
+TEST_P(ValidateIdWithMessage, OpPhiPredecessorNotABlock) {
   std::string spirv = kOpenCLMemoryModel32 + R"(
 %2 = OpTypeBool
 %3 = OpConstantTrue %2
@@ -4949,11 +5189,12 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpPhi's incoming basic block <id> 3[%true] is not an "
-                        "OpLabel."));
+              HasSubstr(make_message(
+                  "OpPhi's incoming basic block <id> '3[%true]' is not an "
+                  "OpLabel.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpPhiNotAPredecessor) {
+TEST_P(ValidateIdWithMessage, OpPhiNotAPredecessor) {
   std::string spirv = kOpenCLMemoryModel32 + R"(
 %2 = OpTypeBool
 %3 = OpConstantTrue %2
@@ -4975,11 +5216,12 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpPhi's incoming basic block <id> 9[%9] is not a "
-                        "predecessor of <id> 8[%8]."));
+              HasSubstr(make_message(
+                  "OpPhi's incoming basic block <id> '9[%9]' is not a "
+                  "predecessor of <id> '8[%8]'.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpBranchConditionalGood) {
+TEST_P(ValidateIdWithMessage, OpBranchConditionalGood) {
   std::string spirv = BranchConditionalSetup + R"(
     %branch_cond = OpINotEqual %bool %i0 %i1
                    OpSelectionMerge %end None
@@ -4990,7 +5232,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
 }
 
-TEST_F(ValidateIdWithMessage, OpBranchConditionalWithWeightsGood) {
+TEST_P(ValidateIdWithMessage, OpBranchConditionalWithWeightsGood) {
   std::string spirv = BranchConditionalSetup + R"(
     %branch_cond = OpINotEqual %bool %i0 %i1
                    OpSelectionMerge %end None
@@ -5001,7 +5243,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
 }
 
-TEST_F(ValidateIdWithMessage, OpBranchConditional_CondIsScalarInt) {
+TEST_P(ValidateIdWithMessage, OpBranchConditional_CondIsScalarInt) {
   std::string spirv = BranchConditionalSetup + R"(
                    OpSelectionMerge %end None
                    OpBranchConditional %i0 %target_t %target_f
@@ -5011,11 +5253,11 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr(
-          "Condition operand for OpBranchConditional must be of boolean type"));
+      HasSubstr(make_message("Condition operand for OpBranchConditional must "
+                             "be of boolean type")));
 }
 
-TEST_F(ValidateIdWithMessage, OpBranchConditional_TrueTargetIsNotLabel) {
+TEST_P(ValidateIdWithMessage, OpBranchConditional_TrueTargetIsNotLabel) {
   std::string spirv = BranchConditionalSetup + R"(
                    OpSelectionMerge %end None
                    OpBranchConditional %true %i0 %target_f
@@ -5024,11 +5266,12 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("The 'True Label' operand for OpBranchConditional must "
-                        "be the ID of an OpLabel instruction"));
+              HasSubstr(make_message(
+                  "The 'True Label' operand for OpBranchConditional must "
+                  "be the ID of an OpLabel instruction")));
 }
 
-TEST_F(ValidateIdWithMessage, OpBranchConditional_FalseTargetIsNotLabel) {
+TEST_P(ValidateIdWithMessage, OpBranchConditional_FalseTargetIsNotLabel) {
   std::string spirv = BranchConditionalSetup + R"(
                    OpSelectionMerge %end None
                    OpBranchConditional %true %target_t %i0
@@ -5037,11 +5280,12 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("The 'False Label' operand for OpBranchConditional "
-                        "must be the ID of an OpLabel instruction"));
+              HasSubstr(make_message(
+                  "The 'False Label' operand for OpBranchConditional "
+                  "must be the ID of an OpLabel instruction")));
 }
 
-TEST_F(ValidateIdWithMessage, OpBranchConditional_NotEnoughWeights) {
+TEST_P(ValidateIdWithMessage, OpBranchConditional_NotEnoughWeights) {
   std::string spirv = BranchConditionalSetup + R"(
     %branch_cond = OpINotEqual %bool %i0 %i1
                    OpSelectionMerge %end None
@@ -5050,12 +5294,12 @@
 
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("OpBranchConditional requires either 3 or 5 parameters"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "OpBranchConditional requires either 3 or 5 parameters")));
 }
 
-TEST_F(ValidateIdWithMessage, OpBranchConditional_TooManyWeights) {
+TEST_P(ValidateIdWithMessage, OpBranchConditional_TooManyWeights) {
   std::string spirv = BranchConditionalSetup + R"(
     %branch_cond = OpINotEqual %bool %i0 %i1
                    OpSelectionMerge %end None
@@ -5064,25 +5308,26 @@
 
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("OpBranchConditional requires either 3 or 5 parameters"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "OpBranchConditional requires either 3 or 5 parameters")));
 }
 
-TEST_F(ValidateIdWithMessage, OpBranchConditional_ConditionIsAType) {
+TEST_P(ValidateIdWithMessage, OpBranchConditional_ConditionIsAType) {
   std::string spirv = BranchConditionalSetup + R"(
 OpBranchConditional %bool %target_t %target_f
 )" + BranchConditionalTail;
 
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 3[%bool] cannot be a "
-                                               "type"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message("Operand '3[%bool]' cannot be a "
+                                     "type")));
 }
 
 // TODO: OpSwitch
 
-TEST_F(ValidateIdWithMessage, OpReturnValueConstantGood) {
+TEST_P(ValidateIdWithMessage, OpReturnValueConstantGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -5096,7 +5341,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpReturnValueVariableGood) {
+TEST_P(ValidateIdWithMessage, OpReturnValueVariableGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0 ;10
@@ -5113,7 +5358,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpReturnValueExpressionGood) {
+TEST_P(ValidateIdWithMessage, OpReturnValueExpressionGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -5128,7 +5373,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpReturnValueIsType) {
+TEST_P(ValidateIdWithMessage, OpReturnValueIsType) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -5139,11 +5384,12 @@
      OpFunctionEnd)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 1[%void] cannot be a "
-                                               "type"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message("Operand '1[%void]' cannot be a "
+                                     "type")));
 }
 
-TEST_F(ValidateIdWithMessage, OpReturnValueIsLabel) {
+TEST_P(ValidateIdWithMessage, OpReturnValueIsLabel) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -5155,10 +5401,10 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Operand 5[%5] requires a type"));
+              HasSubstr(make_message("Operand '5[%5]' requires a type")));
 }
 
-TEST_F(ValidateIdWithMessage, OpReturnValueIsVoid) {
+TEST_P(ValidateIdWithMessage, OpReturnValueIsVoid) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -5170,13 +5416,13 @@
      OpFunctionEnd)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("OpReturnValue value's type <id> '1[%void]' is missing or "
-                "void."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "OpReturnValue value's type <id> '1[%void]' is missing or "
+                  "void.")));
 }
 
-TEST_F(ValidateIdWithMessage, OpReturnValueIsVariableInPhysical) {
+TEST_P(ValidateIdWithMessage, OpReturnValueIsVariableInPhysical) {
   // It's valid to return a pointer in a physical addressing model.
   std::string spirv = kOpCapabilitySetup + R"(
      OpMemoryModel Physical32 OpenCL
@@ -5193,7 +5439,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpReturnValueIsVariableInLogical) {
+TEST_P(ValidateIdWithMessage, OpReturnValueIsVariableInLogical) {
   // It's invalid to return a pointer in a physical addressing model.
   std::string spirv = kOpCapabilitySetup + R"(
      OpMemoryModel Logical GLSL450
@@ -5208,15 +5454,16 @@
      OpFunctionEnd)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpReturnValue value's type <id> "
-                        "'3[%_ptr_Function_uint]' is a pointer, which is "
-                        "invalid in the Logical addressing model."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("OpReturnValue value's type <id> "
+                             "'3[%_ptr_Function_uint]' is a pointer, which is "
+                             "invalid in the Logical addressing model.")));
 }
 
 // With the VariablePointer Capability, the return value of a function is
 // allowed to be a pointer.
-TEST_F(ValidateIdWithMessage, OpReturnValueVarPtrGood) {
+TEST_P(ValidateIdWithMessage, OpReturnValueVarPtrGood) {
   std::ostringstream spirv;
   createVariablePointerSpirvProgram(&spirv,
                                     "" /* Instructions to add to "main" */,
@@ -5230,7 +5477,7 @@
 // *not* allowed to be a pointer.
 // Disabled since using OpSelect with pointers without VariablePointers will
 // fail LogicalsPass.
-TEST_F(ValidateIdWithMessage, DISABLED_OpReturnValueVarPtrBad) {
+TEST_P(ValidateIdWithMessage, DISABLED_OpReturnValueVarPtrBad) {
   std::ostringstream spirv;
   createVariablePointerSpirvProgram(&spirv,
                                     "" /* Instructions to add to "main" */,
@@ -5239,13 +5486,14 @@
   CompileSuccessfully(spirv.str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpReturnValue value's type <id> '7' is a pointer, "
-                        "which is invalid in the Logical addressing model."));
+              HasSubstr(make_message(
+                  "OpReturnValue value's type <id> '7' is a pointer, "
+                  "which is invalid in the Logical addressing model.")));
 }
 
 // TODO: enable when this bug is fixed:
 // https://cvs.khronos.org/bugzilla/show_bug.cgi?id=15404
-TEST_F(ValidateIdWithMessage, DISABLED_OpReturnValueIsFunction) {
+TEST_P(ValidateIdWithMessage, DISABLED_OpReturnValueIsFunction) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeInt 32 0
@@ -5258,17 +5506,18 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, UndefinedTypeId) {
+TEST_P(ValidateIdWithMessage, UndefinedTypeId) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %s = OpTypeStruct %i32
 )";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Operand 2[%2] requires a previous definition"));
+              HasSubstr(make_message(
+                  "Operand '2[%2]' requires a previous definition")));
 }
 
-TEST_F(ValidateIdWithMessage, UndefinedIdScope) {
+TEST_P(ValidateIdWithMessage, UndefinedIdScope) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %u32    = OpTypeInt 32 0
 %memsem = OpConstant %u32 0
@@ -5282,11 +5531,12 @@
 )";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 7[%7] has not been "
-                                               "defined"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message("ID '7[%7]' has not been "
+                                     "defined")));
 }
 
-TEST_F(ValidateIdWithMessage, UndefinedIdMemSem) {
+TEST_P(ValidateIdWithMessage, UndefinedIdMemSem) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %u32    = OpTypeInt 32 0
 %scope  = OpConstant %u32 0
@@ -5300,11 +5550,12 @@
 )";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 7[%7] has not been "
-                                               "defined"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message("ID '7[%7]' has not been "
+                                     "defined")));
 }
 
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        KernelOpEntryPointAndOpInBoundsPtrAccessChainGood) {
   std::string spirv = kOpenCLMemoryModel32 + R"(
       OpEntryPoint Kernel %2 "simple_kernel"
@@ -5337,7 +5588,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpPtrAccessChainGood) {
+TEST_P(ValidateIdWithMessage, OpPtrAccessChainGood) {
   std::string spirv = kOpenCLMemoryModel64 + R"(
       OpEntryPoint Kernel %2 "another_kernel"
       OpSource OpenCL_C 200000
@@ -5372,7 +5623,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, StgBufOpPtrAccessChainGood) {
+TEST_P(ValidateIdWithMessage, StgBufOpPtrAccessChainGood) {
   std::string spirv = R"(
      OpCapability Shader
      OpCapability Linkage
@@ -5399,7 +5650,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateIdWithMessage, OpLoadBitcastPointerGood) {
+TEST_P(ValidateIdWithMessage, OpLoadBitcastPointerGood) {
   std::string spirv = kOpenCLMemoryModel64 + R"(
 %2  = OpTypeVoid
 %3  = OpTypeInt 32 0
@@ -5417,7 +5668,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpLoadBitcastNonPointerBad) {
+TEST_P(ValidateIdWithMessage, OpLoadBitcastNonPointerBad) {
   std::string spirv = kOpenCLMemoryModel64 + R"(
 %2  = OpTypeVoid
 %3  = OpTypeInt 32 0
@@ -5434,12 +5685,12 @@
       OpFunctionEnd)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("OpLoad type for pointer <id> '11[%11]' is not a pointer "
-                "type."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "OpLoad type for pointer <id> '11[%11]' is not a pointer "
+                  "type.")));
 }
-TEST_F(ValidateIdWithMessage, OpStoreBitcastPointerGood) {
+TEST_P(ValidateIdWithMessage, OpStoreBitcastPointerGood) {
   std::string spirv = kOpenCLMemoryModel64 + R"(
 %2  = OpTypeVoid
 %3  = OpTypeInt 32 0
@@ -5458,7 +5709,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
-TEST_F(ValidateIdWithMessage, OpStoreBitcastNonPointerBad) {
+TEST_P(ValidateIdWithMessage, OpStoreBitcastNonPointerBad) {
   std::string spirv = kOpenCLMemoryModel64 + R"(
 %2  = OpTypeVoid
 %3  = OpTypeInt 32 0
@@ -5475,15 +5726,15 @@
       OpFunctionEnd)";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("OpStore type for pointer <id> '11[%11]' is not a pointer "
-                "type."));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "OpStore type for pointer <id> '11[%11]' is not a pointer "
+                  "type.")));
 }
 
 // Result <id> resulting from an instruction within a function may not be used
 // outside that function.
-TEST_F(ValidateIdWithMessage, ResultIdUsedOutsideOfFunctionBad) {
+TEST_P(ValidateIdWithMessage, ResultIdUsedOutsideOfFunctionBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeFunction %1
@@ -5502,14 +5753,13 @@
   )";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr(
-          "ID 7[%7] defined in block 6[%6] does not dominate its use in block "
-          "9[%9]"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message("ID '7[%7]' defined in block '6[%6]' does "
+                                     "not dominate its use in block "
+                                     "'9[%9]'")));
 }
 
-TEST_F(ValidateIdWithMessage, SpecIdTargetNotSpecializationConstant) {
+TEST_P(ValidateIdWithMessage, SpecIdTargetNotSpecializationConstant) {
   std::string spirv = kGLSL450MemoryModel + R"(
 OpDecorate %1 SpecId 200
 %void = OpTypeVoid
@@ -5523,13 +5773,14 @@
   )";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("SpecId decoration on target <id> "
-                        "'1[%uint_3]' must be a scalar specialization "
-                        "constant"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("SpecId decoration on target <id> "
+                             "'1[%uint_3]' must be a scalar specialization "
+                             "constant")));
 }
 
-TEST_F(ValidateIdWithMessage, SpecIdTargetOpSpecConstantOpBad) {
+TEST_P(ValidateIdWithMessage, SpecIdTargetOpSpecConstantOpBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 OpDecorate %1 SpecId 200
 %void = OpTypeVoid
@@ -5545,12 +5796,13 @@
   )";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("SpecId decoration on target <id> '1[%1]' "
-                        "must be a scalar specialization constant"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("SpecId decoration on target <id> '1[%1]' "
+                             "must be a scalar specialization constant")));
 }
 
-TEST_F(ValidateIdWithMessage, SpecIdTargetOpSpecConstantCompositeBad) {
+TEST_P(ValidateIdWithMessage, SpecIdTargetOpSpecConstantCompositeBad) {
   std::string spirv = kGLSL450MemoryModel + R"(
 OpDecorate %1 SpecId 200
 %void = OpTypeVoid
@@ -5565,12 +5817,13 @@
   )";
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("SpecId decoration on target <id> '1[%1]' "
-                        "must be a scalar specialization constant"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("SpecId decoration on target <id> '1[%1]' "
+                             "must be a scalar specialization constant")));
 }
 
-TEST_F(ValidateIdWithMessage, SpecIdTargetGood) {
+TEST_P(ValidateIdWithMessage, SpecIdTargetGood) {
   std::string spirv = kGLSL450MemoryModel + R"(
 OpDecorate %3 SpecId 200
 OpDecorate %4 SpecId 201
@@ -5591,7 +5844,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
 }
 
-TEST_F(ValidateIdWithMessage, CorrectErrorForShuffle) {
+TEST_P(ValidateIdWithMessage, CorrectErrorForShuffle) {
   std::string spirv = kGLSL450MemoryModel + R"(
    %uint = OpTypeInt 32 0
   %float = OpTypeFloat 32
@@ -5612,13 +5865,13 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr(
+      HasSubstr(make_message(
           "Component index 4 is out of bounds for combined (Vector1 + Vector2) "
-          "size of 4."));
+          "size of 4.")));
   EXPECT_EQ(25, getErrorPosition().index);
 }
 
-TEST_F(ValidateIdWithMessage, VoidStructMember) {
+TEST_P(ValidateIdWithMessage, VoidStructMember) {
   const std::string spirv = kGLSL450MemoryModel + R"(
 %void = OpTypeVoid
 %struct = OpTypeStruct %void
@@ -5626,11 +5879,12 @@
 
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Structures cannot contain a void type."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("Structures cannot contain a void type.")));
 }
 
-TEST_F(ValidateIdWithMessage, TypeFunctionBadUse) {
+TEST_P(ValidateIdWithMessage, TypeFunctionBadUse) {
   std::string spirv = kGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeFunction %1
@@ -5643,10 +5897,11 @@
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Invalid use of function type result id 2[%2]."));
+              HasSubstr(make_message(
+                  "Invalid use of function type result id '2[%2]'.")));
 }
 
-TEST_F(ValidateIdWithMessage, BadTypeId) {
+TEST_P(ValidateIdWithMessage, BadTypeId) {
   std::string spirv = kGLSL450MemoryModel + R"(
           %1 = OpTypeVoid
           %2 = OpTypeFunction %1
@@ -5661,11 +5916,12 @@
 
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 4[%float_0] is not a type "
-                                               "id"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message("ID '4[%float_0]' is not a type "
+                                     "id")));
 }
 
-TEST_F(ValidateIdWithMessage, VulkanMemoryModelLoadMakePointerVisibleGood) {
+TEST_P(ValidateIdWithMessage, VulkanMemoryModelLoadMakePointerVisibleGood) {
   std::string spirv = R"(
 OpCapability Shader
 OpCapability VulkanMemoryModelKHR
@@ -5689,7 +5945,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
 }
 
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        VulkanMemoryModelLoadMakePointerVisibleMissingNonPrivatePointer) {
   std::string spirv = R"(
 OpCapability Shader
@@ -5712,12 +5968,13 @@
 
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("NonPrivatePointerKHR must be specified if "
-                        "MakePointerVisibleKHR is specified."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("NonPrivatePointerKHR must be specified if "
+                             "MakePointerVisibleKHR is specified.")));
 }
 
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        VulkanMemoryModelLoadNonPrivatePointerBadStorageClass) {
   std::string spirv = R"(
 OpCapability Shader
@@ -5741,12 +5998,13 @@
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("NonPrivatePointerKHR requires a pointer in Uniform, "
-                        "Workgroup, CrossWorkgroup, Generic, Image or "
-                        "StorageBuffer storage classes."));
+              HasSubstr(make_message(
+                  "NonPrivatePointerKHR requires a pointer in Uniform, "
+                  "Workgroup, CrossWorkgroup, Generic, Image or "
+                  "StorageBuffer storage classes.")));
 }
 
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        VulkanMemoryModelLoadMakePointerAvailableCannotBeUsed) {
   std::string spirv = R"(
 OpCapability Shader
@@ -5770,10 +6028,11 @@
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("MakePointerAvailableKHR cannot be used with OpLoad"));
+              HasSubstr(make_message(
+                  "MakePointerAvailableKHR cannot be used with OpLoad")));
 }
 
-TEST_F(ValidateIdWithMessage, VulkanMemoryModelStoreMakePointerAvailableGood) {
+TEST_P(ValidateIdWithMessage, VulkanMemoryModelStoreMakePointerAvailableGood) {
   std::string spirv = R"(
 OpCapability Shader
 OpCapability VulkanMemoryModelKHR
@@ -5797,7 +6056,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
 }
 
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        VulkanMemoryModelStoreMakePointerAvailableMissingNonPrivatePointer) {
   std::string spirv = R"(
 OpCapability Shader
@@ -5820,12 +6079,13 @@
 
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("NonPrivatePointerKHR must be specified if "
-                        "MakePointerAvailableKHR is specified."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("NonPrivatePointerKHR must be specified if "
+                             "MakePointerAvailableKHR is specified.")));
 }
 
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        VulkanMemoryModelStoreNonPrivatePointerBadStorageClass) {
   std::string spirv = R"(
 OpCapability Shader
@@ -5849,12 +6109,13 @@
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("NonPrivatePointerKHR requires a pointer in Uniform, "
-                        "Workgroup, CrossWorkgroup, Generic, Image or "
-                        "StorageBuffer storage classes."));
+              HasSubstr(make_message(
+                  "NonPrivatePointerKHR requires a pointer in Uniform, "
+                  "Workgroup, CrossWorkgroup, Generic, Image or "
+                  "StorageBuffer storage classes.")));
 }
 
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        VulkanMemoryModelStoreMakePointerVisibleCannotBeUsed) {
   std::string spirv = R"(
 OpCapability Shader
@@ -5878,10 +6139,11 @@
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("MakePointerVisibleKHR cannot be used with OpStore."));
+              HasSubstr(make_message(
+                  "MakePointerVisibleKHR cannot be used with OpStore.")));
 }
 
-TEST_F(ValidateIdWithMessage, VulkanMemoryModelCopyMemoryAvailable) {
+TEST_P(ValidateIdWithMessage, VulkanMemoryModelCopyMemoryAvailable) {
   std::string spirv = R"(
 OpCapability Shader
 OpCapability Linkage
@@ -5908,7 +6170,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
 }
 
-TEST_F(ValidateIdWithMessage, VulkanMemoryModelCopyMemoryVisible) {
+TEST_P(ValidateIdWithMessage, VulkanMemoryModelCopyMemoryVisible) {
   std::string spirv = R"(
 OpCapability Shader
 OpCapability Linkage
@@ -5935,7 +6197,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
 }
 
-TEST_F(ValidateIdWithMessage, VulkanMemoryModelCopyMemoryAvailableAndVisible) {
+TEST_P(ValidateIdWithMessage, VulkanMemoryModelCopyMemoryAvailableAndVisible) {
   std::string spirv = R"(
 OpCapability Shader
 OpCapability Linkage
@@ -5962,7 +6224,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
 }
 
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        VulkanMemoryModelCopyMemoryAvailableMissingNonPrivatePointer) {
   std::string spirv = R"(
 OpCapability Shader
@@ -5988,12 +6250,13 @@
 
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("NonPrivatePointerKHR must be specified if "
-                        "MakePointerAvailableKHR is specified."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("NonPrivatePointerKHR must be specified if "
+                             "MakePointerAvailableKHR is specified.")));
 }
 
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        VulkanMemoryModelCopyMemoryVisibleMissingNonPrivatePointer) {
   std::string spirv = R"(
 OpCapability Shader
@@ -6019,12 +6282,13 @@
 
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("NonPrivatePointerKHR must be specified if "
-                        "MakePointerVisibleKHR is specified."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("NonPrivatePointerKHR must be specified if "
+                             "MakePointerVisibleKHR is specified.")));
 }
 
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        VulkanMemoryModelCopyMemoryAvailableBadStorageClass) {
   std::string spirv = R"(
 OpCapability Shader
@@ -6051,12 +6315,13 @@
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("NonPrivatePointerKHR requires a pointer in Uniform, "
-                        "Workgroup, CrossWorkgroup, Generic, Image or "
-                        "StorageBuffer storage classes."));
+              HasSubstr(make_message(
+                  "NonPrivatePointerKHR requires a pointer in Uniform, "
+                  "Workgroup, CrossWorkgroup, Generic, Image or "
+                  "StorageBuffer storage classes.")));
 }
 
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        VulkanMemoryModelCopyMemoryVisibleBadStorageClass) {
   std::string spirv = R"(
 OpCapability Shader
@@ -6083,12 +6348,13 @@
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("NonPrivatePointerKHR requires a pointer in Uniform, "
-                        "Workgroup, CrossWorkgroup, Generic, Image or "
-                        "StorageBuffer storage classes."));
+              HasSubstr(make_message(
+                  "NonPrivatePointerKHR requires a pointer in Uniform, "
+                  "Workgroup, CrossWorkgroup, Generic, Image or "
+                  "StorageBuffer storage classes.")));
 }
 
-TEST_F(ValidateIdWithMessage, VulkanMemoryModelCopyMemorySizedAvailable) {
+TEST_P(ValidateIdWithMessage, VulkanMemoryModelCopyMemorySizedAvailable) {
   std::string spirv = R"(
 OpCapability Shader
 OpCapability Linkage
@@ -6116,7 +6382,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
 }
 
-TEST_F(ValidateIdWithMessage, VulkanMemoryModelCopyMemorySizedVisible) {
+TEST_P(ValidateIdWithMessage, VulkanMemoryModelCopyMemorySizedVisible) {
   std::string spirv = R"(
 OpCapability Shader
 OpCapability Linkage
@@ -6144,7 +6410,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
 }
 
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        VulkanMemoryModelCopyMemorySizedAvailableAndVisible) {
   std::string spirv = R"(
 OpCapability Shader
@@ -6173,7 +6439,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
 }
 
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        VulkanMemoryModelCopyMemorySizedAvailableMissingNonPrivatePointer) {
   std::string spirv = R"(
 OpCapability Shader
@@ -6200,12 +6466,13 @@
 
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("NonPrivatePointerKHR must be specified if "
-                        "MakePointerAvailableKHR is specified."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("NonPrivatePointerKHR must be specified if "
+                             "MakePointerAvailableKHR is specified.")));
 }
 
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        VulkanMemoryModelCopyMemorySizedVisibleMissingNonPrivatePointer) {
   std::string spirv = R"(
 OpCapability Shader
@@ -6232,12 +6499,13 @@
 
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("NonPrivatePointerKHR must be specified if "
-                        "MakePointerVisibleKHR is specified."));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(make_message("NonPrivatePointerKHR must be specified if "
+                             "MakePointerVisibleKHR is specified.")));
 }
 
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        VulkanMemoryModelCopyMemorySizedAvailableBadStorageClass) {
   std::string spirv = R"(
 OpCapability Shader
@@ -6265,12 +6533,13 @@
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("NonPrivatePointerKHR requires a pointer in Uniform, "
-                        "Workgroup, CrossWorkgroup, Generic, Image or "
-                        "StorageBuffer storage classes."));
+              HasSubstr(make_message(
+                  "NonPrivatePointerKHR requires a pointer in Uniform, "
+                  "Workgroup, CrossWorkgroup, Generic, Image or "
+                  "StorageBuffer storage classes.")));
 }
 
-TEST_F(ValidateIdWithMessage,
+TEST_P(ValidateIdWithMessage,
        VulkanMemoryModelCopyMemorySizedVisibleBadStorageClass) {
   std::string spirv = R"(
 OpCapability Shader
@@ -6298,12 +6567,13 @@
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("NonPrivatePointerKHR requires a pointer in Uniform, "
-                        "Workgroup, CrossWorkgroup, Generic, Image or "
-                        "StorageBuffer storage classes."));
+              HasSubstr(make_message(
+                  "NonPrivatePointerKHR requires a pointer in Uniform, "
+                  "Workgroup, CrossWorkgroup, Generic, Image or "
+                  "StorageBuffer storage classes.")));
 }
 
-TEST_F(ValidateIdWithMessage, IdDefInUnreachableBlock1) {
+TEST_P(ValidateIdWithMessage, IdDefInUnreachableBlock1) {
   const std::string spirv = kNoKernelGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeFunction %1
@@ -6325,11 +6595,12 @@
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("ID 8[%8] defined in block 7[%7] does not dominate its "
-                        "use in block 10[%10]\n  %10 = OpLabel"));
+              HasSubstr(make_message(
+                  "ID '8[%8]' defined in block '7[%7]' does not dominate its "
+                  "use in block '10[%10]'\n  %10 = OpLabel")));
 }
 
-TEST_F(ValidateIdWithMessage, IdDefInUnreachableBlock2) {
+TEST_P(ValidateIdWithMessage, IdDefInUnreachableBlock2) {
   const std::string spirv = kNoKernelGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeFunction %1
@@ -6351,11 +6622,12 @@
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("ID 8[%8] defined in block 7[%7] does not dominate its "
-                        "use in block 10[%10]\n  %10 = OpLabel"));
+              HasSubstr(make_message(
+                  "ID '8[%8]' defined in block '7[%7]' does not dominate its "
+                  "use in block '10[%10]'\n  %10 = OpLabel")));
 }
 
-TEST_F(ValidateIdWithMessage, IdDefInUnreachableBlock3) {
+TEST_P(ValidateIdWithMessage, IdDefInUnreachableBlock3) {
   const std::string spirv = kNoKernelGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeFunction %1
@@ -6377,11 +6649,12 @@
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("ID 8[%8] defined in block 7[%7] does not dominate its "
-                        "use in block 10[%10]\n  %10 = OpLabel"));
+              HasSubstr(make_message(
+                  "ID '8[%8]' defined in block '7[%7]' does not dominate its "
+                  "use in block '10[%10]'\n  %10 = OpLabel")));
 }
 
-TEST_F(ValidateIdWithMessage, IdDefInUnreachableBlock4) {
+TEST_P(ValidateIdWithMessage, IdDefInUnreachableBlock4) {
   const std::string spirv = kNoKernelGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeFunction %1
@@ -6401,7 +6674,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
 }
 
-TEST_F(ValidateIdWithMessage, IdDefInUnreachableBlock5) {
+TEST_P(ValidateIdWithMessage, IdDefInUnreachableBlock5) {
   const std::string spirv = kNoKernelGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeFunction %1
@@ -6423,7 +6696,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
 }
 
-TEST_F(ValidateIdWithMessage, IdDefInUnreachableBlock6) {
+TEST_P(ValidateIdWithMessage, IdDefInUnreachableBlock6) {
   const std::string spirv = kNoKernelGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeFunction %1
@@ -6444,11 +6717,12 @@
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("ID 9[%9] defined in block 8[%8] does not dominate its "
-                        "use in block 7[%7]\n  %7 = OpLabel"));
+              HasSubstr(make_message(
+                  "ID '9[%9]' defined in block '8[%8]' does not dominate its "
+                  "use in block '7[%7]'\n  %7 = OpLabel")));
 }
 
-TEST_F(ValidateIdWithMessage, ReachableDefUnreachableUse) {
+TEST_P(ValidateIdWithMessage, ReachableDefUnreachableUse) {
   const std::string spirv = kNoKernelGLSL450MemoryModel + R"(
 %1 = OpTypeVoid
 %2 = OpTypeFunction %1
@@ -6468,7 +6742,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
 }
 
-TEST_F(ValidateIdWithMessage, UnreachableDefUsedInPhi) {
+TEST_P(ValidateIdWithMessage, UnreachableDefUsedInPhi) {
   const std::string spirv = kNoKernelGLSL450MemoryModel + R"(
        %void = OpTypeVoid
           %3 = OpTypeFunction %void
@@ -6496,12 +6770,13 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("In OpPhi instruction 14[%14], ID 13[%13] definition does not "
-                "dominate its parent 7[%7]\n  %14 = OpPhi %float %11 %10 %13 "
-                "%7"));
+      HasSubstr(make_message(
+          "In OpPhi instruction '14[%14]', ID '13[%13]' definition does not "
+          "dominate its parent '7[%7]'\n  %14 = OpPhi %float %11 %10 %13 "
+          "%7")));
 }
 
-TEST_F(ValidateIdWithMessage, OpTypeForwardPointerNotAPointerType) {
+TEST_P(ValidateIdWithMessage, OpTypeForwardPointerNotAPointerType) {
   std::string spirv = R"(
      OpCapability GenericPointer
      OpCapability VariablePointersStorageBuffer
@@ -6520,11 +6795,12 @@
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Pointer type in OpTypeForwardPointer is not a pointer "
-                        "type.\n  OpTypeForwardPointer %void CrossWorkgroup"));
+              HasSubstr(make_message(
+                  "Pointer type in OpTypeForwardPointer is not a pointer "
+                  "type.\n  OpTypeForwardPointer %void CrossWorkgroup")));
 }
 
-TEST_F(ValidateIdWithMessage, OpTypeForwardPointerWrongStorageClass) {
+TEST_P(ValidateIdWithMessage, OpTypeForwardPointerWrongStorageClass) {
   std::string spirv = R"(
      OpCapability GenericPointer
      OpCapability VariablePointersStorageBuffer
@@ -6544,14 +6820,14 @@
 
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("Storage class in OpTypeForwardPointer does not match the "
-                "pointer definition.\n  OpTypeForwardPointer "
-                "%_ptr_Function_int CrossWorkgroup"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message(
+                  "Storage class in OpTypeForwardPointer does not match the "
+                  "pointer definition.\n  OpTypeForwardPointer "
+                  "%_ptr_Function_int CrossWorkgroup")));
 }
 
-TEST_F(ValidateIdWithMessage, MissingForwardPointer) {
+TEST_P(ValidateIdWithMessage, MissingForwardPointer) {
   const std::string spirv = R"(
                OpCapability Linkage
                OpCapability Shader
@@ -6564,12 +6840,42 @@
 
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr(
-          "Operand 3[%_ptr_Uniform__struct_2] requires a previous definition"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr(make_message("Operand '3[%_ptr_Uniform__struct_2]' "
+                                     "requires a previous definition")));
 }
 
+TEST_P(ValidateIdWithMessage, NVBindlessSamplerInStruct) {
+  std::string spirv = R"(
+            OpCapability Shader
+            OpCapability BindlessTextureNV
+            OpExtension "SPV_NV_bindless_texture"
+            OpMemoryModel Logical GLSL450
+            OpSamplerImageAddressingModeNV 64
+            OpEntryPoint Fragment %main "main"
+            OpExecutionMode %main OriginUpperLeft
+    %void = OpTypeVoid
+       %3 = OpTypeFunction %void
+   %float = OpTypeFloat 32
+       %7 = OpTypeImage %float 2D 0 0 0 1 Unknown
+       %8 = OpTypeSampledImage %7
+       %9 = OpTypeImage %float 2D 0 0 0 2 Rgba32f
+      %10 = OpTypeSampler
+     %UBO = OpTypeStruct %8 %9 %10
+%_ptr_Uniform_UBO = OpTypePointer Uniform %UBO
+       %_ = OpVariable %_ptr_Uniform_UBO Uniform
+    %main = OpFunction %void None %3
+       %5 = OpLabel
+            OpReturn
+            OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+INSTANTIATE_TEST_SUITE_P(, ValidateIdWithMessage, ::testing::Bool());
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_image_test.cpp b/test/val/val_image_test.cpp
index 11b14fb..a97ef7c 100644
--- a/test/val/val_image_test.cpp
+++ b/test/val/val_image_test.cpp
@@ -61,8 +61,11 @@
 
   // 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"(
+  //
+  // For Vulkan, anything Location decoration needs to be an interface variable
+  std::string interface_vars =
+      (env != SPV_ENV_UNIVERSAL_1_4) ? "%input_flat_u32" :
+                                     R"(
 %uniform_image_f32_1d_0001
 %uniform_image_f32_1d_0002_rgba32f
 %uniform_image_f32_2d_0001
@@ -146,6 +149,7 @@
 %u32vec4 = OpTypeVector %u32 4
 %s32vec4 = OpTypeVector %s32 4
 %f32vec4 = OpTypeVector %f32 4
+%boolvec4 = OpTypeVector %bool 4
 
 %f32_0 = OpConstant %f32 0
 %f32_1 = OpConstant %f32 1
@@ -172,6 +176,8 @@
 %u64_0 = OpConstant %u64 0
 %u64_1 = OpConstant %u64 1
 
+%bool_t = OpConstantTrue %bool
+
 %u32vec2arr4 = OpTypeArray %u32vec2 %u32_4
 %u32vec2arr3 = OpTypeArray %u32vec2 %u32_3
 %u32arr4 = OpTypeArray %u32 %u32_4
@@ -214,6 +220,8 @@
 
 %f32vec4_0000 = OpConstantComposite %f32vec4 %f32_0 %f32_0 %f32_0 %f32_0
 
+%boolvec4_tttt = OpConstantComposite %boolvec4 %bool_t %bool_t %bool_t %bool_t
+
 %const_offsets = OpConstantComposite %u32vec2arr4 %u32vec2_01 %u32vec2_12 %u32vec2_01 %u32vec2_12
 %const_offsets3x2 = OpConstantComposite %u32vec2arr3 %u32vec2_01 %u32vec2_12 %u32vec2_01
 %const_offsets4xu = OpConstantComposite %u32arr4 %u32_0 %u32_0 %u32_0 %u32_0
@@ -243,6 +251,11 @@
 %uniform_image_u32_2d_0001 = OpVariable %ptr_image_u32_2d_0001 UniformConstant
 %type_sampled_image_u32_2d_0001 = OpTypeSampledImage %type_image_u32_2d_0001
 
+%type_image_u32_3d_0001 = OpTypeImage %u32 3D 0 0 0 1 Unknown
+%ptr_image_u32_3d_0001 = OpTypePointer UniformConstant %type_image_u32_3d_0001
+%uniform_image_u32_3d_0001 = OpVariable %ptr_image_u32_3d_0001 UniformConstant
+%type_sampled_image_u32_3d_0001 = OpTypeSampledImage %type_image_u32_3d_0001
+
 %type_image_u32_2d_0002 = OpTypeImage %u32 2D 0 0 0 2 Unknown
 %ptr_image_u32_2d_0002 = OpTypePointer UniformConstant %type_image_u32_2d_0002
 %uniform_image_u32_2d_0002 = OpVariable %ptr_image_u32_2d_0002 UniformConstant
@@ -269,6 +282,11 @@
 %uniform_image_f32_3d_0111 = OpVariable %ptr_image_f32_3d_0111 UniformConstant
 %type_sampled_image_f32_3d_0111 = OpTypeSampledImage %type_image_f32_3d_0111
 
+%type_image_f32_3d_0001 = OpTypeImage %f32 3D 0 0 0 1 Unknown
+%ptr_image_f32_3d_0001 = OpTypePointer UniformConstant %type_image_f32_3d_0001
+%uniform_image_f32_3d_0001 = OpVariable %ptr_image_f32_3d_0001 UniformConstant
+%type_sampled_image_f32_3d_0001 = OpTypeSampledImage %type_image_f32_3d_0001
+
 %type_image_f32_cube_0101 = OpTypeImage %f32 Cube 0 1 0 1 Unknown
 %ptr_image_f32_cube_0101 = OpTypePointer UniformConstant %type_image_f32_cube_0101
 %uniform_image_f32_cube_0101 = OpVariable %ptr_image_f32_cube_0101 UniformConstant
@@ -735,6 +753,34 @@
               HasSubstr("Dim SubpassData requires Sampled to be 2"));
 }
 
+TEST_F(ValidateImage, TypeImageWrongSampledForSubpassDataVulkan) {
+  const std::string code = GetShaderHeader("OpCapability InputAttachment\n") +
+                           R"(
+%img_type = OpTypeImage %f32 SubpassData 0 0 0 1 Unknown
+)" + TrivialMain();
+
+  CompileSuccessfully(code.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpTypeImage-06214"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Dim SubpassData requires Sampled to be 2"));
+}
+
+TEST_F(ValidateImage, TypeImageWrongArrayForSubpassDataVulkan) {
+  const std::string code = GetShaderHeader("OpCapability InputAttachment\n") +
+                           R"(
+%img_type = OpTypeImage %f32 SubpassData 0 1 0 2 Unknown
+)" + TrivialMain();
+
+  CompileSuccessfully(code.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpTypeImage-06214"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Dim SubpassData requires Arrayed to be 0"));
+}
+
 TEST_F(ValidateImage, TypeImage_OpenCL_Sampled0_OK) {
   const std::string code = GetKernelHeader() + R"(
 %img_type = OpTypeImage %void 2D 0 0 0 0 Unknown ReadOnly
@@ -1002,6 +1048,26 @@
               HasSubstr("Expected Sampler to be of type OpTypeSampler"));
 }
 
+TEST_F(ValidateImage, SampledImageIsStorage) {
+  const std::string declarations = R"(
+%type_sampled_image_f32_2d_0002 = OpTypeSampledImage %type_image_f32_2d_0002
+)";
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0002 %uniform_image_f32_2d_0002
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0002 %img %sampler
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, "", "Fragment", "",
+                                         SPV_ENV_UNIVERSAL_1_0, "GLSL450",
+                                         declarations)
+                          .c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Sampled image type requires an image type with "
+                        "\"Sampled\" operand set to 0 or 1"));
+}
+
 TEST_F(ValidateImage, ImageTexelPointerSuccess) {
   const std::string body = R"(
 %texel_ptr = OpImageTexelPointer %ptr_Image_u32 %private_image_u32_buffer_0002_r32ui %u32_0 %u32_0
@@ -1059,8 +1125,9 @@
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 136[%136] cannot be a "
-                                               "type"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Operand '148[%148]' cannot be a "
+                        "type"));
 }
 
 TEST_F(ValidateImage, ImageTexelPointerImageNotImage) {
@@ -2290,6 +2357,24 @@
               HasSubstr("Expected Dref to be of 32-bit float type"));
 }
 
+TEST_F(ValidateImage, SampleDrefImplicitLodWrongDimVulkan) {
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_3d_0001 %uniform_image_u32_3d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_u32_3d_0001 %img %sampler
+%res1 = OpImageSampleDrefImplicitLod %u32 %simg %f32vec3_hhh %f32_1
+)";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, "", "Fragment", "", SPV_ENV_VULKAN_1_0).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpImage-04777"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("In Vulkan, OpImage*Dref* instructions must not use "
+                        "images with a 3D Dim"));
+}
+
 TEST_F(ValidateImage, SampleDrefExplicitLodSuccess) {
   const std::string body = R"(
 %img = OpLoad %type_image_s32_3d_0001 %uniform_image_s32_3d_0001
@@ -3131,7 +3216,7 @@
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Expected Image Operand ConstOffsets array componenets "
+              HasSubstr("Expected Image Operand ConstOffsets array components "
                         "to be int vectors of size 2"));
 }
 
@@ -3146,7 +3231,7 @@
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Expected Image Operand ConstOffsets array componenets "
+              HasSubstr("Expected Image Operand ConstOffsets array components "
                         "to be int vectors of size 2"));
 }
 
@@ -3246,6 +3331,23 @@
               HasSubstr("Expected Dref to be of 32-bit float type"));
 }
 
+TEST_F(ValidateImage, DrefGatherWrongDimVulkan) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_3d_0001 %uniform_image_f32_3d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_3d_0001 %img %sampler
+%res1 = OpImageDrefGather %f32vec4 %simg %f32vec4_0000 %f32_0_5
+)";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, "", "Fragment", "", SPV_ENV_VULKAN_1_0).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpImage-04777"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected Image 'Dim' to be 2D, Cube, or Rect"));
+}
+
 TEST_F(ValidateImage, ReadSuccess1) {
   const std::string body = R"(
 %img = OpLoad %type_image_u32_2d_0002 %uniform_image_u32_2d_0002
@@ -3650,6 +3752,17 @@
                         "but given only 1"));
 }
 
+TEST_F(ValidateImage, WriteTexelScalarSuccess) {
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0002 %uniform_image_u32_2d_0002
+OpImageWrite %img %u32vec2_01 %u32_2
+)";
+
+  const std::string extra = "\nOpCapability StorageImageWriteWithoutFormat\n";
+  CompileSuccessfully(GenerateShaderCode(body, extra).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
 TEST_F(ValidateImage, WriteTexelWrongType) {
   const std::string body = R"(
 %img = OpLoad %type_image_u32_2d_0002 %uniform_image_u32_2d_0002
@@ -3663,17 +3776,17 @@
               HasSubstr("Expected Texel to be int or float vector or scalar"));
 }
 
-TEST_F(ValidateImage, DISABLED_WriteTexelNotVector4) {
+TEST_F(ValidateImage, WriteTexelNonNumericalType) {
   const std::string body = R"(
 %img = OpLoad %type_image_u32_2d_0002 %uniform_image_u32_2d_0002
-OpImageWrite %img %u32vec2_01 %u32vec3_012
+OpImageWrite %img %u32vec2_01 %boolvec4_tttt
 )";
 
   const std::string extra = "\nOpCapability StorageImageWriteWithoutFormat\n";
   CompileSuccessfully(GenerateShaderCode(body, extra).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Expected Texel to have 4 components"));
+              HasSubstr("Expected Texel to be int or float vector or scalar"));
 }
 
 TEST_F(ValidateImage, WriteTexelWrongComponentType) {
@@ -5464,7 +5577,8 @@
 )";
 
   EXPECT_THAT(CompileFailure(GenerateShaderCode(body, "", "Fragment", "",
-                                                SPV_ENV_UNIVERSAL_1_3)),
+                                                SPV_ENV_UNIVERSAL_1_3),
+                             SPV_ENV_UNIVERSAL_1_3, SPV_ERROR_WRONG_VERSION),
               HasSubstr("Invalid image operand 'SignExtend'"));
 }
 
@@ -5475,7 +5589,8 @@
 )";
 
   EXPECT_THAT(CompileFailure(GenerateShaderCode(body, "", "Fragment", "",
-                                                SPV_ENV_UNIVERSAL_1_3)),
+                                                SPV_ENV_UNIVERSAL_1_3),
+                             SPV_ENV_UNIVERSAL_1_3, SPV_ERROR_WRONG_VERSION),
               HasSubstr("Invalid image operand 'ZeroExtend'"));
 }
 
@@ -6099,6 +6214,189 @@
                         "execution mode for GLCompute execution model"));
 }
 
+TEST_F(ValidateImage, TypeSampledImageNotBufferPost1p6) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability SampledBuffer
+OpMemoryModel Logical GLSL450
+%float = OpTypeFloat 32
+%image = OpTypeImage %float Buffer 0 0 0 1 Unknown
+%sampled = OpTypeSampledImage %image
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_6);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_6));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("In SPIR-V 1.6 or later, sampled image dimension must "
+                        "not be Buffer"));
+}
+
+TEST_F(ValidateImage, NonTemporalImage) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 " " %4 %5
+OpExecutionMode %2 OriginUpperLeft
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%12 = OpTypeImage %float 2D 0 0 0 1 Rgba8ui
+%13 = OpTypeSampledImage %12
+%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
+%5 = OpVariable %_ptr_UniformConstant_13 UniformConstant
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%4 = OpVariable %_ptr_Input_v4float Input
+%v2float = OpTypeVector %float 2
+%float_1_35631564en19 = OpConstant %float 1.35631564e-19
+%2 = OpFunction %void None %8
+%8224 = OpLabel
+%6 = OpLoad %13 %5
+%19 = OpLoad %v4float %4
+%20 = OpVectorShuffle %v2float %19 %19 0 1
+%21 = OpVectorTimesScalar %v2float %20 %float_1_35631564en19
+%65312 = OpImageSampleImplicitLod %v4float %6 %21 Nontemporal
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_6);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_6));
+}
+
+TEST_F(ValidateImage, NVBindlessSamplerBuiltins) {
+  const std::string text = R"(
+              OpCapability Shader
+              OpCapability Int64
+              OpCapability Image1D
+              OpCapability BindlessTextureNV
+              OpExtension "SPV_NV_bindless_texture"
+         %1 = OpExtInstImport "GLSL.std.450"
+              OpMemoryModel Logical GLSL450
+              OpSamplerImageAddressingModeNV 64
+              OpEntryPoint Fragment %main "main"
+              OpExecutionMode %main OriginUpperLeft
+              OpSource GLSL 450
+              OpName %main "main"
+              OpName %s2D "s2D"
+              OpName %textureHandle "textureHandle"
+              OpName %i1D "i1D"
+              OpName %s "s"
+              OpName %temp "temp"
+      %void = OpTypeVoid
+         %3 = OpTypeFunction %void
+     %float = OpTypeFloat 32
+         %7 = OpTypeImage %float 2D 0 0 0 1 Unknown
+         %8 = OpTypeSampledImage %7
+%_ptr_Function_8 = OpTypePointer Function %8
+     %ulong = OpTypeInt 64 0
+%_ptr_Private_ulong = OpTypePointer Private %ulong
+%textureHandle = OpVariable %_ptr_Private_ulong Private
+        %16 = OpTypeImage %float 1D 0 0 0 2 Rgba32f
+%_ptr_Function_16 = OpTypePointer Function %16
+        %21 = OpTypeSampler
+%_ptr_Function_21 = OpTypePointer Function %21
+%_ptr_Function_ulong = OpTypePointer Function %ulong
+      %main = OpFunction %void None %3
+         %5 = OpLabel
+       %s2D = OpVariable %_ptr_Function_8 Function
+       %i1D = OpVariable %_ptr_Function_16 Function
+         %s = OpVariable %_ptr_Function_21 Function
+      %temp = OpVariable %_ptr_Function_ulong Function
+        %14 = OpLoad %ulong %textureHandle
+        %15 = OpConvertUToSampledImageNV %8 %14
+              OpStore %s2D %15
+        %19 = OpLoad %ulong %textureHandle
+        %20 = OpConvertUToImageNV %16 %19
+              OpStore %i1D %20
+        %24 = OpLoad %ulong %textureHandle
+        %25 = OpConvertUToSamplerNV %21 %24
+              OpStore %s %25
+        %28 = OpLoad %8 %s2D
+        %29 = OpConvertSampledImageToUNV %ulong %28
+              OpStore %temp %29
+        %30 = OpLoad %16 %i1D
+        %31 = OpConvertImageToUNV %ulong %30
+              OpStore %temp %31
+        %32 = OpLoad %21 %s
+        %33 = OpConvertSamplerToUNV %ulong %32
+              OpStore %temp %33
+              OpReturn
+              OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateImage, NVBindlessAddressingMode64) {
+  std::string text = R"(
+         OpCapability Shader
+         OpCapability BindlessTextureNV
+         OpExtension "SPV_NV_bindless_texture"
+         OpMemoryModel Logical GLSL450
+         OpSamplerImageAddressingModeNV 64
+         OpEntryPoint GLCompute %func "main"
+%voidt = OpTypeVoid
+%uintt = OpTypeInt 32 0
+%funct = OpTypeFunction %voidt
+%func  = OpFunction %voidt None %funct
+%entry = OpLabel
+%udef  = OpUndef %uintt
+         OpReturn
+         OpFunctionEnd
+)";
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateImage, NVBindlessAddressingMode32) {
+  std::string text = R"(
+         OpCapability Shader
+         OpCapability BindlessTextureNV
+         OpExtension "SPV_NV_bindless_texture"
+         OpMemoryModel Logical GLSL450
+         OpSamplerImageAddressingModeNV 32
+         OpEntryPoint GLCompute %func "main"
+%voidt = OpTypeVoid
+%uintt = OpTypeInt 32 0
+%funct = OpTypeFunction %voidt
+%func  = OpFunction %voidt None %funct
+%entry = OpLabel
+%udef  = OpUndef %uintt
+         OpReturn
+         OpFunctionEnd
+)";
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateImage, NVBindlessInvalidAddressingMode) {
+  std::string text = R"(
+         OpCapability Shader
+         OpCapability BindlessTextureNV
+         OpExtension "SPV_NV_bindless_texture"
+         OpMemoryModel Logical GLSL450
+         OpSamplerImageAddressingModeNV 0
+         OpEntryPoint GLCompute %func "main"
+%voidt = OpTypeVoid
+%uintt = OpTypeInt 32 0
+%funct = OpTypeFunction %voidt
+%func  = OpFunction %voidt None %funct
+%entry = OpLabel
+%udef  = OpUndef %uintt
+         OpReturn
+         OpFunctionEnd
+)";
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpSamplerImageAddressingModeNV bitwidth should be 64 or 32"));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_interfaces_test.cpp b/test/val/val_interfaces_test.cpp
index a01fc19..22a0e7c 100644
--- a/test/val/val_interfaces_test.cpp
+++ b/test/val/val_interfaces_test.cpp
@@ -217,7 +217,7 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("Non-unique OpEntryPoint interface 2[%var] is disallowed"));
+      HasSubstr("Non-unique OpEntryPoint interface '2[%var]' is disallowed"));
 }
 
 TEST_F(ValidateInterfacesTest, MissingGlobalVarSPV1p3) {
@@ -448,6 +448,8 @@
   CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Location-04918"));
+  EXPECT_THAT(getDiagnosticString(),
               HasSubstr("Members cannot be assigned a location"));
 }
 
@@ -476,6 +478,8 @@
   CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Location-04918"));
+  EXPECT_THAT(getDiagnosticString(),
               HasSubstr("Members cannot be assigned a location"));
 }
 
@@ -707,7 +711,9 @@
 OpEntryPoint Fragment %main "main" %var1 %var2
 OpExecutionMode %main OriginUpperLeft
 OpDecorate %var1 Location 0
+OpDecorate %var1 Flat
 OpDecorate %var2 Location 1
+OpDecorate %var2 Flat
 %void = OpTypeVoid
 %void_fn = OpTypeFunction %void
 %float = OpTypeFloat 32
@@ -1496,6 +1502,42 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
 }
 
+TEST_F(ValidateInterfacesTest, StructWithBuiltinsMissingBlock_Bad) {
+  // See https://github.com/KhronosGroup/SPIRV-Registry/issues/134
+  //
+  // When a shader input or output is a struct that does not have Block,
+  // then it must have a Location.
+  // But BuiltIns must not have locations.
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %in
+OpExecutionMode %main OriginUpperLeft
+; %struct needs a Block decoration
+OpMemberDecorate %struct 0 BuiltIn Position
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%struct = OpTypeStruct %v4float
+%in_ptr = OpTypePointer Input %struct
+%in = OpVariable %in_ptr Input
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Location-04919"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Interface struct has no Block decoration but has BuiltIn members."));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_layout_test.cpp b/test/val/val_layout_test.cpp
index 7ebd7c0..8cca96f 100644
--- a/test/val/val_layout_test.cpp
+++ b/test/val/val_layout_test.cpp
@@ -667,6 +667,98 @@
 
 // TODO(umar): Test optional instructions
 
+TEST_F(ValidateLayout, ValidNVBindlessTexturelayout) {
+  std::string str = R"(
+         OpCapability Shader
+         OpCapability BindlessTextureNV
+         OpExtension "SPV_NV_bindless_texture"
+         OpMemoryModel Logical GLSL450
+         OpSamplerImageAddressingModeNV 64
+         OpEntryPoint GLCompute %func "main"
+%voidt = OpTypeVoid
+%uintt = OpTypeInt 32 0
+%funct = OpTypeFunction %voidt
+%func  = OpFunction %voidt None %funct
+%entry = OpLabel
+%udef  = OpUndef %uintt
+         OpReturn
+         OpFunctionEnd
+)";
+
+  CompileSuccessfully(str);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateLayout, InvalidValidNVBindlessTexturelayout) {
+  std::string str = R"(
+         OpCapability Shader
+         OpCapability BindlessTextureNV
+         OpExtension "SPV_NV_bindless_texture"
+         OpMemoryModel Logical GLSL450
+         OpEntryPoint GLCompute %func "main"
+         OpSamplerImageAddressingModeNV 64
+%voidt = OpTypeVoid
+%uintt = OpTypeInt 32 0
+%funct = OpTypeFunction %voidt
+%func  = OpFunction %voidt None %funct
+%entry = OpLabel
+%udef  = OpUndef %uintt
+         OpReturn
+         OpFunctionEnd
+)";
+
+  CompileSuccessfully(str);
+  ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "SamplerImageAddressingModeNV is in an invalid layout section"));
+}
+
+TEST_F(ValidateLayout, MissingNVBindlessAddressModeFromLayout) {
+  std::string str = R"(
+         OpCapability Shader
+         OpCapability BindlessTextureNV
+         OpExtension "SPV_NV_bindless_texture"
+         OpMemoryModel Logical GLSL450
+         OpEntryPoint GLCompute %func "main"
+%voidt = OpTypeVoid
+%uintt = OpTypeInt 32 0
+%funct = OpTypeFunction %voidt
+%func  = OpFunction %voidt None %funct
+%entry = OpLabel
+%udef  = OpUndef %uintt
+         OpReturn
+         OpFunctionEnd
+)";
+
+  CompileSuccessfully(str);
+  ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Missing required OpSamplerImageAddressingModeNV instruction"));
+}
+
+TEST_F(ValidateLayout, NVBindlessAddressModeFromLayoutSpecifiedTwice) {
+  std::string str = R"(
+        OpCapability Shader
+        OpCapability BindlessTextureNV
+        OpExtension "SPV_NV_bindless_texture"
+        OpMemoryModel Logical GLSL450
+        OpSamplerImageAddressingModeNV 64
+        OpSamplerImageAddressingModeNV 64
+)";
+
+  CompileSuccessfully(str);
+  ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpSamplerImageAddressingModeNV should only be provided once"));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_limits_test.cpp b/test/val/val_limits_test.cpp
index 8fb80a4..364d514 100644
--- a/test/val/val_limits_test.cpp
+++ b/test/val/val_limits_test.cpp
@@ -750,7 +750,7 @@
 }
 
 // Valid. The purpose here is to test the CFG depth calculation code when a loop
-// continue target is the loop iteself. It also exercises the case where a loop
+// continue target is the loop itself. It also exercises the case where a loop
 // is unreachable.
 TEST_F(ValidateLimits, ControlFlowNoEntryToLoopGood) {
   std::string str = header + R"(
diff --git a/test/val/val_literals_test.cpp b/test/val/val_literals_test.cpp
index 6eadf32..7c9aad6 100644
--- a/test/val/val_literals_test.cpp
+++ b/test/val/val_literals_test.cpp
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Validation tests for ilegal literals
+// Validation tests for illegal literals
 
 #include <string>
 #include <utility>
diff --git a/test/val/val_logicals_test.cpp b/test/val/val_logicals_test.cpp
index b57c743..c140672 100644
--- a/test/val/val_logicals_test.cpp
+++ b/test/val/val_logicals_test.cpp
@@ -1053,18 +1053,18 @@
 
 TEST_F(ValidateLogicals, PSBSelectSuccess) {
   const std::string body = R"(
-OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability PhysicalStorageBufferAddresses
 OpCapability Int64
 OpCapability Shader
 OpExtension "SPV_EXT_physical_storage_buffer"
-OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpMemoryModel PhysicalStorageBuffer64 GLSL450
 OpEntryPoint Fragment %main "main"
 OpExecutionMode %main OriginUpperLeft
-OpDecorate %val1 AliasedPointerEXT
+OpDecorate %val1 AliasedPointer
 %uint64 = OpTypeInt 64 0
 %bool = OpTypeBool
 %true = OpConstantTrue %bool
-%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%ptr = OpTypePointer PhysicalStorageBuffer %uint64
 %pptr_f = OpTypePointer Function %ptr
 %void = OpTypeVoid
 %voidfn = OpTypeFunction %void
@@ -1159,6 +1159,61 @@
                         "condition to be equal: Select"));
 }
 
+TEST_F(ValidateLogicals, SelectNVBindlessSamplers) {
+  const std::string spirv = R"(
+               OpCapability Shader
+               OpCapability BindlessTextureNV
+               OpExtension "SPV_NV_bindless_texture"
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpSamplerImageAddressingModeNV 64
+               OpEntryPoint Fragment %main "main"
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 450
+               OpSourceExtension "GL_NV_bindless_texture"
+               OpName %main "main"
+               OpName %s2D "s2D"
+               OpName %pickhandle "pickhandle"
+               OpName %Sampler1 "Sampler1"
+               OpName %Sampler2 "Sampler2"
+               OpDecorate %pickhandle Flat
+               OpDecorate %Sampler1 DescriptorSet 0
+               OpDecorate %Sampler1 Binding 0
+               OpDecorate %Sampler1 BindlessSamplerNV
+               OpDecorate %Sampler2 DescriptorSet 0
+               OpDecorate %Sampler2 Binding 1
+               OpDecorate %Sampler2 BindlessSamplerNV
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+          %7 = OpTypeImage %float 2D 0 0 0 1 Unknown
+          %8 = OpTypeSampledImage %7
+%_ptr_Function_8 = OpTypePointer Function %8
+        %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+      %int_0 = OpConstant %int 0
+       %bool = OpTypeBool
+%_ptr_UniformConstant_8 = OpTypePointer UniformConstant %8
+   %Sampler1 = OpVariable %_ptr_UniformConstant_8 UniformConstant
+   %Sampler2 = OpVariable %_ptr_UniformConstant_8 UniformConstant
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+        %s2D = OpVariable %_ptr_Function_8 Function
+ %pickhandle = OpVariable %_ptr_Function_int Function
+         %14 = OpLoad %int %pickhandle
+         %17 = OpIEqual %bool %14 %int_0
+         %20 = OpLoad %8 %Sampler1
+         %22 = OpLoad %8 %Sampler2
+         %23 = OpSelect %8 %17 %20 %22
+               OpStore %s2D %23
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_memory_test.cpp b/test/val/val_memory_test.cpp
index 2a884c4..4299eda 100644
--- a/test/val/val_memory_test.cpp
+++ b/test/val/val_memory_test.cpp
@@ -203,9 +203,11 @@
 )";
   CompileSuccessfully(src, SPV_ENV_VULKAN_1_1);
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Uniform-06807"));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("From Vulkan spec, section 14.5.2:\n"
+      HasSubstr("From Vulkan spec:\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"));
@@ -277,9 +279,11 @@
 )";
   CompileSuccessfully(src, SPV_ENV_VULKAN_1_1);
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Uniform-06807"));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("From Vulkan spec, section 14.5.2:\n"
+      HasSubstr("From Vulkan spec:\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"));
@@ -318,9 +322,11 @@
 )";
   CompileSuccessfully(src, SPV_ENV_VULKAN_1_1);
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Uniform-06807"));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("From Vulkan spec, section 14.5.2:\n"
+      HasSubstr("From Vulkan spec:\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"));
@@ -466,6 +472,55 @@
                 "= OpVariable %_ptr_Input_float Input %float_1\n"));
 }
 
+TEST_F(ValidateMemory, UniversalInitializerWithDisallowedStorageClassesBad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%float = OpTypeFloat 32
+%float_ptr = OpTypePointer Input %float
+%init_val = OpConstant %float 1.0
+%1 = OpVariable %float_ptr Input %init_val
+%void = OpTypeVoid
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpVariable, <id> '5[%5]', initializer are not allowed for Input"));
+}
+
+TEST_F(ValidateMemory, InitializerWithTaskPayloadWorkgroupEXT) {
+  std::string spirv = R"(
+               OpCapability MeshShadingEXT
+               OpExtension "SPV_EXT_mesh_shader"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint TaskEXT %main "main" %payload
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+%_ptr_TaskPayloadWorkgroupEXT = OpTypePointer TaskPayloadWorkgroupEXT %uint
+     %uint_1 = OpConstant %uint 1
+    %payload = OpVariable %_ptr_TaskPayloadWorkgroupEXT TaskPayloadWorkgroupEXT %uint_1
+       %main = OpFunction %void None %func
+      %label = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_5);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpVariable, <id> '2[%2]', initializer are not allowed "
+                        "for TaskPayloadWorkgroupEXT"));
+}
+
 TEST_F(ValidateMemory, ArrayLenCorrectResultType) {
   std::string spirv = R"(
                OpCapability Shader
@@ -638,7 +693,7 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("The Struture's type in OpArrayLength <id> '11[%11]' "
+              HasSubstr("The Structure's type in OpArrayLength <id> '11[%11]' "
                         "must be a pointer to an OpTypeStruct."));
 }
 
@@ -668,7 +723,7 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("The Struture's last member in OpArrayLength <id> '11[%11]' "
+      HasSubstr("The Structure's last member in OpArrayLength <id> '11[%11]' "
                 "must be an OpTypeRuntimeArray.\n  %11 = OpArrayLength %uint "
                 "%10 0\n"));
 }
@@ -699,7 +754,7 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("The Struture's last member in OpArrayLength <id> '11[%11]' "
+      HasSubstr("The Structure's last member in OpArrayLength <id> '11[%11]' "
                 "must be an OpTypeRuntimeArray.\n  %11 = OpArrayLength %uint "
                 "%10 1\n"));
 }
@@ -763,7 +818,7 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
-          "The Struture's type in OpArrayLength <id> '12[%12]' must be a "
+          "The Structure's type in OpArrayLength <id> '12[%12]' must be a "
           "pointer to an OpTypeStruct.\n  %12 = OpArrayLength %uint %11 0\n"));
 }
 
@@ -787,8 +842,9 @@
 
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 4[%float] cannot be a "
-                                               "type"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Operand '4[%float]' cannot be a "
+                        "type"));
 }
 
 TEST_F(ValidateMemory, PushConstantNotStructGood) {
@@ -834,10 +890,48 @@
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("PushConstant OpVariable <id> '6[%6]' has illegal "
-                        "type.\nFrom Vulkan spec, section 14.5.1:\n"
-                        "Such variables must be typed as OpTypeStruct, "
-                        "or an array of this type"));
+              AnyVUID("VUID-StandaloneSpirv-PushConstant-06808"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("PushConstant OpVariable <id> '6[%6]' has illegal "
+                "type.\nFrom Vulkan spec, Push Constant Interface section:\n"
+                "Such variables must be typed as OpTypeStruct"));
+}
+
+TEST_F(ValidateMemory, VulkanPushConstantArrayOfStructBad) {
+  std::string spirv = R"(
+            OpCapability Shader
+            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
+     %int = OpTypeInt 32 0
+   %int_1 = OpConstant %int 1
+  %struct = OpTypeStruct %float
+   %array = OpTypeArray %struct %int_1
+     %ptr = OpTypePointer PushConstant %array
+      %pc = OpVariable %ptr PushConstant
+
+       %1 = OpFunction %void None %voidfn
+   %label = 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(),
+              AnyVUID("VUID-StandaloneSpirv-PushConstant-06808"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("PushConstant OpVariable <id> '10[%10]' has illegal "
+                "type.\nFrom Vulkan spec, Push Constant Interface section:\n"
+                "Such variables must be typed as OpTypeStruct"));
 }
 
 TEST_F(ValidateMemory, VulkanPushConstant) {
@@ -1558,21 +1652,21 @@
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Operand 1[%incorrect] requires a type"));
+              HasSubstr("Operand '1[%incorrect]' requires a type"));
 }
 
 TEST_F(ValidateMemory, PSBLoadAlignedSuccess) {
   const std::string body = R"(
-OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability PhysicalStorageBufferAddresses
 OpCapability Int64
 OpCapability Shader
 OpExtension "SPV_EXT_physical_storage_buffer"
-OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpMemoryModel PhysicalStorageBuffer64 GLSL450
 OpEntryPoint Fragment %main "main"
 OpExecutionMode %main OriginUpperLeft
-OpDecorate %val1 AliasedPointerEXT
+OpDecorate %val1 AliasedPointer
 %uint64 = OpTypeInt 64 0
-%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%ptr = OpTypePointer PhysicalStorageBuffer %uint64
 %pptr_f = OpTypePointer Function %ptr
 %void = OpTypeVoid
 %voidfn = OpTypeFunction %void
@@ -1585,22 +1679,22 @@
 OpFunctionEnd
 )";
 
-  CompileSuccessfully(body.c_str());
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2));
 }
 
 TEST_F(ValidateMemory, PSBLoadAlignedMissing) {
   const std::string body = R"(
-OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability PhysicalStorageBufferAddresses
 OpCapability Int64
 OpCapability Shader
 OpExtension "SPV_EXT_physical_storage_buffer"
-OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpMemoryModel PhysicalStorageBuffer64 GLSL450
 OpEntryPoint Fragment %main "main"
 OpExecutionMode %main OriginUpperLeft
-OpDecorate %val1 AliasedPointerEXT
+OpDecorate %val1 AliasedPointer
 %uint64 = OpTypeInt 64 0
-%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%ptr = OpTypePointer PhysicalStorageBuffer %uint64
 %pptr_f = OpTypePointer Function %ptr
 %void = OpTypeVoid
 %voidfn = OpTypeFunction %void
@@ -1613,27 +1707,61 @@
 OpFunctionEnd
 )";
 
-  CompileSuccessfully(body.c_str());
-  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-PhysicalStorageBuffer64-04708"));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr(
-          "Memory accesses with PhysicalStorageBufferEXT must use Aligned"));
+      HasSubstr("Memory accesses with PhysicalStorageBuffer must use Aligned"));
+}
+
+TEST_F(ValidateMemory, PSBLoadAlignedMissingWithOtherOperand) {
+  const std::string body = R"(
+OpCapability PhysicalStorageBufferAddresses
+OpCapability Int64
+OpCapability Shader
+OpExtension "SPV_EXT_physical_storage_buffer"
+OpMemoryModel PhysicalStorageBuffer64 GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %val1 AliasedPointer
+%uint64 = OpTypeInt 64 0
+%ptr = OpTypePointer PhysicalStorageBuffer %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 Volatile
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-PhysicalStorageBuffer64-04708"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Memory accesses with PhysicalStorageBuffer must use Aligned"));
 }
 
 TEST_F(ValidateMemory, PSBStoreAlignedSuccess) {
   const std::string body = R"(
-OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability PhysicalStorageBufferAddresses
 OpCapability Int64
 OpCapability Shader
 OpExtension "SPV_EXT_physical_storage_buffer"
-OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpMemoryModel PhysicalStorageBuffer64 GLSL450
 OpEntryPoint Fragment %main "main"
 OpExecutionMode %main OriginUpperLeft
-OpDecorate %val1 AliasedPointerEXT
+OpDecorate %val1 AliasedPointer
 %uint64 = OpTypeInt 64 0
 %u64_1 = OpConstant %uint64 1
-%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%ptr = OpTypePointer PhysicalStorageBuffer %uint64
 %pptr_f = OpTypePointer Function %ptr
 %void = OpTypeVoid
 %voidfn = OpTypeFunction %void
@@ -1646,23 +1774,23 @@
 OpFunctionEnd
 )";
 
-  CompileSuccessfully(body.c_str());
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2));
 }
 
 TEST_F(ValidateMemory, PSBStoreAlignedMissing) {
   const std::string body = R"(
-OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability PhysicalStorageBufferAddresses
 OpCapability Int64
 OpCapability Shader
 OpExtension "SPV_EXT_physical_storage_buffer"
-OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpMemoryModel PhysicalStorageBuffer64 GLSL450
 OpEntryPoint Fragment %main "main"
 OpExecutionMode %main OriginUpperLeft
-OpDecorate %val1 AliasedPointerEXT
+OpDecorate %val1 AliasedPointer
 %uint64 = OpTypeInt 64 0
 %u64_1 = OpConstant %uint64 1
-%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%ptr = OpTypePointer PhysicalStorageBuffer %uint64
 %pptr_f = OpTypePointer Function %ptr
 %void = OpTypeVoid
 %voidfn = OpTypeFunction %void
@@ -1675,27 +1803,168 @@
 OpFunctionEnd
 )";
 
-  CompileSuccessfully(body.c_str());
-  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-PhysicalStorageBuffer64-04708"));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr(
-          "Memory accesses with PhysicalStorageBufferEXT must use Aligned"));
+      HasSubstr("Memory accesses with PhysicalStorageBuffer must use Aligned"));
+}
+
+TEST_F(ValidateMemory, PSBCopyMemoryAlignedSuccess) {
+  const std::string body = R"(
+OpCapability PhysicalStorageBufferAddresses
+OpCapability Int64
+OpCapability Shader
+OpExtension "SPV_EXT_physical_storage_buffer"
+OpMemoryModel PhysicalStorageBuffer64 GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %val1 AliasedPointer
+%int = OpTypeInt 32 0
+%uint64 = OpTypeInt 64 0
+%u64_1 = OpConstant %uint64 1
+%ptr = OpTypePointer PhysicalStorageBuffer %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 %ptr %val1
+OpCopyMemory %val2 %val3 Aligned 4
+OpCopyMemory %val3 %val2 Aligned 4 Aligned 4
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+}
+
+TEST_F(ValidateMemory, PSBCopyMemoryAlignedMissingTarget) {
+  const std::string body = R"(
+OpCapability PhysicalStorageBufferAddresses
+OpCapability Int64
+OpCapability Shader
+OpExtension "SPV_EXT_physical_storage_buffer"
+OpMemoryModel PhysicalStorageBuffer64 GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %val1 AliasedPointer
+%int = OpTypeInt 32 0
+%uint64 = OpTypeInt 64 0
+%u64_1 = OpConstant %uint64 1
+%ptr = OpTypePointer PhysicalStorageBuffer %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 %ptr %val1
+OpCopyMemory %val2 %val3 Volatile Aligned 4
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-PhysicalStorageBuffer64-04708"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Memory accesses with PhysicalStorageBuffer must use Aligned"));
+}
+
+TEST_F(ValidateMemory, PSBCopyMemoryAlignedMissingSource) {
+  const std::string body = R"(
+OpCapability PhysicalStorageBufferAddresses
+OpCapability Int64
+OpCapability Shader
+OpExtension "SPV_EXT_physical_storage_buffer"
+OpMemoryModel PhysicalStorageBuffer64 GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %val1 AliasedPointer
+%int = OpTypeInt 32 0
+%uint64 = OpTypeInt 64 0
+%u64_1 = OpConstant %uint64 1
+%ptr = OpTypePointer PhysicalStorageBuffer %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 %ptr %val1
+OpCopyMemory %val2 %val3 Aligned 4 Volatile
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-PhysicalStorageBuffer64-04708"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Memory accesses with PhysicalStorageBuffer must use Aligned"));
+}
+
+TEST_F(ValidateMemory, PSBCopyMemoryAlignedMissingBoth) {
+  const std::string body = R"(
+OpCapability PhysicalStorageBufferAddresses
+OpCapability Int64
+OpCapability Shader
+OpExtension "SPV_EXT_physical_storage_buffer"
+OpMemoryModel PhysicalStorageBuffer64 GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %val1 AliasedPointer
+%int = OpTypeInt 32 0
+%uint64 = OpTypeInt 64 0
+%u64_1 = OpConstant %uint64 1
+%ptr = OpTypePointer PhysicalStorageBuffer %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 %ptr %val1
+OpCopyMemory %val2 %val3 Volatile
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-PhysicalStorageBuffer64-04708"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Memory accesses with PhysicalStorageBuffer must use Aligned"));
 }
 
 TEST_F(ValidateMemory, PSBVariable) {
   const std::string body = R"(
-OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability PhysicalStorageBufferAddresses
 OpCapability Int64
 OpCapability Shader
 OpExtension "SPV_EXT_physical_storage_buffer"
-OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpMemoryModel PhysicalStorageBuffer64 GLSL450
 OpEntryPoint Fragment %main "main"
 OpExecutionMode %main OriginUpperLeft
-OpDecorate %val1 AliasedPointerEXT
+OpDecorate %val1 AliasedPointer
 %uint64 = OpTypeInt 64 0
-%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
-%val1 = OpVariable %ptr PhysicalStorageBufferEXT
+%ptr = OpTypePointer PhysicalStorageBuffer %uint64
+%val1 = OpVariable %ptr PhysicalStorageBuffer
 %void = OpTypeVoid
 %voidfn = OpTypeFunction %void
 %main = OpFunction %void None %voidfn
@@ -1708,7 +1977,7 @@
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("PhysicalStorageBufferEXT must not be used with OpVariable"));
+      HasSubstr("PhysicalStorageBuffer must not be used with OpVariable"));
 }
 
 std::string GenCoopMatLoadStoreShader(const std::string& storeMemoryAccess,
@@ -2097,6 +2366,8 @@
 
   CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
@@ -2162,6 +2433,8 @@
 
   CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("For Vulkan with RuntimeDescriptorArrayEXT, a variable "
@@ -2217,11 +2490,14 @@
 
   CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
           "For Vulkan, OpTypeStruct variables containing OpTypeRuntimeArray "
-          "must have storage class of StorageBuffer or Uniform.\n  %6 = "
+          "must have storage class of StorageBuffer, PhysicalStorageBuffer, or "
+          "Uniform.\n  %6 = "
           "OpVariable %_ptr_Workgroup__struct_4 Workgroup\n"));
 }
 
@@ -2247,9 +2523,12 @@
   CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
   EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
+  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 "
+                        "has storage class StorageBuffer or "
+                        "PhysicalStorageBuffer.\n  %6 = OpVariable "
                         "%_ptr_StorageBuffer__struct_4 StorageBuffer\n"));
 }
 
@@ -2301,6 +2580,8 @@
   CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
   EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
+  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 "
@@ -2328,6 +2609,8 @@
 
   CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
@@ -2361,6 +2644,8 @@
 
   CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
@@ -2423,6 +2708,8 @@
 
   CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
@@ -2459,6 +2746,8 @@
 
   CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
@@ -2490,6 +2779,8 @@
 
   CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("OpTypeArray Element Type <id> '5[%_runtimearr_4]' is not "
@@ -2524,6 +2815,8 @@
 
   CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
@@ -2558,6 +2851,8 @@
 
   CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
@@ -2595,6 +2890,8 @@
 
   CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
@@ -3015,8 +3312,8 @@
     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"));
+                HasSubstr("Instruction cannot for logical addressing model be "
+                          "used without a variable pointers capability"));
   }
 }
 
@@ -3253,6 +3550,8 @@
 
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Uniform-06925"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("In the Vulkan environment, cannot store to Uniform Blocks"));
@@ -3294,6 +3593,8 @@
 
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Uniform-06925"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("In the Vulkan environment, cannot store to Uniform Blocks"));
@@ -3366,6 +3667,8 @@
 
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Uniform-06925"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("In the Vulkan environment, cannot store to Uniform Blocks"));
@@ -3410,6 +3713,8 @@
 
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Uniform-06925"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("In the Vulkan environment, cannot store to Uniform Blocks"));
@@ -3450,6 +3755,8 @@
 
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Uniform-06925"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("In the Vulkan environment, cannot store to Uniform Blocks"));
@@ -3975,9 +4282,11 @@
 
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Uniform-06807"));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("From Vulkan spec, section 14.5.2:\nVariables identified with "
+      HasSubstr("From Vulkan spec:\nVariables identified with "
                 "the StorageBuffer storage class are used to access "
                 "transparent buffer backed resources. Such variables must be "
                 "typed as OpTypeStruct, or an array of this type"));
@@ -4006,9 +4315,11 @@
 
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Uniform-06807"));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("From Vulkan spec, section 14.5.2:\nVariables identified with "
+      HasSubstr("From Vulkan spec:\nVariables identified with "
                 "the StorageBuffer storage class are used to access "
                 "transparent buffer backed resources. Such variables must be "
                 "typed as OpTypeStruct, or an array of this type"));
@@ -4036,9 +4347,11 @@
 
   CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Uniform-06807"));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("From Vulkan spec, section 14.5.2:\nVariables identified with "
+      HasSubstr("From Vulkan spec:\nVariables identified with "
                 "the StorageBuffer storage class are used to access "
                 "transparent buffer backed resources. Such variables must be "
                 "typed as OpTypeStruct, or an array of this type"));
@@ -4048,7 +4361,7 @@
   const std::string spirv = R"(
 OpCapability Shader
 OpMemoryModel Logical GLSL450
-OpEntryPoint Vertex %main "main"
+OpEntryPoint Vertex %main "main" %var
 OpDecorate %var Location 0
 OpDecorate %var Invariant
 %void = OpTypeVoid
@@ -4070,7 +4383,7 @@
   const std::string spirv = R"(
 OpCapability Shader
 OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main"
+OpEntryPoint Fragment %main "main" %var
 OpExecutionMode %main OriginUpperLeft
 OpDecorate %var Location 0
 OpMemberDecorate %struct 1 Invariant
@@ -4259,8 +4572,10 @@
   CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_0);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Variable initializers in Workgroup storage class are "
-                        "limited to OpConstantNull"));
+              AnyVUID(" VUID-StandaloneSpirv-OpVariable-04734"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpVariable, <id> '5[%5]', initializers are limited to "
+                        "OpConstantNull in Workgroup storage class"));
 }
 
 TEST_F(ValidateMemory, VulkanInitializerWithWorkgroupStorageClassGood) {
diff --git a/test/val/val_mesh_shading_test.cpp b/test/val/val_mesh_shading_test.cpp
new file mode 100644
index 0000000..ce6999d
--- /dev/null
+++ b/test/val/val_mesh_shading_test.cpp
@@ -0,0 +1,604 @@
+// Copyright (c) 2022 The Khronos Group 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.
+
+// Tests instructions from SPV_EXT_mesh_shader
+
+#include <sstream>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "test/val/val_fixtures.h"
+
+namespace spvtools {
+namespace val {
+namespace {
+
+using ::testing::HasSubstr;
+using ::testing::Values;
+
+using ValidateMeshShading = spvtest::ValidateBase<bool>;
+
+TEST_F(ValidateMeshShading, EmitMeshTasksEXTNotLastInstructionUniversal) {
+  const std::string body = R"(
+               OpCapability MeshShadingEXT
+               OpExtension "SPV_EXT_mesh_shader"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint TaskEXT %main "main" %p
+               OpExecutionModeId %main LocalSizeId %uint_1 %uint_1 %uint_1
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+      %float = OpTypeFloat 32
+  %arr_float = OpTypeArray %float %uint_1
+    %Payload = OpTypeStruct %arr_float
+%ptr_Payload = OpTypePointer TaskPayloadWorkgroupEXT %Payload
+          %p = OpVariable %ptr_Payload TaskPayloadWorkgroupEXT
+       %main = OpFunction %void None %func
+     %label1 = OpLabel
+               OpEmitMeshTasksEXT %uint_1 %uint_1 %uint_1 %p
+               OpBranch %label2
+     %label2 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_LAYOUT,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Branch must appear in a block"));
+}
+
+TEST_F(ValidateMeshShading, EmitMeshTasksEXTNotLastInstructionVulkan) {
+  const std::string body = R"(
+               OpCapability MeshShadingEXT
+               OpExtension "SPV_EXT_mesh_shader"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint TaskEXT %main "main" %p
+               OpExecutionModeId %main LocalSizeId %uint_1 %uint_1 %uint_1
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+      %float = OpTypeFloat 32
+  %arr_float = OpTypeArray %float %uint_1
+    %Payload = OpTypeStruct %arr_float
+%ptr_Payload = OpTypePointer TaskPayloadWorkgroupEXT %Payload
+          %p = OpVariable %ptr_Payload TaskPayloadWorkgroupEXT
+       %main = OpFunction %void None %func
+     %label1 = OpLabel
+               OpEmitMeshTasksEXT %uint_1 %uint_1 %uint_1 %p
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(body, SPV_ENV_VULKAN_1_2);
+  EXPECT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Return must appear in a block"));
+}
+
+TEST_F(ValidateMeshShading, BasicTaskSuccess) {
+  const std::string body = R"(
+               OpCapability MeshShadingEXT
+               OpExtension "SPV_EXT_mesh_shader"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint TaskEXT %main "main"
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+       %main = OpFunction %void None %func
+      %label = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
+}
+
+TEST_F(ValidateMeshShading, BasicMeshSuccess) {
+  const std::string body = R"(
+               OpCapability MeshShadingEXT
+               OpExtension "SPV_EXT_mesh_shader"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint MeshEXT %main "main"
+               OpExecutionMode %main OutputVertices 1
+               OpExecutionMode %main OutputPrimitivesEXT 1
+               OpExecutionMode %main OutputTrianglesEXT
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+       %main = OpFunction %void None %func
+      %label = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
+}
+
+TEST_F(ValidateMeshShading, VulkanBasicMeshAndTaskSuccess) {
+  const std::string body = R"(
+               OpCapability MeshShadingEXT
+               OpExtension "SPV_EXT_mesh_shader"
+               OpExtension "SPV_NV_mesh_shader"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint MeshEXT %mainMesh "mainMesh"
+               OpEntryPoint TaskEXT %mainTask "mainTask"
+               OpExecutionMode %mainMesh OutputVertices 1
+               OpExecutionMode %mainMesh OutputPrimitivesEXT 1
+               OpExecutionMode %mainMesh OutputTrianglesEXT
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+   %mainMesh = OpFunction %void None %func
+  %labelMesh = OpLabel
+               OpReturn
+               OpFunctionEnd
+   %mainTask = OpFunction %void None %func
+  %labelTask = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(body, SPV_ENV_VULKAN_1_2);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+}
+
+TEST_F(ValidateMeshShading, VulkanBasicMeshAndTaskBad) {
+  const std::string body = R"(
+               OpCapability MeshShadingEXT
+               OpCapability MeshShadingNV
+               OpExtension "SPV_EXT_mesh_shader"
+               OpExtension "SPV_NV_mesh_shader"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint MeshEXT %mainMesh "mainMesh"
+               OpEntryPoint TaskNV %mainTask "mainTask"
+               OpExecutionMode %mainMesh OutputVertices 1
+               OpExecutionMode %mainMesh OutputPrimitivesEXT 1
+               OpExecutionMode %mainMesh OutputTrianglesEXT
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+   %mainMesh = OpFunction %void None %func
+  %labelMesh = OpLabel
+               OpReturn
+               OpFunctionEnd
+   %mainTask = OpFunction %void None %func
+  %labelTask = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(body, SPV_ENV_VULKAN_1_2);
+  EXPECT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-MeshEXT-07102"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Module can't mix MeshEXT/TaskEXT with MeshNV/TaskNV "
+                        "Execution Model."));
+}
+
+TEST_F(ValidateMeshShading, MeshMissingOutputVertices) {
+  const std::string body = R"(
+               OpCapability MeshShadingEXT
+               OpExtension "SPV_EXT_mesh_shader"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint MeshEXT %main "main"
+               OpExecutionMode %main OutputPrimitivesEXT 1
+               OpExecutionMode %main OutputTrianglesEXT
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+       %main = OpFunction %void None %func
+      %label = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("MeshEXT execution model entry points must specify both "
+                "OutputPrimitivesEXT and OutputVertices Execution Modes."));
+}
+
+TEST_F(ValidateMeshShading, MeshMissingOutputPrimitivesEXT) {
+  const std::string body = R"(
+               OpCapability MeshShadingEXT
+               OpExtension "SPV_EXT_mesh_shader"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint MeshEXT %main "main"
+               OpExecutionMode %main OutputVertices 1
+               OpExecutionMode %main OutputTrianglesEXT
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+       %main = OpFunction %void None %func
+      %label = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("MeshEXT execution model entry points must specify both "
+                "OutputPrimitivesEXT and OutputVertices Execution Modes."));
+}
+
+TEST_F(ValidateMeshShading, MeshMissingOutputType) {
+  const std::string body = R"(
+               OpCapability MeshShadingEXT
+               OpExtension "SPV_EXT_mesh_shader"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint MeshEXT %main "main"
+               OpExecutionMode %main OutputVertices 1
+               OpExecutionMode %main OutputPrimitivesEXT 1
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+       %main = OpFunction %void None %func
+      %label = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("MeshEXT execution model entry points must specify "
+                        "exactly one of OutputPoints, OutputLinesEXT, or "
+                        "OutputTrianglesEXT Execution Modes."));
+}
+
+TEST_F(ValidateMeshShading, MeshMultipleOutputType) {
+  const std::string body = R"(
+               OpCapability MeshShadingEXT
+               OpExtension "SPV_EXT_mesh_shader"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint MeshEXT %main "main"
+               OpExecutionMode %main OutputVertices 1
+               OpExecutionMode %main OutputPrimitivesEXT 1
+               OpExecutionMode %main OutputLinesEXT
+               OpExecutionMode %main OutputTrianglesEXT
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+       %main = OpFunction %void None %func
+      %label = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("MeshEXT execution model entry points must specify "
+                        "exactly one of OutputPoints, OutputLinesEXT, or "
+                        "OutputTrianglesEXT Execution Modes."));
+}
+
+TEST_F(ValidateMeshShading, BadExecutionModelOutputLinesEXT) {
+  const std::string body = R"(
+               OpCapability MeshShadingEXT
+               OpExtension "SPV_EXT_mesh_shader"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main OutputLinesEXT
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+       %main = OpFunction %void None %func
+      %label = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Execution mode can only be used with the MeshEXT or "
+                        "MeshNV execution model."));
+}
+
+TEST_F(ValidateMeshShading, BadExecutionModelOutputTrianglesEXT) {
+  const std::string body = R"(
+               OpCapability MeshShadingEXT
+               OpExtension "SPV_EXT_mesh_shader"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main OutputTrianglesEXT
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+       %main = OpFunction %void None %func
+      %label = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Execution mode can only be used with the MeshEXT or "
+                        "MeshNV execution model."));
+}
+
+TEST_F(ValidateMeshShading, BadExecutionModelOutputPrimitivesEXT) {
+  const std::string body = R"(
+               OpCapability MeshShadingEXT
+               OpExtension "SPV_EXT_mesh_shader"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main OutputPrimitivesEXT 1
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+       %main = OpFunction %void None %func
+      %label = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Execution mode can only be used with the MeshEXT or "
+                        "MeshNV execution model."));
+}
+
+TEST_F(ValidateMeshShading, OpEmitMeshTasksBadGroupCountSignedInt) {
+  const std::string body = R"(
+               OpCapability MeshShadingEXT
+               OpExtension "SPV_EXT_mesh_shader"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint TaskEXT %main "main"
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+       %uint = OpTypeInt 32 0
+      %int_1 = OpConstant %int 1
+     %uint_1 = OpConstant %uint 1
+       %main = OpFunction %void None %func
+       %label = OpLabel
+               OpEmitMeshTasksEXT %int_1 %uint_1 %uint_1
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Group Count X must be a 32-bit unsigned int scalar"));
+}
+
+TEST_F(ValidateMeshShading, OpEmitMeshTasksBadGroupCountVector) {
+  const std::string body = R"(
+               OpCapability MeshShadingEXT
+               OpExtension "SPV_EXT_mesh_shader"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint TaskEXT %main "main"
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+     %v2uint = OpTypeVector %uint 2
+%_ptr_v2uint = OpTypePointer Function %v2uint
+     %uint_1 = OpConstant %uint 1
+  %composite = OpConstantComposite %v2uint %uint_1 %uint_1
+  %_ptr_uint = OpTypePointer Function %uint
+       %main = OpFunction %void None %func
+      %label = OpLabel
+          %x = OpVariable %_ptr_v2uint Function
+               OpStore %x %composite
+         %13 = OpAccessChain %_ptr_uint %x %uint_1
+         %14 = OpLoad %uint %13
+               OpEmitMeshTasksEXT %14 %composite %uint_1
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Group Count Y must be a 32-bit unsigned int scalar"));
+}
+
+TEST_F(ValidateMeshShading, OpEmitMeshTasksBadPayload) {
+  const std::string body = R"(
+               OpCapability MeshShadingEXT
+               OpExtension "SPV_EXT_mesh_shader"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint TaskEXT %main "main" %payload
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+       %task = OpTypeStruct %uint
+%_ptr_Uniform = OpTypePointer Uniform %task
+    %payload = OpVariable %_ptr_Uniform Uniform
+     %uint_1 = OpConstant %uint 1
+       %main = OpFunction %void None %func
+      %label = OpLabel
+               OpEmitMeshTasksEXT %uint_1 %uint_1 %uint_1 %payload
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Payload OpVariable must have a storage class of "
+                        "TaskPayloadWorkgroupEXT"));
+}
+
+TEST_F(ValidateMeshShading, TaskPayloadWorkgroupBadExecutionModel) {
+  const std::string body = R"(
+               OpCapability MeshShadingEXT
+               OpExtension "SPV_EXT_mesh_shader"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %payload
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+%_ptr_TaskPayloadWorkgroupEXT = OpTypePointer TaskPayloadWorkgroupEXT %uint
+    %payload = OpVariable %_ptr_TaskPayloadWorkgroupEXT TaskPayloadWorkgroupEXT
+       %main = OpFunction %void None %func
+      %label = OpLabel
+       %load = OpLoad %uint %payload
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("TaskPayloadWorkgroupEXT Storage Class is limited to "
+                        "TaskEXT and MeshKHR execution model"));
+}
+
+TEST_F(ValidateMeshShading, OpSetMeshOutputsBadVertexCount) {
+  const std::string body = R"(
+               OpCapability MeshShadingEXT
+               OpExtension "SPV_EXT_mesh_shader"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint MeshEXT %main "main"
+               OpExecutionMode %main OutputVertices 1
+               OpExecutionMode %main OutputPrimitivesNV 1
+               OpExecutionMode %main OutputTrianglesNV
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+       %uint = OpTypeInt 32 0
+  %_ptr_int = OpTypePointer Function %int
+      %int_1 = OpConstant %int 1
+     %uint_1 = OpConstant %uint 1
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+          %x = OpVariable %_ptr_int Function
+               OpStore %x %int_1
+       %load = OpLoad %int %x
+               OpSetMeshOutputsEXT %load %uint_1
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Vertex Count must be a 32-bit unsigned int scalar"));
+}
+
+TEST_F(ValidateMeshShading, OpSetMeshOutputsBadPrimitiveCount) {
+  const std::string body = R"(
+               OpCapability MeshShadingEXT
+               OpExtension "SPV_EXT_mesh_shader"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint MeshEXT %main "main"
+               OpExecutionMode %main OutputVertices 1
+               OpExecutionMode %main OutputPrimitivesNV 1
+               OpExecutionMode %main OutputTrianglesNV
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+       %uint = OpTypeInt 32 0
+      %int_1 = OpConstant %int 1
+     %uint_1 = OpConstant %uint 1
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpSetMeshOutputsEXT %uint_1 %int_1
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Primitive Count must be a 32-bit unsigned int scalar"));
+}
+
+TEST_F(ValidateMeshShading, OpSetMeshOutputsBadExecutionModel) {
+  const std::string body = R"(
+               OpCapability MeshShadingEXT
+               OpExtension "SPV_EXT_mesh_shader"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint TaskEXT %main "main"
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpSetMeshOutputsEXT %uint_1 %uint_1
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpSetMeshOutputsEXT requires MeshEXT execution model"));
+}
+
+TEST_F(ValidateMeshShading, OpSetMeshOutputsZeroSuccess) {
+  const std::string body = R"(
+               OpCapability MeshShadingEXT
+               OpExtension "SPV_EXT_mesh_shader"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint MeshEXT %main "main"
+               OpExecutionMode %main OutputVertices 1
+               OpExecutionMode %main OutputPrimitivesNV 1
+               OpExecutionMode %main OutputTrianglesNV
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpSetMeshOutputsEXT %uint_0 %uint_0
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
+}
+
+TEST_F(ValidateMeshShading, OpEmitMeshTasksZeroSuccess) {
+  const std::string body = R"(
+               OpCapability MeshShadingEXT
+               OpExtension "SPV_EXT_mesh_shader"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint TaskEXT %main "main"
+       %void = OpTypeVoid
+       %func = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 1
+       %main = OpFunction %void None %func
+      %label = OpLabel
+               OpEmitMeshTasksEXT %uint_0 %uint_0 %uint_0
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(body, SPV_ENV_UNIVERSAL_1_5);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
+}
+
+}  // namespace
+}  // namespace val
+}  // namespace spvtools
diff --git a/test/val/val_modes_test.cpp b/test/val/val_modes_test.cpp
index 02a6132..689f0ba 100644
--- a/test/val/val_modes_test.cpp
+++ b/test/val/val_modes_test.cpp
@@ -1101,6 +1101,89 @@
   EXPECT_THAT(SPV_SUCCESS, ValidateInstructions());
 }
 
+
+TEST_F(ValidateMode, FragmentShaderStencilRefFrontTooManyModesBad) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability StencilExportEXT
+OpExtension "SPV_AMD_shader_early_and_late_fragment_tests"
+OpExtension "SPV_EXT_shader_stencil_export"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpExecutionMode %main EarlyAndLateFragmentTestsAMD
+OpExecutionMode %main StencilRefLessFrontAMD
+OpExecutionMode %main StencilRefGreaterFrontAMD
+)" + kVoidFunction;
+
+  CompileSuccessfully(spirv);
+  EXPECT_THAT(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Fragment execution model entry points can specify at most "
+                "one of StencilRefUnchangedFrontAMD, "
+                "StencilRefLessFrontAMD or StencilRefGreaterFrontAMD "
+                "execution modes."));
+}
+
+TEST_F(ValidateMode, FragmentShaderStencilRefBackTooManyModesBad) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability StencilExportEXT
+OpExtension "SPV_AMD_shader_early_and_late_fragment_tests"
+OpExtension "SPV_EXT_shader_stencil_export"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpExecutionMode %main EarlyAndLateFragmentTestsAMD
+OpExecutionMode %main StencilRefLessBackAMD
+OpExecutionMode %main StencilRefGreaterBackAMD
+)" + kVoidFunction;
+
+  CompileSuccessfully(spirv);
+  EXPECT_THAT(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Fragment execution model entry points can specify at most "
+                "one of StencilRefUnchangedBackAMD, "
+                "StencilRefLessBackAMD or StencilRefGreaterBackAMD "
+                "execution modes."));
+}
+
+TEST_F(ValidateMode, FragmentShaderStencilRefFrontGood) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability StencilExportEXT
+OpExtension "SPV_AMD_shader_early_and_late_fragment_tests"
+OpExtension "SPV_EXT_shader_stencil_export"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpExecutionMode %main EarlyAndLateFragmentTestsAMD
+OpExecutionMode %main StencilRefLessFrontAMD
+)" + kVoidFunction;
+
+  CompileSuccessfully(spirv);
+  EXPECT_THAT(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateMode, FragmentShaderStencilRefBackGood) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpCapability StencilExportEXT
+OpExtension "SPV_AMD_shader_early_and_late_fragment_tests"
+OpExtension "SPV_EXT_shader_stencil_export"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpExecutionMode %main EarlyAndLateFragmentTestsAMD
+OpExecutionMode %main StencilRefLessBackAMD
+)" + kVoidFunction;
+
+  CompileSuccessfully(spirv);
+  EXPECT_THAT(SPV_SUCCESS, ValidateInstructions());
+}
+
 TEST_F(ValidateMode, FragmentShaderDemoteVertexBad) {
   const std::string spirv = R"(
 OpCapability Shader
@@ -1178,6 +1261,26 @@
               HasSubstr("Expected bool scalar type as Result Type"));
 }
 
+TEST_F(ValidateMode, LocalSizeIdVulkan1p3DoesNotRequireOption) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionModeId %main LocalSizeId %int_1 %int_1 %int_1
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_1 = OpConstant %int 1
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_3));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_non_semantic_test.cpp b/test/val/val_non_semantic_test.cpp
index b80bb1a..85362fe 100644
--- a/test/val/val_non_semantic_test.cpp
+++ b/test/val/val_non_semantic_test.cpp
@@ -105,7 +105,7 @@
                                  Values(""), Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
-    MissingOpExtension, ValidateNonSemanticGenerated,
+    MissingOpExtensionPre1p6, ValidateNonSemanticGenerated,
     Combine(Values(false), Values(true), Values(""), Values(""),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
@@ -187,7 +187,27 @@
   // there's no specific error for using an OpExtInst too early, it requires a
   // type so by definition any use of a type in it will be an undefined ID
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("ID 2[%2] has not been defined"));
+              HasSubstr("ID '2[%2]' has not been defined"));
+}
+
+TEST_F(ValidateNonSemanticString, MissingOpExtensionPost1p6) {
+  const std::string spirv = R"(
+OpCapability Shader
+%extinst = OpExtInstImport "NonSemantic.Testing.Set"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%void = OpTypeVoid
+%test = OpExtInst %void %extinst 3
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_6);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_6));
 }
 
 }  // namespace
diff --git a/test/val/val_ray_query_test.cpp b/test/val/val_ray_query_test.cpp
new file mode 100644
index 0000000..e0eb067
--- /dev/null
+++ b/test/val/val_ray_query_test.cpp
@@ -0,0 +1,631 @@
+// Copyright (c) 2022 The Khronos Group 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.
+
+// Tests ray query instructions from SPV_KHR_ray_query.
+
+#include <sstream>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "spirv-tools/libspirv.h"
+#include "test/val/val_fixtures.h"
+
+namespace spvtools {
+namespace val {
+namespace {
+
+using ::testing::HasSubstr;
+using ::testing::Values;
+
+using ValidateRayQuery = spvtest::ValidateBase<bool>;
+
+std::string GenerateShaderCode(
+    const std::string& body,
+    const std::string& capabilities_and_extensions = "",
+    const std::string& declarations = "") {
+  std::ostringstream ss;
+  ss << R"(
+OpCapability Shader
+OpCapability Int64
+OpCapability Float64
+OpCapability RayQueryKHR
+OpExtension "SPV_KHR_ray_query"
+)";
+
+  ss << capabilities_and_extensions;
+
+  ss << R"(
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+
+OpDecorate %top_level_as DescriptorSet 0
+OpDecorate %top_level_as Binding 0
+
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f32 = OpTypeFloat 32
+%f64 = OpTypeFloat 64
+%u32 = OpTypeInt 32 0
+%s32 = OpTypeInt 32 1
+%u64 = OpTypeInt 64 0
+%s64 = OpTypeInt 64 1
+%type_rq = OpTypeRayQueryKHR
+%type_as = OpTypeAccelerationStructureKHR
+
+%s32vec2 = OpTypeVector %s32 2
+%u32vec2 = OpTypeVector %u32 2
+%f32vec2 = OpTypeVector %f32 2
+%u32vec3 = OpTypeVector %u32 3
+%s32vec3 = OpTypeVector %s32 3
+%f32vec3 = OpTypeVector %f32 3
+%u32vec4 = OpTypeVector %u32 4
+%s32vec4 = OpTypeVector %s32 4
+%f32vec4 = OpTypeVector %f32 4
+
+%mat4x3 = OpTypeMatrix %f32vec3 4
+
+%f32_0 = OpConstant %f32 0
+%f64_0 = OpConstant %f64 0
+%s32_0 = OpConstant %s32 0
+%u32_0 = OpConstant %u32 0
+%u64_0 = OpConstant %u64 0
+
+%u32vec3_0 = OpConstantComposite %u32vec3 %u32_0 %u32_0 %u32_0
+%f32vec3_0 = OpConstantComposite %f32vec3 %f32_0 %f32_0 %f32_0
+%f32vec4_0 = OpConstantComposite %f32vec4 %f32_0 %f32_0 %f32_0 %f32_0
+
+%ptr_rq = OpTypePointer Private %type_rq
+%ray_query = OpVariable %ptr_rq Private
+
+%ptr_as = OpTypePointer UniformConstant %type_as
+%top_level_as = OpVariable %ptr_as UniformConstant
+
+%ptr_function_u32 = OpTypePointer Function %u32
+%ptr_function_f32 = OpTypePointer Function %f32
+%ptr_function_f32vec3 = OpTypePointer Function %f32vec3
+)";
+
+  ss << declarations;
+
+  ss << R"(
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+)";
+
+  ss << body;
+
+  ss << R"(
+OpReturn
+OpFunctionEnd)";
+  return ss.str();
+}
+
+std::string RayQueryResult(std::string opcode) {
+  if (opcode.compare("OpRayQueryProceedKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionTypeKHR") == 0 ||
+      opcode.compare("OpRayQueryGetRayTMinKHR") == 0 ||
+      opcode.compare("OpRayQueryGetRayFlagsKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionTKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionInstanceCustomIndexKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionInstanceIdKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionInstanceShaderBindingTableRecord"
+                     "OffsetKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionGeometryIndexKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionPrimitiveIndexKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionBarycentricsKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionFrontFaceKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionCandidateAABBOpaqueKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionObjectRayDirectionKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionObjectRayOriginKHR") == 0 ||
+      opcode.compare("OpRayQueryGetWorldRayDirectionKHR") == 0 ||
+      opcode.compare("OpRayQueryGetWorldRayOriginKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionObjectToWorldKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionWorldToObjectKHR") == 0) {
+    return "%result =";
+  }
+  return "";
+}
+
+std::string RayQueryResultType(std::string opcode, bool valid) {
+  if (opcode.compare("OpRayQueryGetIntersectionTypeKHR") == 0 ||
+      opcode.compare("OpRayQueryGetRayFlagsKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionInstanceCustomIndexKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionInstanceIdKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionInstanceShaderBindingTableRecord"
+                     "OffsetKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionGeometryIndexKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionPrimitiveIndexKHR") == 0) {
+    return valid ? "%u32" : "%f64";
+  }
+
+  if (opcode.compare("OpRayQueryGetRayTMinKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionTKHR") == 0) {
+    return valid ? "%f32" : "%f64";
+  }
+
+  if (opcode.compare("OpRayQueryGetIntersectionBarycentricsKHR") == 0) {
+    return valid ? "%f32vec2" : "%f64";
+  }
+
+  if (opcode.compare("OpRayQueryGetIntersectionObjectRayDirectionKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionObjectRayOriginKHR") == 0 ||
+      opcode.compare("OpRayQueryGetWorldRayDirectionKHR") == 0 ||
+      opcode.compare("OpRayQueryGetWorldRayOriginKHR") == 0) {
+    return valid ? "%f32vec3" : "%f64";
+  }
+
+  if (opcode.compare("OpRayQueryProceedKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionFrontFaceKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionCandidateAABBOpaqueKHR") == 0) {
+    return valid ? "%bool" : "%f64";
+  }
+
+  if (opcode.compare("OpRayQueryGetIntersectionObjectToWorldKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionWorldToObjectKHR") == 0) {
+    return valid ? "%mat4x3" : "%f64";
+  }
+  return "";
+}
+
+std::string RayQueryIntersection(std::string opcode, bool valid) {
+  if (opcode.compare("OpRayQueryGetIntersectionTypeKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionTKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionInstanceCustomIndexKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionInstanceIdKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionInstanceShaderBindingTableRecord"
+                     "OffsetKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionGeometryIndexKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionPrimitiveIndexKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionBarycentricsKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionFrontFaceKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionObjectRayDirectionKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionObjectRayOriginKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionObjectToWorldKHR") == 0 ||
+      opcode.compare("OpRayQueryGetIntersectionWorldToObjectKHR") == 0) {
+    return valid ? "%s32_0" : "%f32_0";
+  }
+  return "";
+}
+
+using RayQueryCommon = spvtest::ValidateBase<std::string>;
+
+TEST_P(RayQueryCommon, Success) {
+  std::string opcode = GetParam();
+  std::ostringstream ss;
+  ss << RayQueryResult(opcode);
+  ss << " " << opcode << " ";
+  ss << RayQueryResultType(opcode, true);
+  ss << " %ray_query ";
+  ss << RayQueryIntersection(opcode, true);
+  CompileSuccessfully(GenerateShaderCode(ss.str()).c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(RayQueryCommon, BadQuery) {
+  std::string opcode = GetParam();
+  std::ostringstream ss;
+  ss << RayQueryResult(opcode);
+  ss << " " << opcode << " ";
+  ss << RayQueryResultType(opcode, true);
+  ss << " %top_level_as ";
+  ss << RayQueryIntersection(opcode, true);
+  CompileSuccessfully(GenerateShaderCode(ss.str()).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Ray Query must be a pointer to OpTypeRayQueryKHR"));
+}
+
+TEST_P(RayQueryCommon, BadResult) {
+  std::string opcode = GetParam();
+  std::string result_type = RayQueryResultType(opcode, false);
+  if (!result_type.empty()) {
+    std::ostringstream ss;
+    ss << RayQueryResult(opcode);
+    ss << " " << opcode << " ";
+    ss << result_type;
+    ss << " %ray_query ";
+    ss << RayQueryIntersection(opcode, true);
+    CompileSuccessfully(GenerateShaderCode(ss.str()).c_str());
+    EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+
+    std::string correct_result_type = RayQueryResultType(opcode, true);
+    if (correct_result_type.compare("%u32") == 0) {
+      EXPECT_THAT(
+          getDiagnosticString(),
+          HasSubstr("expected Result Type to be 32-bit int scalar type"));
+    } else if (correct_result_type.compare("%f32") == 0) {
+      EXPECT_THAT(
+          getDiagnosticString(),
+          HasSubstr("expected Result Type to be 32-bit float scalar type"));
+    } else if (correct_result_type.compare("%f32vec2") == 0) {
+      EXPECT_THAT(getDiagnosticString(),
+                  HasSubstr("expected Result Type to be 32-bit float "
+                            "2-component vector type"));
+    } else if (correct_result_type.compare("%f32vec3") == 0) {
+      EXPECT_THAT(getDiagnosticString(),
+                  HasSubstr("expected Result Type to be 32-bit float "
+                            "3-component vector type"));
+    } else if (correct_result_type.compare("%bool") == 0) {
+      EXPECT_THAT(getDiagnosticString(),
+                  HasSubstr("expected Result Type to be bool scalar type"));
+    } else if (correct_result_type.compare("%mat4x3") == 0) {
+      EXPECT_THAT(getDiagnosticString(),
+                  HasSubstr("expected matrix type as Result Type"));
+    }
+  }
+}
+
+TEST_P(RayQueryCommon, BadIntersection) {
+  std::string opcode = GetParam();
+  std::string intersection = RayQueryIntersection(opcode, false);
+  if (!intersection.empty()) {
+    std::ostringstream ss;
+    ss << RayQueryResult(opcode);
+    ss << " " << opcode << " ";
+    ss << RayQueryResultType(opcode, true);
+    ss << " %ray_query ";
+    ss << intersection;
+    CompileSuccessfully(GenerateShaderCode(ss.str()).c_str());
+    EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+    EXPECT_THAT(
+        getDiagnosticString(),
+        HasSubstr(
+            "expected Intersection ID to be a constant 32-bit int scalar"));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ValidateRayQueryCommon, RayQueryCommon,
+    Values("OpRayQueryTerminateKHR", "OpRayQueryConfirmIntersectionKHR",
+           "OpRayQueryProceedKHR", "OpRayQueryGetIntersectionTypeKHR",
+           "OpRayQueryGetRayTMinKHR", "OpRayQueryGetRayFlagsKHR",
+           "OpRayQueryGetWorldRayDirectionKHR",
+           "OpRayQueryGetWorldRayOriginKHR", "OpRayQueryGetIntersectionTKHR",
+           "OpRayQueryGetIntersectionInstanceCustomIndexKHR",
+           "OpRayQueryGetIntersectionInstanceIdKHR",
+           "OpRayQueryGetIntersectionInstanceShaderBindingTableRecordOffsetKHR",
+           "OpRayQueryGetIntersectionGeometryIndexKHR",
+           "OpRayQueryGetIntersectionPrimitiveIndexKHR",
+           "OpRayQueryGetIntersectionBarycentricsKHR",
+           "OpRayQueryGetIntersectionFrontFaceKHR",
+           "OpRayQueryGetIntersectionCandidateAABBOpaqueKHR",
+           "OpRayQueryGetIntersectionObjectRayDirectionKHR",
+           "OpRayQueryGetIntersectionObjectRayOriginKHR",
+           "OpRayQueryGetIntersectionObjectToWorldKHR",
+           "OpRayQueryGetIntersectionWorldToObjectKHR"));
+
+// tests various Intersection operand types
+TEST_F(ValidateRayQuery, IntersectionSuccess) {
+  const std::string body = R"(
+%result_1 = OpRayQueryGetIntersectionFrontFaceKHR %bool %ray_query %s32_0
+%result_2 = OpRayQueryGetIntersectionFrontFaceKHR %bool %ray_query %u32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateRayQuery, IntersectionVector) {
+  const std::string body = R"(
+%result = OpRayQueryGetIntersectionFrontFaceKHR %bool %ray_query %u32vec3_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("expected Intersection ID to be a constant 32-bit int scalar"));
+}
+
+TEST_F(ValidateRayQuery, IntersectionNonConstantVariable) {
+  const std::string body = R"(
+%var = OpVariable %ptr_function_u32 Function
+%result = OpRayQueryGetIntersectionFrontFaceKHR %bool %ray_query %var
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("expected Intersection ID to be a constant 32-bit int scalar"));
+}
+
+TEST_F(ValidateRayQuery, IntersectionNonConstantLoad) {
+  const std::string body = R"(
+%var = OpVariable %ptr_function_u32 Function
+%load = OpLoad %u32 %var
+%result = OpRayQueryGetIntersectionFrontFaceKHR %bool %ray_query %load
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("expected Intersection ID to be a constant 32-bit int scalar"));
+}
+
+TEST_F(ValidateRayQuery, InitializeSuccess) {
+  const std::string body = R"(
+%var_u32 = OpVariable %ptr_function_u32 Function
+%var_f32 = OpVariable %ptr_function_f32 Function
+%var_f32vec3 = OpVariable %ptr_function_f32vec3 Function
+
+%as = OpLoad %type_as %top_level_as
+OpRayQueryInitializeKHR %ray_query %as %u32_0 %u32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f32_0
+
+%_u32 = OpLoad %u32 %var_u32
+%_f32 = OpLoad %f32 %var_f32
+%_f32vec3 = OpLoad %f32vec3 %var_f32vec3
+OpRayQueryInitializeKHR %ray_query %as %_u32 %_u32 %_f32vec3 %_f32 %_f32vec3 %_f32
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateRayQuery, InitializeFunctionSuccess) {
+  const std::string declaration = R"(
+%rq_ptr = OpTypePointer Private %type_rq
+%rq_func_type = OpTypeFunction %void %rq_ptr
+%rq_var_1 = OpVariable %rq_ptr Private
+%rq_var_2 = OpVariable %rq_ptr Private
+)";
+
+  const std::string body = R"(
+%fcall_1 = OpFunctionCall %void %rq_func %rq_var_1
+%as_1 = OpLoad %type_as %top_level_as
+OpRayQueryInitializeKHR %rq_var_1 %as_1 %u32_0 %u32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f32_0
+%fcall_2 = OpFunctionCall %void %rq_func %rq_var_2
+OpReturn
+OpFunctionEnd
+%rq_func = OpFunction %void None %rq_func_type
+%rq_param = OpFunctionParameter %rq_ptr
+%label = OpLabel
+%as_2 = OpLoad %type_as %top_level_as
+OpRayQueryInitializeKHR %rq_param %as_2 %u32_0 %u32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, "", declaration).c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateRayQuery, InitializeBadRayQuery) {
+  const std::string body = R"(
+%load = OpLoad %type_as %top_level_as
+OpRayQueryInitializeKHR %top_level_as %load %u32_0 %u32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Ray Query must be a pointer to OpTypeRayQueryKHR"));
+}
+
+TEST_F(ValidateRayQuery, InitializeBadAS) {
+  const std::string body = R"(
+OpRayQueryInitializeKHR %ray_query %ray_query %u32_0 %u32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected Acceleration Structure to be of type "
+                        "OpTypeAccelerationStructureKHR"));
+}
+
+TEST_F(ValidateRayQuery, InitializeBadRayFlags64) {
+  const std::string body = R"(
+%load = OpLoad %type_as %top_level_as
+OpRayQueryInitializeKHR %ray_query %load %u64_0 %u32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Ray Flags must be a 32-bit int scalar"));
+}
+
+TEST_F(ValidateRayQuery, InitializeBadRayFlagsVector) {
+  const std::string body = R"(
+%load = OpLoad %type_as %top_level_as
+OpRayQueryInitializeKHR %ray_query %load %u32vec2 %u32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Operand '15[%v2uint]' cannot be a type"));
+}
+
+TEST_F(ValidateRayQuery, InitializeBadCullMask) {
+  const std::string body = R"(
+%load = OpLoad %type_as %top_level_as
+OpRayQueryInitializeKHR %ray_query %load %u32_0 %f32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Cull Mask must be a 32-bit int scalar"));
+}
+
+TEST_F(ValidateRayQuery, InitializeBadRayOriginVec4) {
+  const std::string body = R"(
+%load = OpLoad %type_as %top_level_as
+OpRayQueryInitializeKHR %ray_query %load %u32_0 %u32_0 %f32vec4_0 %f32_0 %f32vec3_0 %f32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Ray Origin must be a 32-bit float 3-component vector"));
+}
+
+TEST_F(ValidateRayQuery, InitializeBadRayOriginFloat) {
+  const std::string body = R"(
+%var_f32 = OpVariable %ptr_function_f32 Function
+%_f32 = OpLoad %f32 %var_f32
+%load = OpLoad %type_as %top_level_as
+OpRayQueryInitializeKHR %ray_query %load %u32_0 %u32_0 %_f32 %f32_0 %f32vec3_0 %f32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Ray Origin must be a 32-bit float 3-component vector"));
+}
+
+TEST_F(ValidateRayQuery, InitializeBadRayOriginInt) {
+  const std::string body = R"(
+%load = OpLoad %type_as %top_level_as
+OpRayQueryInitializeKHR %ray_query %load %u32_0 %u32_0 %u32vec3_0 %f32_0 %f32vec3_0 %f32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Ray Origin must be a 32-bit float 3-component vector"));
+}
+
+TEST_F(ValidateRayQuery, InitializeBadRayTMin) {
+  const std::string body = R"(
+%load = OpLoad %type_as %top_level_as
+OpRayQueryInitializeKHR %ray_query %load %u32_0 %u32_0 %f32vec3_0 %u32_0 %f32vec3_0 %f32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Ray TMin must be a 32-bit float scalar"));
+}
+
+TEST_F(ValidateRayQuery, InitializeBadRayDirection) {
+  const std::string body = R"(
+%load = OpLoad %type_as %top_level_as
+OpRayQueryInitializeKHR %ray_query %load %u32_0 %u32_0 %f32vec3_0 %f32_0 %f32vec4_0 %f32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Ray Direction must be a 32-bit float 3-component vector"));
+}
+
+TEST_F(ValidateRayQuery, InitializeBadRayTMax) {
+  const std::string body = R"(
+%load = OpLoad %type_as %top_level_as
+OpRayQueryInitializeKHR %ray_query %load %u32_0 %u32_0 %f32vec3_0 %f32_0 %f32vec3_0 %f64_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Ray TMax must be a 32-bit float scalar"));
+}
+
+TEST_F(ValidateRayQuery, GenerateIntersectionSuccess) {
+  const std::string body = R"(
+%var = OpVariable %ptr_function_f32 Function
+%load = OpLoad %f32 %var
+OpRayQueryGenerateIntersectionKHR %ray_query %f32_0
+OpRayQueryGenerateIntersectionKHR %ray_query %load
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateRayQuery, GenerateIntersectionBadRayQuery) {
+  const std::string body = R"(
+OpRayQueryGenerateIntersectionKHR %top_level_as %f32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Ray Query must be a pointer to OpTypeRayQueryKHR"));
+}
+
+TEST_F(ValidateRayQuery, GenerateIntersectionBadHitT) {
+  const std::string body = R"(
+OpRayQueryGenerateIntersectionKHR %ray_query %u32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Hit T must be a 32-bit float scalar"));
+}
+
+TEST_F(ValidateRayQuery, RayQueryArraySuccess) {
+  // This shader is slightly different to the ones above, so it doesn't reuse
+  // the shader code generator.
+  const std::string shader = R"(
+                       OpCapability Shader
+                       OpCapability RayQueryKHR
+                       OpExtension "SPV_KHR_ray_query"
+                       OpMemoryModel Logical GLSL450
+                       OpEntryPoint GLCompute %main "main"
+                       OpExecutionMode %main LocalSize 1 1 1
+                       OpSource GLSL 460
+                       OpDecorate %topLevelAS DescriptorSet 0
+                       OpDecorate %topLevelAS Binding 0
+                       OpDecorate %gl_WorkGroupSize BuiltIn WorkgroupSize
+               %void = OpTypeVoid
+               %func = OpTypeFunction %void
+          %ray_query = OpTypeRayQueryKHR
+               %uint = OpTypeInt 32 0
+             %uint_2 = OpConstant %uint 2
+    %ray_query_array = OpTypeArray %ray_query %uint_2
+%ptr_ray_query_array = OpTypePointer Private %ray_query_array
+         %rayQueries = OpVariable %ptr_ray_query_array Private
+                %int = OpTypeInt 32 1
+              %int_0 = OpConstant %int 0
+      %ptr_ray_query = OpTypePointer Private %ray_query
+       %accel_struct = OpTypeAccelerationStructureKHR
+   %ptr_accel_struct = OpTypePointer UniformConstant %accel_struct
+         %topLevelAS = OpVariable %ptr_accel_struct UniformConstant
+             %uint_0 = OpConstant %uint 0
+           %uint_255 = OpConstant %uint 255
+              %float = OpTypeFloat 32
+            %v3float = OpTypeVector %float 3
+            %float_0 = OpConstant %float 0
+          %vec3_zero = OpConstantComposite %v3float %float_0 %float_0 %float_0
+            %float_1 = OpConstant %float 1
+      %vec3_xy_0_z_1 = OpConstantComposite %v3float %float_0 %float_0 %float_1
+           %float_10 = OpConstant %float 10
+             %v3uint = OpTypeVector %uint 3
+             %uint_1 = OpConstant %uint 1
+   %gl_WorkGroupSize = OpConstantComposite %v3uint %uint_1 %uint_1 %uint_1
+               %main = OpFunction %void None %func
+         %main_label = OpLabel
+    %first_ray_query = OpAccessChain %ptr_ray_query %rayQueries %int_0
+     %topLevelAS_val = OpLoad %accel_struct %topLevelAS
+                       OpRayQueryInitializeKHR %first_ray_query %topLevelAS_val %uint_0 %uint_255 %vec3_zero %float_0 %vec3_xy_0_z_1 %float_10
+                       OpReturn
+                       OpFunctionEnd
+)";
+  CompileSuccessfully(shader);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+}  // namespace
+}  // namespace val
+}  // namespace spvtools
diff --git a/test/val/val_ray_tracing_test.cpp b/test/val/val_ray_tracing_test.cpp
new file mode 100644
index 0000000..58b9356
--- /dev/null
+++ b/test/val/val_ray_tracing_test.cpp
@@ -0,0 +1,583 @@
+// Copyright (c) 2022 The Khronos Group 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.
+
+// Tests ray tracing instructions from SPV_KHR_ray_tracing.
+
+#include <sstream>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "test/val/val_fixtures.h"
+
+namespace spvtools {
+namespace val {
+namespace {
+
+using ::testing::HasSubstr;
+using ::testing::Values;
+
+using ValidateRayTracing = spvtest::ValidateBase<bool>;
+
+TEST_F(ValidateRayTracing, IgnoreIntersectionSuccess) {
+  const std::string body = R"(
+OpCapability RayTracingKHR
+OpExtension "SPV_KHR_ray_tracing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint AnyHitKHR %main "main"
+OpName %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%main = OpFunction %void None %func
+%label = OpLabel
+OpIgnoreIntersectionKHR
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateRayTracing, IgnoreIntersectionExecutionModel) {
+  const std::string body = R"(
+OpCapability RayTracingKHR
+OpExtension "SPV_KHR_ray_tracing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint CallableKHR %main "main"
+OpName %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%main = OpFunction %void None %func
+%label = OpLabel
+OpIgnoreIntersectionKHR
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpIgnoreIntersectionKHR requires AnyHitKHR execution model"));
+}
+
+TEST_F(ValidateRayTracing, TerminateRaySuccess) {
+  const std::string body = R"(
+OpCapability RayTracingKHR
+OpExtension "SPV_KHR_ray_tracing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint AnyHitKHR %main "main"
+OpName %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%main = OpFunction %void None %func
+%label = OpLabel
+OpIgnoreIntersectionKHR
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateRayTracing, TerminateRayExecutionModel) {
+  const std::string body = R"(
+OpCapability RayTracingKHR
+OpExtension "SPV_KHR_ray_tracing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint MissKHR %main "main"
+OpName %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%main = OpFunction %void None %func
+%label = OpLabel
+OpTerminateRayKHR
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpTerminateRayKHR requires AnyHitKHR execution model"));
+}
+
+TEST_F(ValidateRayTracing, ReportIntersectionRaySuccess) {
+  const std::string body = R"(
+OpCapability RayTracingKHR
+OpExtension "SPV_KHR_ray_tracing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint IntersectionKHR %main "main"
+OpName %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%float = OpTypeFloat 32
+%float_1 = OpConstant %float 1
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%bool = OpTypeBool
+%main = OpFunction %void None %func
+%label = OpLabel
+%report = OpReportIntersectionKHR %bool %float_1 %uint_1
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateRayTracing, ReportIntersectionExecutionModel) {
+  const std::string body = R"(
+OpCapability RayTracingKHR
+OpExtension "SPV_KHR_ray_tracing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint MissKHR %main "main"
+OpName %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%float = OpTypeFloat 32
+%float_1 = OpConstant %float 1
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%bool = OpTypeBool
+%main = OpFunction %void None %func
+%label = OpLabel
+%report = OpReportIntersectionKHR %bool %float_1 %uint_1
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpReportIntersectionKHR requires IntersectionKHR execution model"));
+}
+
+TEST_F(ValidateRayTracing, ReportIntersectionReturnType) {
+  const std::string body = R"(
+OpCapability RayTracingKHR
+OpExtension "SPV_KHR_ray_tracing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint IntersectionKHR %main "main"
+OpName %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%float = OpTypeFloat 32
+%float_1 = OpConstant %float 1
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%main = OpFunction %void None %func
+%label = OpLabel
+%report = OpReportIntersectionKHR %uint %float_1 %uint_1
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected Result Type to be bool scalar type"));
+}
+
+TEST_F(ValidateRayTracing, ReportIntersectionHit) {
+  const std::string body = R"(
+OpCapability RayTracingKHR
+OpCapability Float64
+OpExtension "SPV_KHR_ray_tracing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint IntersectionKHR %main "main"
+OpName %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%float64 = OpTypeFloat 64
+%float64_1 = OpConstant %float64 1
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%bool = OpTypeBool
+%main = OpFunction %void None %func
+%label = OpLabel
+%report = OpReportIntersectionKHR %bool %float64_1 %uint_1
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Hit must be a 32-bit int scalar"));
+}
+
+TEST_F(ValidateRayTracing, ReportIntersectionHitKind) {
+  const std::string body = R"(
+OpCapability RayTracingKHR
+OpExtension "SPV_KHR_ray_tracing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint IntersectionKHR %main "main"
+OpName %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%float = OpTypeFloat 32
+%float_1 = OpConstant %float 1
+%sint = OpTypeInt 32 1
+%sint_1 = OpConstant %sint 1
+%bool = OpTypeBool
+%main = OpFunction %void None %func
+%label = OpLabel
+%report = OpReportIntersectionKHR %bool %float_1 %sint_1
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Hit Kind must be a 32-bit unsigned int scalar"));
+}
+
+TEST_F(ValidateRayTracing, ExecuteCallableSuccess) {
+  const std::string body = R"(
+OpCapability RayTracingKHR
+OpExtension "SPV_KHR_ray_tracing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint CallableKHR %main "main"
+OpName %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%data_ptr = OpTypePointer CallableDataKHR %int
+%data = OpVariable %data_ptr CallableDataKHR
+%inData_ptr = OpTypePointer IncomingCallableDataKHR %int
+%inData = OpVariable %inData_ptr IncomingCallableDataKHR
+%main = OpFunction %void None %func
+%label = OpLabel
+OpExecuteCallableKHR %uint_0 %data
+OpExecuteCallableKHR %uint_0 %inData
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateRayTracing, ExecuteCallableExecutionModel) {
+  const std::string body = R"(
+OpCapability RayTracingKHR
+OpExtension "SPV_KHR_ray_tracing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint AnyHitKHR %main "main"
+OpName %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%data_ptr = OpTypePointer CallableDataKHR %int
+%data = OpVariable %data_ptr CallableDataKHR
+%inData_ptr = OpTypePointer IncomingCallableDataKHR %int
+%inData = OpVariable %inData_ptr IncomingCallableDataKHR
+%main = OpFunction %void None %func
+%label = OpLabel
+OpExecuteCallableKHR %uint_0 %data
+OpExecuteCallableKHR %uint_0 %inData
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpExecuteCallableKHR requires RayGenerationKHR, "
+                "ClosestHitKHR, MissKHR and CallableKHR execution models"));
+}
+
+TEST_F(ValidateRayTracing, ExecuteCallableStorageClass) {
+  const std::string body = R"(
+OpCapability RayTracingKHR
+OpExtension "SPV_KHR_ray_tracing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint RayGenerationKHR %main "main"
+OpName %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%data_ptr = OpTypePointer RayPayloadKHR %int
+%data = OpVariable %data_ptr RayPayloadKHR
+%main = OpFunction %void None %func
+%label = OpLabel
+OpExecuteCallableKHR %uint_0 %data
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Callable Data must have storage class CallableDataKHR "
+                        "or IncomingCallableDataKHR"));
+}
+
+TEST_F(ValidateRayTracing, ExecuteCallableSbtIndex) {
+  const std::string body = R"(
+OpCapability RayTracingKHR
+OpExtension "SPV_KHR_ray_tracing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint CallableKHR %main "main"
+OpName %main "main"
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%int_1 = OpConstant %int 1
+%data_ptr = OpTypePointer CallableDataKHR %int
+%data = OpVariable %data_ptr CallableDataKHR
+%main = OpFunction %void None %func
+%label = OpLabel
+OpExecuteCallableKHR %int_1 %data
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("SBT Index must be a 32-bit unsigned int scalar"));
+}
+
+std::string GenerateRayTraceCode(
+    const std::string& body,
+    const std::string execution_model = "RayGenerationKHR") {
+  std::ostringstream ss;
+  ss << R"(
+OpCapability RayTracingKHR
+OpCapability Float64
+OpExtension "SPV_KHR_ray_tracing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint )"
+     << execution_model << R"( %main "main"
+OpDecorate %top_level_as DescriptorSet 0
+OpDecorate %top_level_as Binding 0
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%type_as = OpTypeAccelerationStructureKHR
+%as_uc_ptr = OpTypePointer UniformConstant %type_as
+%top_level_as = OpVariable %as_uc_ptr UniformConstant
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%float = OpTypeFloat 32
+%float64 = OpTypeFloat 64
+%f32vec3 = OpTypeVector %float 3
+%f32vec4 = OpTypeVector %float 4
+%float_0 = OpConstant %float 0
+%float64_0 = OpConstant %float64 0
+%v3composite = OpConstantComposite %f32vec3 %float_0 %float_0 %float_0
+%v4composite = OpConstantComposite %f32vec4 %float_0 %float_0 %float_0 %float_0
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%payload_ptr = OpTypePointer RayPayloadKHR %int
+%payload = OpVariable %payload_ptr RayPayloadKHR
+%callable_ptr = OpTypePointer CallableDataKHR %int
+%callable = OpVariable %callable_ptr CallableDataKHR
+%ptr_uint = OpTypePointer Private %uint
+%var_uint = OpVariable %ptr_uint Private
+%ptr_float = OpTypePointer Private %float
+%var_float = OpVariable %ptr_float Private
+%ptr_f32vec3 = OpTypePointer Private %f32vec3
+%var_f32vec3 = OpVariable %ptr_f32vec3 Private
+%main = OpFunction %void None %func
+%label = OpLabel
+)";
+
+  ss << body;
+
+  ss << R"(
+OpReturn
+OpFunctionEnd)";
+  return ss.str();
+}
+
+TEST_F(ValidateRayTracing, TraceRaySuccess) {
+  const std::string body = R"(
+%as = OpLoad %type_as %top_level_as
+OpTraceRayKHR %as %uint_1 %uint_1 %uint_1 %uint_1 %uint_1 %v3composite %float_0 %v3composite %float_0 %payload
+
+%_uint = OpLoad %uint %var_uint
+%_float = OpLoad %float %var_float
+%_f32vec3 = OpLoad %f32vec3 %var_f32vec3
+OpTraceRayKHR %as %_uint %_uint %_uint %_uint %_uint %_f32vec3 %_float %_f32vec3 %_float %payload
+)";
+
+  CompileSuccessfully(GenerateRayTraceCode(body).c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateRayTracing, TraceRayExecutionModel) {
+  const std::string body = R"(
+%as = OpLoad %type_as %top_level_as
+OpTraceRayKHR %as %uint_1 %uint_1 %uint_1 %uint_1 %uint_1 %v3composite %float_0 %v3composite %float_0 %payload
+)";
+
+  CompileSuccessfully(GenerateRayTraceCode(body, "CallableKHR").c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpTraceRayKHR requires RayGenerationKHR, "
+                        "ClosestHitKHR and MissKHR execution models"));
+}
+
+TEST_F(ValidateRayTracing, TraceRayAccelerationStructure) {
+  const std::string body = R"(
+%_uint = OpLoad %uint %var_uint
+OpTraceRayKHR %_uint %uint_1 %uint_1 %uint_1 %uint_1 %uint_1 %v3composite %float_0 %v3composite %float_0 %payload
+)";
+
+  CompileSuccessfully(GenerateRayTraceCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected Acceleration Structure to be of type "
+                        "OpTypeAccelerationStructureKHR"));
+}
+
+TEST_F(ValidateRayTracing, TraceRayRayFlags) {
+  const std::string body = R"(
+%as = OpLoad %type_as %top_level_as
+OpTraceRayKHR %as %float_0 %uint_1 %uint_1 %uint_1 %uint_1 %v3composite %float_0 %v3composite %float_0 %payload
+)";
+
+  CompileSuccessfully(GenerateRayTraceCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Ray Flags must be a 32-bit int scalar"));
+}
+
+TEST_F(ValidateRayTracing, TraceRayCullMask) {
+  const std::string body = R"(
+%as = OpLoad %type_as %top_level_as
+OpTraceRayKHR %as %uint_1 %float_0 %uint_1 %uint_1 %uint_1 %v3composite %float_0 %v3composite %float_0 %payload
+)";
+
+  CompileSuccessfully(GenerateRayTraceCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Cull Mask must be a 32-bit int scalar"));
+}
+
+TEST_F(ValidateRayTracing, TraceRaySbtOffest) {
+  const std::string body = R"(
+%as = OpLoad %type_as %top_level_as
+OpTraceRayKHR %as %uint_1 %uint_1 %float_0 %uint_1 %uint_1 %v3composite %float_0 %v3composite %float_0 %payload
+)";
+
+  CompileSuccessfully(GenerateRayTraceCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("SBT Offset must be a 32-bit int scalar"));
+}
+
+TEST_F(ValidateRayTracing, TraceRaySbtStride) {
+  const std::string body = R"(
+%as = OpLoad %type_as %top_level_as
+OpTraceRayKHR %as %uint_1 %uint_1 %uint_1 %float_0 %uint_1 %v3composite %float_0 %v3composite %float_0 %payload
+)";
+
+  CompileSuccessfully(GenerateRayTraceCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("SBT Stride must be a 32-bit int scalar"));
+}
+
+TEST_F(ValidateRayTracing, TraceRayMissIndex) {
+  const std::string body = R"(
+%as = OpLoad %type_as %top_level_as
+OpTraceRayKHR %as %uint_1 %uint_1 %uint_1 %uint_1 %float_0 %v3composite %float_0 %v3composite %float_0 %payload
+)";
+
+  CompileSuccessfully(GenerateRayTraceCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Miss Index must be a 32-bit int scalar"));
+}
+
+TEST_F(ValidateRayTracing, TraceRayRayOrigin) {
+  const std::string body = R"(
+%as = OpLoad %type_as %top_level_as
+OpTraceRayKHR %as %uint_1 %uint_1 %uint_1 %uint_1 %uint_1 %float_0 %float_0 %v3composite %float_0 %payload
+)";
+
+  CompileSuccessfully(GenerateRayTraceCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Ray Origin must be a 32-bit float 3-component vector"));
+}
+
+TEST_F(ValidateRayTracing, TraceRayRayTMin) {
+  const std::string body = R"(
+%as = OpLoad %type_as %top_level_as
+OpTraceRayKHR %as %uint_1 %uint_1 %uint_1 %uint_1 %uint_1 %v3composite %uint_1 %v3composite %float_0 %payload
+)";
+
+  CompileSuccessfully(GenerateRayTraceCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Ray TMin must be a 32-bit float scalar"));
+}
+
+TEST_F(ValidateRayTracing, TraceRayRayDirection) {
+  const std::string body = R"(
+%as = OpLoad %type_as %top_level_as
+OpTraceRayKHR %as %uint_1 %uint_1 %uint_1 %uint_1 %uint_1 %v3composite %float_0 %v4composite %float_0 %payload
+)";
+
+  CompileSuccessfully(GenerateRayTraceCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Ray Direction must be a 32-bit float 3-component vector"));
+}
+
+TEST_F(ValidateRayTracing, TraceRayRayTMax) {
+  const std::string body = R"(
+%as = OpLoad %type_as %top_level_as
+OpTraceRayKHR %as %uint_1 %uint_1 %uint_1 %uint_1 %uint_1 %v3composite %float_0 %v3composite %float64_0 %payload
+)";
+
+  CompileSuccessfully(GenerateRayTraceCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Ray TMax must be a 32-bit float scalar"));
+}
+
+TEST_F(ValidateRayTracing, TraceRayPayload) {
+  const std::string body = R"(
+%as = OpLoad %type_as %top_level_as
+OpTraceRayKHR %as %uint_1 %uint_1 %uint_1 %uint_1 %uint_1 %v3composite %float_0 %v3composite %float_0 %callable
+)";
+
+  CompileSuccessfully(GenerateRayTraceCode(body).c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Payload must have storage class RayPayloadKHR or "
+                        "IncomingRayPayloadKHR"));
+}
+
+}  // namespace
+}  // namespace val
+}  // namespace spvtools
diff --git a/test/val/val_ssa_test.cpp b/test/val/val_ssa_test.cpp
index 035c710..c22f4ad 100644
--- a/test/val/val_ssa_test.cpp
+++ b/test/val/val_ssa_test.cpp
@@ -118,7 +118,7 @@
   CompileSuccessfully(str);
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              MatchesRegex("ID .\\[%bad\\] has not been defined\n"
+              MatchesRegex("ID '.\\[%bad\\]' has not been defined\n"
                            "  %8 = OpIAdd %uint %uint_1 %bad\n"));
 }
 
@@ -141,7 +141,7 @@
   CompileSuccessfully(str);
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              MatchesRegex("ID .\\[%sum\\] has not been defined\n"
+              MatchesRegex("ID '.\\[%sum\\]' has not been defined\n"
                            "  %sum = OpIAdd %uint %uint_1 %sum\n"));
 }
 
@@ -204,7 +204,7 @@
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("The following forward referenced IDs have not been "
-                        "defined:\n2[%2]"));
+                        "defined:\n'2[%2]'"));
 }
 
 TEST_F(ValidateSSA, ForwardDecorateGood) {
@@ -1126,8 +1126,8 @@
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      MatchesRegex("ID .\\[%eleven\\] defined in block .\\[%true_block\\] "
-                   "does not dominate its use in block .\\[%false_block\\]\n"
+      MatchesRegex("ID '.\\[%eleven\\]' defined in block '.\\[%true_block\\]' "
+                   "does not dominate its use in block '.\\[%false_block\\]'\n"
                    "  %false_block = OpLabel\n"));
 }
 
@@ -1187,7 +1187,7 @@
   CompileSuccessfully(str);
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              MatchesRegex("ID .\\[%inew\\] has not been defined\n"
+              MatchesRegex("ID '.\\[%inew\\]' has not been defined\n"
                            "  %19 = OpIAdd %uint %inew %uint_1\n"));
 }
 
@@ -1268,12 +1268,12 @@
 
   CompileSuccessfully(str);
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      MatchesRegex("In OpPhi instruction .\\[%phi\\], ID .\\[%true_copy\\] "
-                   "definition does not dominate its parent .\\[%if_false\\]\n"
-                   "  %phi = OpPhi %bool %true_copy %if_false %false_copy "
-                   "%if_true\n"));
+  EXPECT_THAT(getDiagnosticString(),
+              MatchesRegex(
+                  "In OpPhi instruction '.\\[%phi\\]', ID '.\\[%true_copy\\]' "
+                  "definition does not dominate its parent '.\\[%if_false\\]'\n"
+                  "  %phi = OpPhi %bool %true_copy %if_false %false_copy "
+                  "%if_true\n"));
 }
 
 TEST_F(ValidateSSA, PhiVariableDefDominatesButNotDefinedInParentBlock) {
@@ -1396,11 +1396,11 @@
 
   CompileSuccessfully(str);
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      MatchesRegex("ID .\\[%first\\] used in function .\\[%func2\\] is used "
-                   "outside of it's defining function .\\[%func\\]\n"
-                   "  %func = OpFunction %void None %14\n"));
+  EXPECT_THAT(getDiagnosticString(),
+              MatchesRegex(
+                  "ID '.\\[%first\\]' used in function '.\\[%func2\\]' is used "
+                  "outside of it's defining function '.\\[%func\\]'\n"
+                  "  %func = OpFunction %void None %14\n"));
 }
 
 TEST_F(ValidateSSA, TypeForwardPointerForwardReference) {
diff --git a/test/val/val_storage_test.cpp b/test/val/val_storage_test.cpp
index 35f6a8d..8693e80 100644
--- a/test/val/val_storage_test.cpp
+++ b/test/val/val_storage_test.cpp
@@ -251,45 +251,314 @@
               HasSubstr("OpFunctionCall Argument <id> '"));
 }
 
-TEST_P(ValidateStorageExecutionModel, VulkanOutsideStoreFailure) {
-  std::stringstream ss;
+std::string GenerateExecutionModelCode(const std::string& execution_model,
+                                       const std::string& storage_class,
+                                       bool store) {
+  const std::string mode = (execution_model.compare("GLCompute") == 0)
+                               ? "OpExecutionMode %func LocalSize 1 1 1"
+                               : "";
+  const std::string operation =
+      (store) ? "OpStore %var %int0" : "%load = OpLoad %intt %var";
+  std::ostringstream ss;
   ss << R"(
               OpCapability Shader
               OpCapability RayTracingKHR
               OpExtension "SPV_KHR_ray_tracing"
               OpMemoryModel Logical GLSL450
               OpEntryPoint )"
-     << GetParam() << R"(  %func "func" %output
-              OpDecorate %output Location 0
+     << execution_model << R"( %func "func" %var
+              )" << mode << R"(
+              OpDecorate %var Location 0
 %intt       = OpTypeInt 32 0
 %int0       = OpConstant %intt 0
 %voidt      = OpTypeVoid
 %vfunct     = OpTypeFunction %voidt
-%outputptrt = OpTypePointer Output %intt
-%output     = OpVariable %outputptrt Output
+%ptr        = OpTypePointer )"
+     << storage_class << R"( %intt
+%var        = OpVariable %ptr )" << storage_class << R"(
 %func       = OpFunction %voidt None %vfunct
 %funcl      = OpLabel
-              OpStore %output %int0
+              )" << operation << R"(
               OpReturn
               OpFunctionEnd
 )";
 
-  CompileSuccessfully(ss.str(), SPV_ENV_VULKAN_1_0);
+  return ss.str();
+}
+
+TEST_P(ValidateStorageExecutionModel, VulkanOutsideStoreFailure) {
+  std::string execution_model = GetParam();
+  CompileSuccessfully(
+      GenerateExecutionModelCode(execution_model, "Output", true).c_str(),
+      SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
               AnyVUID("VUID-StandaloneSpirv-None-04644"));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("in Vulkan evironment, Output Storage Class must not be used "
+      HasSubstr("in Vulkan environment, Output Storage Class must not be used "
                 "in GLCompute, RayGenerationKHR, IntersectionKHR, AnyHitKHR, "
                 "ClosestHitKHR, MissKHR, or CallableKHR execution models"));
 }
 
+TEST_P(ValidateStorageExecutionModel, CallableDataStore) {
+  std::string execution_model = GetParam();
+  CompileSuccessfully(
+      GenerateExecutionModelCode(execution_model, "CallableDataKHR", true)
+          .c_str(),
+      SPV_ENV_VULKAN_1_2);
+  if (execution_model.compare("RayGenerationKHR") == 0 ||
+      execution_model.compare("ClosestHitKHR") == 0 ||
+      execution_model.compare("CallableKHR") == 0 ||
+      execution_model.compare("MissKHR") == 0) {
+    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+  } else {
+    ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+    EXPECT_THAT(getDiagnosticString(),
+                AnyVUID("VUID-StandaloneSpirv-CallableDataKHR-04704"));
+    EXPECT_THAT(
+        getDiagnosticString(),
+        HasSubstr(
+            "CallableDataKHR Storage Class is limited to RayGenerationKHR, "
+            "ClosestHitKHR, CallableKHR, and MissKHR execution model"));
+  }
+}
+
+TEST_P(ValidateStorageExecutionModel, CallableDataLoad) {
+  std::string execution_model = GetParam();
+  CompileSuccessfully(
+      GenerateExecutionModelCode(execution_model, "CallableDataKHR", false)
+          .c_str(),
+      SPV_ENV_VULKAN_1_2);
+  if (execution_model.compare("RayGenerationKHR") == 0 ||
+      execution_model.compare("ClosestHitKHR") == 0 ||
+      execution_model.compare("CallableKHR") == 0 ||
+      execution_model.compare("MissKHR") == 0) {
+    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+  } else {
+    ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+    EXPECT_THAT(getDiagnosticString(),
+                AnyVUID("VUID-StandaloneSpirv-CallableDataKHR-04704"));
+    EXPECT_THAT(
+        getDiagnosticString(),
+        HasSubstr(
+            "CallableDataKHR Storage Class is limited to RayGenerationKHR, "
+            "ClosestHitKHR, CallableKHR, and MissKHR execution model"));
+  }
+}
+
+TEST_P(ValidateStorageExecutionModel, IncomingCallableDataStore) {
+  std::string execution_model = GetParam();
+  CompileSuccessfully(GenerateExecutionModelCode(
+                          execution_model, "IncomingCallableDataKHR", true)
+                          .c_str(),
+                      SPV_ENV_VULKAN_1_2);
+  if (execution_model.compare("CallableKHR") == 0) {
+    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+  } else {
+    ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+    EXPECT_THAT(getDiagnosticString(),
+                AnyVUID("VUID-StandaloneSpirv-IncomingCallableDataKHR-04705"));
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("IncomingCallableDataKHR Storage Class is limited to "
+                          "CallableKHR execution model"));
+  }
+}
+
+TEST_P(ValidateStorageExecutionModel, IncomingCallableDataLoad) {
+  std::string execution_model = GetParam();
+  CompileSuccessfully(GenerateExecutionModelCode(
+                          execution_model, "IncomingCallableDataKHR", false)
+                          .c_str(),
+                      SPV_ENV_VULKAN_1_2);
+  if (execution_model.compare("CallableKHR") == 0) {
+    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+  } else {
+    ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+    EXPECT_THAT(getDiagnosticString(),
+                AnyVUID("VUID-StandaloneSpirv-IncomingCallableDataKHR-04705"));
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("IncomingCallableDataKHR Storage Class is limited to "
+                          "CallableKHR execution model"));
+  }
+}
+
+TEST_P(ValidateStorageExecutionModel, RayPayloadStore) {
+  std::string execution_model = GetParam();
+  CompileSuccessfully(
+      GenerateExecutionModelCode(execution_model, "RayPayloadKHR", true)
+          .c_str(),
+      SPV_ENV_VULKAN_1_2);
+  if (execution_model.compare("RayGenerationKHR") == 0 ||
+      execution_model.compare("ClosestHitKHR") == 0 ||
+      execution_model.compare("MissKHR") == 0) {
+    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+  } else {
+    ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+    EXPECT_THAT(getDiagnosticString(),
+                AnyVUID("VUID-StandaloneSpirv-RayPayloadKHR-04698"));
+    EXPECT_THAT(
+        getDiagnosticString(),
+        HasSubstr("RayPayloadKHR Storage Class is limited to RayGenerationKHR, "
+                  "ClosestHitKHR, and MissKHR execution model"));
+  }
+}
+
+TEST_P(ValidateStorageExecutionModel, RayPayloadLoad) {
+  std::string execution_model = GetParam();
+  CompileSuccessfully(
+      GenerateExecutionModelCode(execution_model, "RayPayloadKHR", false)
+          .c_str(),
+      SPV_ENV_VULKAN_1_2);
+  if (execution_model.compare("RayGenerationKHR") == 0 ||
+      execution_model.compare("ClosestHitKHR") == 0 ||
+      execution_model.compare("MissKHR") == 0) {
+    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+  } else {
+    ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+    EXPECT_THAT(getDiagnosticString(),
+                AnyVUID("VUID-StandaloneSpirv-RayPayloadKHR-04698"));
+    EXPECT_THAT(
+        getDiagnosticString(),
+        HasSubstr("RayPayloadKHR Storage Class is limited to RayGenerationKHR, "
+                  "ClosestHitKHR, and MissKHR execution model"));
+  }
+}
+
+TEST_P(ValidateStorageExecutionModel, HitAttributeStore) {
+  std::string execution_model = GetParam();
+  CompileSuccessfully(
+      GenerateExecutionModelCode(execution_model, "HitAttributeKHR", true)
+          .c_str(),
+      SPV_ENV_VULKAN_1_2);
+  if (execution_model.compare("IntersectionKHR") == 0) {
+    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+  } else if (execution_model.compare("AnyHitKHR") == 0 ||
+             execution_model.compare("ClosestHitKHR") == 0) {
+    ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+    EXPECT_THAT(getDiagnosticString(),
+                AnyVUID("VUID-StandaloneSpirv-HitAttributeKHR-04703"));
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("HitAttributeKHR Storage Class variables are read "
+                          "only with AnyHitKHR and ClosestHitKHR"));
+  } else {
+    ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+    EXPECT_THAT(getDiagnosticString(),
+                AnyVUID("VUID-StandaloneSpirv-HitAttributeKHR-04701"));
+    EXPECT_THAT(
+        getDiagnosticString(),
+        HasSubstr(
+            "HitAttributeKHR Storage Class is limited to IntersectionKHR, "
+            "AnyHitKHR, sand ClosestHitKHR execution model"));
+  }
+}
+
+TEST_P(ValidateStorageExecutionModel, HitAttributeLoad) {
+  std::string execution_model = GetParam();
+  CompileSuccessfully(
+      GenerateExecutionModelCode(execution_model, "HitAttributeKHR", false)
+          .c_str(),
+      SPV_ENV_VULKAN_1_2);
+  if (execution_model.compare("IntersectionKHR") == 0 ||
+      execution_model.compare("AnyHitKHR") == 0 ||
+      execution_model.compare("ClosestHitKHR") == 0) {
+    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+  } else {
+    ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+    EXPECT_THAT(getDiagnosticString(),
+                AnyVUID("VUID-StandaloneSpirv-HitAttributeKHR-04701"));
+    EXPECT_THAT(
+        getDiagnosticString(),
+        HasSubstr(
+            "HitAttributeKHR Storage Class is limited to IntersectionKHR, "
+            "AnyHitKHR, sand ClosestHitKHR execution model"));
+  }
+}
+
+TEST_P(ValidateStorageExecutionModel, IncomingRayPayloadStore) {
+  std::string execution_model = GetParam();
+  CompileSuccessfully(
+      GenerateExecutionModelCode(execution_model, "IncomingRayPayloadKHR", true)
+          .c_str(),
+      SPV_ENV_VULKAN_1_2);
+  if (execution_model.compare("AnyHitKHR") == 0 ||
+      execution_model.compare("ClosestHitKHR") == 0 ||
+      execution_model.compare("MissKHR") == 0) {
+    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+  } else {
+    ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+    EXPECT_THAT(getDiagnosticString(),
+                AnyVUID("VUID-StandaloneSpirv-IncomingRayPayloadKHR-04699"));
+    EXPECT_THAT(
+        getDiagnosticString(),
+        HasSubstr("IncomingRayPayloadKHR Storage Class is limited to "
+                  "AnyHitKHR, ClosestHitKHR, and MissKHR execution model"));
+  }
+}
+
+TEST_P(ValidateStorageExecutionModel, IncomingRayPayloadLoad) {
+  std::string execution_model = GetParam();
+  CompileSuccessfully(GenerateExecutionModelCode(execution_model,
+                                                 "IncomingRayPayloadKHR", false)
+                          .c_str(),
+                      SPV_ENV_VULKAN_1_2);
+  if (execution_model.compare("AnyHitKHR") == 0 ||
+      execution_model.compare("ClosestHitKHR") == 0 ||
+      execution_model.compare("MissKHR") == 0) {
+    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+  } else {
+    ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+    EXPECT_THAT(getDiagnosticString(),
+                AnyVUID("VUID-StandaloneSpirv-IncomingRayPayloadKHR-04699"));
+    EXPECT_THAT(
+        getDiagnosticString(),
+        HasSubstr("IncomingRayPayloadKHR Storage Class is limited to "
+                  "AnyHitKHR, ClosestHitKHR, and MissKHR execution model"));
+  }
+}
+
+TEST_P(ValidateStorageExecutionModel, ShaderRecordBufferStore) {
+  std::string execution_model = GetParam();
+  CompileSuccessfully(
+      GenerateExecutionModelCode(execution_model, "ShaderRecordBufferKHR", true)
+          .c_str(),
+      SPV_ENV_VULKAN_1_2);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("ShaderRecordBufferKHR Storage Class variables are read only"));
+}
+
+TEST_P(ValidateStorageExecutionModel, ShaderRecordBufferLoad) {
+  std::string execution_model = GetParam();
+  CompileSuccessfully(GenerateExecutionModelCode(execution_model,
+                                                 "ShaderRecordBufferKHR", false)
+                          .c_str(),
+                      SPV_ENV_VULKAN_1_2);
+  if (execution_model.compare("RayGenerationKHR") == 0 ||
+      execution_model.compare("IntersectionKHR") == 0 ||
+      execution_model.compare("AnyHitKHR") == 0 ||
+      execution_model.compare("ClosestHitKHR") == 0 ||
+      execution_model.compare("CallableKHR") == 0 ||
+      execution_model.compare("MissKHR") == 0) {
+    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+  } else {
+    ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+    EXPECT_THAT(getDiagnosticString(),
+                AnyVUID("VUID-StandaloneSpirv-ShaderRecordBufferKHR-07119"));
+    EXPECT_THAT(
+        getDiagnosticString(),
+        HasSubstr("ShaderRecordBufferKHR Storage Class is limited to "
+                  "RayGenerationKHR, IntersectionKHR, AnyHitKHR, "
+                  "ClosestHitKHR, CallableKHR, and MissKHR execution model"));
+  }
+}
+
 INSTANTIATE_TEST_SUITE_P(MatrixExecutionModel, ValidateStorageExecutionModel,
                          ::testing::Values("RayGenerationKHR",
                                            "IntersectionKHR", "AnyHitKHR",
                                            "ClosestHitKHR", "MissKHR",
-                                           "CallableKHR"));
+                                           "CallableKHR", "GLCompute"));
 
 }  // namespace
 }  // namespace val
diff --git a/test/val/val_version_test.cpp b/test/val/val_version_test.cpp
index 98565dd..6b7c4fe 100644
--- a/test/val/val_version_test.cpp
+++ b/test/val/val_version_test.cpp
@@ -74,6 +74,12 @@
     case SPV_ENV_UNIVERSAL_1_4:
     case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
       return "1.4";
+    case SPV_ENV_UNIVERSAL_1_5:
+    case SPV_ENV_VULKAN_1_2:
+      return "1.5";
+    case SPV_ENV_UNIVERSAL_1_6:
+    case SPV_ENV_VULKAN_1_3:
+      return "1.6";
     default:
       return "0";
   }
@@ -103,8 +109,14 @@
     std::make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, vulkan_spirv, true),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_2, vulkan_spirv, true),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_3, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_4, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_5, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_6, vulkan_spirv, true),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_VULKAN_1_0,    vulkan_spirv, true),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_VULKAN_1_1,    vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_VULKAN_1_1_SPIRV_1_4,vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_VULKAN_1_2,    vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_VULKAN_1_3,    vulkan_spirv, true),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_OPENGL_4_0,    vulkan_spirv, true),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_OPENGL_4_1,    vulkan_spirv, true),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_OPENGL_4_2,    vulkan_spirv, true),
@@ -115,8 +127,14 @@
     std::make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_UNIVERSAL_1_1, vulkan_spirv, true),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_UNIVERSAL_1_2, vulkan_spirv, true),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_UNIVERSAL_1_3, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_UNIVERSAL_1_4, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_UNIVERSAL_1_5, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_UNIVERSAL_1_6, vulkan_spirv, true),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_VULKAN_1_0,    vulkan_spirv, false),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_VULKAN_1_1,    vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_VULKAN_1_1_SPIRV_1_4, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_VULKAN_1_2,    vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_VULKAN_1_3,    vulkan_spirv, true),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_OPENGL_4_0,    vulkan_spirv, false),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_OPENGL_4_1,    vulkan_spirv, false),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_OPENGL_4_2,    vulkan_spirv, false),
@@ -127,8 +145,14 @@
     std::make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_1, vulkan_spirv, false),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_2, vulkan_spirv, true),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_3, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_4, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_5, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_6, vulkan_spirv, true),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_VULKAN_1_0,    vulkan_spirv, false),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_VULKAN_1_1,    vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_VULKAN_1_1_SPIRV_1_4, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_VULKAN_1_2,    vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_VULKAN_1_3,    vulkan_spirv, true),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_OPENGL_4_0,    vulkan_spirv, false),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_OPENGL_4_1,    vulkan_spirv, false),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_OPENGL_4_2,    vulkan_spirv, false),
@@ -139,13 +163,73 @@
     std::make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_1, vulkan_spirv, false),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_2, vulkan_spirv, false),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_3, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_4, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_5, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_6, vulkan_spirv, true),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_VULKAN_1_0,    vulkan_spirv, false),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_VULKAN_1_1,    vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_VULKAN_1_1_SPIRV_1_4, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_VULKAN_1_2,    vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_VULKAN_1_3,    vulkan_spirv, true),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_OPENGL_4_0,    vulkan_spirv, false),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_OPENGL_4_1,    vulkan_spirv, false),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_OPENGL_4_2,    vulkan_spirv, false),
     std::make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_OPENGL_4_3,    vulkan_spirv, false),
-    std::make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_OPENGL_4_5,    vulkan_spirv, false)
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_OPENGL_4_5,    vulkan_spirv, false),
+
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_4, SPV_ENV_UNIVERSAL_1_0, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_4, SPV_ENV_UNIVERSAL_1_1, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_4, SPV_ENV_UNIVERSAL_1_2, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_4, SPV_ENV_UNIVERSAL_1_3, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_4, SPV_ENV_UNIVERSAL_1_4, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_4, SPV_ENV_UNIVERSAL_1_5, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_4, SPV_ENV_UNIVERSAL_1_6, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_4, SPV_ENV_VULKAN_1_0,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_4, SPV_ENV_VULKAN_1_1,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_4, SPV_ENV_VULKAN_1_1_SPIRV_1_4, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_4, SPV_ENV_VULKAN_1_2,    vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_4, SPV_ENV_VULKAN_1_3,    vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_4, SPV_ENV_OPENGL_4_0,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_4, SPV_ENV_OPENGL_4_1,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_4, SPV_ENV_OPENGL_4_2,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_4, SPV_ENV_OPENGL_4_3,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_4, SPV_ENV_OPENGL_4_5,    vulkan_spirv, false),
+
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_5, SPV_ENV_UNIVERSAL_1_0, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_5, SPV_ENV_UNIVERSAL_1_1, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_5, SPV_ENV_UNIVERSAL_1_2, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_5, SPV_ENV_UNIVERSAL_1_3, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_5, SPV_ENV_UNIVERSAL_1_4, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_5, SPV_ENV_UNIVERSAL_1_5, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_5, SPV_ENV_UNIVERSAL_1_6, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_5, SPV_ENV_VULKAN_1_0,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_5, SPV_ENV_VULKAN_1_1,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_5, SPV_ENV_VULKAN_1_1_SPIRV_1_4, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_5, SPV_ENV_VULKAN_1_2,    vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_5, SPV_ENV_VULKAN_1_3,    vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_5, SPV_ENV_OPENGL_4_0,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_5, SPV_ENV_OPENGL_4_1,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_5, SPV_ENV_OPENGL_4_2,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_5, SPV_ENV_OPENGL_4_3,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_5, SPV_ENV_OPENGL_4_5,    vulkan_spirv, false),
+
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_6, SPV_ENV_UNIVERSAL_1_0, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_6, SPV_ENV_UNIVERSAL_1_1, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_6, SPV_ENV_UNIVERSAL_1_2, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_6, SPV_ENV_UNIVERSAL_1_3, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_6, SPV_ENV_UNIVERSAL_1_4, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_6, SPV_ENV_UNIVERSAL_1_5, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_6, SPV_ENV_UNIVERSAL_1_6, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_6, SPV_ENV_VULKAN_1_0,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_6, SPV_ENV_VULKAN_1_1,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_6, SPV_ENV_VULKAN_1_1_SPIRV_1_4, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_6, SPV_ENV_VULKAN_1_2,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_6, SPV_ENV_VULKAN_1_3,    vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_6, SPV_ENV_OPENGL_4_0,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_6, SPV_ENV_OPENGL_4_1,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_6, SPV_ENV_OPENGL_4_2,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_6, SPV_ENV_OPENGL_4_3,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_UNIVERSAL_1_6, SPV_ENV_OPENGL_4_5,    vulkan_spirv, false)
   )
 );
 
@@ -156,27 +240,91 @@
     std::make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_UNIVERSAL_1_1, vulkan_spirv, true),
     std::make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_UNIVERSAL_1_2, vulkan_spirv, true),
     std::make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_UNIVERSAL_1_3, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_UNIVERSAL_1_4, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_UNIVERSAL_1_5, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_UNIVERSAL_1_6, vulkan_spirv, true),
     std::make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_VULKAN_1_0,    vulkan_spirv, true),
     std::make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_VULKAN_1_1,    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_0, SPV_ENV_VULKAN_1_2,    vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_VULKAN_1_3,    vulkan_spirv, true),
     std::make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_OPENGL_4_0,    vulkan_spirv, true),
     std::make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_OPENGL_4_1,    vulkan_spirv, true),
     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),
     std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_UNIVERSAL_1_2, vulkan_spirv, false),
     std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_UNIVERSAL_1_3, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_UNIVERSAL_1_4, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_UNIVERSAL_1_5, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_UNIVERSAL_1_6, vulkan_spirv, true),
     std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_VULKAN_1_0,    vulkan_spirv, false),
     std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_VULKAN_1_1,    vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_VULKAN_1_1_SPIRV_1_4, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_VULKAN_1_2,    vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_VULKAN_1_3,    vulkan_spirv, true),
     std::make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_OPENGL_4_0,    vulkan_spirv, false),
     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_VULKAN_1_1_SPIRV_1_4, vulkan_spirv, true)
+
+    std::make_tuple(SPV_ENV_VULKAN_1_1_SPIRV_1_4, SPV_ENV_UNIVERSAL_1_0, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_1_SPIRV_1_4, SPV_ENV_UNIVERSAL_1_1, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_1_SPIRV_1_4, SPV_ENV_UNIVERSAL_1_2, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_1_SPIRV_1_4, SPV_ENV_UNIVERSAL_1_3, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_1_SPIRV_1_4, SPV_ENV_UNIVERSAL_1_4, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_VULKAN_1_1_SPIRV_1_4, SPV_ENV_UNIVERSAL_1_5, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_VULKAN_1_1_SPIRV_1_4, SPV_ENV_UNIVERSAL_1_6, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_VULKAN_1_1_SPIRV_1_4, SPV_ENV_VULKAN_1_0,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_1_SPIRV_1_4, SPV_ENV_VULKAN_1_1,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_1_SPIRV_1_4, SPV_ENV_VULKAN_1_1_SPIRV_1_4, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_VULKAN_1_1_SPIRV_1_4, SPV_ENV_VULKAN_1_2,    vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_VULKAN_1_1_SPIRV_1_4, SPV_ENV_VULKAN_1_3,    vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_VULKAN_1_1_SPIRV_1_4, SPV_ENV_OPENGL_4_0,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_1_SPIRV_1_4, SPV_ENV_OPENGL_4_1,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_1_SPIRV_1_4, SPV_ENV_OPENGL_4_2,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_1_SPIRV_1_4, SPV_ENV_OPENGL_4_3,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_1_SPIRV_1_4, SPV_ENV_OPENGL_4_5,    vulkan_spirv, false),
+
+    std::make_tuple(SPV_ENV_VULKAN_1_2, SPV_ENV_UNIVERSAL_1_0, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_2, SPV_ENV_UNIVERSAL_1_1, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_2, SPV_ENV_UNIVERSAL_1_2, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_2, SPV_ENV_UNIVERSAL_1_3, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_2, SPV_ENV_UNIVERSAL_1_4, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_2, SPV_ENV_UNIVERSAL_1_5, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_VULKAN_1_2, SPV_ENV_UNIVERSAL_1_6, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_VULKAN_1_2, SPV_ENV_VULKAN_1_0,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_2, SPV_ENV_VULKAN_1_1,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_2, SPV_ENV_VULKAN_1_1_SPIRV_1_4, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_2, SPV_ENV_VULKAN_1_2,    vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_VULKAN_1_2, SPV_ENV_VULKAN_1_3,    vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_VULKAN_1_2, SPV_ENV_OPENGL_4_0,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_2, SPV_ENV_OPENGL_4_1,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_2, SPV_ENV_OPENGL_4_2,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_2, SPV_ENV_OPENGL_4_3,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_2, SPV_ENV_OPENGL_4_5,    vulkan_spirv, false),
+
+    std::make_tuple(SPV_ENV_VULKAN_1_3, SPV_ENV_UNIVERSAL_1_0, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_3, SPV_ENV_UNIVERSAL_1_1, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_3, SPV_ENV_UNIVERSAL_1_2, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_3, SPV_ENV_UNIVERSAL_1_3, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_3, SPV_ENV_UNIVERSAL_1_4, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_3, SPV_ENV_UNIVERSAL_1_5, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_3, SPV_ENV_UNIVERSAL_1_6, vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_VULKAN_1_3, SPV_ENV_VULKAN_1_0,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_3, SPV_ENV_VULKAN_1_1,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_3, SPV_ENV_VULKAN_1_1_SPIRV_1_4, vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_3, SPV_ENV_VULKAN_1_2,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_3, SPV_ENV_VULKAN_1_3,    vulkan_spirv, true),
+    std::make_tuple(SPV_ENV_VULKAN_1_3, SPV_ENV_OPENGL_4_0,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_3, SPV_ENV_OPENGL_4_1,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_3, SPV_ENV_OPENGL_4_2,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_3, SPV_ENV_OPENGL_4_3,    vulkan_spirv, false),
+    std::make_tuple(SPV_ENV_VULKAN_1_3, SPV_ENV_OPENGL_4_5,    vulkan_spirv, false)
   )
 );
 
@@ -187,6 +335,9 @@
     std::make_tuple(SPV_ENV_OPENCL_2_0, SPV_ENV_UNIVERSAL_1_1,       opencl_spirv, true),
     std::make_tuple(SPV_ENV_OPENCL_2_0, SPV_ENV_UNIVERSAL_1_2,       opencl_spirv, true),
     std::make_tuple(SPV_ENV_OPENCL_2_0, SPV_ENV_UNIVERSAL_1_3,       opencl_spirv, true),
+    std::make_tuple(SPV_ENV_OPENCL_2_0, SPV_ENV_UNIVERSAL_1_4,       opencl_spirv, true),
+    std::make_tuple(SPV_ENV_OPENCL_2_0, SPV_ENV_UNIVERSAL_1_5,       opencl_spirv, true),
+    std::make_tuple(SPV_ENV_OPENCL_2_0, SPV_ENV_UNIVERSAL_1_6,       opencl_spirv, true),
     std::make_tuple(SPV_ENV_OPENCL_2_0, SPV_ENV_OPENCL_2_0,          opencl_spirv, true),
     std::make_tuple(SPV_ENV_OPENCL_2_0, SPV_ENV_OPENCL_2_1,          opencl_spirv, true),
     std::make_tuple(SPV_ENV_OPENCL_2_0, SPV_ENV_OPENCL_2_2,          opencl_spirv, true),
@@ -199,6 +350,9 @@
     std::make_tuple(SPV_ENV_OPENCL_2_1, SPV_ENV_UNIVERSAL_1_1,       opencl_spirv, true),
     std::make_tuple(SPV_ENV_OPENCL_2_1, SPV_ENV_UNIVERSAL_1_2,       opencl_spirv, true),
     std::make_tuple(SPV_ENV_OPENCL_2_1, SPV_ENV_UNIVERSAL_1_3,       opencl_spirv, true),
+    std::make_tuple(SPV_ENV_OPENCL_2_1, SPV_ENV_UNIVERSAL_1_4,       opencl_spirv, true),
+    std::make_tuple(SPV_ENV_OPENCL_2_1, SPV_ENV_UNIVERSAL_1_5,       opencl_spirv, true),
+    std::make_tuple(SPV_ENV_OPENCL_2_1, SPV_ENV_UNIVERSAL_1_6,       opencl_spirv, true),
     std::make_tuple(SPV_ENV_OPENCL_2_1, SPV_ENV_OPENCL_2_0,          opencl_spirv, true),
     std::make_tuple(SPV_ENV_OPENCL_2_1, SPV_ENV_OPENCL_2_1,          opencl_spirv, true),
     std::make_tuple(SPV_ENV_OPENCL_2_1, SPV_ENV_OPENCL_2_2,          opencl_spirv, true),
@@ -211,6 +365,9 @@
     std::make_tuple(SPV_ENV_OPENCL_2_2, SPV_ENV_UNIVERSAL_1_1,       opencl_spirv, false),
     std::make_tuple(SPV_ENV_OPENCL_2_2, SPV_ENV_UNIVERSAL_1_2,       opencl_spirv, true),
     std::make_tuple(SPV_ENV_OPENCL_2_2, SPV_ENV_UNIVERSAL_1_3,       opencl_spirv, true),
+    std::make_tuple(SPV_ENV_OPENCL_2_2, SPV_ENV_UNIVERSAL_1_4,       opencl_spirv, true),
+    std::make_tuple(SPV_ENV_OPENCL_2_2, SPV_ENV_UNIVERSAL_1_5,       opencl_spirv, true),
+    std::make_tuple(SPV_ENV_OPENCL_2_2, SPV_ENV_UNIVERSAL_1_6,       opencl_spirv, true),
     std::make_tuple(SPV_ENV_OPENCL_2_2, SPV_ENV_OPENCL_2_0,          opencl_spirv, false),
     std::make_tuple(SPV_ENV_OPENCL_2_2, SPV_ENV_OPENCL_2_1,          opencl_spirv, false),
     std::make_tuple(SPV_ENV_OPENCL_2_2, SPV_ENV_OPENCL_2_2,          opencl_spirv, true),
@@ -223,6 +380,9 @@
     std::make_tuple(SPV_ENV_OPENCL_1_2, SPV_ENV_UNIVERSAL_1_1,       opencl_spirv, true),
     std::make_tuple(SPV_ENV_OPENCL_1_2, SPV_ENV_UNIVERSAL_1_2,       opencl_spirv, true),
     std::make_tuple(SPV_ENV_OPENCL_1_2, SPV_ENV_UNIVERSAL_1_3,       opencl_spirv, true),
+    std::make_tuple(SPV_ENV_OPENCL_1_2, SPV_ENV_UNIVERSAL_1_4,       opencl_spirv, true),
+    std::make_tuple(SPV_ENV_OPENCL_1_2, SPV_ENV_UNIVERSAL_1_5,       opencl_spirv, true),
+    std::make_tuple(SPV_ENV_OPENCL_1_2, SPV_ENV_UNIVERSAL_1_6,       opencl_spirv, true),
     std::make_tuple(SPV_ENV_OPENCL_1_2, SPV_ENV_OPENCL_2_0,          opencl_spirv, true),
     std::make_tuple(SPV_ENV_OPENCL_1_2, SPV_ENV_OPENCL_2_1,          opencl_spirv, true),
     std::make_tuple(SPV_ENV_OPENCL_1_2, SPV_ENV_OPENCL_2_2,          opencl_spirv, true),
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
index 6039089..86d0bc4 100644
--- a/tools/CMakeLists.txt
+++ b/tools/CMakeLists.txt
@@ -41,10 +41,11 @@
 
 if (NOT ${SPIRV_SKIP_EXECUTABLES})
   add_spvtools_tool(TARGET spirv-as SRCS as/as.cpp LIBS ${SPIRV_TOOLS_FULL_VISIBILITY})
+  add_spvtools_tool(TARGET spirv-diff SRCS diff/diff.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-diff SPIRV-Tools-opt ${SPIRV_TOOLS_FULL_VISIBILITY})
   add_spvtools_tool(TARGET spirv-dis SRCS dis/dis.cpp LIBS ${SPIRV_TOOLS_FULL_VISIBILITY})
   add_spvtools_tool(TARGET spirv-val SRCS val/val.cpp util/cli_consumer.cpp LIBS ${SPIRV_TOOLS_FULL_VISIBILITY})
   add_spvtools_tool(TARGET spirv-opt SRCS opt/opt.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS_FULL_VISIBILITY})
-  if (NOT DEFINED IOS_PLATFORM) # iOS does not allow std::system calls which spirv-reduce requires
+  if(NOT (${CMAKE_SYSTEM_NAME} STREQUAL "iOS")) # 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_FULL_VISIBILITY})
   endif()
   add_spvtools_tool(TARGET spirv-link SRCS link/linker.cpp LIBS SPIRV-Tools-link ${SPIRV_TOOLS_FULL_VISIBILITY})
@@ -58,7 +59,7 @@
                                                ${SPIRV_HEADER_INCLUDE_DIR})
   set(SPIRV_INSTALL_TARGETS spirv-as spirv-dis spirv-val spirv-opt
                             spirv-cfg spirv-link spirv-lint)
-  if(NOT DEFINED IOS_PLATFORM)
+  if(NOT (${CMAKE_SYSTEM_NAME} STREQUAL "iOS"))
     set(SPIRV_INSTALL_TARGETS ${SPIRV_INSTALL_TARGETS} spirv-reduce)
   endif()
 
@@ -68,9 +69,6 @@
   endif(SPIRV_BUILD_FUZZER)
 
   if(ENABLE_SPIRV_TOOLS_INSTALL)
-    install(TARGETS ${SPIRV_INSTALL_TARGETS}
-      RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
-      LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
-      ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
+    install(TARGETS ${SPIRV_INSTALL_TARGETS} DESTINATION ${CMAKE_INSTALL_BINDIR})
   endif(ENABLE_SPIRV_TOOLS_INSTALL)
 endif()
diff --git a/tools/as/as.cpp b/tools/as/as.cpp
index c8a4445..506b058 100644
--- a/tools/as/as.cpp
+++ b/tools/as/as.cpp
@@ -48,7 +48,7 @@
       argv0, argv0, target_env_list.c_str());
 }
 
-static const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_5;
+static const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_6;
 
 int main(int argc, char** argv) {
   const char* inFile = nullptr;
diff --git a/tools/cfg/bin_to_dot.cpp b/tools/cfg/bin_to_dot.cpp
index 2561eea..72e7693 100644
--- a/tools/cfg/bin_to_dot.cpp
+++ b/tools/cfg/bin_to_dot.cpp
@@ -57,13 +57,13 @@
   // Ends processing for the current block, emitting its dot code.
   void FlushBlock(const std::vector<uint32_t>& successors);
 
-  // The ID of the current functio, or 0 if outside of a function.
+  // The ID of the current function, or 0 if outside of a function.
   uint32_t current_function_id_ = 0;
 
   // The ID of the current basic block, or 0 if outside of a block.
   uint32_t current_block_id_ = 0;
 
-  // Have we completed processing for the entry block to this fuction?
+  // Have we completed processing for the entry block to this function?
   bool seen_function_entry_block_ = false;
 
   // The Id of the merge block for this block if it exists, or 0 otherwise.
diff --git a/tools/cfg/bin_to_dot.h b/tools/cfg/bin_to_dot.h
index 4de2e07..a61c975 100644
--- a/tools/cfg/bin_to_dot.h
+++ b/tools/cfg/bin_to_dot.h
@@ -20,7 +20,7 @@
 #include "spirv-tools/libspirv.h"
 
 // Dumps the control flow graph for the given module to the output stream.
-// Returns SPV_SUCCESS on succes.
+// Returns SPV_SUCCESS on success.
 spv_result_t BinaryToDot(const spv_const_context context, const uint32_t* words,
                          size_t num_words, std::iostream* out,
                          spv_diagnostic* diagnostic);
diff --git a/tools/cfg/cfg.cpp b/tools/cfg/cfg.cpp
index 6a5faa1..5380c21 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_5;
+static const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_6;
 
 int main(int argc, char** argv) {
   const char* inFile = nullptr;
diff --git a/tools/diff/diff.cpp b/tools/diff/diff.cpp
new file mode 100644
index 0000000..d3cad04
--- /dev/null
+++ b/tools/diff/diff.cpp
@@ -0,0 +1,201 @@
+// Copyright (c) 2022 The Khronos Group 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.
+
+#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
+#include <unistd.h>
+#endif
+
+#include "source/diff/diff.h"
+
+#include "source/opt/build_module.h"
+#include "source/opt/ir_context.h"
+#include "spirv-tools/libspirv.hpp"
+#include "tools/io.h"
+#include "tools/util/cli_consumer.h"
+
+static void print_usage(char* argv0) {
+  printf(R"(%s - Compare two SPIR-V files
+
+Usage: %s <src_filename> <dst_filename>
+
+The SPIR-V binary is read from <src_filename> and <dst_filename>.  If either
+file ends in .spvasm, the SPIR-V is read as text and disassembled.
+
+The contents of the SPIR-V modules are analyzed and a diff is produced showing a
+logical transformation from src to dst, in src's id-space.
+
+  -h, --help      Print this help.
+  --version       Display diff version information.
+
+  --color         Force color output.  The default when printing to a terminal.
+                  Overrides a previous --no-color option.
+  --no-color      Don't print in color.  Overrides a previous --color option.
+                  The default when output goes to something other than a
+                  terminal (e.g. a pipe, or a shell redirection).
+
+  --no-indent     Don't indent instructions.
+
+  --no-header     Don't output the header as leading comments.
+
+  --with-id-map   Also output the mapping between src and dst outputs.
+
+  --ignore-set-binding
+                  Don't use set/binding decorations for variable matching.
+  --ignore-location
+                  Don't use location decorations for variable matching.
+)",
+         argv0, argv0);
+}
+
+static const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_6;
+
+static bool is_assembly(const char* path) {
+  const char* suffix = strrchr(path, '.');
+  if (suffix == nullptr) {
+    return false;
+  }
+
+  return strcmp(suffix, ".spvasm") == 0;
+}
+
+static std::unique_ptr<spvtools::opt::IRContext> load_module(const char* path) {
+  if (is_assembly(path)) {
+    std::vector<char> contents;
+    if (!ReadTextFile<char>(path, &contents)) return {};
+
+    return spvtools::BuildModule(
+        kDefaultEnvironment, spvtools::utils::CLIMessageConsumer,
+        std::string(contents.begin(), contents.end()),
+        spvtools::SpirvTools::kDefaultAssembleOption |
+            SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  }
+
+  std::vector<uint32_t> contents;
+  if (!ReadBinaryFile<uint32_t>(path, &contents)) return {};
+
+  return spvtools::BuildModule(kDefaultEnvironment,
+                               spvtools::utils::CLIMessageConsumer,
+                               contents.data(), contents.size());
+}
+
+int main(int argc, char** argv) {
+  const char* src_file = nullptr;
+  const char* dst_file = nullptr;
+  bool color_is_possible =
+#if SPIRV_COLOR_TERMINAL
+      true;
+#else
+      false;
+#endif
+  bool force_color = false;
+  bool force_no_color = false;
+  bool allow_indent = true;
+  bool no_header = false;
+  bool dump_id_map = false;
+  bool ignore_set_binding = false;
+  bool ignore_location = false;
+
+  for (int argi = 1; argi < argc; ++argi) {
+    if ('-' == argv[argi][0]) {
+      switch (argv[argi][1]) {
+        case 'h':
+          print_usage(argv[0]);
+          return 0;
+        case '-': {
+          // Long options
+          if (strcmp(argv[argi], "--no-color") == 0) {
+            force_no_color = true;
+            force_color = false;
+          } else if (strcmp(argv[argi], "--color") == 0) {
+            force_no_color = false;
+            force_color = true;
+          } else if (strcmp(argv[argi], "--no-indent") == 0) {
+            allow_indent = false;
+          } else if (strcmp(argv[argi], "--no-header") == 0) {
+            no_header = true;
+          } else if (strcmp(argv[argi], "--with-id-map") == 0) {
+            dump_id_map = true;
+          } else if (strcmp(argv[argi], "--ignore-set-binding") == 0) {
+            ignore_set_binding = true;
+          } else if (strcmp(argv[argi], "--ignore-location") == 0) {
+            ignore_location = true;
+          } else if (strcmp(argv[argi], "--help") == 0) {
+            print_usage(argv[0]);
+            return 0;
+          } else if (strcmp(argv[argi], "--version") == 0) {
+            printf("%s\n", spvSoftwareVersionDetailsString());
+            printf("Target: %s\n",
+                   spvTargetEnvDescription(kDefaultEnvironment));
+            return 0;
+          } else {
+            print_usage(argv[0]);
+            return 1;
+          }
+        } break;
+        default:
+          print_usage(argv[0]);
+          return 1;
+      }
+    } else {
+      if (src_file == nullptr) {
+        src_file = argv[argi];
+      } else if (dst_file == nullptr) {
+        dst_file = argv[argi];
+      } else {
+        fprintf(stderr, "error: More than two input files specified\n");
+        return 1;
+      }
+    }
+  }
+
+  if (src_file == nullptr || dst_file == nullptr) {
+    print_usage(argv[0]);
+    return 1;
+  }
+
+  spvtools::diff::Options options;
+
+  if (allow_indent) options.indent = true;
+  if (no_header) options.no_header = true;
+  if (dump_id_map) options.dump_id_map = true;
+  if (ignore_set_binding) options.ignore_set_binding = true;
+  if (ignore_location) options.ignore_location = true;
+
+  if (color_is_possible && !force_no_color) {
+    bool output_is_tty = true;
+#if defined(_POSIX_VERSION)
+    output_is_tty = isatty(fileno(stdout));
+#endif
+    if (output_is_tty || force_color) {
+      options.color_output = true;
+    }
+  }
+
+  std::unique_ptr<spvtools::opt::IRContext> src = load_module(src_file);
+  std::unique_ptr<spvtools::opt::IRContext> dst = load_module(dst_file);
+
+  if (!src) {
+    fprintf(stderr, "error: Loading src file\n");
+  }
+  if (!dst) {
+    fprintf(stderr, "error: Loading dst file\n");
+  }
+  if (!src || !dst) {
+    return 1;
+  }
+
+  spvtools::diff::Diff(src.get(), dst.get(), std::cout, options);
+
+  return 0;
+}
diff --git a/tools/fuzz/fuzz.cpp b/tools/fuzz/fuzz.cpp
index 306f925..ca6633a 100644
--- a/tools/fuzz/fuzz.cpp
+++ b/tools/fuzz/fuzz.cpp
@@ -673,19 +673,6 @@
   transformations_file.close();
 }
 
-// The Chromium project applies the following patch to the protobuf library:
-//
-// source.chromium.org/chromium/chromium/src/+/main:third_party/protobuf/patches/0003-remove-static-initializers.patch
-//
-// This affects how Status objects must be constructed. This method provides a
-// convenient way to get the OK status that works both with and without the
-// patch. With the patch OK is a StatusPod, from which a Status can be
-// constructed. Without the patch, OK is already a Status, and we harmlessly
-// copy-construct the result from it.
-google::protobuf::util::Status GetProtobufOkStatus() {
-  return google::protobuf::util::Status(google::protobuf::util::Status::OK);
-}
-
 // Dumps |transformations| to file |filename| in JSON format. Useful for
 // interactive debugging.
 void DumpTransformationsJson(
@@ -696,7 +683,7 @@
   json_options.add_whitespace = true;
   auto json_generation_status = google::protobuf::util::MessageToJsonString(
       transformations, &json_string, json_options);
-  if (json_generation_status == GetProtobufOkStatus()) {
+  if (json_generation_status.ok()) {
     std::ofstream transformations_json_file(filename);
     transformations_json_file << json_string;
     transformations_json_file.close();
@@ -747,8 +734,9 @@
     std::string facts_json_string((std::istreambuf_iterator<char>(facts_input)),
                                   std::istreambuf_iterator<char>());
     facts_input.close();
-    if (GetProtobufOkStatus() != google::protobuf::util::JsonStringToMessage(
-                                     facts_json_string, &initial_facts)) {
+    if (!google::protobuf::util::JsonStringToMessage(facts_json_string,
+                                                     &initial_facts)
+             .ok()) {
       spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error reading facts data");
       return 1;
     }
@@ -828,7 +816,7 @@
     json_options.add_whitespace = true;
     auto json_generation_status = google::protobuf::util::MessageToJsonString(
         transformations_applied, &json_string, json_options);
-    if (json_generation_status != GetProtobufOkStatus()) {
+    if (!json_generation_status.ok()) {
       spvtools::Error(FuzzDiagnostic, nullptr, {},
                       "Error writing out transformations in JSON format");
       return 1;
diff --git a/tools/io.h b/tools/io.h
index 83a85c1..9dc834e 100644
--- a/tools/io.h
+++ b/tools/io.h
@@ -26,9 +26,15 @@
 
 #define SET_STDIN_TO_BINARY_MODE() _setmode(_fileno(stdin), O_BINARY);
 #define SET_STDIN_TO_TEXT_MODE() _setmode(_fileno(stdin), O_TEXT);
+#define SET_STDOUT_TO_BINARY_MODE() _setmode(_fileno(stdout), O_BINARY);
+#define SET_STDOUT_TO_TEXT_MODE() _setmode(_fileno(stdout), O_TEXT);
+#define SET_STDOUT_MODE(mode) _setmode(_fileno(stdout), mode);
 #else
 #define SET_STDIN_TO_BINARY_MODE()
 #define SET_STDIN_TO_TEXT_MODE()
+#define SET_STDOUT_TO_BINARY_MODE() 0
+#define SET_STDOUT_TO_TEXT_MODE() 0
+#define SET_STDOUT_MODE(mode)
 #endif
 
 // Appends the contents of the |file| to |data|, assuming each element in the
@@ -115,6 +121,44 @@
   return succeeded;
 }
 
+namespace {
+// A class to create and manage a file for outputting data.
+class OutputFile {
+ public:
+  // Opens |filename| in the given mode.  If |filename| is nullptr, the empty
+  // string or "-", stdout will be set to the given mode.
+  OutputFile(const char* filename, const char* mode) {
+    const bool use_stdout =
+        !filename || (filename[0] == '-' && filename[1] == '\0');
+    if (use_stdout) {
+      if (strchr(mode, 'b')) {
+        old_mode_ = SET_STDOUT_TO_BINARY_MODE();
+      } else {
+        old_mode_ = SET_STDOUT_TO_TEXT_MODE();
+      }
+      fp_ = stdout;
+    } else {
+      fp_ = fopen(filename, mode);
+    }
+  }
+
+  ~OutputFile() {
+    if (fp_ == stdout) {
+      SET_STDOUT_MODE(old_mode_);
+    } else if (fp_ != nullptr) {
+      fclose(fp_);
+    }
+  }
+
+  // Returns a file handle to the file.
+  FILE* GetFileHandle() const { return fp_; }
+
+ private:
+  FILE* fp_;
+  int old_mode_;
+};
+}  // namespace
+
 // Writes the given |data| into the file named as |filename| using the given
 // |mode|, assuming |data| is an array of |count| elements of type |T|. If
 // |filename| is nullptr or "-", writes to standard output. If any error occurs,
@@ -122,20 +166,19 @@
 template <typename T>
 bool WriteFile(const char* filename, const char* mode, const T* data,
                size_t count) {
-  const bool use_stdout =
-      !filename || (filename[0] == '-' && filename[1] == '\0');
-  if (FILE* fp = (use_stdout ? stdout : fopen(filename, mode))) {
-    size_t written = fwrite(data, sizeof(T), count, fp);
-    if (count != written) {
-      fprintf(stderr, "error: could not write to file '%s'\n", filename);
-      if (!use_stdout) fclose(fp);
-      return false;
-    }
-    if (!use_stdout) fclose(fp);
-  } else {
+  OutputFile file(filename, mode);
+  FILE* fp = file.GetFileHandle();
+  if (fp == nullptr) {
     fprintf(stderr, "error: could not open file '%s'\n", filename);
     return false;
   }
+
+  size_t written = fwrite(data, sizeof(T), count, fp);
+  if (count != written) {
+    fprintf(stderr, "error: could not write to file '%s'\n", filename);
+    return false;
+  }
+
   return true;
 }
 
diff --git a/tools/link/linker.cpp b/tools/link/linker.cpp
index 359e803..bdddeb8 100644
--- a/tools/link/linker.cpp
+++ b/tools/link/linker.cpp
@@ -25,7 +25,7 @@
 
 namespace {
 
-const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_5;
+const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_6;
 
 void print_usage(const char* program) {
   std::string target_env_list = spvTargetEnvList(16, 80);
@@ -49,15 +49,18 @@
   --create-library
                Link the binaries into a library, keeping all exported symbols.
   -h, --help
-                  Print this help.
+               Print this help.
   --target-env <env>
-               Set the target environment. Without this flag the target
-               environment defaults to spv1.5. <env> must be one of
-               {%s}
+               Set the environment used for interpreting the inputs. Without
+               this option the environment defaults to spv1.6. <env> must be
+               one of {%s}.
+               NOTE: The SPIR-V version used by the linked binary module
+               depends only on the version of the inputs, and is not affected
+               by this option.
   --verify-ids
                Verify that IDs in the resulting modules are truly unique.
   --version
-               Display linker version information
+               Display linker version information.
 )",
       program, program, target_env_list.c_str());
 }
@@ -160,10 +163,11 @@
 
   std::vector<uint32_t> linkingResult;
   spv_result_t status = Link(context, contents, &linkingResult, options);
+  if (status != SPV_SUCCESS && status != SPV_WARNING) return 1;
 
   if (!WriteFile<uint32_t>(outFile, "wb", linkingResult.data(),
                            linkingResult.size()))
     return 1;
 
-  return status == SPV_SUCCESS ? 0 : 1;
+  return 0;
 }
diff --git a/tools/lint/lint.cpp b/tools/lint/lint.cpp
index 5c2a82a..d37df83 100644
--- a/tools/lint/lint.cpp
+++ b/tools/lint/lint.cpp
@@ -19,7 +19,7 @@
 #include "tools/io.h"
 #include "tools/util/cli_consumer.h"
 
-const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_5;
+const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_6;
 
 namespace {
 // Status and actions to perform after parsing command-line arguments.
diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp
index 04f81b8..ce2103c 100644
--- a/tools/opt/opt.cpp
+++ b/tools/opt/opt.cpp
@@ -44,7 +44,7 @@
 // initialization and setup. Note that |source| and |position| are irrelevant
 // here because we are still not processing a SPIR-V input file.
 void opt_diagnostic(spv_message_level_t level, const char* /*source*/,
-                    const spv_position_t& /*positon*/, const char* message) {
+                    const spv_position_t& /*position*/, const char* message) {
   if (level == SPV_MSG_ERROR) {
     fprintf(stderr, "error: ");
   }
@@ -59,7 +59,7 @@
   return ss.str();
 }
 
-const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_5;
+const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_6;
 
 std::string GetLegalizationPasses() {
   spvtools::Optimizer optimizer(kDefaultEnvironment);
@@ -157,17 +157,21 @@
                another.  It will only propagate an array if the source is never
                written to, and the only store to the target is the copy.)");
   printf(R"(
-  --decompose-initialized-variables
-               Decomposes initialized variable declarations into a declaration
-               followed by a store of the initial value. This is done to work
-               around known issues with some Vulkan drivers for initialize
-               variables.)");
-  printf(R"(
   --replace-desc-array-access-using-var-index
                Replaces accesses to descriptor arrays based on a variable index
                with a switch that has a case for every possible value of the
                index.)");
   printf(R"(
+  --spread-volatile-semantics
+               Spread Volatile semantics to variables with SMIDNV, WarpIDNV,
+               SubgroupSize, SubgroupLocalInvocationId, SubgroupEqMask,
+               SubgroupGeMask, SubgroupGtMask, SubgroupLeMask, or SubgroupLtMask
+               BuiltIn decorations or OpLoad for them when the shader model is
+               ray generation, closest hit, miss, intersection, or callable.
+               For the SPIR-V version is 1.6 or above, it also spreads Volatile
+               semantics to a variable with HelperInvocation BuiltIn decoration
+               in the fragement shader.)");
+  printf(R"(
   --descriptor-scalar-replacement
                Replaces every array variable |desc| that has a DescriptorSet
                and Binding decorations with a new variable for each element of
@@ -198,6 +202,10 @@
                unused stores to vector components, that are not removed by
                aggressive dead code elimination.)");
   printf(R"(
+  --eliminate-dead-input-components
+               Deletes unused components from input variables. Currently
+               deletes trailing unused elements from input arrays.)");
+  printf(R"(
   --eliminate-dead-variables
                Deletes module scope variables that are not referenced.)");
   printf(R"(
@@ -223,6 +231,10 @@
                loads and stores. Performed only on entry point call tree
                functions.)");
   printf(R"(
+  --fix-func-call-param
+               fix non memory argument for the function call, replace 
+               accesschain pointer argument with a variable.)");
+  printf(R"(
   --flatten-decorations
                Replace decoration groups with repeated OpDecorate and
                OpMemberDecorate instructions.)");
@@ -473,16 +485,16 @@
   --strength-reduction
                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.)");
   printf(R"(
+  --strip-nonsemantic
+               Remove all reflection and nonsemantic information.)");
+  printf(R"(
   --strip-reflect
-               Remove all reflection information.  For now, this covers
-               reflection information defined by SPV_GOOGLE_hlsl_functionality1
-               and SPV_KHR_non_semantic_info)");
+               DEPRECATED.  Remove all reflection information.  For now, this
+               covers reflection information defined by
+               SPV_GOOGLE_hlsl_functionality1 and SPV_KHR_non_semantic_info)");
   printf(R"(
   --target-env=<env>
                Set the target environment. Without this flag the target
diff --git a/tools/reduce/reduce.cpp b/tools/reduce/reduce.cpp
index 4447b35..3760054 100644
--- a/tools/reduce/reduce.cpp
+++ b/tools/reduce/reduce.cpp
@@ -262,7 +262,7 @@
   DumpShader(binary, filename);
 }
 
-const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_5;
+const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_6;
 
 int main(int argc, const char** argv) {
   std::string in_binary_file;
diff --git a/tools/sva/README.md b/tools/sva/README.md
index d80b4d2..cd3d13c 100644
--- a/tools/sva/README.md
+++ b/tools/sva/README.md
@@ -1,6 +1,6 @@
 # SVA
 
-SPIR-V Assember for WebGPU. The SPIR-V Assembler is a JavaScript library to
+SPIR-V Assembler for WebGPU. The SPIR-V Assembler is a JavaScript library to
 convert SPIR-V assembly (as produced by spirv-dis in SPIR-V Tools) into a
 SPIR-V binary. The assembler assumes it is generating WebGPU SPIR-V and thus has
 the following limitations.
diff --git a/tools/sva/yarn.lock b/tools/sva/yarn.lock
index afa11f6..e7b735e 100644
--- a/tools/sva/yarn.lock
+++ b/tools/sva/yarn.lock
@@ -1260,9 +1260,9 @@
   integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==
 
 pathval@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0"
-  integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA=
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d"
+  integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==
 
 prelude-ls@~1.1.2:
   version "1.1.2"
diff --git a/tools/val/val.cpp b/tools/val/val.cpp
index 55321da..880ce46 100644
--- a/tools/val/val.cpp
+++ b/tools/val/val.cpp
@@ -77,7 +77,7 @@
 
 int main(int argc, char** argv) {
   const char* inFile = nullptr;
-  spv_target_env target_env = SPV_ENV_UNIVERSAL_1_5;
+  spv_target_env target_env = SPV_ENV_UNIVERSAL_1_6;
   spvtools::ValidatorOptions options;
   bool continue_processing = true;
   int return_code = 0;
@@ -111,17 +111,20 @@
         printf("%s\n", spvSoftwareVersionDetailsString());
         printf(
             "Targets:\n  %s\n  %s\n  %s\n  %s\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_UNIVERSAL_1_5),
+            spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_6),
             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_VULKAN_1_1_SPIRV_1_4),
+            spvTargetEnvDescription(SPV_ENV_VULKAN_1_2),
+            spvTargetEnvDescription(SPV_ENV_VULKAN_1_3));
         continue_processing = false;
         return_code = 0;
       } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
diff --git a/utils/check_copyright.py b/utils/check_copyright.py
index 49892ee..aa647af 100755
--- a/utils/check_copyright.py
+++ b/utils/check_copyright.py
@@ -14,6 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 """Checks for copyright notices in all the files that need them under the
+
 current directory.  Optionally insert them.  When inserting, replaces
 an MIT or Khronos free use license with Apache 2.
 """
@@ -41,11 +42,28 @@
            'Mostafa Ashraf',
            'Shiyu Liu',
            'ZHOU He']
-CURRENT_YEAR='2021'
+CURRENT_YEAR = 2022
 
-YEARS = '(2014-2016|2015-2016|2015-2020|2016|2016-2017|2017|2017-2019|2018|2019|2020|2021)'
-COPYRIGHT_RE = re.compile(
-        'Copyright \(c\) {} ({})'.format(YEARS, '|'.join(AUTHORS)))
+FIRST_YEAR = 2014
+FINAL_YEAR = CURRENT_YEAR + 5
+# A regular expression to match the valid years in the copyright information.
+YEAR_REGEX = '(' + '|'.join(
+    str(year) for year in range(FIRST_YEAR, FINAL_YEAR + 1)) + ')'
+
+# A regular expression to make a range of years in the form <year1>-<year2>.
+YEAR_RANGE_REGEX = '('
+for year1 in range(FIRST_YEAR, FINAL_YEAR + 1):
+  for year2 in range(year1 + 1, FINAL_YEAR + 1):
+    YEAR_RANGE_REGEX += str(year1) + '-' + str(year2) + '|'
+YEAR_RANGE_REGEX = YEAR_RANGE_REGEX[:-1] + ')'
+
+# In the copyright info, the year can be a single year or a range.  This is a
+# regex to make sure it matches one of them.
+YEAR_OR_RANGE_REGEX = '(' + YEAR_REGEX + '|' + YEAR_RANGE_REGEX + ')'
+
+# The final regular expression to match a valid copyright line.
+COPYRIGHT_RE = re.compile('Copyright \(c\) {} ({})'.format(
+    YEAR_OR_RANGE_REGEX, '|'.join(AUTHORS)))
 
 MIT_BEGIN_RE = re.compile('Permission is hereby granted, '
                           'free of charge, to any person obtaining a')
diff --git a/utils/generate_grammar_tables.py b/utils/generate_grammar_tables.py
index 9ccf410..74aa282 100755
--- a/utils/generate_grammar_tables.py
+++ b/utils/generate_grammar_tables.py
@@ -23,7 +23,7 @@
 PYGEN_VARIABLE_PREFIX = 'pygen_variable'
 
 # Extensions to recognize, but which don't necessarily come from the SPIR-V
-# core or KHR grammar files.  Get this list from the SPIR-V registery web page.
+# core or KHR grammar files.  Get this list from the SPIR-V registry web page.
 # NOTE: Only put things on this list if it is not in those grammar files.
 EXTENSIONS_FROM_SPIRV_REGISTRY_AND_NOT_FROM_GRAMMARS = """
 SPV_AMD_gcn_shader
diff --git a/utils/git-sync-deps b/utils/git-sync-deps
index eecfbe9..7a7e606 100755
--- a/utils/git-sync-deps
+++ b/utils/git-sync-deps
@@ -168,7 +168,7 @@
 
   with open(os.devnull, 'w') as devnull:
     # If this fails, we will fetch before trying again.  Don't spam user
-    # with error infomation.
+    # with error information.
     if 0 == subprocess.call([git, 'checkout', '--quiet', checkoutable],
                             cwd=directory, stderr=devnull):
       # if this succeeds, skip slow `git fetch`.
diff --git a/utils/roll_deps.sh b/utils/roll_deps.sh
index f61f2a3..20c061f 100755
--- a/utils/roll_deps.sh
+++ b/utils/roll_deps.sh
@@ -23,9 +23,9 @@
 effcee_dir="external/effcee/"
 effcee_trunk="origin/main"
 googletest_dir="external/googletest/"
-googletest_trunk="origin/master"
+googletest_trunk="origin/main"
 re2_dir="external/re2/"
-re2_trunk="origin/master"
+re2_trunk="origin/main"
 spirv_headers_dir="external/spirv-headers/"
 spirv_headers_trunk="origin/master"
 
@@ -39,8 +39,11 @@
     exit 1
 fi
 
+echo "*** Ignore messages about running 'git cl upload' ***"
+
 old_head=$(git rev-parse HEAD)
 
+set +e
 roll-dep --ignore-dirty-tree --roll-to="${effcee_trunk}" "${effcee_dir}"
 roll-dep --ignore-dirty-tree --roll-to="${googletest_trunk}" "${googletest_dir}"
 roll-dep --ignore-dirty-tree --roll-to="${re2_trunk}" "${re2_dir}"
diff --git a/utils/update_build_version.py b/utils/update_build_version.py
index 321de74..2a1ca60 100755
--- a/utils/update_build_version.py
+++ b/utils/update_build_version.py
@@ -17,16 +17,16 @@
 # Updates an output file with version info unless the new content is the same
 # as the existing content.
 #
-# Args: <spirv-tools_dir> <output-file>
+# Args: <changes-file> <output-file>
 #
 # The output file will contain a line of text consisting of two C source syntax
 # string literals separated by a comma:
-#  - The software version deduced from the CHANGES file in the given directory.
+#  - The software version deduced from the given CHANGES file.
 #  - A longer string with the project name, the software version number, and
-#    git commit information for the directory.  The commit information
-#    is the output of "git describe" if that succeeds, or "git rev-parse HEAD"
-#    if that succeeds, or otherwise a message containing the phrase
-#    "unknown hash".
+#    git commit information for the CHANGES file's directory.  The commit
+#    information is the output of "git describe" if that succeeds, or "git
+#    rev-parse HEAD" if that succeeds, or otherwise a message containing the
+#    phrase "unknown hash".
 # The string contents are escaped as necessary.
 
 import datetime
@@ -73,9 +73,8 @@
     return stdout
 
 
-def deduce_software_version(directory):
-    """Returns a software version number parsed from the CHANGES file
-    in the given directory.
+def deduce_software_version(changes_file):
+    """Returns a software version number parsed from the given CHANGES file.
 
     The CHANGES file describes most recent versions first.
     """
@@ -85,7 +84,6 @@
     # unexpected carriage returns on a linefeed-only system such as
     # Linux.
     pattern = re.compile(r'^(v\d+\.\d+(-dev)?) \d\d\d\d-\d\d-\d\d\s*$')
-    changes_file = os.path.join(directory, 'CHANGES')
     with open(changes_file, mode='r') as f:
         for line in f.readlines():
             match = pattern.match(line)
@@ -125,16 +123,17 @@
 
 def main():
     if len(sys.argv) != 3:
-        print('usage: {} <spirv-tools-dir> <output-file>'.format(sys.argv[0]))
+        print('usage: {} <changes-files> <output-file>'.format(sys.argv[0]))
         sys.exit(1)
 
     output_file = sys.argv[2]
     mkdir_p(os.path.dirname(output_file))
 
     software_version = deduce_software_version(sys.argv[1])
+    directory = os.path.dirname(sys.argv[1])
     new_content = '"{}", "SPIRV-Tools {} {}"\n'.format(
         software_version, software_version,
-        describe(sys.argv[1]).replace('"', '\\"'))
+        describe(directory).replace('"', '\\"'))
 
     if os.path.isfile(output_file):
         with open(output_file, 'r') as f:
diff --git a/utils/vscode/src/lsp/jsonrpc2/jsonrpc2.go b/utils/vscode/src/lsp/jsonrpc2/jsonrpc2.go
index b8436d2..44dd220 100644
--- a/utils/vscode/src/lsp/jsonrpc2/jsonrpc2.go
+++ b/utils/vscode/src/lsp/jsonrpc2/jsonrpc2.go
@@ -48,7 +48,7 @@
 	requestDone
 )
 
-// Request is sent to a server to represent a Call or Notify operaton.
+// Request is sent to a server to represent a Call or Notify operation.
 type Request struct {
 	conn        *Conn
 	cancel      context.CancelFunc
diff --git a/utils/vscode/src/lsp/jsonrpc2/wire.go b/utils/vscode/src/lsp/jsonrpc2/wire.go
index 3e31c34..fed9a25 100644
--- a/utils/vscode/src/lsp/jsonrpc2/wire.go
+++ b/utils/vscode/src/lsp/jsonrpc2/wire.go
@@ -44,7 +44,7 @@
 	CodeServerOverloaded = -32000
 )
 
-// WireRequest is sent to a server to represent a Call or Notify operaton.
+// WireRequest is sent to a server to represent a Call or Notify operation.
 type WireRequest struct {
 	// VersionTag is always encoded as the string "2.0"
 	VersionTag VersionTag `json:"jsonrpc"`
diff --git a/utils/vscode/src/lsp/protocol/tsprotocol.go b/utils/vscode/src/lsp/protocol/tsprotocol.go
index 50543fc..e0a3594 100644
--- a/utils/vscode/src/lsp/protocol/tsprotocol.go
+++ b/utils/vscode/src/lsp/protocol/tsprotocol.go
@@ -143,7 +143,7 @@
 			 * change notifications.
 			 *
 			 * If a strings is provided the string is treated as a ID
-			 * under which the notification is registed on the client
+			 * under which the notification is registered on the client
 			 * side. The ID can be used to unregister for these events
 			 * using the `client/unregisterCapability` request.
 			 */
@@ -162,7 +162,7 @@
 
 	/*Name defined:
 	 * The name of the workspace folder. Used to refer to this
-	 * workspace folder in thge user interface.
+	 * workspace folder in the user interface.
 	 */
 	Name string `json:"name"`
 }
@@ -1129,7 +1129,7 @@
 			 * change notifications.
 			 *
 			 * If a strings is provided the string is treated as a ID
-			 * under which the notification is registed on the client
+			 * under which the notification is registered on the client
 			 * side. The ID can be used to unregister for these events
 			 * using the `client/unregisterCapability` request.
 			 */
@@ -1803,7 +1803,7 @@
 
 	/*AllCommitCharacters defined:
 	 * The list of all possible characters that commit a completion. This field can be used
-	 * if clients don't support individual commmit characters per completion item. See
+	 * if clients don't support individual commit characters per completion item. See
 	 * `ClientCapabilities.textDocument.completion.completionItem.commitCharactersSupport`
 	 *
 	 * @since 3.2.0
@@ -2844,7 +2844,7 @@
 
 	/*TargetSelectionRange defined:
 	 * The range that should be selected and revealed when this link is being followed, e.g the name of a function.
-	 * Must be contained by the the `targetRange`. See also `DocumentSymbol#range`
+	 * Must be contained by the `targetRange`. See also `DocumentSymbol#range`
 	 */
 	TargetSelectionRange Range `json:"targetSelectionRange"`
 }
@@ -2881,7 +2881,7 @@
 type ColorInformation struct {
 
 	/*Range defined:
-	 * The range in the document where this color appers.
+	 * The range in the document where this color appears.
 	 */
 	Range Range `json:"range"`
 
@@ -3627,14 +3627,14 @@
 
 	/*Range defined:
 	 * The range enclosing this symbol not including leading/trailing whitespace but everything else
-	 * like comments. This information is typically used to determine if the the clients cursor is
+	 * like comments. This information is typically used to determine if the clients cursor is
 	 * inside the symbol to reveal in the symbol in the UI.
 	 */
 	Range Range `json:"range"`
 
 	/*SelectionRange defined:
 	 * The range that should be selected and revealed when this symbol is being picked, e.g the name of a function.
-	 * Must be contained by the the `range`.
+	 * Must be contained by the `range`.
 	 */
 	SelectionRange Range `json:"selectionRange"`
 
@@ -3652,7 +3652,7 @@
 
 	/*Diagnostics defined:
 	 * An array of diagnostics known on the client side overlapping the range provided to the
-	 * `textDocument/codeAction` request. They are provied so that the server knows which
+	 * `textDocument/codeAction` request. They are provided so that the server knows which
 	 * errors are currently presented to the user for the given range. There is no guarantee
 	 * that these accurately reflect the error state of the resource. The primary parameter
 	 * to compute code actions is the provided range.
@@ -4081,13 +4081,13 @@
 	/*TextOnlyTransactional defined:
 	 * If the workspace edit contains only textual file changes they are executed transactional.
 	 * If resource changes (create, rename or delete file) are part of the change the failure
-	 * handling startegy is abort.
+	 * handling strategy is abort.
 	 */
 	TextOnlyTransactional FailureHandlingKind = "textOnlyTransactional"
 
 	/*Undo defined:
 	 * The client tries to undo the operations already executed. But there is no
-	 * guaruntee that this is succeeding.
+	 * guarantee that this is succeeding.
 	 */
 	Undo FailureHandlingKind = "undo"
 
diff --git a/utils/vscode/src/parser/parser.go b/utils/vscode/src/parser/parser.go
index cc6f333..4c0fa8f 100644
--- a/utils/vscode/src/parser/parser.go
+++ b/utils/vscode/src/parser/parser.go
@@ -798,7 +798,7 @@
 	References []*Token     // all the places the identifier was referenced
 }
 
-// Severity is an enumerator of diagnositc seeverities
+// Severity is an enumerator of diagnostic severities
 type Severity int
 
 // Severity levels